跳转到内容

计算机编程/编码风格

0% developed
来自维基教科书,开放世界中的开放书籍

计算机编程中,有许多编码约定用于确保代码一致,并提高代码质量,特别是包括正确性、可读性、可维护性和速度。各个项目、社区、代码库和指南选择特定的约定,形成编码标准风格指南。“编程风格”主要指的是低级约定,例如格式或语言结构的选择(例如gotoreturn),但也可能指的是大型代码结构,甚至在软件工程中的总体设计。这些更高级别的风格主题通常被称为“哲学”,例如Unix哲学

虽然一些约定被广泛认为优于其他选择,但另一些约定有几个常见的替代方案,每个方案都有其优点和缺点,不同的标准做出不同的选择。一致性是一个普遍的价值观,因此即使在特定情况下另一种选择可能更好,但为了保持一致性而做出例外所付出的代价通常超过了收益。然而,如果在特别令人信服的情况下或在规定的情况下,有时会做出例外,不同的标准对一致性的重视程度不同。

除了指定要遵循的良好实践外,编码风格还识别出不良实践,称为“反模式”或“代码异味”,并可能推荐特定的解决方案。本指南讨论语言中立问题,列出各种约定的优缺点。

代码结构

[编辑 | 编辑源代码]

非代码

[编辑 | 编辑源代码]

左手比较

[编辑 | 编辑源代码]

