Skip to content
Belltoy

AI 越来越会写代码,软件工程师的价值在哪里?

Published at:

2026 年,AI Coding Agent 的能力正在快速发展。借助 Claude Code、Codex 等工具,现在实现一个组件、微服务,甚至相对完整的系统,可能只需要过去一小部分的时间。这让我开始重新思考:当写代码变得越来越容易,软件工程师的价值是什么,我们应该把更多精力花在哪里?

过去十几年,我做过网站前端、应用后端,也参与过网络服务、消息中间件和大规模分布式系统的开发与维护。随着经验的积累,我逐渐认识到,软件开发中真正困难的往往不是实现一个功能,而是做出正确的工程判断。

当代码实现不再昂贵

过去,即使需求和技术方案已经明确,工程师仍需要花费大量时间编写代码、调试程序、熟悉框架。经验的差异常常体现在实现速度和避开常见问题的能力上。现在,AI 正在大幅压缩这部分成本。只要需求相对清晰,就能快速生成代码、测试和配置,甚至完成一个可运行的服务。

然而,能够运行的系统,与能够长期可靠运行、持续演进的系统,仍然是两回事。回顾过去参与过的项目,许多长期困扰团队的问题,都源于实现之前没有讨论清楚的设计决策。功能虽然做出来了,这些遗漏却会随着系统的演进逐渐显现,成为需要长期偿还的技术债。

那些被代码固化的隐含决策

现实中的软件开发经常是这样的:产品提出需求,开发人员开始实现。对于需求中没有明确说明的部分,就根据自己的经验做出判断。接口遇到异常输入应该拒绝还是忽略?操作失败后应该重试还是返回错误?一个组件应该承担哪些职责?这些选择虽然没有经过正式讨论,却会随着代码发布成为系统实际行为的一部分。

某些决策一旦发布,就不再完全由开发者控制。其他服务可能已经依赖这些行为,客户端可能已经按照现有接口实现,数据可能已经按照某种格式持久化。即使后来发现设计不合理,也未必能轻易改变。

这让我逐渐形成了一种习惯:对难以撤销的决策格外谨慎,对限制在系统内部、容易修改的细节,则尽量保持简单。

有些错误,一旦发布就无法真正修复

在维护大规模 MQTT Gateway 时,我就遇到过这样的问题。一些早期实现由于开发人员对 MQTT 协议理解不足,错误地处理了协议中的某些细节。而连接 Gateway 的 IoT 设备,又按照这些错误行为进行了适配。

这些设备生产、销售并部署后,很难再改变。尤其是没有 OTA 升级能力的设备,出厂之后就无法更新固件。后来即使发现 Gateway 的实现不符合协议规范,也不能简单地修正。因为新的正确实现,可能导致已经售出的设备无法正常工作。最终,后续版本不仅需要实现正确的协议行为,还必须继续想各种办法兼容早期的错误实现。这种兼容可能持续很多年,甚至伴随设备的整个生命周期。

这类问题最麻烦的地方不是代码不好改,而是正确的修改也可能破坏现有系统。旧系统里的某些行为,就这样变成了明知不合理、却必须保留的兼容负担。这让我意识到,协议、接口、数据格式和错误语义等对外承诺,必须比内部实现更加谨慎地对待。

系统边界决定了问题会传播多远

除了协议兼容性,系统被用于不适合的场景,也会留下长期负担。曾经有业务开发人员需要实现页面上的实时数据展示。由于缺少 Server Push 相关经验,便直接选择了公司现有的 MQTT Gateway。从功能上看,这当然可行。但这套 Gateway 原本是为全球海量 IoT 设备长连接设计的,包含多层复杂组件,需要专门的部署、监控和运维体系。

后来,一些内部聊天系统也采用了类似方案。结果是,我们不仅需要维护全球在线的核心 IoT Gateway,还要为这些规模很小的内部业务单独维护 Gateway 系统。这些内部系统随着时间推移逐渐缺乏维护,依赖关系却已经固化,迁移成本也越来越高。

