跳到正文
返回

难道这次真有银弹?

8分钟阅读

把需求交给编程智能体,看着它读仓库、改实现、跑测试,再沿着报错继续修,人很容易冒出一个念头:软件开发这件事,终于要便宜下来了。紧接着,软件工程史上那句最著名的提醒就会出现——没有银弹。布鲁克斯用它劝我们放弃一种期待:靠单独一项技术或管理发明,让开发的生产率、可靠性或简单性在十年内提高一个数量级。如今,这句提醒值得重新读一遍。

《没有银弹》里,他把困难分成两类。一类来自当时的实现条件,例如难用的语言和紧张的机器资源;另一类来自软件要表达的概念关系。更好的工具能减少前一类工作,却不能替你决定系统究竟应该怎样运行。如果真正费力的是后一类,把写代码加速十倍,也不会让整个项目快十倍。这个判断的力量,来自它对软件本身的理解。

软件把大量工作留在了设计里

  • 实现包含设计。 造一栋楼,需要把设计变成大量真实的材料与连接。软件一旦完成,复制一份通常很便宜;开发中昂贵的部分,集中在第一次把行为想清楚、表达出来。即使已经画好了架构图,程序员落笔时仍要补齐条件、状态和相互作用。写代码本身就包含大量设计,这使软件很难像装配生产那样,把“想清楚”和“照着做”整齐地交给两个阶段。

  • 复杂性来自关系。 软件要服从许多由人制定的规则。身份、权限、时间、历史数据,各自都可能改变一个操作的含义;把每项功能单独讲明白,也不等于说清了它们组合起来会发生什么。这些关系又很难像楼层平面图那样放在一张图里看全。代码结构、数据流向和运行时序各有一张图,错误经常藏在它们的交叉处。软件的复杂性因此直接变成了理解、沟通和验证的成本。

  • 需求在使用中生长。 这些规则还会变化。一部分变化来自外部:业务调整了,合作系统更新了;另一部分来自软件被使用之后,人终于看清自己要什么。一个流程在描述中很合理,真的操作起来才发现多了一次等待,少了一个选择。交付物参与了需求的形成,开发便很难只是执行一份事先完成的答案。做出东西与理解问题,经常需要交替发生。

  • 理解需要人来维持。 承担这些工作的,长期以来是理解能力有限、需要连续思考的人。《人月神话》讨论增加人手带来的培训和沟通负担:新成员需要先理解已有工作,也需要原有成员花时间帮助他理解。《人件》则把目光放到工作环境和团队协作上。它们共同指向一个现实:软件工作的产能,取决于一群人能否形成并保持对同一个系统的有效理解。坐在工位上的时间,不能直接相加成进度。

先想清楚,与尽快得到反馈

传统的计划式开发,可以理解为对这笔成本的一种管理:既然后面的劳动昂贵,就尽量提前排除会让它白做的问题。需求评审减少理解分歧,架构设计划分职责,接口约定让不同的人能够分别工作,变更控制则防止一个局部决定牵动整个项目。稳定的文档和阶段成果,使理解能够在人与人之间传递。这套方法之所以自然,是因为它试图在大量劳动投入之前,先把方向固定下来。

困难在于,有些问题要做过才会暴露。提前写出的约定如果缺少实践反馈,就可能只是在提前固定误解。Royce 在1970 年讨论大型软件开发的论文里,已经强调了早期试做、测试安排和客户参与:把风险一直留到最后,代价会大到难以承受。即使选择周密计划,也要设法让现实尽早进来修正计划。

敏捷把这种反馈放到了更中心的位置。《敏捷宣言》的原则强调频繁交付可工作的软件,并接纳需求变化。每次只承诺一段较短的路,走完就让用户和团队重新判断方向。这里真正有价值的是缩短误解存活的时间:一个错误假设如果几天后就被发现,就不必支撑几个月的后续开发。

频繁变化还需要技术上的支撑。测试驱动开发把预期行为先变成可以运行的例子,持续集成尽早暴露各部分之间的冲突,重构则整理已经长出来的结构。Fowler 在《设计已死?》中解释过这些实践如何支持演进式设计:要让下一次修改继续负担得起,就得持续投入整理和验证。短周期的背后,其实是一整套降低变化成本的工作。