在使用一个符号(通常是单个等号 (=),例如 Visual Basic)进行赋值,而使用另一个符号(通常是两个等号 (==) 进行比较(例如 C/C++JavaActionScript 3PHPPerl 数值上下文以及过去 15 年的大多数语言)的语言中,并且赋值可以在控制结构中进行,采用左手比较风格有优势:在任何比较中将常量或表达式放在左边。 [1] [2]

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

if ($a == 42) { ... }  # A left-hand comparison checking if $a equals 42.
if (42 == $a) { ... }  # Recast, using the right-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# 不支持为了这个原因自动转换为布尔值。

还可以通过使用静态代码分析工具来减轻这种风险,这些工具可以检测到这个问题。

循环和控制结构

[编辑 | 编辑源代码]

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

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 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");

另一种更传统的风格是明确地指示嵌套。这意味着开括号和闭括号位于同一列

for( index = 0 ; index < size ; ++index )
{
    arrayA[ index ] = arrayB[ index ] ;
}

这清楚地指示了代码块的开始和结束,因此很容易从代码中识别出来。

指示关系

[编辑 | 编辑源代码]

关系用多种方式表示。最简单的方法是将关键字和函数名称与其参数相关联。这通过将关键字或函数名称直接放在参数旁边来指示。

if( a == b )
{
    ...
}

接下来是参数本身。在条件语句中存在多个参数的情况下,将这些参数水平和垂直排列成对齐的形式表示它们之间的关系。

if( ( a == b ) ||
    ( a == c )    )
{
    ...
}

关系一直延伸到顶层 - 函数之间的关系、组件之间的关系以及模块之间的关系。它们可以是不同类型的,但都需要明确指示。

指示关系非常重要,因为它是连接点的部分 - 展示代码如何联系在一起的一部分。在构建模块时,非常重要的一点是,在设计模块时要确保其结构在关系方面易于理解,从一个明显的起点开始,并从该点跟踪代码。

分散代码并对齐

[编辑 | 编辑源代码]

这对于可读性非常重要。基本原则是

  1. 用空格分隔每个组件部分。
  2. 以有意义的方式对齐所有内容。

这样一来,人们就可以轻松地扫描代码,查看代码的模式。这不仅对于理解代码非常重要,而且对于查找异常,以及作为整理和整合代码的工具也至关重要。

包含大量“噪声”的代码(大量不必要的变化和凌乱)会导致人们浪费大量时间在它上面。编写良好且格式良好的代码易于快速使用。它可以让使用者轻松地“透过现象看本质”。

压缩的代码

for(i=0;i<s;i++){a[i]=b[i];}

分离的代码

for( index = 0 ; index < size ; index++ )
{
    arrayA[ index ] = arrayB[ index ] ;
}

清晰的格式和有意义的名称使代码更易读,更容易理解。

意义和一致性

[编辑 | 编辑源代码]

意义是指以传达意义的方式进行编码。如果一个 for 循环只是使用字母“i”作为索引,这意义就不大。然而,如果使用“index”这个词,那就更有意义了。

一致性是指在出现相同类型的情况时使用相同的名称。例如,如果对“index”使用不同的词语,例如“i”、“index”、“inx”、“indx”,这就不一致了——这是在编码中引入不必要“噪声”的许多领域之一。然而,如果始终使用“index”,那么产生的代码将更加易于阅读和理解。

尽量减少未知数的数量,并最大限度地增加已知数的数量非常重要。

硬编码和软编码

[编辑 | 编辑源代码]

硬编码(通常称为“魔数”)会产生难以维护的代码。代码会失去对数字代表含义及其出现位置的理解。硬编码应该用枚举值或 #defines(软编码)替换。大多数编译器可以将枚举值处理为无类型的数字,因此这比使用宏(#defines)更安全的方法。软编码的项目可以非常快速、安全地更新。硬编码的项目更新起来可能非常耗时且非常危险。

当列表中的项目放在单独的行上(垂直列表)时,有时将项目分隔符添加到最后一个项目之后,以及每个项目之间被认为是一种良好的做法——最常见的是逗号,因此它也被称为使用**尾随逗号**。例如在 C 中

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

这确保了每行都是一个单独的项目,无论顺序如何,或者是否还有另一个项目紧随其后。这样就不需要在以前是最后一行列表的行的后面添加逗号,也不需要从新最后一个项目中删除逗号,当列表项目重新排序或在末尾添加或删除项目时。除了减少乏味和防止语法错误之外,它还有两个更微妙的好处。首先,在使用版本控制时,两个文件版本之间的行差异将只显示项目的插入和删除(以及重新排序),而不会为添加或删除尾随逗号额外添加一行。[3] 其次,在某些语言(例如 Python)中,相邻的字符串文字会连接起来,因此列表中间缺少逗号,不会导致语法错误,而是会导致两个相邻的项目连接起来,这可能是一个难以发现的错误。

这在某些语言中的某些结构中得到支持,例如CJavaPython中的数组(列表)。在其他情况下,尾随逗号是语法错误,或者在末尾用空条目扩展列表。即使是支持尾随逗号的语言,这些语言中并非所有类似列表的语法结构都需要支持它。ECMAScript就是一个语言发生变化的例子:版本 3 不允许尾随逗号——尽管这只是在Internet Explorer中强制执行的——而版本 5 允许可选的尾随逗号。偶尔会有一些微妙之处。例如,在一些 FORTRAN 方言中,尾随逗号被解释为一个额外的空参数,然后一个空参数列表 FOO() 被认为是一个单独的空参数;因此不能传递空参数列表。许多系统通过允许传递一个额外的参数来处理这种方言差异。[4] 在 Python 中,尾随逗号允许在元组(以及其他类型)中使用,并且一个 1 元组由带有尾随逗号的表达式定义,如 1,——括号是允许的,会产生 (1,)(1 元组)而不是简单的带括号的表达式 (1),但定义元组的是逗号(这里尾随);这里空元组由空括号 () 定义。[5][6]

在列表中使用尾随逗号可以与在许多语言中使用分号作为语句终止符进行比较,在这些语言中,语句通常写在单独的行上,并且允许或需要尾随分号。这与ALGOL和早期的Pascal形式形成对比,在这些形式中,分号严格地是语句分隔符,并且尾随分号是非法的;参见Pascal:分号作为语句分隔符

尾随逗号也可以用在水平列表中,例如 f(x, y,),它具有使列表更容易修改的好处,就像在垂直列表中一样,但这通常被认为是不雅观的,并且不太常见。

尾随逗号通常在解析器中处理,作为短语语法的部分。类似的现象是分号插入,它通常在词法分析期间完成。但是,在某些情况下,它们会结合起来——ECMAScript 包含分号插入(在词法分析器中),但也包含一些短语生成规则中的可选尾随分号。

注释和文档

[编辑 | 编辑源代码]

对代码进行注释非常重要。人们无法读懂他人的想法。注释有两种用途

  1. 确保代码按预期编写。这种注释通常在编写代码之前放在代码中。它作为一种手段来确保代码执行它应该执行的操作。
  2. 确保代码的描述足够详细,以便任何查看代码的人都可以轻松地理解代码的设计和目的。

文档非常重要。它独立于代码,但与代码相关联。它包含诸如规范、设计说明、测试和测试结果等文档。

另请参见

[编辑 | 编辑源代码]

参考文献

[编辑 | 编辑源代码]



请仅在书籍标题页添加 {{alphabetical}}


参考文献

[编辑 | 编辑源代码]
  1. Sklar, David (2003). PHP Cookbook. O'Reilly. {{cite book}}: 未知参数 |coauthors= 被忽略 (|author= 建议使用) (帮助), 食谱 5.1 "避免 == 和 = 混淆", 第 118 页
  2. "C 编程常见问题解答:常见问题". Addison-Wesley, 1995. 2010 年 11 月. {{cite web}}: 检查日期值: |date= (帮助)
  3. "为什么在资源中使用尾随逗号?", Puppet 食谱, Dean Wilson
  4. 9.9.4 丑陋的空参数
  5. 5.3. 元组和序列
  6. TupleSyntax
华夏公益教科书