代码结构 — 让代码长得像问题本身¶
这一章讲代码的「建筑学」:类怎么切、类之间怎么连、行宽多少、用什么工具守门。 它是前半本(语言机制)的收束:命名、数据结构、异常,最终都要装进一个「结构」里, 而结构的判据只有一句——你的代码应该长得像你要解决的问题,不多一分。
1. 这一章讲什么¶
原书开头直说现状:新手写代码没有任何结构——他们忙着让代码跑通,顾不上结构; 「这是个大错误。结构好,长期来看能省大量时间。」1
加框的一句是全章的纲:「代码的复杂度,应该只等于你要解决的问题的复杂度。高出一截,就该重想结构。」
本章三件事,由抽象到具体:
① 问题的形状 → 代码只实现「解决问题所必需」的部分(车与引擎的例子)
② 连接的形状 → 杜绝循环依赖;降低耦合、提高内聚
③ 文本的形状 → 行宽、缩进、linter
图说:①管「写什么」,②管「怎么连」,③管「怎么摆」。
2. 核心原理一:抽象到最小必要(车与引擎)¶
解决什么问题: 代码该模仿现实到几分?答案是只模仿解决问题需要的部分—— 就像电脑游戏不模拟真实世界的每个细节,一个「简化的现实模型」反而更好玩(更好用)。
怎么做(主线例子): 假设你在做汽车生意,要建模一辆车。2
走一遍两个场景:
| 业务关心什么 | 代码里的 Car 需要什么 | 不需要什么 |
|---|---|---|
| 造车、关心零部件 | Car 类,成员是 engine(引擎)、tires(轮胎)…… |
—— |
| 只是卖车 | Car 类,一个 price 字段,也许加几个选装项 |
引擎、轮胎一概不出现 |
第二个场景就是「抽象到最小必要」:对卖车业务来说,引擎不是 car 的一部分。 少建模的那部分不是偷懒,是防止无关复杂度污染代码。
配套的职业判据:「你应该能和市场部的人谈你的问题,用的词和代码里的类名是同一套,
而且这些词在代码里的性质和现实一致」——卖车人口中的「车」只有价格,代码里的 Car 就只有价格。2
循环依赖:绝不允许的连接¶
借这个例子,书里立了一条禁令:永远不要循环依赖。车有一个引擎作为成员变量, 那么引擎就不应该知道外面那辆车的存在——现实里你也只能透过车接触到引擎, 代码里保持同样方向就对了。3
允许: Car ──包含──▶ Engine (单向:车用引擎,引擎不知车)
禁止: Car ◀────────▶ Engine (互相知道:改任何一边都要看另一边,理解成本翻倍)
图说:循环依赖之所以是 antipattern(公认的坏模式),是因为它让「理解一块代码」
永远等价于「理解全部代码」。
第 10 章讲函数时还有一条同款禁令(函数 a 调 b、b 又调 a)——循环依赖在哪种粒度上都是坏味道。
3. 核心原理二:耦合与内聚¶
解决什么问题: 「结构好」到底怎么度量?书里给了两把互补的尺子。
怎么做: 先给定义:4
- 耦合(coupling):代码不同部分之间相互依赖的程度。耦合不可避免——没有耦合代码就是散沙—— 但书里有个贴切的比喻:耦合是胶水,「胶太少粘不住,胶太多到处黏,代码一改就牵一发动全身」。 所以目标:引入尽可能少的耦合。
- 内聚(cohesion):一个部分内部各元素属于一起的程度。主要用于类:把不属于这个类的东西都挪走, 内聚就提高。
原书配了两张图(图 3.1 与 3.2):每个虚线框是一个对象,圆是元素(变量或函数),线是依赖—— 目标是框内连线多、框间连线少。4
内聚与单一职责的同向性: 提高内聚的过程,就是把不相关成员往外挪的过程—— 挪到最后,类自然只剩一件事,这正是单一职责原则(SRP,Single Responsibility Principle): 「任何对象(类、函数)只做一件事。」书里的经验信号:类太长了,就说明它在做好几件事,该拆。5
为什么有效: 耦合决定「改一处会震几处」的半径,内聚决定「读一处需要装几件事」的负荷; 两把好,意味着修改的爆炸半径小、理解的单次负荷低——这正是第 02 章「读多于写」的结构化表达。
4. 核心原理三:格式与工具——用物理限制逼出简单¶
解决什么问题: 复杂度会悄悄长在一行里、一层缩进里;人眼和约定是它的第一道防线。
怎么做: 书里给了三条:6
- 行宽有限:PEP 8 建议行最长 79 字符;更宽松的标准是 100。「行太长,本身就是单行复杂度超标的信号。」 作者还引了 Linux 的做法:tab 定为 8 个空格宽,主开发者的名言:「如果你需要超过 3 层缩进,你就已经有麻烦了,该去修程序。」
- 缩进层数有限:嵌套是复杂度的视觉化——缩进越深,越该拆函数(第 01 章的禅句「扁平优于嵌套」在此落地)。
- 用 linter 守门:linter(代码检查工具)自动查格式错误和其他问题;最常用的是 pylint, 可以集成进 IDE;写书时更快的现代替代品是 Ruff。
为什么有效: 行宽和缩进是「物理护栏」——把一行能装的信息量硬封顶,超载就逼你拆; linter 把「风格纪律」从人脑记忆里卸载到工具上,跟第 01 章「类型检查靠测试」同一逻辑: 凡是能交给工具的监督,别留在脑子里。
5. 作者的判断与证据¶
书里给了证据的: 耦合/内聚的两张示意图是直接的视觉论证;行宽数字来自 PEP 8 等外部标准; Linux 内核的 3 层缩进是引用的公开事实。6
作者的判断(立场与经验):
- 「代码复杂度 = 问题复杂度」是无法精确证明的设计公理,但它的可操作性高:与市场部共用词汇是绝妙的检验法;
- 「类太长就在做好几件事」——经验法则,普遍认同;
- 「循环依赖绝不允许」——强硬的禁令式表述;小项目里偶尔的无害环(如双向关联的数据结构)也常被容忍,但他的默认是对的。
判断(我们的,不是书里的): 「与市场部共用词汇」是本章最实用的一招,值得单独带走: 它把「抽象对不对」这个抽象问题,变成「业务方听到类名会不会点头」这个可以当场验证的问题。 如果错,会错在: 高度技术性的底层系统(编译器、数据库)没有「市场部」,它们的「问题词汇」是形式化的; 那时这条检验法退化成「和领域专家共用词汇」,精神不变,但需要一个真的懂领域的人在场。
6. 边界与局限¶
- 本章没讲怎么拆(拆类的具体手法、何时新建模块)——那是第 10、13 章的函数与模块话题;
- 没讲设计模式——作者明显不信任模式目录,全章只出现 SRP 一个原则;
- 对「内聚」只给了「把不属于的挪走」一个方向,没提「怎么判断不属于」(经验题);
- 行宽 79 vs 100 之争,作者两可,没给个人裁决。
7. 可带走的¶
本章主线例子,一行写完: 卖车业务的 Car 只需要 price——
引擎是造车业务才需要的成员;「抽象到最小必要」,词和业务对齐。2
- 代码复杂度 = 问题复杂度;超出的部分是债。
- 问题抽象到最小必要:不实现解决问题不需要的现实细节。
- 与市场部共用词汇:类名、属性名就是业务词,性质也要一致。
- 杜绝循环依赖:车用引擎,引擎不知车;任何粒度上都成立。
- 降低耦合、提高内聚:框间连线少、框内连线多;胶水是必要的恶,少抹。
- 类太长 = 做多件事:向「只做一件事」拆(SRP)。
- 行宽 ≤ 79/100 字符、缩进 ≤ 3 层:物理护栏逼出简单;linter(pylint/Ruff)替你守纪律。
7A. 把本章折进日常工作¶
三个轻量习惯,把「结构」从一章知识变成日常动作(我们的建议,不在书里):
- 写完一个类,数一下行数——超过 100 先别合入,回去看 SRP;
- 画依赖图——手头模块超过五个,拿纸笔画框和线,线最多的那个框就是要拆的;
- 和市场部对词——需求评审时,把类名念给业务方听,皱眉的名字回去重想。
三条加起来的时间成本,一周不到一小时;省下的重构时间,以月计。
7B. 与第 15 章的接头¶
本章的「可测试 = 结构好」在第 15 章变成操作:为一段代码写测试的难易程度, 就是它结构质量的即时反馈——测试难写,多半不是测试技术问题,是耦合/内聚问题。 所以结构审查可以并入测试编写:不用单独的评审会,写测试的过程就是评审。
7C. 一张图记住本章¶
问题 ──抽象到最小必要──▶ 模块
│ │ 耦合少(框间线少)
│ 和市场部共用词汇 │ 内聚高(框内线多)
▼ ▼
类/函数 ◀──SRP:一个职责── 无循环依赖
│ │
└──── 行宽79、缩进≤3、linter 守门 ────┘
图说:上半是「写什么」,下半是「怎么摆」;上下都为了同一个读者。
8. 原文地图¶
| 主题 | 原书章 | 原文位置 |
|---|---|---|
| 新手无结构之诫、复杂度=问题复杂度 | Code Structure | text/19-fm-code-structure.txt:28(搜「without any structure」) · text/19-fm-code-structure.txt:33(搜「as high as that of the problem」) |
| 抽象到最小必要、与市场部共用词汇 | Code Structure | text/19-fm-code-structure.txt:43(搜「bare minimum」) · text/19-fm-code-structure.txt:44(搜「marketing team」) |
| 车与引擎的两个场景 | Code Structure | text/19-fm-code-structure.txt:47(搜「Car class」) · text/19-fm-code-structure.txt:49(搜「selling cars」) |
| 循环依赖禁令 | Code Structure | text/19-fm-code-structure.txt:53(搜「Cyclic Dependencies」) · text/19-fm-code-structure.txt:57(搜「surrounding car」) |
| 耦合:胶水的比喻 | Code Structure | text/19-fm-code-structure.txt:85(搜「glue」) · text/19-fm-code-structure.txt:87(搜「as little coupling」) |
| 内聚与 SRP、两张图 | Code Structure | text/19-fm-code-structure.txt:95(搜「belong together」) · text/19-fm-code-structure.txt:98(搜「only one thing」) · text/19-fm-code-structure.txt:101(搜「Figures 3.1」) |
| 行宽 79/100、Linux 3 层缩进 | Code Structure | text/19-fm-code-structure.txt:67(搜「79 characters」) · text/19-fm-code-structure.txt:70(搜「screwed anyway」) |
| pylint 与 Ruff | Code Structure | text/19-fm-code-structure.txt:74(搜「linters」) · text/19-fm-code-structure.txt:77(搜「Ruff」) |
-
出处:「Code Structure」第 28–31 段(
text/19-fm-code-structure.txt:28,搜「without any structure」)。 ↩ -
出处:「Code Structure」第 40–51 段(
text/19-fm-code-structure.txt:43,搜「abstract the problem」;第 45 段,搜「marketing team」;第 47 段,搜「Car class」;第 49 段,搜「selling cars」)。 ↩↩↩ -
出处:「Code Structure」第 53–59 段(「Cyclic Dependencies」框,
text/19-fm-code-structure.txt:57,搜「surrounding car」)。 ↩ -
出处:「Code Structure」第 84–104 段(
text/19-fm-code-structure.txt:85,搜「glue」;第 95 段,搜「belong together」;第 101 段,搜「many dependencies within」)。 ↩↩ -
出处:「Code Structure」第 96–100 段(
text/19-fm-code-structure.txt:98,搜「only one thing」)。SRP 全名 Single Responsibility Principle,书中以缩写直接引用。 ↩ -
出处:「Code Structure」第 61–77 段(
text/19-fm-code-structure.txt:67,搜「79 characters」;第 70 段,搜「3 levels of indentation」;第 74 段,搜「linters」;第 77 段,搜「Ruff」)。 ↩↩