从这里看,两条路线处理的是同一组约束:问题里有尚未弄清的关系,环境会变化,而人的理解与实现都很昂贵。提前设计,希望少走弯路;缩短迭代,希望弯路尽早结束。它们都要在“现在多想一点”与“先做出来再知道”之间计算成本。许多软件工程方法,就是在这两个选择之间寻找合适的比例。

这一次,思考也开始成为可以调用的劳动

编程智能体改变了这笔账。它可以沿着一次失败追查状态怎样流转,可以跨文件寻找某个约定的实际含义,也可以做出两个实现,让测试和运行结果帮助比较。这里涉及了理解、设计与验证。过去由程序员承担的一部分概念工作,开始能够交给模型反复完成。软件依然复杂,但处理复杂性的劳动有了新的供给方式。

问题固有的困难,并不等于人类必须亲自付出的劳动。 这是我认为这次值得重新讨论银弹的原因。那些条件、状态和关系仍然要被处理,只是处理它们的速度、价格和可并行程度正在变化。如果我们把一部分人类认知成本也当成软件永恒的本质,就会把“这件事必须做”误读成“这件事永远只能这样做”。

下面用一笔简化的工作量说明这个差别。先只拖动“实现加速”,会看到分析设计与外部等待逐渐成为主要成本;再拖动“分析设计加速”,原本挡在前面的那部分工作也开始缩短。判断一种工具能改变多少,取决于它能接手哪些劳动。

如果理解和设计也能加速呢?从顺序完成的 100 份工作出发。先加速实现,再加速分析与设计。
原始工作量:100 份
分析与设计: 50实现: 40等待与外部确认: 10
剩余工作量: 100.0整体加速: 1.00×
用于理解关系的成本模型,不代表实测生产率:分析设计 50 份、实现 40 份、等待与外部确认 10 份。本次固定依赖关系与返工情况。

劳动成本变了,开发过程怎样重排?

文档与迭代,都要重新计算代价

计划式开发首先会感受到这个变化。当一个疑问可以通过一小段可运行的实现来回答,继续为它补充几页文字规格,未必是最便宜的办法。设计文档仍能保存关键决定、约束和理由,但许多为了向实现者交代步骤而写的内容,可以直接通过探索和执行解决。人的提前设计应该越来越集中在那些做错之后难以收回的决定上,例如数据的含义、对外承诺和系统之间的责任边界。

敏捷也需要重新计算自己的节奏。按周安排的冲刺、等待下一场会议的决定、为了分派给不同成员而切碎的任务,都带着人类团队的时间尺度。如果智能体能够连续推进一段完整任务,人再逐步派工、逐步确认,就会把自动化的工作重新排成人工队列。频繁得到反馈依然重要,反馈发生的时机却可以贴着问题本身:模型发现了影响需求的歧义,再把那个决定带回来。

把反馈与评审放到关键决定上

这会让测试驱动开发中“先说明什么算对”的部分更加重要。智能体可以很快写出实现,也可以很快写出与实现相互吻合的测试;两者一致,只说明它们表达了同一种理解。人需要看的,是测试固定了什么业务行为、遗漏了什么关键条件,以及验证环境能否观察到我们关心的结果。判断一旦落实成可靠的反馈,后续许多修改就可以由智能体自行推进。

评审的对象也会随之变化。把智能体生成的每一行代码都重新翻译回人的理解,需要让人的阅读速度追上机器的生产速度。更有效的安排,是把业务语义和关键工程取舍提取到容易审阅的位置:人确认行为与边界,模型负责实现、查错和反复验证。架构、测试和仓库组织由此多了一项职责——让重要决定清楚地浮现,让常规工作能够连续完成。

《人月神话》和《人件》关心的理解连续性,也会有新的承载方式。让另一个智能体接手工作,可以从一份保存下来的任务状态、可检索的决定记录和能重现问题的环境开始。团队积累的理解因此有机会更多地保存在工作系统里。人的注意力仍然稀缺,却可以少花在反复解释背景上,多花在决定哪些问题值得解决、哪些结果值得接受上。

所以,这一次我愿意认真期待银弹。智能体开始接手过去最难机械化的理解与设计劳动,这足以让我们重新安排整个开发过程:把值得固定的决定固定下来,把能够获得反馈的工作交给它持续完成。传统方法对复杂性的尊重、敏捷对反馈的重视,都能保留下来;围绕昂贵人力建立的分工与节奏,则需要重新设计。软件工程终于有机会在新的劳动成本上,再做一遍工程。