通读笔记(按主题聚类,不按原书顺序)
通读完成 2026-08-28。正文:前言(02-fm.txt)、序章(04-fm.txt)、第1章(3 条)、第2章(7 条)、第3章(64 节,六大群)、第4章(6 条)、第5章(6 条)、第6章(6 条)、第7章(9 条)、后记(3 个做人原则)。合计 3+7+64+6+6+6+9=101 条,书名里的「101 个方法」对上了。导航章(版权页/目录/谢辞/版权声明)跳过。
作者:上田勋,前言落款 2016 年 1 月(中译 2020)。日系汇编书,每条原则带「是什么/为什么/怎么做/扩展/关联信息/出处/相关图书」七个版块。思想史线索是本书特色:每条都标出处(布鲁克斯《人月神话》、Robert Martin、Kent Beck《实现模式》、Raymond《UNIX 编程艺术》、Gancarz《UNIX 哲学》、温伯格《程序开发心理学》、福勒《重构》、埃文斯《领域驱动设计》……),前言明说「技术是为实现原则的目的而诞生的」「只有真正解决本质问题的技术留存到今天」——原则与技术是两个演化速率。
簇 1:前提——软件为什么 难、为什么必然被改(第1章)
05-ch01.txt1.1 没有银弹:软件本质四性质=复杂性(依赖关系随规模非线性增加)、同步性(与现实世界硬件/网络/人的习惯相连)、可变性(成品改变世界→新需求,「无法驻足于安宁之地是软件的宿命」)、不可见性(概念集合体,画面表达必舍象)。出处布鲁克斯《人月神话》。- 同节扩展:本质 vs 非本质(构建环境/语言/库/框架是非本质);非本质部分改善成效最大的是自动化(测试/构建/环境搭建)。
- 1.2 代码即设计:上游基本设计/详细设计/编程/测试/调试全是设计,设计输出物=代码;制造=编译器和构建系统。「能真正称得上工程文档的,只有代码」。罗塞塔石碑文档(写给未来维护负责人的手册);敏捷不是不写文档,是不写无用文档;代码表达不了「为什么」→注释补设计理由。
- 1.3 代码必然被修改:故障修复/新需求/商务环境变化/迭代本身都是修改。读远比写费时间→可读性优先。
- 序章 0.4:三条特征——①重复只因重要(关键内容在多条原则里换形态重现);②刻意不给代码范例(防理解被禁锢在范例里);③原则相悖取平衡点,追求「更优解」不是「最优解」。
- 序章 0.3 术语约定:产物统称软件;函数统称一切可调用代码块;模块统称类/文件/组件/库。
簇 2:交流/可读 性——代码写给人看(2.4 PIE、2.5 SLAP、2.7 命名、3.2、3.35、3.39、6.6)
- 2.4 PIE:代码是了解软件运行方式的唯一线索(需求文档/基本设计/详细设计都靠不住,详细设计常与动态变更的代码脱节)。读>写→「读的效率」优先于「写的效率」和「执行的效率」。打地鼠式开发=牺牲质量换速度的恶性循环。注释表达「为什么」。文学编程(Knuth 的 WEB,weave/tangle,TeX+Pascal)没普及,但「代码自我说明」以 PIE 留存。
- 2.5 SLAP:同一函数统一抽象级别;函数一览=目录、低级处理=正文;复合函数(composed method)尽量小,一行也可成函数;类设计=抽象类放高级概念、继承类放低级概念。图书类比:序言=文件头注释、章=函数、段落=代码块(空行区分)、句=语句。
- 2.7 命名:名字=面向代码阅读者的用户界面;命名=设计完成一大半;坏名字=代码「负债」。写代码前先取名;名字说明效果和目的而不是手段;能念、能搜;先写测试可从使用者角度验名。心理映射要避免(读者要把名字转换成已知概念才能懂);单字母变量名即心理映射。环回检测(名字可逆性):说明文本→名字→再倒推说明文本,绕一圈一致才合格。例子:「一种用语音来操作软件的功能」→语音识别功能×(看不出操作)→语音操作功能△→语音控制功能△→语音命令功能○(但可能被读成输出语音)。
- 3.2 交流:维护成本占软件大部分成本;开发阶段也在与自己片刻前写的代码交流;换位思考(从计算机转向阅读者)。
- 3.35 清晰原理:逻辑要能自证正确;「不择手段」加注释/文档/图;复用有风险(软件的上下文=代码与代码的相互作用,盲目复用会出意想不到的故障);修复必须建立在充分理解上(「代码能运行就好」不适用于修 bug)。
- 3.39 清晰原则(UNIX):不巧妙而清晰;同一段代码第二次还需要解读就要处理。
- 6.6 语境:读写代码的语境(模块名=章标题,函数名=节标题,自上而下);阅读前导实验(打乱的四字词,给「食物」类别提示后联想能力急剧提高);高手看语境不用条条框框;工作委托=给高手目标、给初学者步骤(浴室打扫例子);高语境团队;代码通用化要小心「偶然的共同」;程序员的语境切换与流态(15 分钟进入;一通电话 5 分钟→浪费 20 分钟,一天 10 通=半天);系统思维;领域驱动设计;亚里士多德三类知识(客观/技术/实践知识);关系主义(故障排查别只盯错误代码,要看关系)。
簇 3:重复与多余(2.1 KISS、2.2 DRY、2.3 YAGNI、3.3、3.6、3.43)
- 2.1 KISS:代码越改越乱的自然趋势;三个制造多余的诱惑=想用新学会的技术/以备将来之需/擅自增加需求。less is more、奥卡姆剃刀。
- 2.2 DRY:重复来源=复制粘贴整个逻辑、复制后单改分支体(条件相同的控制语句)、硬编码常量(
if( Ϙ < 16 )出现两次)、翻译型注释。重复的代价=可 读性降、改要逐处判断(有细微差别更要深读)、重复代码多为无测试的遗留代码。解法=抽象化(命名、函数化、常量化)。阻抗失配(O-R 三处重复)是不得已的重复,对策=集中一处+自动生成。WET;OFOP(数据库一个事实一处);OAOO(语法级 DRY)。遗留代码新定义=没有测试程序的代码;遇到遗留代码先补测试再改。 - 2.3 YAGNI:预想大多不成真;为扩展性写的东西后来成了「没人记得为什么存在的垃圾」;简单方案往往通用性更强(改起来容易);DTSTTCPW=选能用的最简单方案。
- 3.3 简洁(思想):多余的复杂性=修改遗留的痕迹,不是问题本身的复杂;玉与石要分开。
- 3.6 重复最少化(六原则版):重复违背效应局部化;「判断各处是否要改」成本极高。
- 3.43 简约原则(UNIX):不写大代码;代码变大就尽快分割。
簇 4:布局——为修改而设计(3.1、3.5、3.7–3.10、3.11–3.21、2.6、4.4)
- 3.1 编程理论(Kent Beck《实现模式》):三大思想=交流/简洁/灵活性;六原则桥梁=效应局部化/重复最少化/逻辑与数据一体化/对称性/声明式表达/变动率。思想是选技术的「理由」;形态遵从于功能——学技术要学它的演化史。
- 3.5 效应局部化:修改影响限局部;模块互相频繁调用=「原本该放在一起的要素被分开了」。
- 3.7 逻辑与数据一体化:常一起改的东西放一起;一开始难判断, 跑起来后关联渐显,再调整。
- 3.8 对称性:相同思路以相同形式出现;添加↔删除、同组函数同参数、同生存周期;对称化是消除重复的准备工作。
- 3.9 声明式表达:命令式描述解法(数据结构+算法),声明式描述问题定义;声明式没有流程限制所以可读;函数式/HTML/CSS/SQL 是声明式;命令式语言里用注释和 DSL 补。
- 3.10 变动率:同一时间改的放一起。税额例子:一般计算逻辑 vs 各年固有逻辑分开放,每年只动后者。数据例子:「金融商品」模块里的通货/金额抽成「货币」模块。→单一职责原则 SRP:一个模块一个修改理由;按变动率分组自然达成 SRP。
- 3.11–3.21 十大基本技法(空手道的「型」,源自 Buschmann 等 POSA):
- 抽象=舍象+一般化;抽象的反面具体,分析结果不可迁移。
- 封装=相关数据和逻辑组模块;不许无关元素混入。
- 信息隐藏=只展示必要信息;封装≠信息隐藏(封装是分组,信息隐藏是阻断访问);Parnas 原则(对使用者/开发者各只给必要信息)。
- 打包=模块分组;自下而上设计包(包是表现构建方法的图纸,不是功能分解单位);包要随开发成长。
- 关注点分离=修改以关注点为单位;MVC;AOP 管横切关注点(日志/事务/访问控制)。
- 充足性/完整性/原始性:收集概念的 add/remove/size 例子;有了 add 就不要 add10(原始性)。
- 策略和实现的分离:策略依赖软件前提(不稳定),实现独立(稳定、可复用);维护时会重新混在一起,改前先问「改的是策略还是实现」。
- 接口与实现的分离:针对接口编程。
- 单一引用点:变量赋值一次后不再改(单一赋值);副作用=状态变化影响后续结果;引用透明 性(结果只依赖参数+无副作用)→易测、可优化(记忆化)。
- 分治:软件按部分独立设计、模块按职责分、算法如归并排序、数据如 MapReduce。
- 2.6 OCP:对扩展开放对修改关闭;客户端接口由服务器实现;不过分预估变更——「先豁出去挨第一发子弹」;预估的不是变更内容而是「可能变化的部分」(图形种类的例子)→流动元素的胶囊化;多态/Strategy/Observer/Template Method/Decorator;GRASP 受保护变化=接口当防火墙/防水堤。
- 4.4 可逆性:不存在最终方案;框架中途被迫放弃(安全/许可证问题)→推翻全部设计的损失;数据库中途更换的例子(访问层抽象化→几步换完);可逆与简单之间按风险概率×影响找平衡。
簇 5:模块刻度——内聚、耦合、正交(4.1、4.2、4.3)
- 4.1 内聚度七等级(低→高):巧合(恰巧重复的命令群拼成模块,没法命名)→逻辑(抽象整合,一个接口按参数选功能,参数易错;但有信息隐蔽的优点)→时间(初始化模块:生成作业空间/清表,只因为「都在同一时间做」)→流程(流程图的一段)→通信(操作同一数据)→信息(同一数据结构由同一模块访问,多个入口各自单功能)→功能(所有命令为一件工作服务,最纯)。低内聚症状=难懂/难维护/难复用/脆弱。信息强度实用价值也很高;不得不用低内聚时也要讨论替代方案。
- 4.2 耦合度六等级(劣→优):内容(共享命令/直接引用内部,汇编常见)→公共(全 局变量;数据不出现在接口上→难解读;安全低;阻碍复用;A 改 X 长度→C 想不到地坏。但省参数,现实中不少;真因是模块设计不当,重审数据位置能减参数)→外部(public 声明)→控制(传选择变量指示被调方做什么→调用方必须懂对方逻辑,被调方内聚降到逻辑强度)→特征(传数据结构但只用一部分)→数据(只传标量,黑箱,最优)。混合耦合(税率函数正常返税率、出错返负数=一个值多重意义)。耦合还要看个数与方向(双向=高危)。幂等性+安全性→HTTP GET 重发无害。
- 4.3 正交性:改 A 不影响 B=正交;数据库访问代码⊥UI 代码。手段=层次化(网络协议例子:不懂以太网也能懂 FTP over TCP;换物理层不影响 FTP)。层次化的坏处=连锁修改(加字段要穿所有层)+性能损耗。全局数据最大化耦合。
簇 6:老化与保养(4.5、4.6、7.3、7.4、5.2、3.23 软件老化)
- 4.5 坏味:代码重复/函数太长(翻几页见不到底,没法一句话概括)/模块太大(责任过重)/模块太多(关联过多)/名称不一致(代码活着,名字会过期)。重构=不改外部行为改内部结构=「代码体质优化」;嗅觉是重构的前提(目录对不上现场)。
- 4.6 技术负债:不整洁代码=欠款;利息=越放越难改、修改让它更乱;负债过多=既不能加功能又不能维护。信用卡模式:赶工发布后立刻回滚重写,趁还记得赶紧还;没时间还至少把本应的设计写进文档。技术负债的比喻可向不懂代码 的上司争取时间。诱因清单(经验不足/交付压力/团队文化:不提倡整洁、不给重构留时间、发布前才回归测试……)。技术病(重构=手术,慢且精,一次改善一次测试);临时解决方案的惯性法则(「临时」变既成事实)。
- 7.3 破窗效应:一扇破窗不修→整栋楼被弃;信箱实验(涂鸦+垃圾→信件被盗率 25%);机制=「不安」+模仿;对策=发现即修;没时间修也要标注(IDE 任务列表)——「这段代码不好」被管理着。
- 7.4 熵增原理:软件逃不出熵增;不管理就越来越乱。七个腐坏征兆:刻板(改一处牵全身)、脆弱(一改坏别处;老出现在故障列表的模块)、可移植性差、难以掌控(做错容易做对难;编译太慢也会逼人抄近道)、复杂(预埋的应对机制,「很遗憾,这样做只会带来相反的效果」)、重复(看似相同实有细微差别=没做抽象化)、不透明。敏捷=不给初期设计留时间+持续测试。
- 5.2 童子军规则:离开时代码比来时干净;量变引起质变;改善多少都没关系。稳中求胜:省单元测试→软件脆弱;强用不合目的的系统→返工;将就不合适的库→层层遮蔽,再也无法优化。
- 3.23 软件老化:四原因(灵活性不足的修改破坏架构/改的人不懂设计/架构难懂被混乱修改破坏/更新停滞)。对策=文档化、保护架构、审查、灵活设计预测修改位置。
簇 7:出错的那一天(3.36、3.49、6.2、6.3)
- 6.2 契约式设计(Meyer):前置条件=调用方的责任(传对参数);后置条件=函数方的责任;不履行→异常/终止。好处=正确性/简洁(函数可假定前置成立)/早发现理解偏差(立刻崩溃好排查)。函数方不调整参数(转换参数是调用方的事);断言不用于用户输入检查(契约≠用户输入);前置要严格,后置要宽容(「懒惰的代码」);不变式=对类使用方一直为真。断言不能带副作用、发布版要剔除;发生不该发生的事→尽快停止。
- 6.3 防御性编程(《代码大全》):外部输入=预想之内的错误→错误处理;参数=预想之外→断言。路障战术:船体隔离区/防火墙/手术室(消毒后进门都是干净的);门外用错误处理、门内用断言。错误处理九变种(无害值/下一个数据/上一个值/近似值/日志警告/返回错误/统一处理函数/显示信息/终止)。正当性 vs 坚固性:医疗软件以正当性为先(宁停不错),文字处理软件以坚固性为先(宁错不停保数据)。输入在入口立刻转成正确格式(Yes 当颜色输入的例子);不忽视错误代码是铁则(系统调用也要查)。「到语言中」编程(不是"在语言中"):想法不被语言机制束缚。
- 3.49 修复原则:修复失败立刻停止+响亮的错误提示;静默破坏数据是最坏结局。Postel 法则(对输入宽容、输出保守)与 HTML 的苦果(各浏览器选不同超集);该宽容的是「运行方式」不是「运行方式的解释」。
- 3.36 安全原理:刻意把不可能的条件写进代码(必真的 if 也写 else、必中的 case 也写 default、不可能为空也查 NULL);取暖器倾倒断电类比;说明书只是必要条件,充分条件靠程序员+安全性补。
簇 8:UNIX(3.37–3.64)
- 3.37 UNIX 思想 17 原则=1969 年以来设计判断的沉淀;3.55 UNIX 哲学 9 定理(Gancarz)。
- 组合原则:软件做成过滤器(收数据流→加工→输出流,最好文本);文本接口简单→信息隐藏→能替换。
- 小就是美:易理解/易维护/资源负担小(轻量)/易组合;大软件=独立世界,备齐功能却无法应对例外。
- 工作唯一:显示目录的命令不该带美化功能,另做一个再组合;写不出单责软件=对问题理解不透;抵御「多功能主义」。
- 过滤器化+标准输入输出=可连接性;GUI 也是过滤器(事件流→显示更新)。
- 十准则(3.64 扩展):用户自定义环境、内核小而轻(应用程序进内核=不可修改+故障连坐)、小写短名(README 因此醒目)、保护森林(纸上的数据不能搜索)、沉默是金(无输出=有意义数据不被淹没;其他系统显示「没有找到文件」UNIX 什么都不显示)、并行思考、部件联动(总和大于整体)、完成 90%(最难的 10% 最贵,故意无视)、劣就是优(最大公约数系统活得久)、层次化思维(文件系统/进程树)。
- 3.47 最小意外:接口符合使用者已有知识(「+」必须永远表示加法);get↔fetch(远程取值换名);最糟是「相似但稍有不同」。
- 3.48 沉默:输出给别的软件当输入时玉石混淆会破坏组合;冗余模式默认关。
- 3.58 原型+三系统论:第一系统缺功能、第二系统牺牲性能堆功能、第三系统平衡;不能跳步,只能用原型缩短周期。
- 3.59 可移植性>效率:依赖特定硬件=硬件失去竞争力时软件贬值;深度优化用硬件特殊能力→伤可移植;「等更好的硬件解决速度」。
- 3.60 文本数据:文本=可移植性最强的格式;CSV/XML 等标准格式。
- 3.61–3.62 杠杆:借代码(「好的程序员能写出好的代码,伟大的程序员能借来好的代码」);shell 当胶水(解释型→可移植);编译型当胶水伤可移植还打断节奏。
- 3.63 避开交互式接口:用户被捆绑、软件间无法对话、人的输入速度成机器瓶颈、菜单诱发多功能主义;给初学者和老用户两套接口。
- 3.50 经济原则:程序员时间>设备钱;规章制度阻碍开发=本末倒置。
- 3.51 生成原则:写生成代码的代码;重复的、形式固定的代码最适合。
- 3.52 优化原则(UNIX 版):先正确后快;过早优化牺牲透明性/简单性、局部优化妨碍整体优化;「最有力的优化工具是删除键」。
- 3.53 多样性:不存在唯一正确方法,宣称唯一正确=狂妄且不成熟。
- 3.54 可扩展性:可插拔+「如果需要××」注释;数据格式自我描述(版本号)。
- 3.44 透明性+公示性:调试占用开发周期大部分时间→为调试的功能不是附属品。
- 3.45 健壮性:健壮=透明+简单(结构能被轻松识别)。
- 3.46 表达性:信息尽量放数据侧不放逻辑侧(换算表数组 vs switch);复杂化时选数据复杂化。
簇 9:日常手法与习惯(5.1–5.6、6.1、6.4、6.5)
- 5.1 三大美德(Larry Wall):懒惰=不遗余力减少整体劳力(自动化);急躁=计算机偷懒时发火→立即修改+预想未来;傲慢=自尊心→写别人挑不出毛病的代码。行动=自动化/雏形化/模块化;自动化前先找出效果最好的地方,别一上来就自动化;文档重可搜索不重整理(全文搜索>