CodingTour
Clean Code 格式

抛掉“只要代码能跑”的想法,今天编写出的代码,可能要在下个版本中修改,但代码的可读性却会对未来潜在修改行为产生深远的影响。

垂直原则

用 200 行、最长 500 行的文件组装成系统。虽然这并非不可违背的原则,但也应该乐于接受 — 通常小文件比大文件更易于理解。

向报纸学习

想象一篇写得很好的报纸文章,你是从顶部开始垂直阅读它的,标题会告诉你是关于什么的故事,并让你决定是否要进一步阅读。然后第一段给你整个故事的概要,隐藏了所有细节的同时为你提供了粗略的概念。随着你不断往下阅读,信息会不断增加,直至你得到了所有的像是日期、姓名、报价等各种细枝末节的信息。

好的源代码也应该像报纸上的文章那样,名称应当简单、一目了然。名称本身应该包含足够的信息告诉我们是否在正确的模块中,源文件最顶部应当给出高层次概念和算法,然后再往下逐渐展开,直至在源文件最底部找到函数实现和具体细节。

一份报纸由许多文章组成,大多数都非常短小,有些会稍微长一些,很少有页面会包含大量的文本,如果一份报纸只是包含了一堆杂乱无章的事实,那么没人想要阅读它。

分隔垂直方向上的不同概念

几乎所有的代码都是从上往下、从左往右读,每行展现一个表达式或一个子句,每组代码行展示一条完整的思路,这些思路用空行分隔开。

在封包声明、导入声明和每个函数之间,都用空行分隔开,这条极其简单的规则极大地影响了代码的视觉外观。每个空行都是一条线索,它标识出了不同的概念,往下读代码时,你的目光总会停留于空行之后的那一行。

垂直密度

如果说空行隔开了不同的概念,靠近的代码行则暗示了它们之间的紧密关系,所以紧密相关的代码应该相互靠近。

以下面两个代码块为例,就像你看到的,后者比前者更易读:

该代码包含了无意义的注释,并破坏了实例变量之间的关联关系。

这段明显更易读,代码 “eye-full”,至少对我来说是如此(Bob)。能一眼看出这个类包括两个实例变量和一个方法,不需要移动眼睛和头,而上一段代码将被迫移动眼睛和头多次才能达到相同的理解程度。

垂直距离

关系密切的概念应该相互靠近。但这条规则并不适用于分布在不同文件中的概念,除非有很好的理由,否则就不要把关系密切的概念放到不同的文件中 — 这也是避免使用 protected 变量的理由之一。

对于那些关系密切、放置于同一文件中的概念,他们之间的间隔距离应该作为可读性的重要衡量指标,这能有效避免读者被迫在源文件或类中跳来跳去。

变量声明: 变量声明应尽可能靠近其使用位置。如果函数很短,本地变量应该出现在函数的顶部:

循环体中的控制变量应该总是在循环语句中声明:

极少数情况下,变量也可能声明在代码块的顶部或循环体之前:

两个嵌套的 for 循环使用了代码块顶部声明的 tr 变量。

实例变量: 实例变量应该在类的顶部声明。这应该不会增加变量的垂直距离,因为在设计良好的类中,实例变量是被该类中很多方法同时使用的,而每个人都知道该去哪里找实例变量的声明。

依赖函数: 若某个函数调用了另外一个函数,就应该把它们放在一起,而且调用者应该尽可能放在被调用者上面,这样程序就了一个自然顺序。若坚定地遵循这条约定,读者将总能确信函数定义会在其调用后很快出现:

概念相关性: 某些代码有相同的概念,因此应该把它们放在一起,相关性越强,它们之间的垂直距离就应该越短。

这种相关性可能来自于一个函数调用了另一个函数,或一个函数使用了一个变量,但也可能是来自它们有相似的操作:

这些函数有很强的相关性,比如名称有公共前缀,以及它们的行为也类似。

垂直顺序

一般而言,我们想自上而下展示函数调用依赖的顺序。也就是说,被调用的函数应该放在执行调用的函数下面。这样就建立了一种自顶向下贯穿源代码的良好信息流。

就像报纸上的文章,我们期望将最重要的概念排在第一位,并用最精简的话语来表达它们,然后在最后才包含 low-level 细节,这使我们在浏览源文件时,可以从前几个函数中获取要点,而不必过早陷入到细节之中。

横向原则

应该尽力保持代码行短小。死守 80 个字符的上限有些僵化,而且也不反对代码行长度达到 100 个字符或 120 个字符。但是再多的话,大抵就是肆意妄为了。

水平方向上的区隔与靠近

我们使用空格把紧密相关的事物连接到一起,也用空格把相关性较弱的事物分隔开。

空格的另一种用法是强调其前面的运算符,如在赋值操作符周围加上空格,以此达到强调目的。赋值语句有两个确定而重要的要素:左值和右值。空格的存在加强了分隔效果:

在乘法因子之前不加空格,因为它们具有较高优先级。加减法运算项之间用空格隔开,因为加法和减法优先级较低。

另一方面不要在函数名和左圆括号之间加空格。这是因为函数与其参数是密切相关的,隔开就会显得毫无关系。但函数调用括号中的参数可以用逗号一一隔开,强调参数之间是彼此分离的。

水平对齐

对齐并没有什么用,水平上的对齐似乎在强制错误的事,它不仅使眼睛偏离了原有的轨道,而且很难同时关注变量的名称与它的类型,这与赋值操作不同,赋值操作的左值和右值是可以独立阅读的,但变量声明却不是。更糟糕的是,格式化工具通常会消除这种对齐,所以如果有真的有较长的列表想做对齐处理,那么问题就是在列表的长度上而不是对齐上:

这张图与原文略有不同,原文在 FitNesseExpediter 构造函数之上有一个空行

缩进

源文件是一种继承结构,而不是一种大纲结构。其中的信息涉及整个文件、文件中每个类、类中的每个方法、方法中的每个代码块,也涉及代码块中的代码块。这种继承结构中的每一层级都圈出一个范围,名称可以在其中声明,而声明和执行语句也可以在其中解释。

要让这种范围式继承结构可见,我们依源代码行在继承结构中的位置对源代码行做缩进处理。在文件顶层的语句,例如大多数的类声明不用缩进,类中的方法相对该类缩进一个层级,方法的实现相对方法声明缩进一个层级,代码块的实现相对于其容器代码块缩进一个层级,以此类推:

团队规则

记住,好的软件系统是由一系列读起来还不错的代码文件组成起来的,它们拥有一致和顺畅的风格。读者要能确信,他们在一个源文件中看到的格式风格和在其他文件中是相同的。绝对不要用各种不同的风格来编写源代码,这样会增加其复杂度。