极简主义编码指南






4.69/5 (42投票s)
本文提出了一套与语言无关的编码指南;遵循这些指南编写的代码将比不遵循这些指南的代码更易于维护。
本文提出了一套与语言无关的编码指南。遵循这些指南编写的代码将比不遵循这些指南的代码更易于维护。
本文提出了一套与语言无关的编码指南。我坚信,一些开发者会针对其中一条或多条指南提出异议。但每条指南都有其背后的原理,有助于减轻痛苦。然而,如果全部采纳,我向您保证,遵循这些指南编写的代码将比不遵循这些指南的代码易于维护得多。引言
我知道。我知道。大多数程序员将编码标准和指南视为对其创造力和艺术才能的侵犯。但请稍作思考,大多数这类程序员很可能是为他人(例如公司、客户等)从事合同工作。如果是这种情况,所开发的软件并非程序员可以享有版权意义上的艺术作品。此外,程序员并不拥有该软件。最后,程序员承受着压力(即快节奏的环境),维护性的想法被搁置,留待以后,而这个“以后”通常永不到来。
作者的背景和视角
我相信大多数程序员都试图编写易于维护的代码。但如果他们不使用编码标准,他们通常会失败。也许是因为当今的程序员进入劳动力市场的方式与过去不同。就在 20 世纪 90 年代末,初级程序员被分配到维护任务。他们很少被赋予开发或设计原创软件的责任。结果呢?程序员从他人的错误中学习——最重要的是学习如何不编写软件。在大多数情况下,这些维护程序员发誓他们绝不会像别人对待他们那样对待他人。
没有被开发出来的软件正在被维护。维护软件是因为它存在错误。我将错误定义为程序员未能正确实现需求,或者架构师或设计师未能正确陈述需求。用程序员的行话来说,前者就是“bug”;后者则被委婉地称为“enhancement”。
那么,编码标准对程序员有什么作用呢?请注意,必须有一些回报,否则程序员就真的没有理由花费时间来遵守。回报就是简单易读性。易读性提高了可维护性。无论您是原始开发者还是负责修复或增强软件的程序员,在修改代码之前都必须理解代码的作用。
多年前,我还在读研究生时,编写了一个相当复杂的软件,可以将高速铁路交通线路适应由多个地块组成的通行权。线路必须尽量减少侧向加速度。问题如图所示,通常不可能购买允许直线线路(虚线)的地块。相反,我们必须购买一些地块,以便能够连接两端(实线)。

