跳转到内容

软件工程/实现/代码约定简介

来自维基教科书,开放的书籍,开放的世界

编码约定是一组针对特定编程语言的指南,建议针对用这种语言编写的程序片段的每个方面,推荐编程风格、实践和方法。这些约定通常涵盖文件组织、缩进、注释、声明、语句、空白、命名约定、编程实践、编程原则、编程经验法则等。强烈建议软件程序员遵循这些指南,以帮助提高其源代码的可读性,并使软件维护更轻松。编码约定仅适用于软件项目的维护人员和同行评审人员。约定可以在整个团队或公司遵循的一套文档化的规则中正式化,也可以像个人习惯的编码实践一样非正式。编码约定不受编译器强制执行。因此,不遵循某些或所有规则不会影响从源代码创建的可执行程序。代码编制对使用计算机非常有帮助

软件维护

[编辑 | 编辑源代码]

降低软件维护成本是遵循编码约定的最常被引用的原因。在他们关于 Java 编程语言的代码约定的介绍中,Sun Microsystems 提供了以下理由:[1]

代码约定对程序员来说很重要,原因有很多

  • 80% 的软件生命周期成本用于维护。
  • 几乎没有软件是由最初的作者维护其整个生命周期的。
  • 代码约定提高了软件的可读性,使工程师能够更快、更彻底地理解新代码。
  • 如果您将源代码作为产品交付,您需要确保其包装和清洁程度与您创建的任何其他产品一样好。

软件同行评审经常涉及阅读源代码。这种类型的同行评审主要是缺陷检测活动。根据定义,只有代码的最初作者在代码提交评审之前阅读过源文件。使用一致指南编写的代码更容易让其他评审人员理解和吸收,从而提高缺陷检测过程的效率。

即使对于最初的作者来说,一致的编码软件也简化了可维护性。不能保证个人会记住很久以前编写的一段特定代码以特定方式编写的确切理由。编码约定可以提供帮助。一致使用空白会提高可读性,并缩短理解软件所需的时间。

重构是指一种软件维护活动,其中修改源代码以提高可读性或改进其结构。通常会对软件进行重构,使其在初始发布后符合团队的既定编码标准。任何不改变软件行为的更改都可以被认为是重构。常见的重构活动包括更改变量名、重命名方法、移动方法或整个类以及将大型方法(或函数)分解成更小的方法。

敏捷软件开发方法规划了定期(甚至持续)重构,使其成为团队软件开发过程的一个组成部分。[2]

任务自动化

[编辑 | 编辑源代码]

编码约定允许使用简单的脚本或程序,这些脚本或程序的任务是处理源代码,以实现除将其编译为可执行文件之外的其他目的。计算软件大小(源代码行数)以跟踪当前项目进度或为将来的项目估算建立基线是常见的做法。

一致的编码标准反过来可以使测量结果更加一致。源代码注释中的特殊标记通常用于处理文档,javadoc 和 doxygen 是两个著名的例子。该工具指定了一组标记的使用,但它们在项目中的使用是通过约定决定的。

编码约定简化了编写处理现有软件的新软件。自 20 世纪 50 年代以来,静态代码分析的使用一直在不断增长。这种开发工具的增长部分源于从业人员自身的成熟度和复杂性(以及现代对安全性的关注),但也源于语言本身的性质。

语言因素

[编辑 | 编辑源代码]

所有软件从业人员都必须解决组织和管理大量详细指令的问题,这些指令最终将被处理以执行其编写任务。对于除最小的软件项目之外的所有项目,源代码(指令)都将被划分为单独的文件,并且经常在多个目录中。程序员自然地将密切相关的函数(行为)收集到同一个文件中,并将相关的文件收集到目录中。随着软件开发从纯粹的程序化编程(如 FORTRAN 中的编程)发展到面向对象结构(如 C++ 中的编程),编写单个(公共)类的代码在单个文件(“每个文件一个类”约定)中成为一种惯例。[3][4] Java 更进一步 - 如果 Java 编译器在文件中发现多个公共类,则会返回错误。

