本书分为三个部分:理论、实践与测试。理论部分阐述了软件工程师在编写代码时是如何考虑与权衡的,包括什么样的代码算是好代码,好代码应该拥有怎样的结构,与其他工程师交流时该用怎样的代码契约,以及在错误发生时怎样通知才能更快地定位问题。
在该部分,作者还提出了代码质量的“6大支柱”,分别对应实践部分的章节。
实践部分则是在讲理论知识可以怎样真正运用到代码中。每章的标题就很能说明问题:
这5章分别对应代码6大支柱的前5个,也是书中篇幅最长,讲解最详细的一部分。测试部分对应最后一个支柱——“编写可测试的代码并适量测试”,该部分也分为理论和实践两个章节。正如书名中“好代码”与“坏代码”相互对应,书中的绝大部分内容也都是成对出现的。在具体章节中,会先讲坏代码的“长相”,以及坏代码可能会造成的影响等。讲完坏处,作者再提出可能的优化方向,展示好代码该有的样子。本书的结构清晰,遵循“提出问题,然后解决问题”的思路。开始阅读本书时,我刚刚完成了一个12k行代码的后端框架搭建项目,正在进行收尾、完善的工作。到整理本篇读书笔记时,这个项目已基本完成。所以,整个阅读过程也是我的开发过程,在这个理论与实践互相印证的过程中,我印象最深刻的是以下4点。
代码契约在书中的定义是:
工程师将不同代码段之间的交互视为一种契约:调用者必须履行一些义务,作为回报,调用的代码将返回预期的值或修改某些状态。不应该有任何不清晰或意外的情况。因为一切都该在契约中定义。
这是最完美的情况,现实世界并不是完美的。契约有明确无疑的部分,也会存在并不明确的附属细则。好代码应该尽量将不明确的点减少到最少,不过分依赖附属细则。
细化到项目中,以函数举例,一个函数的功能应该由它本身的输入输出准确界定,而并非由相关文档、注释来辅助说明。文档可能是过期的,真正“说话算数”的只是代码。我们写一个函数,要尽量保证它不需要额外的依赖项。一切都该是准确的。
我还在网易做游戏的时候,项目组内有一个口口相传,传了至少十几年的规则:服务端的报错要直接报出来,客户端的报错可以做兼容处理,但也应该把堆栈打到 log中去。
待读到本书“显式报错、隐式报错”时,真正感受到理论是来源于实践的,作者肯定写过许多代码,他的整理与大佬们的经验一致。
书中示例“寻找松露的狗”是很形象的比喻:我们借助狗的嗅觉找松露,好狗会在找到松露时立马停下并叫出声来,坏狗找到松露后随意向周围走10步再叫。明显好狗找松露的效率更高。
当代码中有错误出现时,应该立马让这些错误报出来。这能更准确地定位问题,由此才能更快地解决问题。(经过本书的再次提醒,我将代码中好几处返回空值做兼容处理的地方改成了断言。)
书中说“如果我们能将一个问题拆分为很多的小问题,并创建抽象层次,任何一段单独的代码看起来都不会很复杂,因为一次只需要理解几个概念。”
我从这一理念中受益匪浅。在软件开发中,分层与模块处处存在,大到网络协议的分层,中到引擎与逻辑的拆分,再小到我的项目,都使用了分层结构。其他的不提,以我的小项目举例,它的分层结构来源于Django 的REST framework框架:
每个层又拆分为(尽量)各不相关的小模块。借助REST framework设计系统,我在定位问题、迭代需求时,速度是很快的。小时候切菜,老妈经常告诉我:“切完菜,菜刀全身不要伸出灶台,以免误碰到地上,掉落过程很危险;刀口要朝里,再拿的时候不给机会划到手。”这是生活中的防御性习惯。
代码应该是防御性设计的,要避免编写拥有误导性的函数,要减少调用者出现误用的可能性。模块、类、函数的命名,以及参数、变量的作用域,都应该是含义准确的。
读完本书,我很认同作者表达的观点——我们每个人都会犯错误。作为程序员,我们写的代码是肯定会出bug的。我们能做的是认真对待自己的代码,多在代码上面花心思,借助前人的经验,使用良好的代码风格,避免犯前人犯过的重复错误,最终减少bug的数量。
作者在介绍编写代码的许多原则之外,还说到“切忌生搬硬套规则,现实世界需求多变,我们是可以根据需求做些变化的。”
作者说本书适合有0~3年软件工程师工作经验的人阅读。我想将这一年限再扩大些:1~10年经验的程序员都可以读上一读。这两个数字由我自己的经验得来,如果刚毕业的我看本书,极可能看不下去。我现在敲代码快满10年,读完依然能有许多感悟,我认为是很值得看的。