实际上,这些需求可能只需要 WebSocket 就能满足。即使需要 MQTT,也可以部署独立、轻量的 Broker,而不是引入一套为海量设备设计的复杂基础设施。如果最初认真评估需求和技术选型,长期成本可能降低一个数量级甚至更多。

这些功能确实能够工作,但技术上能够实现,不代表工程上值得这样实现。 这看似节省了一次技术选型或独立实现的工作,却把简单功能与大型基础设施的复杂度长期绑定在了一起。好的系统边界,不仅能限制故障和变化的传播范围,也能阻止不必要的复杂度从一个系统扩散到另一个系统。

系统不能只定义正常情况下的行为

在很多项目的立项和设计阶段,我们往往更关注功能需求:提供哪些接口、实现什么逻辑、正常情况下数据如何流转。而系统不可用时怎么办、依赖服务故障时如何处理、能够承受多大负载等问题,经常没有得到足够重视。

我还发现,一些经验不足的程序员特别害怕程序「报错」。他们习惯性地捕获异常、忽略错误,或者返回看起来正常的结果,仿佛没有抛出异常就意味着运行正常。

但没有暴露错误,不等于没有发生错误。有些错误必须明确传递给调用方,有些应该触发降级或快速失败。如果习惯性地吞掉错误,系统表面上可能一切正常,实际上却掩盖了内部 Bug、依赖故障或者基础设施问题。当故障真正发生时,维护人员既无法准确定位问题,也难以判断哪些操作已经成功,哪些已经失败。

更严重的是,错误处理不当还可能导致故障扩散。例如,一个系统为了提高吞吐量,不断接受请求,却没有合理的背压和资源限制。正常情况下,它可能表现得很好。但当下游服务变慢或不可用时,请求开始积压、超时,上游持续重试,反而进一步增加系统负载。如果缺乏背压、超时和重试约束,原本局部的故障就可能演变成整个调用链的崩溃。

这些问题无法仅靠增加几个异常捕获来解决,通常还涉及接口语义、资源管理、故障隔离和系统架构。系统如何失败,与系统如何成功一样,都应该在设计之初就被定义。

更应该把问题想清楚

AI 能辅助分析代码、编写测试,甚至发现潜在的设计问题。但 AI 并不会天然知道系统应该承担哪些职责、哪些错误可以容忍、哪些行为必须保持兼容,以及生产环境中需要满足什么可靠性约束。当需求没有明确规定这些细节时,AI 也会根据常见模式自行补全。而 AI 生成代码越快,那些未经讨论的假设,也可能越快被固化进系统。

所以,我认为工程师应该把更多精力投入实现之前的工程判断。在开发之前,明确系统职责和边界,定义接口契约、失败语义和关键的非功能性需求。考虑依赖故障、资源耗尽、请求积压等场景,并识别哪些决策一旦发布就难以改变。

这并不要求一开始就预测所有问题。好的工程判断,也包括知道哪些地方必须谨慎,哪些地方可以保持简单,以及如何为未来的变化留下空间。

在维护已有系统时,我也逐渐把精力更多地投入到收紧边界、减少不必要的耦合、明确错误处理和故障隔离,并将这些原则推广到后续的新系统、新产品中。AI 可以帮助我们探索方案、检查假设,但最终仍然需要工程师理解真实的业务和系统约束,并为决策负责。

结语

软件开发有点像是在不断做出承诺。有些承诺一旦对外发布,被其他系统、设备和用户依赖,就像泼出去的水,很难再收回来。

工程师的价值不仅在于做出能跑的功能,更在于运用知识、经验和判断力,做出经得起时间检验的工程决策。 AI 可以帮助我们更快地构建系统,但系统未来需要承担多少复杂度、维护成本和历史包袱,很大程度上仍然取决于今天做出的判断。


Share this post:

Previous Post
如何为一门语言编写 Tree-sitter 解析器
Next Post
一套规则引擎,为什么最后被我们删除了?