题记与说明
- 条目类型:单本经典图书精读(多来源交叉阅读综合)
- 日期:2026-11-06
- 精读对象:Dustin Boswell & Trevor Foucher, *The Art of Readable Code*, O'Reilly Media, 2011(193 页,四部分;英文版 14 章结构,中文版分章略有出入,见"局限")。O'Reilly 书目页:https://www.oreilly.com/library/view/the-art-of/9781449318482
- 本次实际抓取并阅读的来源(5 个):
- Jake Gordon: The Art of Readable Code(书评)——全书 14 章标题、作者总结引述、测试章引述、资深读者视角评价。全文精读。
- Burkhard Stubert: Book Review — The Art of Readable Code(smallstepsystems.com, 2022-01)——含英文版页码引述(p.46、p.73),精读第 5/7/10 章并给出总评。全文精读。
- huansky(博客园):《编写可读代码的艺术》阅读笔记(2017-01)——中文版逐章"关键思想"摘录(命名、不误解的名字、控制流等)。全文精读。
- TecHug:《编写可读代码的艺术》读书笔记——四部分 16 章逐章要点,最完整的中文结构化摘要。全文精读。
- angelialau (GitHub Gist): A summary of The Art of Readable Code——按"从最易改到最耗时"排序的全书技巧清单(curl 抓取原文 6981 字节,通读)。全文精读。
- 辅助检索来源(未逐页精读,仅用于定位与版本信息):O'Reilly 书目页、Harvey Mudd College 课程风格节选 PDF(检索命中,PDF 抓取未成功)、知乎读书笔记(403 被拒)、hungf (Medium) 摘要(连接超时)。
- 检索方式说明:本笔记全部论断来自上列 5 个实际读取的来源;引述均标注来源编号。未能抓取的来源已如实列出,其内容不作为论据。
一、书与主题概述
《编写可读代码的艺术》回答的问题朴素而根本:怎么写让别人一读就懂的代码。它的关键一步是把"可读性"这个含糊的审美概念变成一个可度量的工程目标——
"Code should be written to minimize the time it would take for someone else to understand it."(作者总结,转引自来源 1;Gist 版补全了定义:这里的"理解"指 *able to modify the code and spot bugs*,即能改得动、能看出 bug)
中文版表述:"代码的写法应当使别人理解它所需的时间最小化"——可读性基本定理。(来源 3、4)
全书围绕这把尺子展开四部分:表面层次的改进(命名、注释、审美)→ 简化循环和逻辑(控制流、拆分超长表达式、变量)→ 重新组织代码(抽取子问题、一次只做一件事、把想法变成代码、少写代码)→ 精选话题(测试与可读性、设计改进)。每章末尾附 "Summarizing" 检查清单,这为本文落成"个人检查清单"提供了原装骨架。
两位作者均有 Google 工程背景(本书源自作者在 Google 内部讲授的"代码可读性"课程材料,此点为来源 4 语境与多方书评公认背景;确切表述以原书为准)。本书常与《Clean Code》并列推荐,但取向不同:*Clean Code* 谈原则与品味,本书谈每次落键时都能用的具体技术——这在 5 个来源中被反复确认。
二、七维提取
1. 核心问题
- 如何把"代码可读性"从主观品味变成可度量、可训练、可检查的工程目标?
- 具体拆成四层子问题:
- 表面层:名字、注释、排版如何让信息密度最大化?
- 逻辑层:控制流与表达式如何不逼读者"重读第二遍"?
- 组织层:函数边界怎么划,才能让每行代码都处于同一抽象层级?
- 验证层:测试代码如何同时具备可读性与可改动性?
2. 主要主张
- 可读性基本定理:以"别人理解所需的时间最小化"为唯一北极星度量;"理解"的标准是能修改代码、能发现 bug。(来源 1、3、4、5)
- 可读性优先于其他目标:即使需要高度优化的代码也能兼顾可读性,且可读性好的代码往往架构更好、更易测试。(来源 3、4)
- 好代码 > 坏代码 + 好注释:"good code is always more readable than bad code plus comments"——注释救不了坏名字,先改名。(来源 2)
- 注释的目的是信息传递:"The purpose of commenting is to help the reader know as much as the writer did"(p.46)——写"导演评论"(为什么选这条路、放弃了哪些替代方案),而不是复述代码。(来源 2、4)
- 行数不是美德:"Instead of minimizing the number of lines, a better metric is to minimize the time needed for someone to understand it."(p.73)——三目运算符、密集的一行流只有在更易理解时才用。(来源 2、4)
- 最好的代码是没有代码:"最好读的代码就是没有代码。"——砍需求、删死代码、拒绝过度设计。(来源 4)
- 测试名即注释:"Don't be afraid of having a long or clunky name here… The test function name is effectively acting like a comment."(来源 1)
- 一致性比"正确"重要:"it's more important to be consistent than to follow the 'right' style"——团队风格指南优先于个人偏好。(来源 4、5)
3. 方法
本书的方法论形态(区别于实证研究):
- 北极星度量 + 逐层技术:先立度量(时间到理解),再按"改动成本从低到高"排布技术——Gist 来源 5 明确按 *ranked from easiest change to most time intensive* 组织:命名 → 审美 → 注释 → 控制流 → 拆分复杂度 → 删变量 → 抽取重构 → 写新代码的心法 → 少写代码 → 测试。
- 贯穿示例 before/after 改写:每章用同一段真实风格代码反复重写(C++/Python/JavaScript 为主),让读者看见"改动前后理解时间的差别"而非记住抽象原则。
- 章末 Summarizing 清单:每章以检查项收尾,全书构成一份可执行的自审表(本笔记第四节的直接素材)。
- 把想法变成代码的三步法(来源 4):像向同事解释一样用自然语言描述代码要做什么 → 抓住描述中的关键词短语 → 写出与描述匹配的代码。配套引爱因斯坦:"如果不能向祖母解释一件事,说明还没真正理解。"
- 抽取子问题的操作程序(来源 2,第 10 章,被评审者称为"对单一职责原则最实用的描述"):问自己这段代码的高层次目标是什么 → 逐行检查是否直接服务于该目标 → 把不服务的行抽成独立函数。
4. 数据与证据
- 证据形态:这是经验法则(craft)型著作,证据是贯穿示例的 before/after 改写与作者在 Google 的工程实践,不含受控实验数据——"理解时间"从未被实际测量,这是本书论证方式的固有边界(我基于全部 5 个来源的判断:无一处报告实验设计或量化数据)。
- 外部佐证一(时效性):Stubert(来源 2,2022-01)读完 11 年前的书后判断其内容"will stay relevant at least for the next 10 years"。
- 外部佐证二(教育界采纳):Harvey Mudd College 将本书前几章节选为 CS70 课程的风格必读 PDF(styleboswell.pdf,检索命中标题与用途,PDF 未抓取成功)。
- 外部佐证三(资深读者一致性):Jake Gordon(来源 1)自述为有经验开发者,读后"pretty much agree[s] with all of the author's points",并称全书 "a really easy read in 14 clean (and short) chapters"。
- 充分性评估:对"这些技巧是否好"这一问题,证据充分(多方独立一致);对"这些技巧能带来多少可测量的收益",证据缺失——本书自己也只承诺方向不承诺数字。
5. 结论
可读性是一门有度量、有技术栈、有自审清单的手艺:北极星是"最小化别人的理解时间";手段按成本递增排序是命名 → 审美 → 注释 → 控制流 → 表达式 → 变量 → 函数组织 → 少写代码 → 测试。落地形态就是一份随身检查清单(第四节),在写完、提交前、评审时逐项过。
6. 局限
- 时代局限:2011 年出版,示例基于 C++98/Python 2 时代风格;未覆盖现代自动化(formatter/linter 已接管大量"审美"建议,如列对齐 clang-format 反而禁止)、类型标注、AI 辅助编程等新语境。
- 方法论局限:全书无一处受控实验或量化数据支撑"理解时间"度量;主张成立靠共识与示例,不靠测量。
- 受众局限:对资深读者多为已知模式复述——来源 1 原话 "mostly a review of ideas and patterns I have already come to agree with";对新手则是高杠杆。
- 来源间事实出入(本次精读如实记录):章节数三个来源说法不一——来源 1 列出 14 章标题,来源 2 称 "15 chapters across 4 parts",来源 3、4 称 16 章(中文版分章或有调整)。本文引用各章内容时以技术要点为准,不以章号为准。
- 建议的可争议点:作者明确支持提前返回、反对"单返回点"(来源 2 转述作者称之为 "nonsense"),与部分行业规范(如 MISRA C)冲突;goto 在 C 中被作者有限度接受——这类建议需按团队规范取舍,不宜照单全收。
- 未覆盖的深水区:命名与注释无法解决架构级可读性问题(模块边界、依赖方向),本书只在第三部分浅尝,真正深入需读《Code Complete》《A Philosophy of Software Design》。(此点为我对来源 1 推荐书单语境的推断)
7. 对我的启发
- 给 AI 生成代码配一把现成的尺:AI 写的代码高频复现本书反面案例——空洞命名(
data/result/tmp)、过深嵌套、"聪明"的一行流、复述代码的注释。第四节的清单可直接当作 review AI 产出的自审表,也能整段贴进项目的 AGENTS.md / CLAUDE.md 作为生成约束("布尔名加 is/has/can/should""测试名 Test_<Function>_<Situation>"这类规则对 LLM 极易遵循)。 - "时间到理解"度量与提示词工程同构:让同事第一次读懂的努力,与让 LLM 第一次生成正确的努力,是同一种"消歧"工作——给代码消歧的手段(具体名字、单位后缀、显式边界 first/last)和给 prompt 消歧的手段完全同型。写代码和写 prompt 可以共用一套"读者第一"的检查习惯。
- 检查清单的使用姿势:不建议每次提交全表过一遍(太重);按"改动成本从低到高"用——平时只过 T0+T9 三问,重构时过全表。
- 两个立刻可养成的微习惯:(1) 书中建议的"卡壳标记"——读自己代码卡壳处立刻留下
TODO(readability)之后再回来;(2) 来源 4 转述的"每隔一段时间花 15 分钟浏览标准库的名字",在 Windows 个人工具链里即定期浏览 Python stdlib / .NET BCL 的目录页。 - 测试文件当文档写:
Test_<Function>_<Situation>命名让测试清单变成可执行的行为规格书,这直接改善我"隔半年回看自己项目"时的重新上手速度。
三、全书四部分精读要点(逐层展开)
第一部分:表面层次的改进(命名 / 注释 / 审美)
命名——把信息装进名字里(来源 3、4、5):
- 选专业词:
GetPage→FetchPage()/DownloadPage();BinaryTree.Size()拆成Height()/NumNodes();Thread.Stop()歧义,改Kill()(不可恢复)或Pause()/Resume()(可恢复)。 - 避免空洞名:
tmp/retval/foo。原则:"清晰和准确比装可爱好"(来源 4)。tmp只允许用于"短期存在且临时性正是其存在理由"的变量。 - 具体胜过抽象:
CanListenOnPort()优于ServerCanStart()(后者描述的是一种实现方案而非能力本身)。 - 名字是微型注释:带单位(
start_ms)、带状态(raw_前缀表未转义/未处理、hex_id表十六进制)。 - 长度与作用域匹配:"在小的作用域使用短的名字"(来源 3);判断底线是"团队新成员能否看懂";丢掉无用词(
ConvertToString()→ToString())。 - 用格式表达含义:
kMaxOpenFiles表类常量、MAX_OPEN_FILES表宏;state局部变量 vsstate_成员变量(Google 风格)。
不误解的名字(来源 3、4、5)——审查式提问:"这个名字会被别人解读成其他的含义吗?"
Filter()有歧义(挑出还是剔除?)→ 用Select()/Exclude()。- 界限用
min_/max_前缀;范围用first/last(双闭)或begin/end(半开,惯例上 end 是"不含")。 - 布尔名加
is/has/can/should前缀,且用肯定式——disable_ssl = false这种双重否定读起来费劲。
审美(来源 4、5)——三原则:一致布局让读者习惯风格;相似代码看上去相似("similar silhouettes",来源 5);相关代码分组成块。手段:换行节奏一致、列对齐、按有意义顺序声明并处处保持、块间空行、"代码分段+注释分节标题"。底线:"一致性比'正确'风格更重要"。
注释(来源 2、4)——
- 核心句(p.46):"The purpose of commenting is to help the reader know as much as the writer did."
- 不写:代码能自解释的事实、为注释而注释、给坏名字打的补丁注释(应改名,"good code is always more readable than bad code plus comments",来源 2)。
- 要写:"导演评论"(记录取舍与被否方案)、瑕疵标记
TODO/FIXME/HACK/XXX、常量的"为什么是这个值"、预料读者疑问的答案、陷阱警告、文件/类级大局注释、代码块的总结性注释。 - 紧凑性(第 6 章):"注释应该有很高的信息/空间率"(来源 4)——避免
it/this指代不明、精确描述函数行为、用输入→输出例子代替冗长散文(来源 4 作者注:这种"用例子定义行为"用单元测试表达更好——本书自己建议部分注释技术可被测试取代)。
第二部分:简化循环和逻辑
控制流(来源 2、3、4、5)——目标:"读者不用停下来重读"(来源 3:"把条件、循环以及其他控制流改变做得越自然越好")。
- 条件参数顺序:左边放"被检查/会变化的"表达式,右边放"常量/被比较的"——
if (length >= 10)优于if (10 <= length);x == null优于null == x(来源 4 明确不支持 "Yoda 条件")。 - if/else 顺序三准则(来源 2、5):先处理肯定分支(positive case first)、先处理简单分支(缩小 if 与 else 之间的视觉距离)、先处理有趣/可疑分支。
- 三目运算符:"Instead of minimizing the number of lines, a better metric is to minimize the time needed for someone to understand it."(p.73,来源 2)——默认 if/else,仅极简单情况用三目,绝不嵌套。
- 循环:避免
do/while(条件出现在被保护代码之后);while(true)+ 中途 break 优于 do/while。 - 函数返回:提前返回(卫语句)优先;作者称"单返回点"教条为 "nonsense"(来源 2 转述)。
- 嵌套:每层嵌套都在读者脑中压栈,尽早返回/继续以削平金字塔。
拆分超长表达式(来源 3、4、5)——关键思想:"把你的超长表达式拆分成更容易理解的小块"(来源 3);警告:"要小心'智能'的小代码段——它们往往在以后会让别人读起来感到困惑"(来源 3)。手段:解释变量(把子表达式捕获成有名变量)、摘要变量(如 boolean user_owns_document = request.user.id == document.owner_id,来源 5)、德摩根定律化简("分别取反,转换与或",来源 4)、复杂逻辑尝试从反向目标实现、必要时提取函数。
变量与可读性(来源 4、5)——三招:删掉无价值变量(中间结果变量、纯控制流变量如 done);缩小作用域(成员变量降为局部、传参代替共享、"把大类拆小"、"把方法变静态");偏好"只写一次"的变量——"操作越多的变量越难确定其值"(来源 4)。
第三部分:重新组织代码
抽取不相关的子问题(来源 2、4、5)——操作程序:这段代码的高层次目标是什么?每一行是否直接服务于它?不服务的行数足够多就抽成函数。抽取对象:纯工具代码、通用代码、项目专有代码、简化已有接口、按需重塑接口。收益(来源 5):读者聚焦高层目标不被细节拖累;抽出的函数可复用、可独立测试、易识别边界情况。落点:"把一般代码和专有代码分离"(来源 4)。注意"避免过犹不及"(来源 4)。
一次只做一件事(来源 4)——把代码组织成一次只做一件事;任务可以很小(小到只是"从对象中抽取一个值");并列的任务先列清单再逐个完成。
把想法变成代码(来源 4)——三步:自然语言描述 → 抓关键词 → 写匹配的代码。价值在于描述语言会暴露你尚未想清楚的部分(爱因斯坦祖母引述,来源 4)。
少写代码(来源 4、5)——关键思想:"最好读的代码就是没有代码。"手段:不做不需要的功能(质疑与拆分需求,做"最容易版本");保持小代码库、无情删除无用代码;模块化解耦;熟悉周边库——"每隔一段时间花 15 分钟阅读标准库的名字"(来源 4),避免重造轮子。
第四部分:精选话题(测试与可读性)
- 测试的可读性目标(来源 3):"测试应当具有可读性,以便其他程序员可以舒服地改变或者增加测试。"
- 技巧(来源 4、5):测试函数名长而笨没关系——
Test_<Function>_<Situation>;"The test function name is effectively acting like a comment"(来源 1);错误信息里带上输入、期望输出、实际输出;测试用例既简单又能有效暴露 bug;大用例拆小以获得粒度、方便日后调试。 - 测试友好设计的正向特征(来源 5):接口定义清晰、几乎无需 setup、没有隐藏状态要检查、类少内部状态、只做一件事、高度解耦。
- 要避开的(来源 5):非 const 全局变量(每个测试都要重置,副作用难追踪)、依赖大量外部组件(setup 成本与故障点双增)、非确定性行为(难测试且易藏着竞态与不可复现 bug)。
- 作者的刹车(来源 4):明确警告"走得太远的风险"——为可测试性牺牲可读性、执迷 100% 可测试、让测试变成开发阻碍。
四、成品:个人代码可读性检查清单
用法:T0 与 T9 每次提交必过;T1–T8 在写完一个函数/模块后或重构时过;评审他人与 AI 生成代码时全表过。 每项括号内标注来源依据(来源编号见文首)。清单刻意保持"能以是/否回答"的粒度。
T0 · 北极星(一把尺)
- [ ] 这段代码能否让"别人"(包括半年后的我)第一次读就理解并敢于修改?
- [ ] 我是否在用"理解时间"而非"行数/聪明程度"做取舍?(来源 1、2 p.73)
T1 · 命名
- [ ] 名字具体、专业、无空洞词:没有
tmp/retval/data/foo(除非tmp确实"短命且临时即其存在理由")。(来源 3、4) - [ ] 动词选得准:不是泛泛的
get/stop,而是fetchPage/downloadPage/kill/pause。(来源 4) - [ ] 名字描述能力/意图而非实现方案:
CanListenOnPort而非ServerCanStart。(来源 4) - [ ] 关键属性已入名:单位
start_ms、状态raw_/hex_/unsafe_。(来源 3、4) - [ ] 名字长度与作用域匹配;跨作用域的名字新手能看懂。(来源 3)
- [ ] 名字不会被误读:
filter→select/exclude;界限用min/max;双闭区间first/last;半开区间begin/end。(来源 4、5) - [ ] 布尔名带
is/has/can/should,且是肯定式(无disable_xxx = false式双重否定)。(来源 4、5)
T2 · 审美
- [ ] 相似代码长得相似;同文件风格一致(一致性 > 个人心中的"正确")。(来源 4、5)
- [ ] 逻辑相关的代码物理上聚在一起,块间空行,注释作分节标题。(来源 4)
- [ ] 声明有有意义的顺序,且处处保持同一顺序。(来源 4)
- [ ] 需要时列对齐,但不在 formatter 反对的地方硬对齐。(来源 4、5)
T3 · 注释
- [ ] 注释讲的是"读者不知道而我知道的":为什么这样、为什么不用那种方案(导演评论)。(来源 2、4)
- [ ] 没有复述代码的废话注释;没有为坏名字打的补丁注释——坏名字已改名。(来源 2)
- [ ] 瑕疵有标记:
TODO/FIXME/HACK/XXX。(来源 4) - [ ] 魔法常量有"为什么是这个值"。(来源 4)
- [ ] 注释紧凑高密度:无指代不明的 it/this;行为用"输入 → 输出"例子说明。(来源 4)
- [ ] 文件/类顶部有一段"大局注释"讲组织方式。(来源 4)
T4 · 控制流
- [ ] 条件左变右常:
length >= 10、x == null。(来源 4) - [ ] if/else:先正后反、先简后繁、先可疑;两分支都不过长。(来源 2、5)
- [ ] 三目仅限极简单分支,无嵌套三目。(来源 2、4)
- [ ] 无
do/while;循环条件在进循环前可见。(来源 2、5) - [ ] 用卫语句提前返回削平嵌套(嵌套 ≤ 2 层);不受"单返回点"教条约束。(来源 2、4)
T5 · 表达式
- [ ] 一行内不出现需要读两遍以上的布尔逻辑;子表达式已抽成解释变量/摘要变量。(来源 5)
- [ ] 用德摩根定律化简过条件(
!(a&&b)→!a||!b)。(来源 4) - [ ] 没有"聪明的一行流";凡聪明处必有注释或改名补偿。(来源 3)
- [ ] 复杂逻辑试过反向实现是否更简单。(来源 5)
T6 · 变量
- [ ] 无纯中间结果变量、无纯控制流变量(
done/found能删则删)。(来源 4、5) - [ ] 变量作用域已压到最小(成员→局部;共享→传参;能静态则静态)。(来源 5)
- [ ] 偏好只写一次的变量;被多次改写的变量已拆分或收敛。(来源 4)
T7 · 组织
- [ ] 每个函数有一句话能说清的高层次目标;不服务于该目标的行已抽成函数。(来源 2、4)
- [ ] 通用代码与项目专有代码分离(utils 归 utils)。(来源 4、5)
- [ ] 一个函数一次只做一件事;并列任务已列成清单逐个处理。(来源 4)
- [ ] 写之前先用自然语言向(想象中的)同事解释过;代码与解释逐词对应。(来源 4)
- [ ] 没有写了没人用的功能;死代码已删;需求已被砍到"最容易版本"。(来源 4)
- [ ] 这个月浏览过一次标准库/常用库的名字,没有重造轮子。(来源 4)
T8 · 测试
- [ ] 测试名长而具体:
Test_<Function>_<Situation>;测试名即注释。(来源 1、4) - [ ] 测试里不重要细节已隐去,重要细节(输入/期望输出)突出。(来源 4)
- [ ] 断言失败消息自带输入、期望、实际。(来源 5)
- [ ] 别人愿意改我的测试(不会因为"怕改坏"而绕开)。(来源 3)
- [ ] 没有为 100% 可测试牺牲产品代码可读性;没有全局可变态、隐藏状态、非确定性。(来源 4、5)
T9 · 提交前最后三问
- [ ] 读自己代码时卡壳的地方,都标记过并回访了吗?(书中方法论:先标记难读处,稍后重读,来源 3 综述语境)
- [ ] 注释与名字解释的是"为什么",代码解释的是"是什么"——分工没有颠倒吧?(来源 2、4)
- [ ] 最难的那个函数,敢不敢直接发给未参与的人读?(北极星的自检形式)
五、原文引述汇编(便于直接引用)
- "Code should be written to minimize the time it would take for someone else to understand it." —— 作者总结,转引自 来源 1
- "代码的写法应当使别人理解它所需的时间最小化。"(可读性基本定理,中文版表述)—— 来源 3、来源 4
- "The purpose of commenting is to help the reader know as much as the writer did."(p.46)—— 来源 2
- "good code is always more readable than bad code plus comments." —— 来源 2
- "Instead of minimizing the number of lines, a better metric is to minimize the time needed for someone to understand it."(p.73)—— 来源 2
- "Don't be afraid of having a long or clunky name here… The test function name is effectively acting like a comment."(第 14 章测试)—— 来源 1
- "最好读的代码就是没有代码。"(第 13 章关键思想)—— 来源 4
- "清晰和准确比装可爱好。"(命名章)—— 来源 4
- "it's more important to be consistent than to follow the 'right' style." —— 来源 5;中文:"一致性比'正确'风格更重要",来源 4
- "a really easy read in 14 clean (and short) chapters" / "mostly a review of ideas and patterns I have already come to agree with" —— 来源 1
- "will stay relevant at least for the next 10 years"(2022 年书评对 2011 年书的判断)—— 来源 2
- "测试应当具有可读性,以便其他程序员可以舒服地改变或者增加测试。"(第 14 章关键思想)—— 来源 3
六、本次精读的检索过程记录(诚实留痕)
- 实际读取成功:来源 1(WebFetch 全文)、来源 2(WebFetch 全文)、来源 3(WebFetch 全文)、来源 4(WebFetch 全文)、来源 5(WebFetch 失败后改用
curl -sL抓取 raw gist 成功,6981 字节,通读)。 - 检索命中但未读取:O'Reilly 书目页(仅用 URL 中的 ISBN/年份信息)、HMC 课程节选 PDF(WebFetch 不支持 application/pdf)。
- 抓取失败(内容未采用):知乎笔记(HTTP 403)、hungf Medium 摘要(连接超时)、artofreadablecode.com(域名无法解析,该站点疑似已下线)。
- 全书 PDF(mcusoft.files.wordpress.com 镜像)存在,但本次以二手精读来源交叉验证代替直接读 PDF;引述页码(p.46、p.73)均转引自来源 2,未在原书 PDF 上核验——此为本次精读的最大留痕局限。