2.0

查看英文版

2.0 第二部分导读:软件编程

第二部分讲的是编写软件这项日常技艺()写出许多人都能读懂、能够修改、并能长期信赖的软件。第一部分确立了团队如何组织和决策的基础。这一部分则转向代码本身:你所遵循的约定、你塑造设计和接口的方式、你测试和评审工作成果的方式、你管理源代码历史的方式,以及你记录信息的方式。正是这些实践,将一个能加速交付的代码库,与一个每次变更都举步维艰的代码库区分开来。

在一个大型团队中,技艺并非个人品味的问题,而是协作方式的体现。当成百上千名工程师、承包商和后继者共同接触同一套系统时,共享的约定和清晰的契约,正是让所有人能够并行工作而不至于不断相互冲突的关键。要记住,代码被阅读的次数远远多于被编写的次数,而这些阅读活动中有很大一部分,是多年之后由你永远不会遇见的人来完成的。

在企业和政府场景中,风险更进一步升高。系统的寿命往往比其作者的任期长十年甚至更久。监管和审计要求提供可控性的文档化证据。知识必须能够跨越人员流动和合同边界得以传递。因此,本部分各章将质量视为一种经过工程化、在很大程度上自动化的属性,而不是靠英雄主义式的个人努力。

本部分各章

  • 2.1 编码标准与风格: 共享的、自动强制执行的命名、格式和惯用法约定,使众多作者写出的代码看起来就像出自一位细致的作者之手,从而让评审者把注意力放在设计上,而不是风格上。

  • 2.2 软件设计原则: SOLID(五项面向对象设计原则)、DRY(不要重复自己)、耦合与内聚,以及领域驱动设计(用业务领域的语言对软件建模)等启发式方法,被当作具有适用范围和已知失效模式的工具来对待,而不是必须遵守的法则。

  • 2.3 API 与接口设计: 设计系统和团队相遇之处的契约,使独立的团队能够在不破坏消费方、也不强迫步调一致部署的情况下,改变自己的内部实现。

  • 2.4 测试策略: 有意识地决定测试什么、在哪个层级测试,以及要达到多高的信心水平,从而构建一张快速而可信的安全网,让大型组织能够频繁而安全地进行部署。

  • 2.5 代码评审与协作: 在变更合并之前对其加以审查,以发现缺陷、传播知识、强制执行标准并满足合规控制要求,同时保持评审快速而有建设性,而不是流于形式。

  • 2.6 版本控制与源代码管理: 每一次变更的官方记录系统,以及维持主干可发布、历史可读、审计线索完整所需的分支、代码仓库和提交纪律。

  • 2.7 文档: 从入门指南到操作手册(一步步的运维流程)和决策日志等书面知识,用以防范关键人物风险、加速新人上手,并跨越多年和合同边界传递理解。

  • 2.8 软件需求: 引出、明确、验证和管理软件必须做什么、以及做到什么程度,并提供受监管工作和政府工作所要求的可追溯性。

  • 2.9 软件构建: 构建可工作软件的技艺:最小化复杂性、为可验证性和可变更性而构建、防御性编程,以及有纪律的复用。

  • 2.10 软件配置管理: 识别、控制并审计每一个配置项(任何版本必须被跟踪和控制的产物)及其变更,使发布可复现、审计线索完整。

  • 2.11 软件质量: 将质量视为一项比测试更广泛的、受管理的属性:质量模型、保证与控制之分、度量、缺陷管理,以及质量成本。

  • 2.12 软件建模与方法: 何时以及如何建模,涵盖结构模型和行为模型、形式化方法(基于数学的规约与验证)、原型设计和敏捷方法,以及何时建模只是在浪费时间。

  • 2.13 计算、数学与工程基础: 这门实践之下经久不衰的基本功:算法与数据结构、逻辑与概率,以及实证式的工程方法。

  • 2.14 项目与代码仓库结构: 组织一个解决方案及其代码仓库的一致性约定,包括标准文件夹、作为入口的 README,以及共享配置,使任何工程师都能在任何代码库中找到方向。

  • 2.15 调试与故障排查: 把查找和修复缺陷当作一门有纪律、可传授的实践:复现问题、用二分法定位、提出并检验假设,并将每一次修复固化为一个回归测试,而不是靠猜测和乱枪打鸟式的修改。

  • 2.16 性能工程: 通过设定性能预算、在优化之前先测量和做性能分析、理解算法成本和尾部延迟,以及防范回归,在代码和组件层面有意识地让软件足够快。

  • 2.17 并发与并行: 默认采用不可变性和消息传递来编写正确的并发代码,理解竞态条件、死锁和内存可见性,选择合适的同步机制和更高层次的模型,并有意识地测试非确定性行为。

  • 2.18 依赖与供应链管理: 通过版本纪律和锁定文件、稳定的更新节奏、精简且经过审查的依赖足迹,以及来源信息和软件物料清单,来管理构成现代系统大部分内容的第三方代码,从而建立可信的供应链。

  • 2.19 重构与技术债务: 在可信测试套件的保护下改进可运行代码的内部设计,识别代码异味并运用小型的、有命名的重构手法,对较大规模的变更采用绞杀者模式(strangler fig),并将技术债务作为一个可见的、有资金投入的组合来管理,而不是当作一种道德上的失败。

  • 2.20 错误处理与弹性模式: 通过明确的错误契约、快速失败与安全失败之间的取舍、带退避和幂等性的重试、断路器和优雅降级,有意识地决定代码如何失败和恢复,绝不悄悄吞掉一个错误。

  • 2.21 类型系统与静态分析: 通过让非法状态无法表示的静态类型和渐进式类型,以及接入编辑器和流水线的代码检查工具(linter)、类型检查器和分析工具,在代码运行之前就捕获整类缺陷。

这些章节之间如何相互关联

第二部分的主线是规模化条件下的可变更性。这里的每一项实践,其存在的目的都是让许多人能够满怀信心地改动一个共享的、长期存在的系统。编码标准(2.1)和设计原则(2.2)塑造代码,使你能够理解和修改它。接口设计(2.3)划定了让团队能够独立改变自身内部实现的边界。测试(2.4)提供了让变更变得安全的安全网。代码评审(2.5)是个人工作与集体所有权相遇的地方,也是标准真正得到强制执行的地方。版本控制(2.6)是评审、集成和审计都赖以立足的基础。而文档(2.7)则为后来者保存了贯穿这一切背后的意图。

这些章节也为本指南的其余部分提供了养料。此处的接口和设计原则,会成为第三部分中各系统的构建模块,尤其是架构基础(第 3.1 章)。测试策略(2.4)和版本控制(2.6)是第 8.1 章中自动化交付流水线的原材料。文档实践(2.7)直接连接到运维的操作手册和可观测性内容,例如第 9.2 章。而整个第二部分,都建立在第一部分所确立的价值观和决策基础之上,将共享的原则转化为具体的日常技艺。