这是我刚入职的时候,mentor 推荐的一本书。当时的代码写得真的挺糟糕的,根本不知道什么是好的代码。这本书帮我建立了一个模糊的标准,知道写好代码应该有哪几个可以努力的方向。最近又重读了一遍,很多之前不能完全理解的内容慢慢开始理解,以前觉得自己懂了的内容又有了新的认识。
软件成本由开发成本与维护成本组成,而往往维护成本要远高于开发成本。这其中耗费的主要成本就是由于理解代码和修改代码造成的。
——— 《Structured Design》
代码应当易于理解
可读性基本定理
- 代码的写法应当使别人理解它所需的时间最小化
- 那个“其他人”可能就是 6 个月后的你自己,那时你自己的代码看上去已经很陌生了
代码总是越小越好吗?
- 相比减少代码行数,把理解代码所需的时间最小化是一个更好的目标
理解代码所需的时间是否与其他目标有冲突
- 让你的代码容易理解往往会把它引向好的架构且容易测试
表面层次的改进
选择好的名字、写好的注释以及把代码整洁地写成更好的格式。
1. 把信息装到名字里
1. 选择专业的词
“把信息装入名字中”包括要选择非常专业的词,避免使用“空洞”的词
找到更有表现力的词:用于使用同义词典或者问朋友更好的名字建议
2. 避免泛泛的名字
- 避免使用像 tmp 或 retval 这样的名字
- 好的名字应当描述变量的目的或者它所承载的值
- tmp 这个名字只应用于短期存在且临时性为其主要存在因素的变量
- 循环迭代器中长使用 i、 j、 iter、 it 等名字做索引,但在嵌套的循环迭代器中,可以将索引的第一个字母与数据的第一个字符匹配
3. 用具体的名字替代抽象的名字
- 把名字描述得更具体而不是更抽象
- 因为一个名字含糊婉转而选择它可能不是一个好主意
4. 使用前缀或后缀来给名字附带更多信息
- 一个变量名就像是一个小小的注释
- 如果变量是一个度量的话,最后把名字带上它的单位
- 对于变量存在危险或意外的时候都应该附带上额外信息
- 匈牙利表示法:把变量的“类型”信息编写进名字的前缀
5. 决定名字的长度
- 选择好名字时,有一个隐含的约束是名字不能太长
- 在小的作用域里可以使用短的名字
- 所有的信息(变量的类型、初值等)都很容易看到,所以可以用很短的名字
- “不会输入”不再是避免使用长名字的理由,编辑器的补全功能可以解决这个问题
- 使用项目所特有的缩写词是糟糕的选择——经验原则是:团队的新成员是否能理解这个名字的含义
- 丢掉没用的词——拿掉后不会损失任何信息的单词
6. 利用名字的格式来表达含义
- 对于下划线、连字符和大小写的使用方式可以把更多信息装到名字中
- 对不同的实体使用不同的格式,能帮你更容易地阅读代码
2. 不会误解的名字
- 要多问自己几遍:“这个名字会被别人解读成其他的含义吗?”也仔细审视这个名字——可以主动寻找“误解点”
- 推荐用 min 和 max 来表示(包含)极限
- 推荐用 first 和 last 来表示包含的范围
- 尽管 start 是个合理的参数名,但 stop 可以有多重解读。对于这样包含的范围(这种范围包含开头和结尾),一个好的选择是 first/last
- 推荐用 begin 和 end 来表示包含/排除范围
- 当为布尔变量或者返回布尔值的函数选择名字时,要确保返回 true 和 false 的意义很明确
- 加上像 is、 has、 can 或 should 这样的词,可以把布尔值变得更明确
- 最后避免使用反义名字
- 避免使用用户对它的含义有先入为主印象的名字,从而导致误解
3. 审美
使用从审美角度讲让人愉悦的代码
三大原则
使用一致的布局,让读者很快习惯这种风格
让相似的代码看上去相似
让相关的代码行分组,形成代码块
技巧
- 重新安排换行来保持一致和紧凑
- 用方法来整理不规则的东西——“看上去漂亮”通常会带来不限于表面层次的改进,它可能会帮你把代码的结构做得更好
- 在需要时使用列对齐
- 选择一个有意义的顺序,始终一致地使用它
- 让变量的顺序与对应的 HTML 表单中的字段顺序相匹配
- 从“最重要”到“最不重要”排序
- 按字母顺序排序
- 把声明按块组织起来
- 把代码分成“段落”
- 选择一种风格——选择哪种风格不会影响到代码的可读性,但是把两种风格混在一起就会影响了
4. 该写什么样的注释
注释的目的是尽量帮助读者了解得和作者一样多
什么不需要注释
- 不要为那些从代码本身就能快速推断的事实写注释
- 不要为了注释而注释——避免“没有价值的注释”
- 不要给不好的名字加注释——应该把名字改好
- 注释不应用于粉饰不好的名字
- 一个好的名字比一个好的注释更重要,因为在任何用到这个函数的地方偶读能看得到它
- 不要“拐杖式注释”——试图粉饰可读性差的代码的注释
- 好代码 > 坏代码 + 好注释
用代码记录你的思想
- 加入注释记录你对代码的有价值的见解
- 为代码中的瑕疵写注释
TODO:
还没有处理的事情FIXME:
已知的无法运行的代码HACK:
对一个问题不得不采用的比较粗糙的解决方案XXX:
危险!这里有重要的问题
- 给常量加注释——它是什么或者为什么它是这个值
站在读者的角度,去详细他们需要知道什么
- 给意料之中的提问加上注释
- 可能的陷阱——预料人们使用你的代码时可能会遇到的问题
- “全局观”注释——各部分如何一起工作
- 总结性注释——避免读者迷失在细节中
5. 写出言简意赅的注释
注释应当有很高的信息/空间率
- 让注释保持紧凑
- 避免使用不明确的代词
- 润色粗糙的句子
- 精确地描述函数的行为
- 用输入/输出例子来说明特别的情况
- 声明代码的高层次意图,而非明显的细节
- 用嵌入的注释来解释难以理解的函数参数
- 用含义丰富的词来使注释简洁
简化循环和逻辑
1. 把控制流变得易读
- 把条件、循环以及其他对控制流的改变做得越“自然”越好。运用一种方式使读者不用停下来重读你的代码。
条件语句中参数的顺序
- 左侧更倾向于不断变化,右侧更倾向于常量
if/else 语句块的顺序
- 首先处理正逻辑而不是负逻辑的情况
- 先处理简单的情况
- 先处理有趣的或者说可疑的情况
三目运算符
- 相对于追求最小化代码行数,一个更好的度量方法是最小化人们理解它所需的时间
- 默认情况下都用 if/else。三目运算符只有在最简单的情况下使用
避免 do/while 循环
- 一般会从前向后读代码,do/while由其后的一个条件决定是否执行,不太滋润
- do/while 循环中的 continue 语句让人迷惑
从函数中提前返回
最小化嵌套
- 当你对代码做改动时,从全新的角度审视它。把它当做一个整体来看待
- 通过提前返回来减少嵌套
- 减少循环内的嵌套
2. 拆分超长的表达式
- 代码中的表达式越长,它就越难以理解
- 把你的超长表达式拆分成更容易理解的小块
解释变量
- 拆分表达式最简单的方法是引入一个额外的变量,让它来表示一个小一点的子表达式
总结变量
- 用一个短很多的名字来替代一大块代码,会更容易管理和思考
德摩根定理
- 分别取反,转换与/或
1 | not (a or b or c) <=> (not a) and (not b) and (not c) |
不滥用短路逻辑
- 布尔操作会做短路计算
- 要小心“智能”的小代码段——它们往往在以后会让别人读起来感到困惑
找到更优雅的方式
- 看看能否从“反方向”解决问题
3. 变量与可读性
- 变量越多,就越难全部跟踪它们的动向
- 变量的作用域越大,就需要跟踪它的动向越久
- 变量改变得越频繁,就越难以跟踪它的当前值
减少变量
- 没有价值的临时变量
- 减少中间结果
- 减少控制流变量——没有保护任何程序的数据,只是控制程序的执行
缩小变量的作用域
- 让你的变量对尽量少的代码行可见
只写一次的变量更好
- 操作一个变量的地方越多,越难确定它的当前值
- 中间变量通常可以通过提前返回来消除
重新组织代码
1. 抽取不相关的子问题
- 积极地发现并抽取出不相关的子逻辑
- 把一般代码和项目专有代码分开
通用代码
- 从项目的其他部分解耦出来
2. 一次只做一件事
- 应该把代码组织得一次只做一件事情
把想法变成代码
- 用自然语言描述程序然后用这个描述来帮助你写出更自然的代码。
少写代码
- 最好读的代码就是没有代码
质疑和拆分需求
- 把需求削减成一个简单的问题,只需要较少的代码
- 让你的代码块越小,越轻量级越好
- 创建好的“工具”代码来减少重复代码
- 减少无用代码或没有用的功能
- 让你的项目保持分开的子项目状态
- 小心代码的“重量”
熟悉周边的库
- 每隔一段时间,花 15 分钟来阅读标准库中的所有函数/模块/类型的名字