一种语言中的约定可能是另一种语言中的要求。语言约定也会影响单个源文件。用于处理源代码的每个编译器(或解释器)都是唯一的。编译器应用于源代码的规则创建了隐式标准。例如,Python 代码的缩进比 Perl(比如)要一致得多,因为空白(缩进)对于解释器来说实际上是有意义的。Python 不使用 Perl 用来分隔函数的大括号语法。缩进的更改用作分隔符。[5][6] Tcl 使用类似于 Perl 或 C/C++ 的大括号语法来分隔函数,但不允许以下内容,这对 C 程序员来说似乎相当合理

 set i 0
 while {$i < 10} 
 {
    puts "$i squared = [expr $i*$i]"
    incr i
 }

原因是在 Tcl 中,大括号不仅用于像 C 或 Java 中那样分隔函数。更一般地说,大括号用于将单词组合成一个参数。[7][8] 在 Tcl 中,单词 while 接受两个参数,一个条件和一个操作。在上面的例子中,while 缺少第二个参数,即操作(因为 Tcl 还使用换行符来分隔命令的结束)。

常见约定

[编辑 | 编辑源代码]

如上所述,常见的编码约定可能涵盖以下领域

  • 注释约定
  • 缩进样式约定
  • 命名约定
  • 编程实践
  • 编程原则
  • 编程经验法则
  • 编程风格约定

每行只应出现一条语句

[edit | edit source]

例如,在 Java 中,语句应该这样写

a++;
b = a;

而不是这样

a++; b = a;

决策结构中的布尔值

[edit | edit source]

一些程序员认为,当决策结果仅仅是计算一个布尔值时,代码过于冗长且容易出错。他们更喜欢在计算本身中进行决策,如下所示

return (hours < 24) && (minutes < 60) && (seconds < 60);

这完全是风格上的差异,因为优化编译器可能会对两种形式生成相同的目标代码。然而,在风格上,程序员对哪种形式更易读和维护存在分歧。

支持较长形式的论据包括:它可以使程序员在决策的一个分支上设置单行断点;可以在一个分支上添加更多代码行,而无需重构 return 行,这将增加引入错误的可能性;较长形式始终允许调试器单步执行到变量仍在作用域内的行。

左端比较

[edit | edit source]

