第一部分 表面层次的改进

第一章 代码应当易于理解

  • 可以用标准写法就用标准写法如: for循环
  • 三元运算符不宜嵌套使用, 太长不好易理解
  • 可读性基本原则: 代码的写法应当使别人理解它所需要的时间最小化
  • 代码的长短应该以好理解为目标而不是一味追求最短最少的代码
  • 添加必要的注释

第二章 把信息写到名字里

  • 选择专业的词

    get一词在web领域可以替换为download或者fetch

    对于二叉树来说hight或者num_nodes比size更合适

    在进程操作方便kill/pause/resum比stop更合适

  • 找到更有表现力的词

    关键思想: 清晰和精确比装可爱好

    |单词|更多选择|

    |:—-:|:—-:|

    |send|deliver, dispatch, announce, distribute, route|

    |find|search, extract, locate, recover|

    |start|lauch, create, begin, open|

    |make|create, setup, build, generate, compose, add, new|

  • 避免使用tmp和retval这样泛泛的名字

  • 循环替代器

    i,j,iter和it都是常用索引和循环迭代器, 尽管泛泛但是已成标准不要用它们做另外含义的作用

    多层迭代器时可以为迭代器变量加上有意义的补充: memner_i=>mi

  • 对于空泛名字的裁定

    如果要使用tmp, it, retval空泛的名字, 必须要好理由

  • 名称应该具体具象

  • 为名字附带更多信息: id=>hex_id

  • 带单位的值: start=>start_ms, elapsed=>elapsed_ms

  • 附带其他重要属性

    data=>data_urlenc

    html=>html_utf8

    password=>plaintext_password

    comment=>unescaped_comment

    微软广泛使用的匈牙利表示法, 变量前加其类型

  • 名字应该有多长

    • 在小的作用域中可以使用短名字

    • 首字母缩略和缩写: 简写的原则是看新成员是否可以理解这个名字

    • 丢掉没有用的词

  • 利用名字的格式来传递含义

    使用CameCase表示类名, 使用lower_separaed表示变量名, 使用offset_表示类成员


第三章 不被误解的名字

关键思想: 多读几遍看看有没有其他含义?

  • 多意义的词如filter,length和limit替换为更明确的使用

  • 使用first/last来表示包含的范围

  • 使用begin/end表示包含/排除的范围

  • 使用max_/min_定义上下限

  • 布尔值定义: is/has/should前缀, 避免使用反义词

  • 小心用户对特定词的期望如get()/sieze()


第四章 审美

关键思想: 一致的风格化比"正确"的风格更重要

  • 如果有个代码块做相似的事情, 尝试让他们有同样的剪影

  • 把代码按照列对齐可以让代码更容易浏览

  • 如果在一段代码中提到A,B和C, 那么不要在另一段中B,C和A, 选择一个有意义的顺序, 并始终用这样的顺序

  • 用空行吧大块代码分成逻辑上的段落


第五章 该写什么样的注释

什么地方不需要注释:

  • 关键思想: 不要为那些从代码本身就能快速推断的事实写注释

  • 不要给不好的名字加注释–应该把名字改好(拐杖式注释)

记录下来自己的想法:

  • 对于为什么代码写成这样而不是那样的内在理由

  • 代码中的缺陷, 使用TODO:或者XXX:这样的标记

  • 常量背后的故事, 为什么是这个值

站在读者立场思考:

  • 预料代码中哪些部分会让读者说为什么

  • 为普通读者意料之外的行为添加注释

  • 在文件/类的级别上使用全局观注释来解释所有的部分是如何一起工作的

  • 用注释来总结代码, 使读者不至迷失在细节中


第六章 写出言简意赅的注释

关键思想: 注释应当有很高的信息/空间率

  • 让注释保持紧凑: 尽量不多行逐一描述, 精简描述

  • 避免使用不确定的代词: 如it, this, 使用具体的名字

  • 润色粗糙的句子

  • 精确描述函数的行为: 能精确就不泛泛的描述

  • 用户输入输出: 在注释中用精心挑选的输入输出例子进行说明

  • 声明代码的意图: 技术意图+业务意图

  • 具名函数参数的注释

  • 采用信息含量高的词: cache layer,是否可以使用典型场景


第二部分 简化循环和逻辑

