写点什么

《代码整洁之道》原则整理

用户头像
insight
关注
发布于: 2020 年 04 月 30 日

代码整洁之道

第一章:整洁代码

程序员的迷思:有那么几年经验的开发者都知道,之前的混乱拖了自己的后腿。但开发者们背负期限的压力,只好制造混乱。这其实是错的:制造混乱无助于赶上期限。混乱只会立刻拖慢你,叫你错过期限。赶上期限的唯一方法,即做得快的唯一方法 :始终尽可能保持代码整洁。

为什么呢?

  1. 我们读写代码的时间比例大概是10:1,换言之,我们花费大量时间去阅读之前的代码,写出整洁的代码有助于减少读代码的时间。

  2. 整洁的代码每个部分往往都足够小,这让编写单元测试变得简单可行,从而能减少代码的错误率。

  3. JVM底层会对编译短的方法,而遇到逻辑复杂度高的代码,则会使用解释器来执行,所以某种程度来说,整洁的代码效率更高。

最后还要注意:光把代码写好可不够。必须时时保持代码整洁。我们都见过代码随时间流逝而腐坏。我们应当更积极地阻止腐坏的发生。借用童子军军规就是:让营地比你来时更干净。而且清理并不一定要花多少功夫,也许只是改好一个变量名,拆分一个有点过长的函数,消除一点点重复代码,清理一个嵌套if语句,这样就能保持代码的整洁。

第二章:有意义的命名

命名要想有意义,必须满足以下的要求:

  1. 名副其实:即命名能够准备表达代码的逻辑或者抽象出来的内容。命名应该能告诉你,它为什么会存在,它做什么事,该怎么用。

  2. 避免误导:避免留下掩盖代码本意的错误线索

  3. 做有意义的区分:命名同一个范围中的不同抽象时,要使用区分度足够大的命名。

  4. 使用读得出来的名称:用单词而不是无意义的缩写来命名。

  5. 使用可搜索的名称:名称长短应与作用域相对应;广泛使用的变量或常量应该赋予方便搜索的长名称。

  6. 避免使用编码:即将类型或作用域编进名称里面,换言之就是避免三种情况:

  7. 不用使用匈牙利语标记法

  8. 不要使用成员前缀

  9. 接口不要加前导字母I

  10. 避免思维映射:不应当让读者在脑中把你的名称翻译为他们熟知的名称。

  11. 类名和对象名应该是名词和名词短语。

  12. 方法名应该是动词或动词短语。

  13. 别扮可爱:不要再代码中使用俗语或俚语。

  14. 每个概念对应一个词:给每个抽象概念选择一个词,并且一以贯之。

  15. 别用双关语:避免将同一单词用于不同目的。

  16. 添加有意义的语境:即通过良好命名的类、函数或名称空间来放置名称,给读者提供语境。

  17. 不要添加没用的语境:

  18. 给每个类加上应用前缀不是好主意。

  19. 只要短名称足够清楚,就比长名称要好,别给名称增加不必要的语境。

编程中的小tips

  1. 计量相关变量,名称应指明计量对象和计量单位

  2. 提防使用不同之处较小的名称

  3. 废话是没有意义的区分:Info 和 Data 就像 a、an 和 the 一样,是意义含混的废话。

  4. 避免使用 Manager、Processor、Data 或 Info 这样的类名。



第三章:函数

函数是代码里很关键的一部分,只要遵循一些原则,马上就能让你的代码变得可读性强,复杂度低。

一、短小

函数要足够短,什么样的程度叫做短呢?书中提到了几点:

  1. 20行封顶最佳

  2. 每个函数都应该一目了然,每个函数都只说一件事,并依序把你带到下一个函数当中。

  3. 注意代码块:if、else、while语句等,其中的代码应该只有一行。该行大抵应该是一个函数调用语句。

这样做的好处是:代码短小,且块内调用的函数拥有较具说明性的名称,从而增加了文档的价值。

  1. 注意缩进:函数不应该大到足够容纳嵌套结构。因此函数的缩进层级不该多于一层或两层。



二、只做一件事

函数应该做一件事,做好这件事,只做这一件事。

如何判断函数是否只做了一件事呢?

  1. 看函数中的内容是否是同一个抽象层级上的步骤。

  2. 看能够再拆出一个函数,该函数不仅只是单纯地重新描绘其实现。

  3. 如果一个函数能被划分成好几个区段,就是做事太多的体现。只做一件事的函数无法被合理地切分成多个函数。



三、每个函数一个抽象层级

要确保函数只做一件事,函数中的语句都要在同一抽象层级上。

