规范驱动开发是一种实践,将可执行规范视为团队所拥有的主要制品,代码作为该意图的下游投影而生成。当规范具有足够的精确度以再生实现时,规范便成为事实来源——人们会对其进行版本管理、审阅和讨论——而代码则成为其下方可派生且可替换的层。
- 规范成为持久的制品;代码成为其可再生的投影。
- 审查重心上移,从实现差异转为意图差异。
- 再生的确定性比任何单一输出的优雅更重要。
- 当意图与实际运行不符时,规范会积累自身的债务。
- 对规范进行版本控制会改变系统内部“变更”的含义。
- 随着系统增长,规范之间的缝隙比代码先出现应力。
为什么规范,而不是代码,会成为事实来源
可执行规范指的是对期望行为的描述,其精确度足以让生成器在无需额外人工编写的情况下产出可工作的实现。它带来的变化是静默的,但却具有结构性。数十年来,代码是保留下来的制品;注释、设计文档和票据在其周围逐渐失效,运行中的程序成为系统行为的唯一真实记录。以规范为先则颠倒了这种关系。
当意图被捕获为机器可执行的形式时,规范不再是文档而成为资产。代码由其再生,任一时刻存在的实现只是众多有效投影之一。跨越再生而持续存在的是规范。这就是变化的核心:仓库中最持久的事物不再是最详尽的那一项。
这往往会重组所有权。在审查中被人捍卫的制品是行为声明,而不是实现这些行为的代码行。将这一点内化的团队不再把生成代码视为珍贵,而把它看作“天气”——存在、重要且会变化。手动润色函数的本能会减弱,因为这些润色在下一次再生产时就可能被抹去。
当审查从代码转向意图时会发生什么变化
一旦规范成为事实来源,审查的重心随之移动。审查者不再逐行阅读实现差异,而开始阅读规范的变更——行为的新增、约束的收紧、假设的移除。实现差异成为用于抽查的输出,而非审查的对象。
这比听起来更难。规范差异往往看起来很小,而其下方的再生代码可能发生重大变化。对行为条款添加的一句话可能会在数百行生成代码中引起连锁反应。审查者要学会在不逐行阅读所有投影代码的情况下推演意图变更的影响范围,这是一种与阅读代码不同的技能,需要时间来培养。
一个显现出的权衡是可追溯性。当生成代码中出现缺陷时,问题不再是“谁写了这行代码”,而是“是规范的哪一条款生成了它”。实践以规范为先的团队会养成将缺陷追溯到意图的习惯,然后判断是规范错误还是生成器误解了正确的规范。后一种情况属于一类特殊的错误——忠实度失败——在手写代码中没有对应物。
再生确定性如何决定规范是否可信
要使规范作为事实来源,再生必须足够可预测以供依赖。如果相同的规范每次生成的实现都有实质性差异,那么规范就不是真正的事实来源——而是取决于生成器的“心情”。因此,这一实践非常重视确定性:稳定的输出、固定的生成行为以及将投影与意图绑定的测试。
这里存在一个谱系。积极型再生在每次有意义的规范变更时重建实现,并将代码视为完全可丢弃。惰性再生保留一个生成的基线,只对变更触及的部分重新投影。两者各有成本,选择通常取决于系统规模多于团队偏好。
| 属性 | 积极再生 | 惰性再生 |
|---|---|---|
| 代码持久性 | 完全可丢弃;整体重建 | 在变更间部分保留 |
| 漂移风险 | 低;输出始终跟随规范 | 较高;可能存在陈旧区域 |
| 每次变更成本 | 计算成本高,推理成本低 | 计算成本低,推理成本高 |
| 失败模式 | 系统范围的意外 | 局部不一致 |
抽象地说,两端都不是放之四海而皆准的答案。小系统廉价地容忍积极再生,并从其保证输出始终跟随规范中获益。大型系统往往无力重投影全部内容,于是倾向于惰性策略,这又会重新引入规范优先旨在消除的某些偏差。
规范何时会积累其自身的债务
规范债务是指书面意图与运行系统实际需求不再匹配时产生的差距。它是规范优先的“遗留代码”对应物,以熟悉的方式累积:条款互相矛盾,边缘情况被作为例外拼接而非纳入模型。规范通过增积而增长,直到没有人能掌握其全貌。
这种失败比混乱的代码更微妙,因为规范在表面上可能读起来很清晰,实际上却编码着不一致的意图。一份格式良好但隐含矛盾的规范依然会生成东西——只是自信地生成错误的东西。审查者可能会被看似周密的文字所麻痹,而生成器不会像编译器对类型错误那样对矛盾发出警告。
- 规范漂移
- 陈述的意图与系统实际所需行为之间缓慢的偏离。
- 投影忠实度
- 生成代码在不添加或遗漏行为的情况下忠实实现规范的程度。
- 意图表面
- 必须触及以故意改变系统行为的一组条款和约束。
管理这类债务意味着像重构代码一样重构规范:合并条款、移除无用约束、为模型命名以保持其随增长仍可读性。这项工作不光鲜且容易被推迟,而推迟正是让一份干净的规范变得晦涩的方式。
有意的规范优先变更通常如何进行
在成熟的规范优先工作流中,操作顺序往往遵循一致的模式,无论领域为何。该顺序保持规范的权威性,防止生成代码悄然成为与规范竞争的第二事实来源。重要的不是具体步骤,而是每一步所指向的方向:变更从意图进入并向外流向实现,绝不相反。
- 将行为变更以对规范的修订形式陈述,而非代码编辑。
- 将新意图编码为生成器和测试套件都能读取的约束和示例。
- 重新生成受影响的投影,并让绑定测试确认忠实度。
- 将行为差异(而非代码行差异)与陈述的意图进行对照检查。
- 将任何意外结果折回规范,以便下一次再生带上该教训。
这一纪律体现在第五步。当再生带来意外时,最大的诱惑是手动修补输出。每一次未回到规范的手工修补都会再次打开规范优先旨在封闭的裂缝,规范悄然停止成为真实。修补的那行今天可用,但明天新一轮再生触发时便会失效。遵守纪律的团队会把意外的投影视为意图表述不足的信号,并投入精力更精确地说明其含义,而不是在下游覆盖输出。
随着系统增大该模型出现应力的地方
在小规模时,单一规范足以囊括整个系统,且再生成本低廉。应力出现在缝隙处。大型系统不是由一份规范构成,而是由多份规范组合而成,各规范之间的契约成为脆弱的表层——这恰是普通架构也会失败的地方,但现在问题升级到意图的语言层面,而非接口。
有界组合(每份规范拥有明确接口并独立重投影)倾向于可扩展。无界组合(规范互相涉入对方的假设)会产生各自有效但整体不一致的再生结果。耦合从代码迁移到意图,而意图耦合更难察觉,因为单个规范里通常看不出问题。
另一种应力来自人。超出单个读者可把握范围的规范会成为一种不透明的遗产,规范作为可读、可拥有的制品这一承诺便会削弱。将规范保持得足够小以便推理,成为首要关切,而非风格偏好;忽视这一点的团队会重建他们曾离开的迷雾。曾经约束代码的分解纪律现在约束意图,并带来同样的警告:划错边界比没有边界更难以修正且代价更高。
随着规范硬化为产品,该领域正朝什么方向发展
其发展趋势是把规范视为交付的产品,而把代码视为构建产物,就像几十年前编译后的二进制被降级一样。随着这种模式稳定下来,难题会向上迁移。意图的版本管理、行为差异对比和约束审查会成为日常技艺,而阅读生成实现将成为偶发的诊断性行为,仅在忠实度受损时采用。工具链会随重心而转,团队引以为傲的制品将是干净、可读的规范,而不是其下方的巧妙代码。
更难的是对含义的治理。当规范是产品时,谁被允许修改哪一条款、以及在再投影前如何验证意图变更,成为核心纪律。对规范的权限承担起曾经对发布分支的权责,因为一次编辑就能重塑规范投影到处的行为。
能够经受住考验的系统,是那些以曾经用于生产代码的严谨对待其规范的系统——谨慎地版本化、认真审查,并保持足够小以保持诚实。相反的诱惑是真实存在的:一份能生成可用软件的规范会让人假设它必然是健全的。正是这一假设将成为未来十年艰难教训的落脚点,而最早抵抗这种假设的团队,将以最少的代价学到这些教训。
F.A.Q.
