基础介绍
程序员荐书的榜单上,屡屡看到《代码整洁之道》 ,处于程序员自我修养的需要,买来拜读一下,按照原书的章节,整理相关内容如下。
全书梗概
有意义的命名
- 代码中命名必须名副其实,保证命名不需要额外的注释来解释;
- 避免误导性的命名,比如与特定名词相同的命名,以及不同之处较小的命名;
- 命名应该是有意义,避免无意义的废号,比如 a, the, variable 等无意义的名字;
- 类名的命名应该是名词或名词短语,方法名应该是动词或动词短语;
- 在工程中,每个概念对应一个词,并且一以贯之,不要随意更换;
- 使用解决方案领域的名称,尽量使用计算机科学术语,算法名,模式名或数学术语,因为阅读你的代码都是程序员,使用程序员共同的词汇易于理解;
- 使用源自问题领域的名称,如果某些问题与问题领域相关,可以使用问题领域相关的词汇,这样程序员至少可以请教问题领域相关的专家来交接相关名词;
- 给名称添加有意义的语境,如果可以的话,可以使用累,函数,或命名空间来放置名称,提供语境;如果这些都做不到,可以在名称前添加前缀;
函数
-
函数最重要的规则就是要短小,短小的代码更易于理解;
-
函数应该只做一件事情,判断函数是否只做了一件事的方法:判断函数内的代码是否是同一抽象层上的步骤。另一判断方法是:查看当前函数是否还能被拆出一个函数;
-
每个函数只有一个抽象层级,多个抽象层级难以理解,读者难以理解某个表达式是基础概念还是细节,不利于代码的阅读;
-
switch会导致函数比较大,建议是将switch埋在抽象工厂地下,不需要其他人关注;
-
函数参数尽量少,避免三个及以上的参数;
-
代码需要保证无副作用,尽量避免执行一些隐含的副作用,而且在函数名上还看不到此副作用。这种代码很容易被错误地使用;
-
分隔指令和查询,将修改型的方法与查询型的方法分隔,修改型的方法不要提供返回值;
-
使用异常提到错误码,使用错误码的方式就会导致违背指令和查询分隔的原则,这样调用者被迫写出类似如下所示的代码:
if (deletePage(page) == E_OK)
-
抽离 try/catch 代码块,将 try/catch 代码块从正常的主体部分抽离出来,因为函数只做一件事,而 try/catch 异常处理本身就是一件事,那么就不应该与正常的逻辑代码在写在同一个函数中了;
-
不要重复自己,重复的代码是邪恶的根源;
-
结构化编程,每个函数只有一个入口,一个出口,因此代码中只应该出来一个 return 语句;
-
不一定最初就要写出完全符合规范的函数,可以先满足需求,接着再打磨和重构代码;
注释
- 最好的注释是没有注释,尽量使用代码本身去解释行为,而注释本身也不能挽救差的代码;
- 下面是仅剩的值得写注释的情况:
- 法律信息,比如 copyright 之类的信息;
- 提供信息的注释,比如使用注释去解释特定的返回值,但是更好的办法是使用函数名称代替注释;
- 对意图的解释,有时候给出一个特定的解决方案,为了让后来的人理解意图,可以加上注释;
- 对一些难以理解的参数或返回值的意义的注释,可以将难以理解的值的可读性提高;
- 警示,对后续修改此段代码的警示;
- TODO 注释,表示一些后续带完成或优化的内容;
- 公共 API 中的 Javadoc
格式
-
代码格式和可读性会影响可维护性和可拓展行,必须慎重对待;
-
垂直格式:
- 封包声明、导入声明和每个函数之间,都需要空白行隔开;
- 垂直方向关系紧密的代码应该靠近,关系疏远的代码之间应该分隔;
- 变量声明应该靠近使用的位置;
- 相关的函数应该靠近,调用函数应该放在被调用函数的上面;
-
水平格式:
- 单行代码的长度不应该超过可视范围,建议不超过120字符;
- 根据代码的层次关系正确使用缩进,方便阅读;
- 空范围的分号需要另起一行并增加缩进,否则很难阅读吗,类似如下:
while (dis.read(buf, 0, readBufferSize) != -1) ;
-
团队之间应该有统一的格式;
对象与数据结构
- 对象与数据结构看起来类似,事实上有一些不同之处,对象将数据隐藏在抽象之后,暴露操作数据的函数;而数据结构则暴露数据,不提供有意义的数据;
- 德墨忒尔律:模块不应了解其所操作对象的内部情形,对象主要暴露操作,而隐藏数据,因此不应该给对象添加存取器暴露其内部结构;
- 数据传送对象,可以通过构建的数据结构传输一系列数据,此时就不需要增加相关操作,只需要增加存取器即可;
错误处理
- 不要使用返回码而应该使用异常,这样方便写出更清晰的代码,否则扰乱使用者,而且使用者不得不将异常处理与正常的业务逻辑写在一起,非常不优雅;
- 抛出的每个异常都应该有足够的环境信息,表明错误的来源与出处;
- 可以根据自己需要定义异常类,比如在使用第三方API时,可以将第三方API中的异常进行打包,降低对第三方API的依赖;
- 特例对象,某些情况下,可以将异常处理转化为返回特定的特例对象,方便调用者写出更漂亮的代码;
- 方法不要返回null值,这样会只要使用者没有检查 null 值就会导致
NullPointerException
, 可以选择抛出异常或者返回特例对象; - 不要传递null值,这样也容易导致异常;
浏览和学习边界
- 在使用第三方服务时,为了避免因为第三方服务的修改而需要大量修改业务代码,可以根据业务场景都第三方服务进行封装,定义好相关边界;也可以利用适配器模式对第三方接口进行一对一的封装;
单元测试
- 测试驱动开发(TDD)原则:
- 在编写不能通过的单元测试的代码之前,不能编写生产代码;
- 只可编写刚好无法通过的单元测试,不能编译也算不通过;
- 只可编写刚好足以通过当前失败测试的生产代码;
- 必须保证测试代码的整洁,否则测试代码质量的低下会导致生产代码的质量的腐坏;
- 每个测试只有一个断言,每次测试一个概念,主要强调测试应该尽可能小,不要将多个测试糅合在一起;
- 测试应该遵循的原则:
- 快速,因为测试需要反复多次执行,执行太慢会让使用者失去耐心,放弃测试;
- 独立,测试用例之间应该是独立的,不应该存在相互依赖的关系;
- 可重复,测试用例会反复执行,需要保证可重复性;
- 自足验证,测试用例本身就需要能验证测试是否通过,不应该再依赖日志等去验证测试用例;
- 及时,测试用例应该及时编写,在生产代码编写之前就应该编写测试代码;
类
- 类的变量和工具函数应该尽量私有化;
- 类应该尽量短小,而类的短小在于权责应该尽量少,一般情况下,类应该只有一个权责,也就是说类只有一个修改它的理由;
- 类应该保持内聚性,如果类中内聚性不足,可以考虑分拆类;
迭进
- 遵循 Kent Beck 关于简单设计的规则,对于设计出好的软件帮助巨大,下面是具体的规则:
- 运行所有测试;
- 不可重复;
- 表达了程序员的意图;
- 尽可能减少类或方法的数量;
- 运行所有测试:保证系统的可测试性,只要系统可测试,就会导向保持类短小且单一的设计方案。保证系统的可测试性,也会将项目导向松耦合的设计方案;
- 不可重复:重复是良好测试的大敌,必须努力清除重复;类似的代码也可以通过调整得更相似之后进行重构;
- 表达力:提升代码的表达力,让代码易于理解和维护,提升的方法如下:
- 选用好的名称,好的类名和函数名会利于理解;
- 保持函数和类的尺寸短小;
- 通过采用标准命名法来实现,比如通过VISITOR去表达访问者模式,清晰表达你的设计;
- 编写好的单元测试,单元测试在一定程度上可以起到文档的作用;
- 最重要的方法还是尝试,在完成功能之后,努力对代码进行重构以便更容易理解;
- 尽可能减少类和方法:不要因为教条主义去增加无意义的类和方法,在保证类和方法短小的基础上,尽可能减少类和方法的数量;
并发编程
- 并发编程防御原则
- 单一权责原则,分离并发相关代码与其他代码;
- 限制数据作用域,临界区的数量越多,可能出错的可能性越大,限制临界区的数量;
- 使用数据副本,对于需要使用共享数据的地方,看看是否可以通过使用数据副本进行替代,避免增加临界区;
- 线程独立,将数据尽可能分离,各个线程使用独立的数据,避免数据的共享;
- 尽可能减少同步区域,避免不必要的代码加入同步区域;
总结
整体看下来,《代码整洁之道》是一本还不错的技术书,介绍了各种写出更漂亮代码的原则与实践,与《重构》可以配合看,基本的原则就都覆盖到了。但是和《重构》比较起来,后续章节大段粘代码,而且和重构比起来,总结的优雅代码原则的全面性和丰富性上差了一些。比较了一下豆瓣评分,群众的眼睛还是雪亮的。