我对这个软件很满意;它做到了应有的工作。几年后,出现了一个可以通过修改此软件来解决的问题。我重新打开了软件,惊恐地发现它无法使用了。在我编写原始软件时,我完全没有考虑过风格,尤其是那种有助于后续程序员(包括我自己)理解解决方案的风格。结果是完全不必要的“重复发明轮子”。
就在我因自己的编码风格感到沮丧的同时,一本名为《编程风格要素》的书出版了。作者 Brian Kernighan 和 P. J. Plauger 都是受人尊敬的计算机科学家,他们编写了一套指南,如果我遵循了这些指南,就可以恢复我早期的代码。对我来说,这本书最大的教训是:“编写软件就好像为他人而写——因为六个月后,你就是他人了!”
为他人写作是什么意思?我认为这意味着使用清晰的写作风格和一致的格式化方案。这正是这些指南的全部内容。
指南
以下是编码指南,希望它们与语言无关。具体的示例可能使用 C 或 C# 作为示例语言。但指南本身与 C 或 C# 都没有关联。
致谢
我希望感谢 Knowledge Software 的 Derek M. Jones 的贡献,他的网页位于
- http://coding-guidelines.com/cbook/sent0.pdf
- http://coding-guidelines.com/cbook/sent766.pdf
- http://coding-guidelines.com/cbook/sent787.pdf
这些网页为这些指南的大部分原理提供了基础。
首要目标
一致性是编写清晰计算机程序的关键指南。如果原始程序员混合使用风格,后续的维护程序员就更容易分心而无法进行修复。这会增加维护成本。
标识符拼写
我认为在标识符应具有自描述性这一点上没有异议。主要的争论似乎出现在标识符拼写的问题上。驼峰式命名。帕斯卡式命名。大写。小写。下划线。匈牙利命名法。所有这些方法都吸引了部分人。但似乎一种方法优于另一种方法的倡导者只是在宣扬某种个人偏好,而不是陈述事实,而是在陈述一种观点。我认为这与该问题的理性方法不符。
- 使用完整的英语单词创建标识符。
- 限制缩写的使用,仅限于授权集合。
原理
可读性是衡量拼写在开发者母语字符序列特征方面的契合度的简便方法。在两者之间进行选择时,优先选择易于发音的字符序列,而不是难以发音的字符序列。
- 使用下划线分隔英语单词。这种分隔形式将程序员定义的标识符与系统定义的标识符区分开来,后者通常代表了例如 API 的入口点。
- 变量标识符使用小写字母。
- 枚举、结构、类、接口、委托、命名空间等标识符使用标题式大小写(首字母大写)。
- 枚举值标识符和常量变量标识符使用大写字母。
- 缩写使用大写字母。
原理
书面英语用空格分隔单词。当标识符拼写由几个不同的子组件组成时,在子组件之间使用下划线字符是读者在阅读散文(即用空格分隔)时的最接近近似体验。
一些开发者将每个子组件的首字母大写。这种用法创建了字符序列,其视觉外观与读者已习惯的字符序列不同。因此,需要付出额外的努力来处理它们。在某些情况下,使用一个或多个附加字符可能会增加理解包含标识符的构造体所需的精力(也许是因为需要换行来组织可见源代码)。
- 选择自包含且有意义的标识符。
原理
标识符拼写能唤起语义联想,这对读者有益。然而,在不同读者中可靠地唤起所需的语义联想非常困难。在两者之间进行选择时,优先选择能引起许多人对标识符所指内容产生相关语义联想的拼写,而不是引起较少人联想或通常引起与标识符所指内容无关的语义联想的拼写。
- 通过首字母区分标识符。
原理
出于多种原因,英语单词的开头比其他部分更重要。心智词典似乎按词的开头存储单词,口语英语似乎经过优化,可以从单词的开头识别单词。这表明,在标识符拼写开头处(例如,cat、bat、mat 和 rat)进行区分比在结尾处(例如,cat、cab、can 和 cad)进行区分更好。
- 避免重载。
原理
在任何上下文中,一个词都应只有一个含义。例如,要理解 a=b+c,不需要知道 a、b 和 c 的含义(预处理后)。在支持重载的计算机语言中,这不一定成立。
缩进(参见下面的 Visual Studio)
- 使用一致的缩进方案,以增强边缘检测。
原理
大脑的视觉接收区域对边缘的方向有选择性反应。在感知组织的一个理论中,边缘检测是输入到人类视觉系统的信号上执行的第一个操作。源代码是从左到右、从上到下阅读的。通常的做法是将一行中的第一个非空白字符之前的开始位置设置为相同的水平位置。这种用法已被发现可以减少在视觉处理共享某些内容的行代码时所需的精力;例如,语句缩进通常用于指示块嵌套。
边缘检测似乎是人们可以毫不费力地执行的操作。如果一个项目沿着边缘出现,边缘也可以用来加快搜索速度。在以下两个声明序列中,在第二个声明块中查找特定标识符所需的精力较少。在第一个块中,读者首先必须扫描一系列标记来定位要声明的标识符。在另一个块中,标识符的位置显而易见。
Block Declaration
1 private List < Color > known_colors;
private Panel [ ] panels = null;
private Sort_By sort_by = Sort_By.HSL;
private ToolTip tooltip = new ToolTip ( );
2 private List < Color > known_colors;
private Panel [ ] panels = null;
private Sort_By sort_by = Sort_By.HSL;
private ToolTip tooltip = new ToolTip ( );
边缘检测还可以提高阅读方法声明的理解能力。在以下两个声明中,在第二个声明中理解声明所需的精力较少。在第一个声明中,读者首先必须扫描一系列标记来定位标识符。在第二个声明中,标识符显而易见。
Block Declaration
1 [ DllImport ( "gdi32.dll", EntryPoint = "BitBlt" ) ]
public static extern bool BitBlt ( IntPtr hdcDest, int nXDest,
int nYDest, int nWidth, int nHeight,
IntPtr hdcSrc, int nXSrc, int nYSrc,
int dwRop );
2 [ DllImport ( "gdi32.dll",
EntryPoint = "BitBlt" ) ]
public static extern bool BitBlt ( IntPtr hdcDest,
int nXDest,
int nYDest,
int nWidth,
int nHeight,
IntPtr hdcSrc,
int nXSrc,
int nYSrc,
int dwRop );
- 使用一致的语句缩进方案。
原理
有两种常见的语句缩进方案。它们采取以下形式
if ( x ) if ( x )
{ {
if ( y ) if ( y )
{ {
F ( ) ; F ( );
} }
else else
{ {
G ( ) ; G ( );
} }
} }
必须选择这两种形式中的一种来缩进语句体。没有首选的语句体缩进方式。相反,开发者的偏好是决定因素。同样,唯一的规则就是保持一致。
空白(参见下面的 Visual Studio)
- 仅将空格用作空白。
原理
ISO 标准之一将空白定义为空格字符、水平制表符、垂直制表符和换页符,并建议它们可用于分隔标记。使用除空格字符以外的任何这些字符都可能导致一致的缩进方案丢失。
源代码中使用水平制表符有一个历史原因——可用的旋转式海量存储空间有限。这个原因已不复存在。大多数计算机都连接着吉比特甚至太比特的海量存储设备。因此,源代码不再需要节省磁盘空间。此外,使用空格具有显着优势。使用空格进行缩进,无论显示设备如何(例如,监视器、打印页面等),都能创建一致的缩进方案。
例如,假设制表符每八个字符位置定义一次(激光打印机的标准制表位设置)。维护程序员进行修改,但使用空格而非制表符进行缩进。新修改的行很有可能与其余代码的缩进不一致。
制表符的另一个困难在于显示设备如何解释它们。假设程序员将源代码编辑器的制表位设置为四个字符位置。编码照常进行。程序员打印源代码副本以供设计审查。大多数激光打印机每八个字符位置定义一次制表符。打印代码的结果是显着的右移缩进,以至于程序员面临两个选择:以横向模式打印代码,或重新格式化源代码。两者都不是一项令人愉快的工作。更糟糕的是,这个问题本来可以通过使用空格进行缩进来避免。
源代码行长度
- 将源代码行限制为 70 个字符。
原理
“标准”信纸尺寸为 8-1/2 x 11 英寸,每侧“标准”页边距为 1 英寸。建议的最小字体大小为 11 点。打印源代码时,打印页面通常以 Courier New 字体渲染。当满足这些条件时,未断行的最大宽度为 70 个 Courier New 字符。
在监视器上,要求后续程序员向右滚动才能阅读源代码行,这与要求网页阅读者向右滚动一样傲慢。我们不会对我们的网站访问者这样做。为什么要对同行程序员这样做?
换行
- 当源代码行必须断开时,为了满足前面的源代码行长度指南,请在以下一个运算符和标点符号之后换行
{ [ ( . , : ;
+ - * / % & |
^ ! ~ = < > ?
?? :: ++ -- && || ->
== != <= >= += -= *=
/= %= &= |= ^= << <<=
- 或在指示语句尚未完成的关键字之后换行
as in is new
原理
读取源代码时,如果其中一个运算符、标点符号或关键字是行中的最后一个标记,则读者会获得一个宝贵的视觉线索,表明该行已被断开。
标记分隔(参见下面的 Visual Studio)
- 在逗号后面留一个空格。
- 在二元运算符及其操作数之间留一个空格。
- 在所有其他标记之间留一个空格。
代码折叠
- 避免代码折叠。
原理(经许可使用)
我后来才意识到,代码折叠鼓励我编写越来越大的方法,而不去考虑将它们分解成更小的、可管理的方法。结果是我常常最终得到“一次性编写,永不维护”的程序,其中包含大的单体方法。
代码折叠已开始在现代 IDE 中重新出现。这很奇怪,因为代码折叠最初要解决的问题已经以其他更简洁、更不 transient 的方式得到根除——即面向对象设计。
如果您盯着程序看,却顾此失彼,那么代码折叠是错误的答案。答案是更好地构建程序;将细节封装到不同的类中,使用接口、小方法等等。
代码折叠的另一件事是,您最终会浪费大量时间折叠和展开方法,而这实际上并没有带来任何进展。感觉您在工作,因为您在积极地点击;但您实际上并没有取得任何进展。这就像试图通过不断打开和关闭橱柜门来重新整理橱柜里的东西。
Visual Studio
早些时候,在本指南中,我提到了这段文字。Visual Studio 为满足其中一些指南提供了帮助。Microsoft Visual Studio IDE 中的空白和缩进指南可以自动化。在“工具”→“选项”→“文本编辑器”下,有无数设置可以控制源代码的格式。我强烈建议程序员花时间查看这些设置。其中一个优点是,如果您不喜欢给您的代码的格式,您可以简单地剪切所有内容(Ctrl-A;Ctrl-X)并粘贴(Ctrl-V)代码。如果您的文本编辑器设置是您想要的,那么 Visual Studio IDE 将代码重新格式化为您喜欢的样式(这也可以通过“编辑”→“高级”菜单项完成)。
结论
如前所述,没有“正确的方法”来格式化计算机程序,无论布道者怎么说。唯一重要的规则是一致性。如果违反了这条规则,就会导致代码难以阅读。而难以阅读的代码就是难以维护的代码。
我编程几十年了。正如我之前提到的,开发可用指南是我的一种自我防范机制。但有些事情发生过几次,增加了我对这些指南的信心。在我离开一个用 Ada 编写的项目几年后,我接到负责维护我编写的驱动程序的程序员的电话。他认出了我的编码风格,打电话说“谢谢”。尽管该驱动程序有数百行代码,但他还是能够轻松地维护代码。
历史
- 2011年8月4日 - 原始文章