博客文章
值得每个程序员认真读不止一遍的《代码整洁之道》究竟说了些什么?
《代码整洁之道》我读过5遍以上。这篇文章记录最让我对照自己代码脸红的几个判断:童子军军规、可搜索的命名、只做一件事的函数、解释「为什么」的注释、对象与数据结构的边界、不返回null,以及简单设计四规则的优先级顺序。它不教新概念,而是把常识用可执行的标准串起来。
值得每个程序员认真读不止一遍的《代码整洁之道》究竟说了些什么?
这本书我真的是读过5遍以上了,每次看还都能有一些收获,这本书究竟讲了些什么呢?他不讲数据结构,不讲语法,只说了代码怎么写好维护。下面听老宫展开讲讲吧
童子军军规
全书我记得最牢的一句话,反而不是哪条具体规则,是那句童子军军规:离开营地时,让它比你来的时候更干净。
以前改一个 bug,顺手改完就走,绝不多碰一行——生怕改多了引入新问题,也没那个心思。现在心态变了:改到哪块代码,只要顺路,看到一个命名很烂就顺手改了,看到一段重复逻辑就顺手提出来。不是刻意去做”重构任务”,就是路过的时候擦一下灰。这个习惯坚持了几个月,明显感觉到自己维护的几个模块比以前顺眼多了(不过这里又违背了开闭原则)
命名很重要(一定要可搜索)
书里讲命名那一章,说实话道理都不新鲜,但我读的时候是确实有点羞耻。data、info、temp、flag,这些词我写代码的时候用得飞起,图省事,反正自己知道是什么意思。
问题是三个月之后我自己都不知道那个 flag 到底控制的是什么。后来养成一个习惯:写变量名的时候,多花两秒问自己一句”如果这行代码脱离上下文单独拿出来,我还能看懂吗”。答不上来就换个名字。布尔变量尤其如此,isXxx、hasXxx 这种前缀,看着啰嗦,但比 flag 强一百倍。
魔法数字也是,之前写超时时间直接写 3600,谁看了都得反应半天是不是一小时。现在起码会给它起个名字,哪怕只是个局部常量。
函数要短 (只做一件事,本来就短)
“函数只做一件事”这句话听起来像句正确的空话,谁都会说。真正让我改主意的是书里那个判断标准:如果你能从这段代码里再拆出一个函数、并且新函数的名字不是对原函数的简单重复,那说明原函数确实干了不止一件事。
这个标准比”多少行以内”实用多了。我以前写一个处理流程,习惯把校验、算钱、存库、发通知全堆在一个函数里,中间穿插着注释分段。看着挺整齐,其实就是偷懒。现在这种活会拆成好几个小函数,主函数读起来跟看目录一样:
function register(user: User): void {
validate(user);
applyDiscount(user);
persist(user);
notify(user);
}
拆完之后有个意外的好处:写单测方便多了。以前那种大杂烩函数,写测试的时候要构造一堆前置条件才能覆盖到中间某一步,现在每个小函数单独测,轻松很多。
参数个数那条我倒是没完全照做,书里说超过两三个参数就该封装成对象,道理对,但很多时候懒得为一个用一次的场景专门建个类。
注释这块,我以前的理解是反的
我以前觉得注释多是负责任的表现,改完代码习惯性写一句”这里是干什么的”。看完这本书才意识到,这种注释基本等于承认代码没写清楚——如果命名和结构到位,代码本身就该讲清楚”做什么”,注释真正该干的事是解释”为什么”:为什么这里绕了个弯、为什么不能用更直接的写法。
过期注释更让人难受。我确实写过那种改了代码却没改注释的情况,后来的人(包括我自己)直接信了注释,结果被坑。看完这段之后,我现在宁可少写注释,也不留一句可能过期的说明。
被注释掉的死代码也一样,以前总舍不得删,”说不定以后用得上”,现在想开了,有版本控制记着呢,删就删。
对象和数据结构,这条彻底刷新了我的理解
这一章我看了两遍才真的转过弯。之前一直觉得”面向对象”就是把数据和方法包一起,写着写着就写出一堆四不像的类——公开一堆字段又挂几个方法,两头都不占。
书里说得很直接:对象应该隐藏数据、暴露行为;数据结构应该反过来,暴露数据、没有行为。
混着写,两边的好处都拿不到。这条思路帮我理清了不少之前设计不清楚的类,遇到只是拿来传数据的场景,我现在会大方地让它只是个数据结构,不硬塞方法进去。
得墨忒耳定律(最少知识原则)也是这章里让我印象深的,说白了就是别写 a.getB().getC().doSomething() 这种链式穿透。这种写法我以前写起来还挺顺手,觉得省事,现在看就是把内部结构暴露得一览无余,牵一发动全身。
错误处理,别返回 null
用异常代替返回码这条我一直是认同的,倒不算新知识。真正让我改变做法的是”别返回 null,也别传 null”这条。
我以前写方法,查不到东西习惯性 return null,调用的地方各自加判断。问题是总有人忘了判断,NullPointerException 就是这么来的,而且往往炸在离出错原因很远的地方,排查半天。现在会尽量返回空对象或者直接抛异常,让调用方不用猜”这里会不会是 null”。
单测这件事,从”应该写”变成”顺手写”
以前对单测的态度是”知道该写,但总没时间”。这本书没有讲什么新道理,但那句”测试代码和生产代码同等重要”这句话,配合前面函数拆小的习惯一起看,让我真正觉得写测试这件事门槛低了很多——函数一旦拆小、职责单一,测试自然就好写了,不再是一件额外的苦活。
简单设计四规则,短到我第一遍差点漏读
有一章特别短,短到我第一次读的时候几乎没留下印象,当成过渡章节就翻过去了。后来才反应过来,Kent Beck 那套”简单设计四规则”其实到处都有人引用,只是我当时没往心里去。
四条规则,是按优先级排的:
- 通过所有测试
- 不包含重复代码
- 表达了程序员的意图
- 尽量减少类和方法的数量
一开始看这四条觉得又是正确的空话,后来发现顺序才是关键:第一条摆在最前面,意思是先把测试跑通、把行为锁死,剩下三条才是在测试保护下才敢动手做的事——没有测试兜底,谁敢一边重构一边保证没改坏东西。
第二、第三条我平时不知不觉就在做,重复代码看到就想提取,命名不清楚就想改,这些跟前面几章讲的其实是一回事。真正让我多想了一下的是第四条,”尽量减少类和方法的数量”和第一章那种”拆函数、拆类”的建议摆在一起看,其实是有张力的:拆得太狠,元素一多,理解成本也上去了。书里把它排在最后,意思也很明白——先满足前三条,元素数量才是最后才该权衡的事,不能为了少写几个类,反过来牺牲表达力和去重。
没完全被说服的地方
这本书也不是每条都让我心服口服。比如格式那一章讲垂直对齐、水平间距的一些细节,现在基本都靠 formatter 自动搞定了,专门花心思去手动对齐感觉意义不大。系统与并发那一章讲得比较抽象,看完印象不深,可能是我自己实际接触并发场景不算多,共鸣有限。
坏味道那一章列的清单挺实用,但说实话看完没记住几条具体名字,更多是变成了一种”看到重复代码/长函数/多参数就浑身不舒服”的直觉,具体叫什么”坏味道”倒没那么重要。
写在最后
这本书对我最大的作用,不是教会我什么新概念——里面大部分道理,稍微写过几年代码的人多少都听过。它真正做的是把这些散落的常识,用一套具体、可执行的判断标准串起来,逼着你去对照自己写的代码,一条一条地脸红。
如果只让我留一句话提醒自己,还是那句童子军军规:离开时,比来的时候更干净一点就够了。不用等一个”重构专项”,也不用等”有空的时候”,就是这一次改动,顺手做一点。