第七章 把控制流变得易读

  • 条件语句的顺序: 把改变的值放到左边, 把稳定的值放到右边

  • 重新排列if/else语句块, 优先处理正确的/简单的/有趣的情况

  • 三目运算符只在简单的语句场景下用

  • do/while循环和goto导致可读性变差尽量不要使用

  • 嵌套代码可以尝试整理理解, 然后改写成线性的代码避免过深的嵌套

  • 通常来说提早返回可以减少嵌套让代码整洁


第八章 拆分超长的表达式

关键思想: 把超长的表达式拆分为可快速理解的小段

关键思想: 小心当下的智能小代码段成为日后的困惑点

  • 引入解释变量拆分大段的表达式

  • 利用摩尔定律整洁多重条件判断: !(a && !b)=> !a || b

  • 如果if判断条件超过两个可以考虑是否可以把问题反向化解决, 考虑对立的方式解决

  • 拆分代码块和表达式类似, 提取公共或者相似部分重构, 不要写重复代码


第九章 变量与可读性

关键思想: 让你的变量对尽量少的代码可见

关键思想: 操作一个变量的地方越多, 越难确定它的当前值

  • 减少变量

  • 减小每个变量作用域, 越小越好

  • 只写一次的变量更好


第三部分 重新组织代码

第十章 抽取不相关的子问题

  • 把一般代码和项目专有代码分开

  • 提取纯工具代码, 通用代码

  • 突出原有函数功能, 封装无关任务

  • 按需重构代码, 但是不要过犹不及


第十一章 一次只做一件事情

关键思想: 应该把代码组织的一次只做一件事情

  • 任务可以很小, 按照小块任务从代码内聚, 分块书写

  • 抽出的任务如果重复或者相似就封装方法


第十二章 把想法变成代码

  • 使用自然语言描述自己的思路和想法, 大声的描述或者在纸上书写, 明确问题和思路的过程就是代码编写的思路

  • 如果不能讲问题语义化, 那么说明少了缺少了什么, 需要去追踪明白

  • 想法要精练明确, 最后一步转化为代码


第十三章 少写代码

关键思想: 最好读的代码就是没有代码

  • 关注与哪些优先级高些简单的东西, 不要追求那些远的难得东西, 追求最重要最近最小开发

  • 代码通常写完之后还需要维护, 维护时间也是写代码需要考虑的事情

  • 质疑和拆分你的需求: 不是所有需求都要求100%输入正确跑得快, 合理情况下最简单化需求用较少的代码去实现

  • 保持小代码库

    • 创建工具库减少重复代码

    • 减少无用代码或者功能

    • 让你的项目保持分开的子项目状态

    • 小心代码重量, 让它轻巧灵活

  • 经常通读标准库, 熟悉特性和新语法一遍之后的开发进行使用而不是自己书写一堆东西


第四部分 精选话题

第十四章 测试与可读性

关键思想: 测试应当具有可读性, 以便其他程序员可以舒服的改变或者增加测试

关键思想: 选择一组最简单的输入, 它能完整的使用被测试的代码

关键思想: 又简单又能完城工作的测试值更好

  • 封装测试不是主要的部分, 凸显主要的部分

  • 构造增加测试的用例的方法, 让别人乐于增加测试

  • 良好的输入选择

  • 简单的测试值

  • 为测试函数起一个好名字

    • 被测试的类

    • 被测试的函数

    • 被测试的情形或者bug: 不用怕长, 因为正式代码中不用使用这些函数

    • Test_前缀或者其他

  • 可测试性差的代码特征

    • 使用全局变量

    • 对外部组件的大量依赖

    • 代码有不确定行为

  • 可测试性好的代码特征

    • 类中有很少或者没有内部状态

    • 类或者函数只做一件事情

    • 每个类对别的类的依赖很少, 低耦合

    • 函数的接口简单, 定义明确

  • 走得太远

    • 牺牲真实代码可读性, 只是为了使其能测试

    • 着迷于100%测试覆盖率

    • 让测试成为产品开发的阻碍

  • 总结

    • 每个测试最高一层应该越简明越好. 最好每一个测试输入输出都可以用一行代码描述

    • 如果测试失败了, 它所发出的错误消息应该能让你容易跟踪和修正这个bug

    • 使用最简单的并且能够完成运用代码的测试输入

    • 给测试函数取一个有完整描述的名字

    • 使测试易于改变和增加新的测试