因为函数中混杂不同的抽象层级,往往让人迷惑。读者往往无法判断某个表达式时基础概念还是细节,并且还会带来破窗效应。

向下规则:每个函数后面都跟着位于下一抽象层级的函数。(当同一抽象层级中包含多个下一抽象层级时,先将下一抽象层级以及之后层级全部引出之后再引出下一个)



四、switch语句的优化

我们很可能遇到根据某个字段执行不同行为的情况,而且一旦出现,可能就不止出现在一个函数当中,而是会在多个函数中重复出现,如果出现新的字段时,就需要对多个函数进行修改,不符合开闭原则,也很难保证不发生错漏。

因此对于这种情况最好的优化,就是将switch语句埋到抽象工厂模式底下,不让任何人看到。所谓埋到抽象工厂底下,是指通过switch语句创建同一个接口的不同实体,而不同函数则通过调用实体的实现方法来执行,这样就可以保证开闭原则,也能将修改限制在一处。

所以,如果switch语句只出现一次,用于创建多态对象,而且隐藏在继承关系中,在系统其他部分看不到,就能容忍。当然还是要就事论事。



五、使用描述性名称

取名有几个原则:

  1. 别害怕长名称,长而具有描述性的名称,要比短而令人费解的名称好。

  2. 长而具有描述性的名称要比描述性的长注视好。

  3. 使用某种命名规范,让函数名称中的多个单次容易阅读,然后使用这些单次给函数取一个能说清其功用的名称。

  4. 命名方式要保持一致性。使用与模块名一脉相承的短语、名词和动词给函数命名。



取名带来的启发:

  1. 函数越短小、功能越集中,就越便于取个好名字。

  2. 选择描述性的名称能理清你关于模块的设计思路,并帮你改进之。



六、函数参数应该尽可能少

最理想的参数数量是零个,其次是一,再次是二、应尽量避免三,除非有足够特殊的理由。

为什么要少,原因如下:

  • 参数带有太多概念性,且往往和函数处于不同的抽象层级,它要求你了解目前并不特别重要的细节。

  • 从测试的角度来看,编写能确保参数的各种组合运行正常的测试用例非常困难。

一元函数

向函数传入单个函数有两个普遍的理由:

  1. 询问:问关于那个参数的问题。比如 boolean fileExists("MyFile")

  2. 转换:将该参数转换为其他东西,再输出之。

注:取名时应该选用较能区别这两种理由的名称,且总在一致的上下文中使用这两种形式。

还有一种不那么普遍但较有用的一元函数——事件。即有输入参数而无输出参数,将函数看作一个事件,使用该参数修改系统状态。

二元函数

二元函数比一元函数难懂,而且如果类型相同,则很可能导致传入参数位置错误。

因此应尽量利用一些机制将其转换为一元函数,比如对于 writeField(outputStream,name)

  1. writeField 方法写成 outputStream 的成员之一,从而可以这样用: outputStream.writeField(name)

  2. outputStream 写成当前类的成员变量,从而无需再传递它。

  3. 分离出类似 FieldWriter 的新类,在构造器中接收 outputStream,并且包含一个 write 方法。

三元函数

三元函数会比二元函数难懂的多,排序,忽略等问题会加倍出现,因此,写之前要尽可能想清楚。

将参数封装成对象

如果函数看起来需要两个、三个或者三个以上参数,就说明其中一些参数应该封装成类了。

输出参数

输出参数就是指将参数传递进函数中后,修改参数的内容,最后再使用修改后的参数。

使用输出参数会让人难以理解。读函数时,我们惯于认为信息通过参数输入函数,通过返回值从函数中输出。因此我们不太期待信息通过参数输出。因此如果要对输入参数进行转换,转换结果就应该体现在返回值上,即使只是简单地返回输入参数也好。

标识参数(Boolean 类型参数)

标识参数最好不要使用,因为这标志着函数不止做一件事:如果标识为true将会这样做,如果为false则会那样做。

最好的方式是把函数一分为二。

动词和关键字

给函数取一个好名字,能较好地解释函数的意图,以及参数的顺序和意图。

  • 对于一元函数:函数和参数应该形成一种非常良好的动词/名词对形式。如:writeField(name),说明 name 这个 Field 要被 write。

  • 对于其他函数,将参数名称编码成函数名,并且遵循顺序,能大大减轻记忆参数顺序的负担。



第四章:注释

(未完待续)

第五章:格式

(未完待续)

第六章:对象和数据结构

(未完待续)



用户头像

insight

关注

还未添加个人签名 2018.11.17 加入

顺丰科技小码农

评论

发布
暂无评论
《代码整洁之道》原则整理