软件工程的核心在于管理复杂性
摘要
本文认为,软件工程主要通过架构决策和权衡来管理复杂性,人工智能在代码生成方面很有效,但不擅长处理这些更高层次的方面。它强调了构建软件涉及关于约束、成本和演化的关键选择,而不仅仅是语法。
暂无内容
查看缓存全文
缓存时间: 2026/08/27 15:23
# 软件工程在于管理复杂度 – H[ack]-∞S
来源:https://hack8s.com/422/once-again-software-engineering-is-about-managing-complexity
关于软件工程存在一个误解,AI正使其日益明显:我们常常将编写代码与构建软件混为一谈。
这两者在某些方面有重叠,但并非同一回事。
编写代码意味着将想法转化为计算机可执行的指令。而构建软件意味着决定哪些指令应当存在、它们应如何交互、哪些约束至关重要、决策的代价是什么、哪些权衡可被接受,以及生成的系统如何在自身约束和限制下持续演进而不崩溃。
我们先从一个基本前提开始:AI是一项关键工具,因为它极其擅长解决第一个问题。
而第二个问题才是软件工程真正的起点。
## 从未困难的是敲代码
考虑一个相对普通的工程需求。
> 我们需要处理传入的事件并更新某些数据。
以下是技术讨论初期会浮现的一些问题:
- 应当同步处理还是异步处理?
- 是否应将它们放入队列?
- 需要恰好一次处理,还是至少一次即可?
- 系统能否容忍最终一致性?
- 如果处理中途失败会怎样?
- 是否需要重试?重试多少次?
- 如果消费者三小时内不可用怎么办?
- 事件是否需要保持顺序?
- 今天的预期流量是多少?两年后呢?
- 如果一个事件被处理两次会有什么后果?
……等等。
这些问题与语法几乎无关。
编程语言的选择很重要,因为它影响团队熟练度、团队效率、系统性能、安全性、可维护性、工具链和运维特性,但它并不能回答那些根本问题。
真正的难点在于选择一个代表正确权衡组合的架构。
而这很少有普适的正确答案。
## 一个问题可以有N个完全不同的正确解
当软件存在于业务环境中时,这一点尤为明显。
想象两家公司让它们的工程团队构建听起来完全相同的功能。
表面上看,它们的需求可能完全一致。
但实际情况是:
- A公司有500名用户,而B公司有2000万用户。
- A公司可能只有三名工程师维护系统,而B公司可能有200名。
- 一家可能需要强一致性,因为错误会导致严重的财务损失;另一家可能欣然接受最终一致性,以换取可用性和吞吐量。
- 一家可能需要在三周内上线。
- 另一家可能希望系统能持续运行十五年。
- 一家可能已有Kafka、Kubernetes、PostgreSQL、可观测性基础设施以及熟悉分布式系统的工程师;另一家可能只有一个应用服务器和由四名开发者维护的PostgreSQL数据库。
对一家公司而言技术上出色的方案,对另一家公司可能就是不负责任的方案。
这就是为什么架构不能简化为询问:
> 实现X的最佳方式是什么?
正确的问题通常更接近于:
> 鉴于这些约束条件、这个团队、这个业务、这个基础设施、这个预算、这些风险以及产品的预期演进,在今天实现X的最合适方式是什么?
这是一个截然不同的问题。
## AI生成解决方案,工程师主导权衡
这一区别正变得越来越重要,因为软件组织正在采用AI的方式。
AI对软件开发极其有用。我们将其用作加速器:生成样板代码、探索API、提出实现方案、查找潜在bug、解释陌生代码、生成测试、比较不同方案,或者简单地减少将想法转化为可运行代码所需的机械性工作量。
但存在一种危险的趋势,即将这种能力扩展到更广泛的领域:
> 委托工程判断本身
你可以:
- 给AI模型一个需求,要求它“零失误”地设计系统——它会设计出一个方案。
- 让它选择数据库——它会选出一个。
- 询问是否应引入队列、微服务、缓存、CQRS、事件溯源、Kubernetes、Redis或另一种抽象——它会给你一个答案。
然而,答案的存在并不意味着底层的工程问题已得到解决。
真正的问题在于,正确的决策取决于上下文——通常是大量的上下文,需要数小时、数天甚至数周来分析、理解和评估,往往涉及同一组织内的多个部门,并且常常留下难以形式化的灰色地带,未来需要变更时可能成为挑战。
这就是现实世界的运作方式:部分上下文存在于文档中,但很多并不在其中。
它存在于与客户的对话中、产品的历史中、工程团队的技能中、三年前的运维事件中、预算约束中、截止日期中、合同义务中、无人愿意触碰的遗留系统的怪异行为中,以及对“客户可能在六个月后请求特定功能”的预判中。
有时,它仅仅存在于经验中:认识到理论上优雅的架构将成为期望维护它的团队的运维噩梦。
你不能将所有这些视为一个细微之处,以为只需在AI提示词中增加一段话就能捕捉到。
## 无权衡,不架构
工程在很大程度上是一门决定“你愿意接受哪些问题”的学科。即使你不在FAANG规模的企业工作,你的项目仍可能需要处理大量数据,因此做出正确的工程决策对于维持足够性能至关重要,而无需用昂贵的硬件来弥补系统性的低效。
即使是“简单”的选择也需要考虑多种因素:
- 激进地规范化数据库,可能获得数据完整性,但会让某些查询变得更复杂。
- 反规范化,可能提高读取性能,但会增加同步复杂性。
- 添加缓存,能降低延迟,但会引入失效问题。
- 引入异步处理,可以解耦组件,但会创造新的故障模式。
- 将单体应用拆分为微服务,获得了独立部署边界,但引入了分布式系统的复杂性。
- 激进地优化,可能提升性能,但会牺牲可读性。
- 激进地抽象,可能减少重复,但会使系统更难理解。
- 避免抽象,最终又会出现相反的问题。
这些因素都无法独立评估。每一次优化都在某处消耗复杂度。因此,工程师的工作不仅仅是让系统运行起来。
而是要决定复杂度应归属于何处。
## 算法思维更重要而非更不重要
这就是为什么我认为在AI时代,算法思维变得更加重要,而非不再重要。
我所说的算法思维,并非仅仅指了解教科书算法或能够凭记忆实现红黑树。
我所说的算法思维,是指定义一套需要遵循的基本规则并每日应用。我总结了十点原则:
1. 分解问题。
2. 识别不变量。
3. 理解数据流。
4. 选择合适的数据结构。
5. 推理时间和空间复杂度。
6. 识别竞争条件。
7. 理解故障模式。
8. 辨别哪些操作开销大,哪些真正重要。
9. 区分本质复杂度与偶然复杂度。
10. 理解当假设不再成立时应发生什么。
我知道,在生产力提升的时代,这听起来可能有点奇怪。AI可以在过去工程师编写一个实现的时间内生成十个实现。挑战在于将AI作为工具,通过有针对性的、可控的迭代来应用这些原则,同时保持人类在决策循环中。
这使得选择正确的实现方案变得十倍重要。这才是生产力提升的真正含义。
## 你的编程语言是工具,而非身份认同
同样适用于编程语言。
选择正确的工具来做正确的事很有价值。语言有不同的优势、生态系统、性能特性、类型系统、并发模型、部署模型和安全保证。
这些差异很重要,但工程讨论有时对编程语言选择“过度估值”。在许多业务系统中,选择两种合理语言中的哪一种,其重要性远低于选择错误的架构、错误的数据模型或错误的运维策略。
选择适合场景的语言。如果有多种语言适合,选择你的团队深入理解的那种具有重要价值。不要基于“大语言模型对某种编程语言表现更好”的假设来做决策。永远将团队放在首位。
五个重要的标准是:
- 熟练度很重要。
- 生态系统成熟度很重要。
- 可维护性很重要。
- 调试很重要。
- 部署很重要。
这就是为什么,今天比以往任何时候都更甚,在拥有大语言模型的便利下,我鼓励工程师们不要狂热地忠诚于单一编程语言,而应尽可能成为多语言使用者,学会将AI助手作为学习工具,而不仅仅是代码生成器,并始终仔细核对官方文档。
## 真正的优化目标是系统的生命周期
另一个错误是仅针对当前需求优化软件。
现代软件不能是静态的,它必须能够应对以下变化:
- 用户
- 客户
- 流量
- 商业模式
- 法规
- 团队
- 基础设施
每个方面的权重都可能随时间变化。因此,今天看似微不足道的功能,两年后可能成为核心工作流。
因此,良好的工程需要具备一定的预见变化的能力,同时要避免陷入相反的陷阱:仅为可能永远不发生的假设未来设计精巧的抽象,只因为团队接受了某种代码产出,其篇幅过长而无法完全审查。这种平衡很难把握,因为:
- 这个需求变化的可能性有多大?
- 我们应该在哪些地方特意引入灵活性?
- 哪些地方应该保持简单?
- 哪些技术债务是可接受的?
- 哪些技术债务可能变得在结构上代价高昂?
- 我们现在应该优化什么?
- 我们应该特意留待测量证明其重要后再优化什么?
这就是工程判断。
没有AI能正确回答这些以及许多其他问题,你也不能简单地将AI代理投入到这些问题上并期待可靠的结果。没有任何提示能让这些问题消失。
## AI生成的代码带来新的所有权问题
还有一个问题我认为软件组织正在低估:代码库的所有权。
AI使得生成代码变得极其廉价。这听起来完全是好事,直到你想起生成代码和维护代码是两种不同的经济活动。现在,工程师可以极其快速地生成数百或数千行看似合理的代码。
团队可以合并它们,测试通过,功能生效,生产力似乎提高了,但一些关键的东西可能已经丢失了。如果你想了解丢失了什么,请问自己:
1. 团队是否真正理解整体架构?
2. 他们是否理解数据结构?
3. 他们是否理解为什么存在特定的抽象?
4. 他们是否理解故障行为?
5. 他们是否知道生成的实现依赖于哪些假设?
6. 我们能否假设,六个月后他们能自信地修改它,而无需再让另一个AI来解释之前由AI生成的代码?
如果答案是否定的,那么团队并未消除复杂度。它很可能引入了其他复杂度,而这些复杂度会随着时间的推移变得越来越重要。
## 代码行廉价,理解昂贵
这可能是AI给软件经济学带来的最大变化。
历史上,生成代码相对昂贵。现在它正变得极其廉价。而理解一个系统,正变得越来越昂贵。
这个区别很重要:一个包含3000行AI生成代码的拉取请求,仍然是向你的系统认知面积增加了3000行代码。而且,一套通过的测试套件并非答案,因为它会让你产生一种虚假的信心,认为一切尽在掌控,因为系统“显然”按预期工作。
## 如果你发布了,你就得负责
因此,我认为强有力的技术管理应坚持一个非常简单的原则:
> 绝不发布你不拥有的代码。
我所说的“拥有”是什么意思?即完全理解代码库,并验证其中的每一个部分。
AI生成的代码不应因其是通过某种先进/昂贵/前沿/(随便什么)大语言模型快速生成的,而受到更低的审查标准。
现在的挑战在于确定是否(以及何时)投入时间审查此类大型拉取请求是合理的,还是最好通过更小的迭代缓慢推进,其中少量的应用逻辑通过受控的人机交互来开发。
## 软件工程可能正变得更难
最后一点,我想聚焦于一个似乎近期出现的有趣悖论:
> AI让编程更容易,却可能让软件工程更难。
我们能生成更多代码,速度前所未有。我们可以几乎瞬间创建原型。我们可以在不花几天时间阅读文档的情况下集成陌生的库。我们可以以惊人的速度生成测试、API、数据库层、基础设施配置、前端组件、迁移和整个服务。
但我们理解系统的能力并未以同样的速度增长。这就产生了一个新的瓶颈,不再是:
> 我们能多快写出这个?
而是:
> 这个组织实际能理解和控制多少复杂度?
我们迄今使用的生产力指标已经失效:一个产出五倍代码的团队未必有五倍的生产力。我们使用AI系统进行软件工程的时间还不够长,尚无可靠数据,但越来越清楚的是,我们在大语言模型采用初期看到的生产力提升,如今在维护和演进中等复杂度系统方面正付出代价。
因此,从现在起,生产力不应再通过生成多少软件来衡量。
而应通过我们能多有效地解决问题,同时将由此产生的复杂度控制在何种程度来衡量。
## AI应增强工程杠杆,而非取代工程判断
所有这些并非反对AI辅助开发。恰恰相反。AI可能是软件工程师几十年来获得的最强大的生产力工具。
但我们应该利用这种杠杆,将更多时间投入到工程中,而非更少。我们可以让AI编写重复的适配器、生成测试套件的第一版、解释一个陌生的库。让它帮助调查错误、提出3种实现方案……
相似文章
软件开发正从“你能写代码吗?”转向“你能让正确的系统存在吗?”
文章认为,随着AI使代码生成变得更便宜,软件工程的价值从编写代码转向定义、监督、验证并拥有最终系统,这有利于积极采用AI工具的工程师,同时提高了验证标准。
实用AI软件工程
一篇讨论AI软件工程实践方法的文章。
软件工程基础更为重要
本文强调,在人工智能和智能工具时代,软件工程基础仍然至关重要,突出了大型语言模型的推理局限性,以及对可维护、良好设计的软件的需求。
AI代理是否让构建软件比理解软件更容易?
一位开发者反思了AI编码代理如何能够快速构建和修改软件,但开发者往往失去对代码库架构和决策的理解,从而带来新的工程挑战。
工程纪律
一位开发者讨论了在使用AI编码代理时,代码生成速度超过理解速度,如何保持工程纪律和架构完整性的挑战,并寻求避免创建低质量软件的最佳实践。