Clean Code

代码整洁之道

Posted by Wanglizhi on September 10, 2018

阅读本书有两种原因:第一,你是一个程序员;第二,你想成为更好的程序员。

第一章 整洁代码

代码会不会消失?人工智能会不会替代程序员的工作?

作者认为我们永远抛弃不掉代码,因为代码呈现了需求的细节,无法被忽略或者抽象,必须明确之。即便是人类,倾其直觉和创造力,也造不出满足客户模糊感觉的成功系统来。语言的抽象成都会继续提升,领域特定语言的数量也会继续增加,但代码终结不了。从我的认知来看,或许有些程序员的部分工作会被取代,最近微软发布了一个通过草图生成界面的软件,一方面我对其代码质量表示怀疑,是否方便维护,另一方面,这种生成的界面其设计水平是否可以达到上线的需求也有待商榷,或许只是对于原型来说是一个很好的辅助工具。

糟糕的代码

我们有专有的词来形容糟糕的代码:沼泽(wading)。这些代码怎么产生的呢?或许是赶时间快点完成任务,总是想着回头再清理,而勒布朗法则告诉我们:稍后等于永不(Later equals never)。制造混乱无助于赶上期限,它只会拖慢你,让你错过期限。所以你始终尽可能要保持代码整洁。写整洁代码,需要遵循大量的小技巧,贯彻刻苦习得的“整洁感”。

什么是整洁代码

Bjarne Stroustrup,C++之父

我喜欢优雅和高效的代码。代码逻辑应当直截了当,叫缺陷难以隐藏;尽量减少依赖关系,使之便于维护;依据某种分层战略完善错误处理代码;性能调至最优,省得引诱别人做没规矩的优化,搞出一堆混乱来。整洁的代码只做好一件事。

Grady Booch,《Object Oriented Analysis and Design with Applications》作者

整洁的代码简单直接。整洁的代码如同优美的散文,从不隐藏设计者的意图,充满了干净利落的抽象和直截了当的控制语句。

Dave Thomas,OTI公司创始人,Eclipse战略教父

整洁的代码应可由作者之外的开发者阅读和增补。它应有单元测试和验收测试,使用有意义的命名,它只提供一种而非多种做一件事的途径。它只有尽量少的依赖关系,而且要明确定义和提供尽量少的API。代码应通过其字面表达含义。

Michael Feathers,《Working Effectively with Legacy Code》作者

整洁代码根本性的特点:总是看起来像是某位特别在意它的人写的。几乎没有改进的余地,代码作者什么都想到了,如果你企图改进它,总会回到原点,赞叹某人留给你的代码。

第二章 有意义的命名

名副其实

变量、函数或类的名称应该已经答复了所有的大问题,它告诉它你为什么会存在,它做什么事,应该怎么用。注意命名,而且一旦发现有更好的名称,就替换掉旧的。如果名称需要注释来补充,那就不算是名副其实。

避免误导

  • 程序员必须避免留下掩藏代码本意的错误线索。例如:hp、aix、sco都不应该用变量名,它们都是专有名称;
  • 别用accountList来指称一组账号,除非它真的是一个List类型,用accountGroup或bunchOfAccounts,甚至accounts都会好一些。即便容器是一个List,最好也别在名称中写出容器类型名。
  • 提防使用差异较小的名称。两个名字外形相似不利于查找和区分。
  • 以同样的方式拼写出同样的概念才是信息。个人推荐每个Team对于一些专有名字的命名有一个checklist。

做有意义的区分

  • Info和Data就像a,an和the一样,是意义含混的废话。

  • 废话就是冗余。Variable一词永远不应当出现在变量名中,Table不应当出现在表名中

程序员怎么能知道该调用哪个函数呢?

getActiveAccount();
getActiveAccounts();
getActiveAccountInfo();

使用读得出来的名称:人类长于记忆和使用单词,应该尽量避免使用自造词。

使用可搜索的名称:名称长短应与其作用域大小相对应。对于全局变量或常量,应该赋予便于搜索的名称。

避免使用编码

  • Java程序员不需要在名字中体现类型,对象都是强类型的。

  • 不需要用m_前缀来标明成员变量

  • 有时候会出现采用编码的特殊情形,比如在接口前加I,但作者认为被滥用了,使用者并不需要知道是接口,如果接口和实现必须选一个进行编码的话,ShapeFactoryImpl更好。

类名和方法名

类名和对象名都应该是名词或名词短语,如Customer、Wi 看 iP 阿哥、Account和AddressParser,避免使用Manager、Processor、Data或Info这样的类名。

方法名应当是动词或动词短语,如postPayment、deletePage或save。

其他

每个概念对应一个词,并且一以贯之。例如,不要将fetch, retrieve和get混用。

别用双关语,避免将同一个单词用于不同目的。

第三章 函数

短小,只做一件事,做好这件事。

每个函数一个抽象层级

Switch语句,将其埋到抽象工厂地下,不让任何人看到。

参数:最理想的参数数量是0,其次是一,再次是二,应尽量避免三。标识参数丑陋不堪,isTrue;

无副作用,避免对类变量做出未预期的改动;避免把变量搞成函数参数或系统全局变量

错误处理

  • 使用异常替代返回错误码

  • 抽离Try/Catch代码块,把主体部分抽离出来,另外形成函数;

  • 错误处理就是一件事,try/catch/finally前后不应该有其他内容

别重复自己

不断打磨代码:分解函数,修改名词,消除重复。

第四章 注释

别给糟糕的代码加注释——重新写吧

注释是弥补我们在用代码表达意图时遭遇的失败,注释总是一种失败。代码在变动,在演化,但程序员不能坚持维护注释。只有代码能忠实地告诉你它做的事。

好注释

  • 法律信息
  • 提供基本信息的注释
  • 对意图的解释
  • 阐释
  • 警示
  • TODO注释
  • 公共API中的Javadoc

坏注释

  • 喃喃自语
  • 多余的注释,简单函数的头部注释都是多余的。
  • 每个函数都要有Javadoc或每个变量都有注释事愚蠢的,会让代码变得散乱
  • 日志式注释,模块开始记录模块的修改日志,对于使用Git的我们来说,完全没用
  • 归属与署名,源码控制系统会记录谁在何时添加了什么,所以也是没必要的。
  • 注释掉的代码,直接把代码注释掉是讨厌的做法。删掉即可。
  • HTML注释,源代码注释中的HTML标记是一种厌物。

第七章 错误处理

使用异常而非返回码

使用不可控异常,即不用声明throw

别返回null值,别传递null值

第九章 单元测试

TDD三定律

  • 在编写不能通过的单元测试前,不可编写生产代码
  • 只可编写刚好无法通过的单元测试,不能编译也算不通过
  • 只可编写刚好足以通过当前失败测试的生产代码

保持测试代码整洁,三个要素:可读性,可读性和可读性。

整洁的测试遵循5条规则

  • 快速(Fast)

  • 独立(Independent)
  • 可重复(Repeatable)
  • 自足验证(Self-Validating)
  • 及时(Timely),测试应及时编写,写生产代码之前。

第十章 类

类应当短小,只有少量的实体变量,保持类的内聚性。如果一个类的每个变量都被每个方法所使用,则该类具有最大的内聚性。内聚性强,类代码就会变短。

单一职责原则:类或模块应有且只有一条加以修改的理由。

隔离修改,对新增功能开放,对修改关闭。

依赖倒置原则,类应当依赖于抽象而不是具体细节。