在使用一个符号(通常是单个等号 (=))进行赋值,另一个符号(通常是两个等号 (==) 进行比较(例如 C/C++、Java、ActionScript 3、PHP、Perl 数值上下文以及过去 15 年中的大多数语言)的语言中,以及在控制结构中可能进行赋值的情况下,采用左端比较风格具有一定的优势:在任何比较中将常量或表达式放在左侧。 [9] [10]


以下是应用于 Perl 代码行的左端和右端比较风格。在这两种情况下,这都会将变量 $a 中的值与 42 进行比较,如果匹配,则执行后续块中的代码。

if ( $a == 42 ) { ... }  # A right-hand comparison checking if $a equals 42.
if ( 42 == $a ) { ... }  # Recast, using the left-hand comparison style.

当开发人员不小心输入 = 而不是 == 时,就会出现差异

if ( $a = 42 ) { ... }  # Inadvertent assignment which is often hard to debug
if ( 42 = $a ) { ... }  # Compile time error indicates source of problem

第一行(右端)现在包含一个潜在的细微缺陷:它不再像以前那样运行,而是将 $a 的值设置为 42,然后始终运行以下块中的代码。由于这在语法上是合法的,因此程序员可能不会注意到错误,并且软件可能会包含错误。

第二行(左端)包含一个语义错误,因为不能将数值赋值给它。这将导致在编译代码时生成诊断消息,因此程序员无法忽视错误。

一些语言内置了防止意外赋值的保护措施。例如,Java 和 C# 不会为了这个原因而支持自动转换为布尔值。

还可以通过使用可以检测此问题的静态代码分析工具来减轻风险。

循环和控制结构

[edit | edit source]

使用逻辑控制结构进行循环也有助于提高编程风格。它有助于阅读代码的人更好地理解程序的执行顺序(在命令式编程语言中)。例如,在伪代码中

 i = 0
 '' ''
 '''while''' i < 5
   '''print''' i * 2
   i = i + 1
 '''end while'''
 '' ''
 '''print''' "Ended loop"

上面的代码片段遵守了命名和缩进风格指南,但以下对“for”结构的使用可能被认为更易读

 '''for''' i = 0, i < 5, i=i+1
   '''print''' i * 2
 '' ''
 '''print''' "Ended loop"

在许多语言中,经常使用的“for each element in a range”模式可以缩短为

 '''for''' i = 0 '''to''' 5
   '''print''' i * 2
 '' ''
 '''print''' "Ended loop"

在允许使用花括号的编程语言中,风格文档通常要求即使是可选的,花括号也应与所有控制流结构一起使用。

 '''for''' (i = 0 '''to''' 5) {
   '''print''' i * 2;
 }
 '' ''
 '''print''' "Ended loop";

这可以防止程序流错误,这些错误可能很耗时才能找到,例如在结构末尾引入终止分号(常见错误)的情况

 for (i = 0; i < 5; ++i);
    printf("%d\n", i*2);    /* The incorrect indentation hides the fact 
                               that this line is not part of the loop body. */
 
 printf("Ended loop");

...或者在第一行之前添加另一行

 for (i = 0; i < 5; ++i)
    fprintf(logfile, "loop reached %d\n", i);
    printf("%d\n", i*2);    /* The incorrect indentation hides the fact 
                               that this line is not part of the loop body. */
 
 printf("Ended loop");

列表

[edit | edit source]

当列表中的项目放在单独的行上时,有时将项目分隔符添加到最后一个项目之后以及每个项目之间被认为是一种好的做法,至少在那些语法支持这样做(例如 C、Java)的语言中是这样。

const char *array[] = {
    "item1",
    "item2",
    "item3",  /* still has the comma after it */
};

这可以防止在重新排序列表项目或在末尾添加更多项目时出现语法错误或细微的字符串连接错误,而程序员没有注意到列表中先前最后一行上的“缺失”分隔符。但是,此方法可能会在某些语言中导致语法错误(或误导性的语义)。即使对于确实支持尾随逗号的语言,这些语言中并非所有类似列表的语法结构都可能支持它。

参考文献

[edit | edit source]
  1. "Java 编程语言的代码规范 : 为什么需要代码规范". Sun Microsystems, Inc. 1999-04-20.
  2. Jeffries, Ron (2001-11-08). "什么是极限编程? : 设计改进". XP 杂志.
  3. Hoff, Todd (2007-01-09). "C++ 编码规范 : 命名类文件".
  4. FIFE 编码标准
  5. van Rossum, Guido (2006-09-19). "Python 教程 : 编程入门". Python 软件基金会. {{cite web}}: 未知参数 |coauthors= 被忽略 (|author= 建议) (帮助)
  6. Raymond, Eric (2000-05-01). "为什么选择 Python?". Linux 杂志.
  7. Tcl 开发者交流. "Tcl 语言语法概述". ActiveState.
  8. Staplin, George Peter (2006-07-16). "为什么我不能在花括号组之前开始新行". 'the Tcler's Wiki'.
  9. Sklar, David (2003). PHP Cookbook. O'Reilly. {{cite book}}: Unknown parameter |coauthors= ignored (|author= suggested) (help), recipe 5.1 "Avoiding == Versus = Confusion", p118
  10. "C Programming FAQs: Frequently Asked Questions". Addison-Wesley, 1995. Nov. 2010. {{cite web}}: Check date values in: |date= (help)
[编辑 | 编辑源代码]

语言编码规范

[编辑 | 编辑源代码]

项目编码规范

[编辑 | 编辑源代码]
华夏公益教科书