应用软件工艺的元原则:DRY、正交性、曳光弹和契约式设计。当用户提到“最佳实践”、“务实方法”、“破窗效应”、“曳光弹”、“软件工艺”、“避免技术债务”、“代码所有权”或“如何成为更好的开发者”时使用。在评估自建与购买决策、设计估算方法或选择可逆与不可逆架构决策时也会触发。涵盖估算、领域语言和可逆性。关于代码级质量,请参阅 clean-code。关于重构技术,请参阅 refactoring-patterns。
务实程序员框架
来自 Hunt 和 Thomas 的《务实程序员》(20周年纪念版)的软件工艺系统级方法。在设计系统、审查架构、编写代码或就工程文化提供建议时应用这些元原则——思考如何对待软件,而不仅仅是编写它。
核心原则
关心你的手艺。 软件开发需要持续学习、严格训练和个人责任——务实程序员超越眼前问题,思考上下文、权衡和长期后果。优秀的软件来自优秀的习惯:无情地避免重复,保持组件正交,将每一行代码视为必须证明其价值的活资产。目标不是完美——而是易于变更、易于理解和易于信任的系统。
评分
目标:10/10。 对照七个快速诊断行评分:每行回答“是”得约1.4分(7个是=10分)。然后对结果进行分档:
- 9-10:所有原则都成立——DRY知识、正交层、可工作的曳光切片、边界处的契约、无破窗、可逆的供应商/数据库选择、范围估算。
- 5-6:1-2个违规导致实际变更成本(例如,业务逻辑耦合到数据库、单点估算)。
- <=3:普遍重复、全局状态或累积的破窗——熵正在获胜。
始终给出分数,指出失败的诊断行,并从“行动”列给出具体修复以达到10/10。
七个元原则
构建持久软件的七个原则:
1. DRY(不要重复自己)
核心概念: 每一条知识在系统中必须具有单一、明确、权威的表示。DRY是关于知识而非代码——重复的逻辑、业务规则或配置比重复的语法危险得多。
为什么有效: 重复的知识必须在多个地方修改;最终会遗漏一处,导致不一致。DRY减少了错误的表面积,使系统更易于变更。
关键见解:
- DRY适用于知识和意图,而非文本相似性——两个服务于不同业务规则的相同代码块并非重复
- 四种重复类型:强加的(环境迫使)、无意的(开发者未意识到)、不耐烦的(懒得抽象)、开发者之间的(多人重复)
- 重述代码的注释违反DRY——解释为什么,而不是什么
- 数据库模式、API规范和文档除非从单一来源生成,否则会重复知识
- DRY的反面是WET:“写两次”或“我们喜欢打字”
代码应用:
| 上下文 | 模式 | 示例 |
|---|---|---|
| 配置值 | 单一事实来源 | 数据库连接在一个环境文件中,各处引用 |
| 验证规则 | 共享模式 | 一个JSON Schema或Zod模式用于客户端和服务器 |
| API契约 | 从规范生成 | OpenAPI规范生成类型、文档和客户端代码 |
参见:references/dry-orthogonality.md 用于分类特定重复或判断两个代码块是否真正相同知识——每种重复类型的示例和缓解措施。
2. 正交性
核心概念: 如果两个组件的变更互不影响,则它们是正交的。设计系统时,组件应自包含、独立且具有单一明确目的。
为什么有效: 解耦将变更局部化——一个模块的修复不会波及无关模块,因此爆炸半径保持有限。更改数据库层,UI不应损坏;更改认证提供者,业务逻辑不应关心。
关键见解:
- 问:“如果我大幅改变某个功能背后的需求,会影响多少个模块?”答案应为一个
- 消除无关事物之间的影响——日志记录变更不应破坏计费
- 分层架构促进正交性:表示层、领域逻辑、数据访问
- 避免全局数据——每个全局状态的消费者都与其耦合
- 强制你继承其类的框架会降低正交性
代码应用:
| 上下文 | 模式 | 示例 |
|---|---|---|
| 架构 | 分层分离 | Controller -> Service -> Repository,每个可替换 |
| 依赖 | 依赖注入 | 传递Notifier接口,而非SlackClient具体类 |
| 测试 | 隔离单元测试 | 测试业务逻辑时不依赖数据库、网络或文件系统 |
参见:references/dry-orthogonality.md 用于衡量耦合或重构为解耦层——变更影响和陌生人测试、分层架构图以及直升机类比。
3. 曳光弹与原型
核心概念: 曳光弹是端到端实现,以最少功能连接系统的所有层。与原型(可丢弃)不同,曳光弹代码是生产代码——薄但真实。
为什么有效: 曳光弹在投入填充每个功能之前提供即时端到端反馈。用户看到真实的东西,开发者有构建框架,集成问题早期暴露。
关键见解:
- 曳光弹:通过系统的薄但完整路径(UI -> API -> DB)——保留它
- 原型:专注于探索单个风险方面——丢弃它
- 在“黑暗中射击”时使用曳光弹——模糊需求、未经验证的架构
- 如果曳光弹未命中,调整并再次发射——迭代成本低
- 明确标记原型为可丢弃——永远不要让它成为生产代码
代码应用:
| 上下文 | 模式 | 示例 |
|---|---|---|
| 新项目 | 垂直切片 | 一个功能端到端:按钮 -> API -> DB -> 响应 |
| 不确定技术 | 尖峰原型 | 在承诺之前测试WebSocket性能 |
| 微服务 | 行走骨架 | 通过完整CI/CD管道的Hello-world服务 |
参见:references/tracer-bullets.md 用于在新项目上决定曳光弹还是原型,或构建行走骨架——黑暗中射击决策、迭代循环和常见陷阱。
4. 契约式设计与断言式编程
核心概念: 通过前置条件(调用前必须为真)、后置条件(调用后保证为真)和不变量(始终为真)定义并强制执行软件模块的权利和责任。当契约被违反时,立即且大声地失败。
为什么有效: 契约使假设显式化。系统不会静默地损坏数据或在无效状态下跛行,而是在问题点崩溃——死程序不说谎。
关键见解:
- 前置条件:调用者的责任——“我只接受正整数”
- 后置条件:例程的保证——“我将返回排序列表”
- 不变量:始终为真——“账户余额永不为负”
- 尽早崩溃:死程序造成的损害远小于残废程序
- 对绝不应发生的事情使用断言;对可能发生的事情使用错误处理
- 在动态语言中,通过运行时检查和守卫子句实现契约
代码应用:
| 上下文 | 模式 | 示例 |
|---|---|---|
| 函数入口 | 前置条件守卫 | 函数开始时assert age >= 0, "Age cannot be negative" |
| 类状态 | 不变量验证 | 每次状态变更后调用validate! |
| API边界 | 模式验证 | 在处理之前根据模式验证请求体 |
参见:references/contracts-assertions.md 用于向例程添加契约或决定断言与错误处理——前/后/不变量模式、动态语言守卫子句以及断言与错误处理的边界。
5. 破窗理论
核心概念: 一扇破窗——一段设计糟糕的代码、一个糟糕的管理决策、一个“以后修复”的hack——开始腐烂。一旦系统显示出忽视,熵加速,纪律崩溃。
为什么有效: 心理学。当代码干净时,开发者感到社会压力要保持干净;当代码已经混乱时,添加更多混乱的阈值降至零。质量是团队习惯,而非个人英雄主义。
关键见解:
- 不要留下未修复的破窗(糟糕的设计、错误的决策、糟糕的代码)
- 如果现在无法修复,先封起来:带有工单的TODO、禁用的功能、存根
- 成为变革的催化剂:向人们展示未来的工作片段(石头汤)
- 警惕缓慢退化(温水煮青蛙)——随时间监控技术债务指标
- 第一个hack最昂贵,因为它为所有后续hack提供了许可
代码应用:
| 上下文 | 模式 | 示例 |
|---|---|---|
| 遗留代码 | 封窗 | 在添加功能之前用干净接口包装糟糕代码 |
| 代码审查 | 对新债务零容忍 | 拒绝添加// TODO: fix later而无工单的PR |
| 技术债务 | 债务预算 | 每个冲刺分配20%用于修复破窗 |
参见:references/broken-windows.md 用于团队正常化忽视或需要推动转变时——修复策略、石头汤催化剂玩法以及建立质量文化。
6. 可逆性与灵活性
核心概念: 没有最终决定。构建系统时,使其易于改变关于数据库、框架、供应商、架构和部署目标的想法——变更成本应与变更范围成比例。
为什么有效: 需求变化,供应商被收购,技术失宠。如果你的架构硬编码了关于这些的任何假设,每次变更都变成重写;灵活架构将决策视为配置而非结构。
关键见解:
- 将第三方依赖抽象到自己的接口后面——绝不让供应商API泄漏到业务逻辑中
- “岔路”测试:你能在一周内从Postgres切换到DynamoDB吗?如果不能,你就耦合了
- 元数据驱动系统(配置文件、功能开关)比硬编码逻辑更灵活
- YAGNI也适用于过早抽象——不要构建你尚不需要的灵活性
- 可逆性不是预测未来;而是不把自己逼入角落
代码应用:
| 上下文 | 模式 | 示例 |
|---|---|---|
| 数据库 | 仓库模式 | 业务逻辑调用repo.save(user),而非pg.query(...) |
| 外部API | 适配器/包装器 | PaymentGateway接口包装Stripe;稍后切换到Braintree |
| 功能开关 | 运行时切换 | 新结账流程在开关后,秒级回滚 |
参见:references/reversibility.md 用于承诺供应商或框架时,或权衡决策的可逆程度——每层可逆性模式、岔路测试以及何时不优化可逆性。
7. 估算与知识投资组合
核心概念: 通过理解范围、构建模型、分解为组件和分配范围来学习可靠估算。像管理金融投资组合一样管理你的学习:定期投资、多样化、再平衡。
为什么有效: 诚实的估算建立与利益相关者的信任(“1-3周”优于自信错误的“2周”)。知识投资组合让你在技术变迁中保持相关性——停止学习的程序员停止有效。
关键见解:
- 问“这个估算用于什么?”——上下文决定精度(预算规划 vs. 冲刺规划)
- 使用PERT:(乐观 + 4x最可能 + 悲观)/ 6
- 分解为组件并估算每个;总和比单一猜测更准确
- 保留估算日志:比较估算与实际并校准
- 投资组合规则:定期投资(每周学习),多样化超出你的技术栈,混合安全与投机性赌注,早期学习新兴技术(低买)
代码应用:
| 上下文 | 模式 | 示例 |
|---|---|---|
| 冲刺规划 | 范围估算 | “3-5天”带置信水平,而非单一数字 |
| 新技术 | 限时尖峰 | “评估2天;然后我能正确估算” |
| 学习 | 每周投资 | 每周1小时学习新语言、工具或领域 |
参见:references/estimation-portfolio.md 用于生成将被追究的估算或校准过去的失误——PERT和分解程序、估算日志校准循环以及投资组合再平衡。
常见错误
| 错误 | 为什么失败 | 修复 |
|---|---|---|
| 对服务于不同目的的相似代码应用DRY | 耦合无关概念;一个变更破坏另一个 | 仅DRY知识,而非巧合的代码相似性 |
| 跳过曳光弹,逐层构建 | 集成问题晚期暴露;无端到端反馈 | 先构建一个薄垂直切片 |
| 忽略破窗“因为以后重构” | 熵加速;以后永远不会来;士气下降 | 立即修复或用跟踪工单封窗 |
| 估算作为单点承诺 | 虚假精度在未达成时侵蚀信任 | 始终给出带置信水平的范围 |
| 预先使一切“灵活” | 过度工程;无证据需要的抽象 | 在有具体证据需要时添加灵活性 |
| 移除生产断言“为了性能” | 断言本可捕获的bug现在静默损坏数据 | 保留关键断言;在移除前进行基准测试 |
| 全局状态“为了方便” | 破坏正交性;一切与一切耦合 | 使用依赖注入和显式参数 |
快速诊断
| 问题 | 如果否 | 行动 |
|---|---|---|
| 我能否在不触及业务逻辑的情况下更改数据库? | 正交性违规 | 引入仓库/适配器模式 |
| 我是否有可工作的端到端切片? | 缺少曳光弹 | 在扩展之前构建一个垂直切片 |
| 每条业务规则是否只在一个地方定义? | DRY违规 | 识别权威来源;移除重复 |
| 新开发者会称此代码库“干净”吗? | 存在破窗 | 安排专门的清理冲刺 |
| 我的估算是否包含范围和置信水平? | 估算问题 | 切换到PERT或基于范围的估算 |
| 我能否在5分钟内回滚此部署? | 可逆性差距 | 添加功能开关和蓝绿部署 |
| 我是否每周学习新东西? | 知识投资组合停滞 | 安排每周学习时间并跟踪 |
延伸阅读
- 《务实程序员:通往精通之旅,20周年纪念版》 by Andrew Hunt and David Thomas
关于作者
Andrew Hunt 和 David Thomas 共同创立了Pragmatic Bookshelf,并且是敏捷宣言的17位原始作者之一。Thomas创造了“DRY”和“Code Kata”,合著了《Programming Ruby》(镐头书);Hunt专注于团队如何学习、沟通和保持质量。他们共同撰写了《务实程序员》,这是有史以来最具影响力的软件书籍之一。






