AI 会写代码之后,工程师更需要哪些思维原理?
现在,一个刚入门的开发者,也可以借助 Claude Code、Codex 这样的 Coding Agent,独立完成以前需要一定工程经验才能完成的需求。
假设我们正在开发一款阅读摘录 App,需要增加收藏夹功能。用户可以创建收藏夹、移动摘录、删除收藏夹,并按照收藏夹查看内容。
把需求交给 Agent,它很快就能分析项目、设计数据结构、编写代码、补充测试,甚至打开模拟器检查功能。整个过程可能不需要开发者亲自写几行代码。
但有几个问题值得想一想。
一条摘录能不能同时放进多个收藏夹?删除收藏夹时,里面的摘录应该怎么办?如果老用户升级 App,原来的数据如何处理?
假如需求没有明确说明,Agent 可能会替我们作出这些决定。它选择的方案或许很合理,代码质量也很好,所有测试都能通过。但等到产品上线,我们才发现它实现的行为和用户真正需要的并不一致。
这类问题并不是 AI Coding 独有的。需求不清楚、设计有遗漏、测试无法覆盖真实意图,一直都是软件工程中的难题。
变化在于,AI 大幅降低了实现一个方案的成本,却没有同比降低判断一个方案是否值得实现的难度。
以前,一个不合理的设计可能要经过几天开发才能暴露问题;现在,Agent 可能在很短时间内把它实现得相当完整。实现过程中的阻力变小了,许多原本会引发讨论的决定,也更容易直接变成代码。
我们已经有很多成熟的工程实践:TDD、Code Review、CI、灰度发布、系统化调试。它们依然重要。不过,实践往往告诉我们应该怎么做,面对具体约束和多个合理选择时,我们还需要知道为什么这样做,以及什么时候可以改变做法。
为此,我重新整理了几个来自科学哲学、决策理论、经济学和软件工程的经典思维原理。
它们并不是专门为 AI 发明的,也不组成一套统一的理论。它们的价值在于帮助我们处理一些具体问题:当 Agent 给出了看起来正确的答案,我们应该如何判断?
竞争性假设与证伪:不要急着相信第一个解释
假设收藏夹功能开发完成后,出现一个 Bug:用户把摘录移入收藏夹后,偶尔会看到两条完全相同的内容。
我们告诉 Agent:“这里出现了重复内容,修一下。”
Agent 可能很快发现列表存在重复更新,添加一次去重处理。重新运行后,问题消失了。
这次修复可能是正确的。但仅仅看到问题消失,我们还无法确定它是否解决了真正的原因。
重复内容可能来自数据库写入了两次,也可能是查询产生了重复结果,还可能是界面状态将同一条数据追加了两次。
这三种原因都能解释我们看到的现象,却需要完全不同的修复方式。
竞争性假设(Competing Hypotheses)的价值,在于要求我们考虑多个能够解释现象的原因,而不是立即接受第一个合理解释。
证伪(Falsification)则进一步要求我们寻找可能推翻某种解释的证据。
例如,我们可以先检查数据库里有多少条记录,再检查查询结果,最后检查界面最终接收到的数据。
如果数据库和查询结果都只有一条,但界面状态里出现了两条,就可以排除前两个方向,将调查集中在状态更新逻辑上。
这里真正有用的,并不是机械地要求 Agent 列出三个原因,而是寻找能让不同假设产生不同预测的观察。
这也解释了为什么系统化调试如此重要。好的实验应该尽可能缩小问题范围,而不是不断尝试看起来可能有效的补丁。
当然,并非所有问题都能稳定复现。竞态条件、生产环境故障、特定硬件状态都可能难以重建。此时可以结合日志、监控和代码分析逐步提高判断的可信度,不必把稳定复现作为唯一的修复标准。
证伪也不能简单等同于 TDD。TDD 同时承担着驱动接口设计、组织实现和持续重构的职责。即使测试先于实现编写,如果测试与实现共享同一个错误假设,测试通过也无法证明需求理解正确。
切斯特顿的栅栏:旧代码可能保存着已经遗失的知识
继续开发收藏夹功能。
Agent 发现,旧版摘录查询里存在一段看起来多余的逻辑:
clips.filter {
$0.deletedAt == nil &&
$0.importState != .staging
}deletedAt 很容易理解,但为什么还要过滤 .staging?
Agent 查看现有代码,发现导入流程已经多次改版,相关状态很少出现,于是建议删除这个判断,简化查询。
这时可以引入切斯特顿的栅栏(Chesterton’s Fence)。
它的意思是:当我们不知道一道栅栏为什么存在时,不应该仅仅因为看不出它的用途,就认定它没有必要。
在软件工程里,许多不够优雅的代码都有历史原因。
某个看起来重复的状态检查,可能是为了解决多年前的竞态问题;一个奇怪的延迟,可能是在等待某个硬件进入稳定状态;某段复杂的兼容逻辑,可能保护着旧版本写入的数据。
回到我们的例子。
假设检查 Git 历史后发现,.staging 是过去批量导入功能使用的中间状态。导入过程中如果 App 被系统终止,数据库里可能留下尚未完成的摘录。过滤条件负责避免这些半成品内容出现在用户界面。
如果直接删除,日常操作可能全部正常,现有测试也都通过。但只要发生一次导入中断,用户就可能看到不完整的数据。
这里有一个重要区别:
理解旧代码为什么存在,并不意味着必须永远保留旧代码。
我们完全可以用更简洁的实现替换它,只要新的实现仍然保护必要的行为。
这也是切斯特顿的栅栏与 YAGNI(You Aren’t Gonna Need It)之间值得注意的关系。
YAGNI 提醒我们,不要为了尚未发生的需求提前引入复杂度;切斯特顿的栅栏提醒我们,现有复杂度可能承担着尚未被理解的职责。
二者看起来矛盾,但实际讨论的是不同问题:前者要求新增复杂度有理由,后者要求移除既有约束有依据。
如果旧逻辑确实不再被使用,测试与运行行为也支持这一判断,就应该删除。反过来,如果删除可能破坏重要行为,却暂时无法确认它的用途,就值得继续调查或限制改动范围。
可逆性:Git 能回退代码,但不能撤销所有后果
在技术设计中,我们经常需要从几个合理方案中作出选择。
仍然以收藏夹为例。
一种数据库设计是直接给摘录增加 folderId,每条摘录最多属于一个收藏夹。另一种是使用关联表,让同一条摘录可以属于多个收藏夹。
如果当前需求只需要单一归类,第一个方案可能更简单。
但如果产品未来希望支持多个收藏夹,修改已经持久化到用户设备上的数据模型,就可能涉及迁移和兼容。
这时值得考虑可逆性(Reversibility)。
可逆性关心的是:当我们的判断被证明不合适时,改变方向需要付出多少代价?
它并不要求所有设计都提前支持无限扩展,而是提醒我们关注不同选择的错误成本。
特别是在移动端,代码回退与行为回退可能完全是两回事。
已经写入用户设备的数据、执行过的数据库迁移、发送出去的推送、完成的支付交易,都不会因为执行一次 git revert 就自动恢复原状。
Feature Flag 可以关闭某条代码路径,却无法撤销已经产生的外部影响。
不过,可逆性也不是简单的单向门和双向门分类。
一个数据模型即使已经上线,也可能通过保留兼容字段、分阶段迁移、版本化格式等方式降低修改成本。另一方面,一个看似普通的 UI 调整,也可能改变用户习惯或已经对外承诺的行为。
可逆性在很大程度上是可以通过设计主动创造的。
因此,遇到高风险技术决策时,除了比较实现复杂度,还可以问:如果三个月后发现选错了,我们能否在不破坏现有用户状态的前提下改变方向?
有些时候,多花一点成本保留回退能力,比提前猜中未来更可靠。
信息价值:什么时候值得继续调查?
有了这些原理,另一个问题随之出现。
如果每次设计都要讨论未来,每次删除代码都要查历史,每个 Bug 都要充分调查,那么开发效率很可能会下降。
我们需要决定哪些问题值得深入。
这里可以借用决策理论中的信息价值(Value of Information)。
想象一下,在确定收藏夹数据结构之前,我们不清楚用户是否需要多重归类。
如果向产品经理确认只需要五分钟,而这个答案可能直接决定数据库设计,那么这五分钟很值得花。
反过来,如果我们正在讨论收藏夹按钮应该放在页面左侧还是右侧,两种方案都容易实现,也能够根据实际使用反馈快速调整,那么提前花几个小时研究可能没有多少收益。
信息的价值取决于它是否可能改变行动,以及这种改变能带来多大收益。
它同时包含一个经常被忽略的变量:调查本身也有成本。
这个原理还可以用于调试。
假设 Agent 已经连续尝试几次修复,结果都失败了。继续猜测下一个补丁,可能不如先收集一条能排除多个原因的证据。
但在严重生产事故中,行动顺序可能反过来。
如果系统正在持续产生损失,而关闭功能或回滚到已知安全状态能够有效止损,那么未必需要等待根因完全查明。先保存必要的现场信息、控制损失,再继续调查,可能是更合理的选择。
当然,回滚本身也可能有风险。如果新版本已经写入旧版本无法读取的数据,贸然回退甚至可能造成更大的损失。
这正是信息价值和可逆性需要共同考虑的地方。
值得注意的是,并非所有调查都只服务于眼前的一个决策。探索性研究、长期知识积累同样具有价值。只是当我们正在完成一个具体任务时,应当意识到继续分析也会消耗本可以用于行动的时间。
古德哈特定律:测试全部通过,为什么仍然可能做错?
假设 Agent 将收藏夹功能开发完成,为每种操作都编写了单元测试。
新建、重命名、移动、删除,测试全部通过。
但上线后,用户发现删除收藏夹会同时删除里面的摘录。产品原本的预期却是保留摘录,只解除归类关系。
Agent 的实现可能没有任何程序错误,测试也确实检查了它所实现的行为。
问题在于,Agent 在需求没有明确说明时,自行采用了一个业务假设,并按照这个假设同时编写实现和测试。
这类现象可以从古德哈特定律(Goodhart’s Law)得到一些启发。
这条定律通常被概括为:当一个指标成为优化目标时,它往往会逐渐失去作为指标的可靠性。
在 AI Coding 中,测试通过率、代码覆盖率、Lint 结果都只是我们用来评价软件的部分指标。
当 Agent 被要求让所有测试通过,它完全可能在达成这些指标的同时,遗漏指标没有覆盖的真实需求。
这并不意味着测试没有价值,更不意味着 Agent 不应该自己写测试。自动化测试依然是控制回归风险最有效的工程手段之一。
关键在于:验收依据不能完全由被验收的实现自行定义。
例如,在开发之前,由需求方明确几个具体的用户行为:删除收藏夹后摘录应当保留;移动摘录后重新启动 App,归属关系不能丢失;旧版本保存的摘录在升级之后依然可以读取。
Agent 可以将这些行为转换成自动化测试,但如果它想修改原本的验收预期,就需要说明理由并获得确认。
对重要行为,还应该尽可能使用真实数据、真实设备或独立于实现过程的检查方式。
测试通过是重要证据,但我们始终需要知道:它究竟证明了什么,又有哪些事情没有被检查?
编程即理论构建:当代码主要由 AI 编写,系统理解留在哪里?
1985 年,Peter Naur 在《Programming as Theory Building》中提出一个很有启发性的观点。
程序设计的关键成果,不只是程序文本,还包括程序员对问题以及程序如何解决问题所形成的理解。
这种理解让开发者能够解释系统行为,也能够在需求发生变化时作出合理修改。
四十多年后,Coding Agent 让这个问题以新的形式出现。
我们可以在很短时间内生成大量代码,但生成代码并不意味着使用者同步建立了对系统的理解。
一个新人可能已经借助 Agent 实现了完整的收藏夹功能,却不知道摘录的身份如何保持稳定、不理解数据迁移可能造成什么影响,也无法判断某个状态检查是否可以安全删除。
短期内,这未必会影响功能交付。但随着系统不断演化,这些理解缺口可能逐渐成为维护工作的障碍。
Anthropic 在 2026 年进行过一项小规模随机实验,研究 AI 辅助对编程技能学习的影响。研究发现,AI 辅助组在任务后的技能测验中表现较低,尤其在调试方面;不同的 AI 使用方式又与不同的理解水平相关。
这项研究只有 52 名参与者,考察的是短期学习任务,不能直接推导出 AI 会普遍损害长期工程能力。不过,它提醒我们,完成任务与掌握任务涉及的知识,是两个不同的结果。
那么,人类是否必须理解 AI 生成的每一行代码?
我认为没有这个必要。
软件工程本来就依赖抽象和分工。我们不需要逐行阅读操作系统、数据库和所有第三方库的实现,才能开发可靠的软件。
更重要的是掌握关键的业务约束、状态变化、失败条件,以及系统各部分如何相互影响。
对收藏夹功能而言,你未必需要记住每个数据库查询的具体写法,但应该理解删除收藏夹是否影响摘录本身、升级时如何保护旧数据,以及哪些操作可能留下不一致的状态。
这些知识可以由文档、测试、运行记录和 Agent 辅助保存与重建。人的理解也可以随着任务深入逐渐形成。
AI 时代对工程理解提出的新问题,或许不在于每个人必须掌握多少代码细节,而在于:当实现速度持续增长时,我们怎样确保系统中重要的知识没有随一次次自动生成和修改而流失?
事前验尸:假设这个版本已经失败了
最后一个值得保留的方法是事前验尸(Premortem)。
心理学家 Gary Klein 曾将它用于项目决策:在项目开始或关键决策之前,先假设事情已经失败,再反过来寻找可能导致失败的原因。
这种方法在软件发布之前同样适用。
假设收藏夹功能已经上线两周,现在出现大量用户投诉。最可能发生了什么?
可能是旧版本数据升级后丢失,可能是删除收藏夹时误删了摘录,也可能是用户在离线状态下操作,重新联网后发现数据不一致。
这些问题未必会出现在正常的功能演示中,却可能直接影响真实用户。
事前验尸的好处,是帮助我们暂时摆脱对成功路径的关注。
不过,它很容易退化成漫长的风险清单。真正重要的是识别少数损失较大、又有现实可能性的失败路径,并决定哪些需要在发布前检查。
对一些风险很低、容易撤回的修改,完整的事前验尸没有必要。对涉及用户数据、支付、权限和外部系统的改动,花几分钟做这样的思考可能非常值得。
这些原理会互相冲突吗?
当然会。
切斯特顿的栅栏让我们谨慎删除旧逻辑,YAGNI 却鼓励减少不必要的复杂度。
证伪要求寻找证据,信息价值又提醒我们调查并非没有成本。
可逆性鼓励保留改变方向的空间,但为了提高可逆性增加额外机制,也会带来新的维护负担。
这些冲突并不意味着某条原理是错的。
它们分别提醒我们注意不同的变量。最终仍然需要结合具体情境:当前有哪些证据、潜在损失有多大、错误多久能够发现、后果能恢复多少、继续调查会推迟什么。
例如,面对一段用途不明的旧代码,如果删除后很容易验证和恢复,那么可以通过小范围实验加快判断;如果它涉及支付状态或用户持久化数据,而测试又无法覆盖历史行为,就应该更加谨慎。
同样,虽然系统化调试通常应该先分析原因,但在生产故障仍然持续扩大时,控制损失可能具有更高优先级。
思维原理的价值不在于提供永远正确的操作规则,而在于让我们注意到那些容易被忽略、却足以改变决定的因素。
最后:AI 改变了执行成本,也改变了我们容易犯错的方式
软件工程已经积累了大量实践经验。TDD、Code Review、持续集成、灰度发布和架构设计仍然很重要,没有必要因为 AI 的出现重新发明一套开发方法。
但 Coding Agent 确实改变了日常工作的节奏。
当编写、修改和重构代码越来越容易时,我们可能更少受到实现成本的约束,也更容易在尚未充分理解问题时推进实现。
与此同时,真实用户的需求、历史数据的兼容性、生产环境的故障代价、系统长期演化的复杂性,并不会因为代码更容易生成就自动消失。
竞争性假设提醒我们不要过早接受解释;证伪让我们主动寻找反面证据;切斯特顿的栅栏要求我们尊重尚未理解的历史;可逆性帮助我们考虑错误后的恢复成本;信息价值帮助我们分配有限的注意力;古德哈特定律提醒我们警惕指标与目标的偏离;编程即理论构建,则让我们重新思考系统理解应该如何形成和保存。
这些思想已经存在很多年,其中大部分也早已体现在优秀的软件工程实践中。
我们没有必要为它们创造一个新的理论名称。
真正值得做的,是把它们重新带回具体的工程决策中,让使用 AI 的开发者知道什么时候应该继续行动,什么时候应该多问一个问题,什么时候应该怀疑一份看起来完美的结果。
尤其对于刚入门的开发者,掌握这些原理未必能让他立即拥有资深工程师的知识,但至少可以帮助他更早意识到:哪些事情自己已经知道,哪些仍然只是 Agent 作出的假设,以及哪些决定值得花时间认真判断。
随着 Agent 能够独立完成的工作越来越多,这种判断能力可能会变得更加重要。