表面层次的改进
如果你的代码有很棒的名字、写得很好的注释,并且整洁地使用了空白符,你的代码会变得易读很多。
第 1 章 代码应当易于理解
- 可读性基本定理:代码的写法应当使别人理解它所需的时间最小化。
- 因此尽管减少代码行数是一个好目标,但把理解代码所需的时间最小化是一个更好的目标。
第 2 章 把信息装到名字里
- 选择专业的词:清晰和精确比装可爱好。例如,不用
Get
,而用Fetch
或者Download
可能会更好,这由上下文决定。 - 避免使用
tmp
和retval
这样泛泛的名字:好的名字应当描述变量的目的或者它所承载的值。retval
这个名字没有包含很多信息。用一个描述该变量的值的名字来代替他。tmp
这个名字只应用于短期存在且临时性为其主要存在因素的变量。- 如果你要使用
tmp
、it
或者retval
这样空泛的名字,那么你要有个好的理由。
- 使用具体的名字来更细致地描述事物 ——
ServerCanStart()
这个名字就比CanListenOnPort()
更不清晰。 - 为名字附带更多信息
- 具体的格式
- 带单位的值
- 附带其他重要属性
- 为作用域大的名字采用更长的名字 —— 不要让人费解的一个或两个字母的名字来命名几屏之间可见的变量。对于只存在于几行之间的变量用短一点的名字更好。
- 有目的地使用大小写、下划线等 - 例如,你可以在类成员和局部变量后面加上”_”来区分他们。
第 3 章 不会误解的名字
- 不会误解的名字是最好的名字 —— 阅读你代码的人应该理解你的本意,并且不会有其他的理解。
- 在你决定使用一个名字以前,要吹毛求疵一点,来想象一下你的名字会被误解成什么。最好的名字是不会误解的。
- 当要定义一个值的上限或下限时,
max_
和min_
是很好的前缀。对于包含的范围,first
和last
是好的选择。对于包含/排除范围,begin
和end
是好的选择,因为它们最常用。 - 当为布尔值命名时,使用
is
和has
这这样的词来明确表示它是个布尔值,避免使用反义的词,例如disable_ssl
。 - 要小心用户对特定词的期望。例如,用户会期望
get()
或者size()
是轻量的方法。
第 4 章 审美
- 好的源代码应当“看上去养眼”。有三条原则:
- 使用一致的布局,让读者很快就习惯这种风格。
- 让相似的代码看上去相似。
- 把相关的代码行分组,形成代码块。
- 大家都愿意读有美感的代码。通过把代码用一致的、有意义的方式“格式化”,可以把代码变得更容易读,并且可以读得更快。
- 如果有多个代码块做相似的事情,尝试让他们有同样的剪影。
- 把代码按“列”对齐可以让代码更容易浏览。
- 如果在一段代码中提到 A、B 和 C,那么不要另一段中说 B、C 和 A。选择一个有意义的顺序,并始终用这样的顺序。
- 用空行来把大块代码分成逻辑上的“段落”。
第 5 章 该写什么样的注释
注释的目的是帮助读者了解作者在写代码时已经知道的那些事情。
什么地方不需要注释:
- 能从代码本身中迅速地推断的事实。
- 用来粉饰烂代码(例如蹩脚的函数名)的“拐杖式注释” —— 应该把代码改好:好代码 > 坏代码 + 好注释。
你应该记录下来的想法包括:
- 对于为什么代码写成这样而不是那样的内在理由(“指导性批注”)。
- 代码中的缺陷,使用
TODO:
或者XXX:
这样的标记。 - 常量背后的故事,为什么是这个值。
站在读者的立场上思考:
- 预料到代码中哪些部分会让读者说:“啊?”并且给它们加上注释。
- 为普通读者意料之外的行为加上注释。
- 在文件/类的级别上使用“全局观”注释来解释所有的部分是如何一起工作的。
- 用注释来总结代码块,使读者不至于迷失在细节中。
克服“”作者心里阻滞“:把写蛛丝这件事拆成几个简单的步骤,并且通过过早写注释和常写注释,你可以避免在最后要写一大堆注释这种令人不快的状况。
- 不管你心里想什么,先把它写下来。
- 读一下这段注释,看看有没有什么地方可以改进。
- 不断改进。
第 6 章 写出言简意赅的注释
- 注释应当有很高的信息/空间率。
- 当像
it
和this
这样的代词可能指代多个事物时,避免使用它们。 - 尽量精确地描述函数的行为。
- 在注释中用精心挑选的输入/输出例子进行说明。
- 声明代码高层次意图,而非明显的细节。
- 用嵌入的注释(如
Function(/*arg =*/...)
)来解释难以理解的函数参数。 - 用含义丰富的词来使注释简洁。
简化循环和逻辑
复杂的逻辑、巨大的表达式或者一大堆变量会增加阅读者头脑中的思维包袱,很可能在不知不觉中就会产生 bug,代码会变得难以改变。
第 7 章 把控制流变得易读
- 把条件、循环以及其他对控制流的改变做得越“自然”越好。运用一种方式使读者不用停下来重读的你的代码。
- 在写一个比较时(
while (bytes_expected > bytes_received)
),把改变的值写在左边并且把更稳定的值写在右边更好一些(while (bytes_received < bytes_expected)
)。 - 你也可以重新排列 if/else 语句中的语句块。
- 首先处理正逻辑而不是负逻辑的情况。例如,用
if(debug)
而不是if(!debug)
。 - 先处理掉简单的情况。这种方式可能还会使得
if
和else
在屏幕之内都可见,这很好。 - 先处理有趣的或者是可疑的情况。
- 首先处理正逻辑而不是负逻辑的情况。例如,用
- 某些编程结构,像三目运算符(
:?
)、do/while
循环,以及goto
经常会导致代码的可读性变差。最好不要使用它们,因为总是有更整洁的代替方式。 - 嵌套的代码块需要更加集中精力去理解。每层新的嵌套都需要读者把更多的上下文“压入栈”。应该把它们改写成更加“线性”的代码来避免深嵌套。当你对代码做改动时,从全新的角度审视它,把它作为一个整体来看待,以免日积月累添加了大量嵌套,降低代码可读性。
- 通常来讲提早返回可以减少嵌套并让代码整洁。“保护语句”(在函数顶部处理简单的情况时)尤其有用。
第 8 章 拆分超长的表达式
- 把你的超长表达式拆分成更容易理解的小块
- 一个简单的技术是引入“解释变量”来代表较长的子表达式。这种方式有三个好处:
- 它把巨大的表达式拆成小段。
- 它通过用简单的名字描述子表达式来让代码文档化。
- 它帮助读者识别代码中的主要概念。
- 使用德摩根定理来重写逻辑表达式,使它更加整洁。
- 有时候把问题“反向”或者考虑目标的对立面会给你简化代码的思路。
- 要小心“智能“的小代码段 —— 它们往往在以后会让别人读起来感到困惑。
第 9 章 变量与可读性
- 减少变量,即哪些妨碍的变量。例如,没有价值的临时变量、控制流变量这些中间变量。
- 减小每个变量的作用域,越小越好。把变量移到一个有最少代码可以看到它的地方。眼不见,心不烦。让你的变量对尽量少的代码可见。
- 只写一次的变量更好。那些只设置一次值的变量(或者
const
、final
、常量)使得代码更容易理解。 - 操作一个变量的地方越多,越难确定它的当前值。
重新组织代码
第 10 章 抽取不相关的子问题
- 积极地发现并抽取出不相关的子逻辑:
- 看看某个函数或代码块,问问你自己:这段代码高层次的目标是什么?
- 对于每一行代码,问一下:它是直接为了目标而工作吗?这段代码高层次的目标是什么呢?
- 如果足够的行数在解决不相关的子问题,抽取代码到独立的函数中。
- 不相关的子逻辑包括:纯工具代码、其他多用途代码。
- 创建大量通用代码,形成自己的独立库。
- 简化已有接口:永远都不要安于使用不理想的接口。
- 小心过犹不及。
- 提高自己的抽象能力和分解问题能力,关注小而定义良好的问题。
第 11 章 一次只做一件事
- 应该把代码组织得一次只做一件事情:
- 列出代码所做的所有“任务”。这里的“任务”没有很严格的定义 —— 它可以小得如“确保这个对象有效”,或者含糊得如“遍历树中所有节点”。
- 尽量把这些任务拆分到不同的函数中,或者至少是代码中不同的段落中。
- 如果你有很难读的代码,尝试把它所做的所有任务列出来。其中一些任务可以很容易地变成单独的函数(或类)。其他的可以简单地成为一个函数中的逻辑“段落”。具体如何拆分这些任务没有它们已经分开这个事情那样重要。难的是要准确地描述你的程序所做的所有这些小事情。
第 12 章 把想法变成代码
- 我们会用一个简单的过程来使你编写更清晰的代码:
- 像对着一个同事一样用自然语言描述代码要做什么。
- 注意描述中所用的关键词和短语。
- 写出与描述所匹配的代码。
- 用自然语言描述程序然后用这个描述来帮助你写出更自然的代码。这个技巧出人意料地简单、但很强大。看到你在描述中所用的词和短语还可以帮助你发现哪些子问题可以拆分出来。
- 如果你不能把问题说明白或者用词语来做设计,估计是缺少了什么东西或者什么东西缺少定义。把一个问题(或想法)变成语言真的可以让它更具体。
第 13 章 少写代码
- 从项目中消除不必要的功能,不要过度设计。
- 重新考虑需求,解决版本最简单的问题,只要能完成工作就行。
- 经常性地通读标准库的整个 API,保持对它们的熟悉程度。
- 每隔一段时间,花 15 分钟来阅读标准库中的所有函数/模块/类型的名字。
- 让你的代码库越小,越轻量级越好:
- 创建越多越好的“工具”代码来减少重复代码。
- 减少无用代码或没有用的功能。
- 让你的项目保持分开的子项目状态。
- 总的来说,要小心代码的“重量”。让它保持又轻又灵。
精选话题
第 14 章 测试与可读性
- 测试应当具有可读性,以便其他程序员可以舒服地改变或者增加测试。
- 对使用者隐去不重要的细节,以便更重要的细节会更突出。
- 通过创建最小的测试声明、实现定制的“微语言“等方法让测试更可读。
- 让错误消息具有可读性。
- 如果你把事实代码设计得容易测试,代码的整个设计会变得更好。
- 每个测试的最高一层应该越简明越好。最好每个测试的输入/输出可以用一行代码来描述。
- 如果测试失败了,它所发出的错误消息应该能让你容易跟踪并修正这个 bug。
- 使用最简单的并且能够完整运用代码的测试输入。
- 给测试函数取一个有完整描述性的名字,以使每个测试所测到的东西很明确。不要用
Test1()
,而用Test_<FunctionName>_<Situation>
这样的名字。 - 最重要的是,要是它易于改动和增加新的测试。