写点什么

Cursor 们疯狂生码,引爆无限软件危机!Netflix 大佬警告:氛围编程正把我们带向灾难,程序员得动脑子

  • 2025-12-25
    北京
  • 本文字数:7541 字

    阅读完需:约 25 分钟

大小:3.54M时长:20:37
Cursor们疯狂生码,引爆无限软件危机!Netflix大佬警告:氛围编程正把我们带向灾难,程序员得动脑子

在上世纪六十年代末,随着系统规模增长到开发者已无法有效掌控的程度,“软件危机”(Software Crisis)这一说法首次出现。此后,每一代人似乎都用更强大的工具“解决”了这场危机,但结果往往只是制造出了更大的问题。

 

Netflix 工程主管 Jake Nations 表示,如今,AI 正在把这一循环加速到一个新的阶段:无限软件危机(Infinite Software Crisis)。由 AI 生成的代码库,本质上是生成它们的那一连串曲折对话的映射。每一次澄清、每一次方向调整,都会被直接固化进系统架构中。我们正在用 vibe coding 的方式,一步步走向灾难。

 

Jake Nations 至今在软件工程和大规模 AI/ML 系统设计领域拥有 13 年以上经验,专注于管理复杂代码库与推动高质量工程实践。在 Netflix,他负责推动技术架构与严谨开发流程,强调要理解系统本质、控制复杂性以及在 AI 时代保持代码和设计的可维护性。

 

针对上述问题,Jake 认为解决之道只有一个:选择“简单”,而不是“容易”。一次冗长的对话很容易;而划分清晰、边界明确的独立阶段,才是真正的简单。他提出了一种三阶段方法论,并指出,当所有人都在以机器的速度竞相生成代码时,真正能够脱颖而出的工程师,是那些能够判断系统何时开始变得纠缠、复杂的人。在无限代码生成的时代,人类在最关键的节点上进行判断,将成为核心竞争优势。

 

下面是 Jake 的演讲分享,我们对此进行了翻译,并在不改变原意基础上进行了删减,以飨读者。

 

我们正在交付自己并不真正理解的代码

 

我交付过一些自己其实并不完全理解的代码。这些代码是 AI 生成出来的,测试也跑过,上线也没出问题,但如果你让我解释它到底是怎么工作的,我说不清楚。说实话,我敢打赌,在座的每一个人都做过同样的事。

 

所以,不如我们干脆承认一个事实:我们现在都在交付自己并不完全理解的代码。

 

我想带大家回顾一下,这种情况是如何发生的。首先,回顾历史,你会发现历史总是在重复。其次,我们其实掉进了一个陷阱:把“容易”和“简单”混为一谈。最后,我认为是有解法的,但前提是,我们不能把“思考”这件事外包出去。

 

过去几年,我在 Netflix 推动 AI 工具的落地应用,可以非常负责任地说,这种加速是真实存在的。以前需要好几天才能完成的待办事项,现在几个小时就能搞定;那些在计划里躺了好几年的大型重构,终于开始被真正推进。

 

但问题在于,大型生产系统总是会以意想不到的方式出现故障。看看最近 Cloudflare 发生的事情就知道了。一旦真的出问题,你必须非常清楚自己正在调试的代码是怎么运作的。而现实是,我们现在生成代码的速度和规模实在太快了,理解能力已经明显跟不上了。

 

说实话,我自己就干过这种事:生成了一大段代码,看了一眼,心里很清楚自己完全不知道它在干嘛。但测试过了,也能跑,那就先上线再说吧。这其实并不是什么新鲜事。每一代软件工程师最终都会遇到一个瓶颈:软件复杂度超出了他们的管理能力。

 

我们并不是第一批面临“软件危机”的人,但我们是第一批面临这种无限生成规模情况的人。

 

软件危机,一直在循环

 

如果我们把时间往前拨,你会看到类似的故事一再发生。

 

在上世纪六十年代末到七十年代初,一群当时最聪明的计算机科学家聚在一起,提出了一个判断:我们正身处一场软件危机之中。社会对软件的需求急剧增长,但我们的开发能力却严重跟不上,项目周期过长、效率低下,整体表现并不理想。

 

Edsger Dijkstra 曾有一句非常经典的话,大意是:当我们只有少量、性能很弱的计算机时,编程只是一个小问题;而当我们拥有了性能极其强大的计算机之后,编程反而变成了一个巨大的问题。他解释道,随着硬件性能提升了几个数量级,社会对软件的需求也成比例增长,最终压力就全部落在了程序员身上:我们必须在“手段”和“目标”之间找到支撑如此庞大软件体系的方法。

 

这种循环不断上演。七十年代,我们有了 C 语言,可以构建更大的系统;八十年代,个人计算机普及,每个人都能写软件;九十年代,对象化编程盛行,继承层级复杂到失控,某种程度上“感谢”Java;进入新世纪,敏捷开发、冲刺、敏捷教练登场,瀑布模型被宣判“过时”;再后来是云计算、移动开发、DevOps,软件真正“吞噬了世界”。

 


而今天,我们迎来了 AI。Copilot、Cursor、Claude、Codex、Gemini,只要你能描述清楚需求,代码几乎可以瞬间生成。模式没有变,但规模彻底变了:它已经是无限的了。

 

难点从来不在“写代码”

 

Fred Brooks 除《人月神话》作者之外,还写过一篇非常重要的论文,叫《没有银弹》(No Silver Bullet)。他在文中明确指出:不存在任何一种单一的技术创新,能够在软件生产率上带来数量级的提升。

 

原因很简单:真正困难的部分,从来不是代码的机械层面,比如、输入、样板代码这些,而是理解问题本身,并设计出正确的解决方案。这一点,是任何工具都无法替代的。

 

我们过去发明的所有工具和方法,几乎都在让“机械部分”变得更容易,但核心挑战始终没有改变:我们仍然需要弄清楚到底该构建什么,以及它应该如何运作。

 

既然问题不在写代码本身,那为什么我们总是在优化这些“机械层面”的东西?为什么经验丰富的工程师,也会写出自己看不懂的代码?

 

我认为,答案就在两个我们经常混用的词上:简单和容易。

 


Rich Hickey,也就是 Clojure 语言的创造者,在他的演讲《Simple Made Easy》中,对这两个概念做过非常清晰的区分。他认为,“简单”指的是结构上的单一、无纠缠,每个部分只做一件事,彼此之间不互相缠绕;而“容易”指的则是距离上的接近,是不是顺手、是不是不用费力就能拿到,比如复制、粘贴、直接上线。

 

简单关乎结构,容易关乎便利。

 

问题在于,简单是无法靠“许愿”得到的,需要思考、设计和拆解。而容易却随处可得:装个包、让 AI 生成、从 Stack Overflow 抄一段代码。人类天生就会选择容易的那条路。但容易并不等于简单。容易意味着你可以很快往系统里加东西;简单意味着你能真正理解自己已经做过的事情。每一次我们选择容易,其实都是在用当下的速度,换未来的复杂度。

 

说实话,这种权衡在过去确实是有效的。复杂度积累得足够慢,我们还有机会通过重构、反思和重建来解决。但 AI 打破了这个平衡。它把“容易”推向了极致,极致到我们甚至不再考虑“简单”这条路。

 

对话式 AI,正在放大复杂度

 

当代码能瞬间生成时,为什么还要思考架构呢?通过对话一步步生成代码,看起来很自然,也很舒服,但它非常容易把一个本来简单的任务,演变成一团复杂的混乱。

 

比如,我们有一个应用,想为它引入一套身份认证机制。我们对 AI 说:“加一个 OAuth 认证流程”,于是生成了一个看起来很干净的 oauth.js 文件。随着不断迭代,又多出来一些配套的处理文件,看起来一切似乎还算合理。接着,我们又提出新的需求:“还要支持另一种 OAuth 认证流程。”于是代码库里同时出现了 oauth.js 和 oauth2.js 两套实现。我们继续通过对话不断迭代,很快就发现会话管理开始出问题,各种冲突接踵而至。

 

到了第二十次迭代时,你已经不再是在“讨论设计”,而是在被迫管理一个极其复杂的上下文,甚至已经记不清自己到底引入过多少约束条件。代码库里开始出现来自废弃方案的死代码,有为了“先跑起来”而被强行修复的测试,还有来自三种不同解决思路的残留片段,因为每一次新的指令,都会在不知不觉中覆盖之前的架构决策。

 

我们说“让认证逻辑在这里生效”,它照做了;我们说“修复这个错误”,它同样完成了。但它对糟糕的架构决策本身没有任何约束或抵抗力,代码只会不断变形,以满足你最新提出的需求。

 

每一次对话,其实都在选择“容易”而不是“简单”。而“容易”的必然结果,就是复杂度不断叠加。AI 会机械地满足你的每一次最新指令,却不会对糟糕的架构决策产生任何阻力。

 

AI 真的把“容易”推向了逻辑极致:你想要什么,代码就能瞬间得到。但这里的危险在于,生成的代码会平等对待你代码库中的每一个模式。当智能体分析你的代码库时,每一行代码都会变成一个需要保留的模式:第 47 行的身份验证检查是一个模式,我 2019 年写的那段奇怪的、像 GraphQL 一样工作的 gRPC 代码也是一个模式。

 

技术债在 AI 眼里并不是“债”,只是更多的代码而已。

 

真正的问题在于复杂度。我知道我在演讲中反复提到这个词,却没有真正定义它,但最好的理解方式是:它是简单的对立面,本质就是“纠缠”。当系统变得复杂时,一切都会相互影响,你几乎无法只改一个地方而不波及其他部分。每一次交互都是在选择容易而非简单,而容易总是意味着更多的复杂度。我们其实知道更好的做法,但当容易的路这么容易走时,我们还是会选择它。复杂度会不断累积,直到为时已晚。

 

回到 Fred Brooks 的理论,他将系统中的复杂度分为两类:

 

  • 第一类是本质复杂度(Essential Complexity),也就是问题本身的难度:用户要支付、订单要履约,这些决定了系统为什么存在。

  • 第二类是偶然复杂度(Accidental Complexity),这是我们在实现过程中不断叠加上去的东西:临时方案、防御性代码、曾经合理但现在过时的框架和抽象。

 

在真实的代码库里,这两种复杂度往往纠缠在一起,想要分离它们,需要对历史、上下文和经验的深刻理解。而 AI 并不会区分这些,它只会把所有模式一并保留下来。

 

AI 真正需要的不是“更好的提示词”

 

有一个来自 Netflix 实际工作的例子。

 

我们有一个系统,在大约五年前写的旧授权代码和新的集中式 OAuth 系统之间建有一个抽象层。我们当时没有时间重建整个应用,所以就在两者之间加了一个适配层。现在有了 AI,这似乎是一个直接重构代码以使用新系统的好机会,对吧?但事实并非如此。

 

旧代码与它的授权模式耦合得太紧了:权限检查穿插在业务逻辑中,角色假设嵌入在数据模型里,OAuth 调用分散在数百个文件中。智能体会开始重构并处理了几个文件后,就会遇到无法梳理的依赖,然后要么失控放弃,要么更糟。它会试图保留旧系统中的一些现有逻辑,并使用新系统重新实现,我觉得这也很糟糕。

 

问题在于,它无法看到表象之下的东西,无法区分业务逻辑在哪里结束、授权逻辑在哪里开始。所有东西都纠缠在一起,即使有完整的信息,AI 也找不到一条清晰的路径。当你的偶然复杂度到这种程度时,AI 不仅帮不上忙,反而只会在上面添加更多的层。

 

但我们人类可以区分,至少当我们放慢速度思考时可以。我们知道哪些模式是本质的,哪些只是几年前某人的解决方案。我们掌握着 AI 可以推断但需要我们提前花时间区分的上下文。

 

那么具体该怎么做呢?当你面对一个庞大的代码库时,如何区分本质复杂度和偶然复杂度?

 

我在 Netflix 工作的代码库有大约一百万行 Java 代码,上次检查时大约有五百万个 tokens,我能访问的任何上下文窗口都装不下它。因此,在着手处理这个问题时,我最初的设想是,将代码库的大量内容直接复制到上下文中,看看模型能否自行识别出关键模式并理解系统结构。但结果就和之前的授权重构并无二致,生成的结果很快陷入了自身复杂度之中。

 

这迫使我不得不尝试另一种方法:我必须选择要包含的内容,如设计文档、架构图、关键接口等等,然后花时间写下组件应该如何交互以及要遵循的模式。

 

我其实是在写一个规格说明。五百万个 tokens 最终变成了两千字的规格说明。更进一步,我把这个规格说明转化为了一套精确的代码执行步骤,没有模糊的指令,只有精确的操作序列。我发现这样生成的代码更干净、更聚焦,因为我先定义了需求,再规划了执行过程。

 

我把这种方法称为“上下文压缩”,你也可以叫它“上下文工程”或“规格驱动开发”,名字并不重要。重要的是,思考和规划成为了工作的主体。让我给你们详细介绍一下这个方法在实践中是如何运作的。

 

三阶段方法论

 

第一阶段:研究。我会把所有相关的东西都提供给 AI,包括架构图、文档、Slack 对话记录。

 

这一点我其实已经反复强调过很多次了,但核心只有一句话:尽可能把所有与你即将做出的修改相关的上下文一次性带进来,然后再使用智能代理去分析整个代码库,梳理系统的组成部分以及它们之间的依赖关系。

 

这绝对不是一次性完成的过程。我通常会不断追问,比如缓存是怎么处理的?失败场景是如何应对的?如果它的分析有误,我会直接纠正;如果它缺少关键信息,我就补充上下文。每一轮交互,都会让它的分析更加精细。

 

这一阶段的产出是一份完整的研究文档:系统里目前都有哪些东西、它们之间是如何相互连接的、你即将做出的改动会影响到哪些部分。原本需要花上好几个小时甚至更久的探索过程,被压缩成了几分钟的阅读时间。

 

我知道 Dex 之前也提到过这一点,但我还是要强调:这里的人工检查点(human checkpoint)至关重要。这是整个流程中投入产出比最高的时刻,你需要把分析结果与现实系统进行对照,在这里发现错误的话就能避免之后发生的灾难性后果。

 

第二阶段:制定可以“照着做”的实现计划。已经有了可靠研究结论的前提下,我们开始制定一份极其详细的实现计划,包括真实的代码结构、函数签名、类型定义、数据流等。

 

这份计划的目标,是做到任何一名开发者都可以直接照着执行。我常把它比作“数字填色”:你可以把它交给团队里最初级的工程师,对他说“照着这个来做”。只要他逐行照抄,最终结果就应该是正确可用的。

 

大量关键的架构决策,正是在这一阶段完成的。我们会在这里确保复杂逻辑是正确的,业务需求遵循良好实践,服务边界清晰,职责划分干净,同时避免任何不必要的耦合。之所以能在问题发生之前就识别它们,是因为我们曾经亲身踩过这些坑。而 AI 并没有这种经验,它会把代码中看到的每一种模式,都当作“必须遵守的约束”。

 

这一阶段真正的“魔法”,在于评审速度。我们可以在几分钟之内完成对整个方案的验证,并且清楚地知道接下来到底会构建出什么。而如果我们想跟上代码生成的速度,就必须以同样快的速度理解自己正在做的事情。

 

第三阶段:实现。最后才是实现,一旦我们有了清晰的研究结论和明确的实现计划,这一步反而应该是最简单的,而这正是我们希望看到的结果。

 

当 AI 有一份清楚、具体的规格说明可以遵循时,上下文就会保持干净且聚焦。我们避免了冗长对话导致的复杂度螺旋。相比五十轮不断“进化”的代码修改,现在只需要三次高度聚焦的输出,而且每一步都会在进入下一阶段前完成验证。没有被放弃的方案,没有相互冲突的模式,也不会再出现那种“等等,其实应该这样”的瞬间,没有留下遍地的死代码。

 

在我看来,这种方法真正的好处是:你可以把大量工作交给后台运行的智能体来完成。因为所有真正困难、需要动脑子的事情,已经提前由你完成了。智能体只需要按照计划开始实现。你可以去处理别的事情,回来时只需要快速评审结果,因为你检查的只是“它是否遵循了计划”,而不是试图弄清楚它是不是凭空“发明”了什么。

 

我们不是用 AI 来替我们思考,而是用它来加速机械性的工作,同时保持我们对代码的理解能力。研究更快了,规划更全面了,实施也更干净了。但思考、综合和判断,仍然是我们的责任。

 

还记得我之前说的那个 AI 无法处理的授权系统重构吗?现在我们实际上已经开始推进它,并且取得了一些实质性的进展。但这并不是因为我们找到了更好的提示词,恰恰相反,我们发现,连研究、规划、实现这些阶段都无法一开始就交给 AI。最终,我们不得不亲自下场,手工完成这次修改:不借助 AI,只是阅读代码、理解依赖关系,然后一步步改动,看哪里会出问题。

 

说实话,这次人工迁移过程非常痛苦,但至关重要。它揭示了所有隐藏的约束条件、必须遵守的不变式,以及一旦授权逻辑发生变化,哪些服务会直接崩溃。这些信息,是任何自动化代码分析都不可能替我们挖掘出来的。

 

然后,我们把这个手动迁移的拉取请求纳入了研究过程,让它作为后续所有研究工作的基础。即便如此,由于不同实体之间仍然存在差异,我们还是需要不断追问:这个该怎么处理、哪些数据是加密的哪些不是,每一次都要补充额外的上下文,并经过多轮迭代。

 

直到这时,我们才有可能生成一份“或许能一次成功”的计划。注意,这里的关键词是“或许”。我们仍然在持续验证、不断调整,并持续发现新的边界情况。

 

这套三阶段方法并不是什么魔法。它之所以能奏效,只是因为我们曾经亲手完成过一次迁移。在把理解写进流程之前,我们必须先赢得这种理解。

 

我依然认为,并不存在所谓的银弹。没有更好的提示词,没有万能的模型,甚至也不是写得更漂亮的规格文档。真正重要的,始终是那件最朴素、也最困难的事情:足够深入地理解你的系统,理解到你可以安全地对它做出修改为止。

 

那为什么还要折腾这一整套流程呢?为什么不干脆一直和 AI 反复迭代,直到“能用”为止?反正模型迟早会变得足够强,最后不就都能跑起来了吗?

 

在我看来,“能跑”本身并不够。通过测试的代码和能在生产环境长期稳定运行的代码之间,是有本质区别的;今天还能工作的系统,和未来还能被别人安全修改的系统,也是两回事。

 

这中间存在一个认知鸿沟。当 AI 可以在几秒钟内生成成千上万行代码时,理解这些代码却可能要花你几个小时;如果系统足够复杂,可能需要几天;甚至在极端情况下,你可能永远都无法真正理解它。

 

软件,终究是一项人类的事业

 

还有一个我觉得几乎没人认真讨论过的问题:每一次为了跟上生成速度而跳过思考,我们失去的并不只是对代码的理解。我们正在逐渐丧失一种能力:识别问题的能力,那种会提醒你“等等,这里开始变复杂了”的直觉,如果你长期不真正理解自己的系统,是会慢慢退化的。

 

模式识别(pattern recognition)来源于经验。我一眼就能看出某种架构是危险的,那是因为我就那个曾经凌晨三点还在被它折磨的人;当我坚持推动更简单的方案时,是因为我曾经接手并维护过别人留下的复杂系统。

 

AI 只会生成你让它生成的东西,它并不会内化那些来自失败的教训。

 

三阶段方法,正是用来弥合这道鸿沟的。它把“理解”压缩成一系列可以在生成速度下被快速审查的产出物。如果没有这一层,我们只是在用一种自己无法理解的速度,不断累积复杂度。

 

AI 确实彻底改变了我们写代码的方式,但坦率地说,我并不认为但它并没有改变软件失败的根本原因。每一代人都会遭遇属于自己的软件危机。Dijkstra 那一代,通过建立软件工程这门学科来应对;而我们这一代,面对的是无限规模的代码生成。

 

答案并在于下一个工具或新的方法论,而在于重新记起一个我们早就知道的事实:软件始终是一项人类的事业。困难的从来不是敲代码,而是在一开始就知道该敲什么。

 

最终能够走得更远的开发者,并不只是那些生成代码最多的人,而是那些仍然理解自己在构建什么、能够看清系统接缝、敢于质疑问题本身是否正确的人。

 

我想用一个问题来结束今天的分享,这个问题并不是“我们会不会使用 AI”,那早已是既定事实。真正的问题是:当 AI 写下大部分代码的时候,我们是否仍然理解自己的系统?

 

参考链接:

https://www.youtube.com/watch?v=eIoohUmYpGI

2025-12-25 07:001

评论

发布
暂无评论

chatgpt小试牛刀

阿呆

ChatGPT

自动驾驶汽车芯片的发展和分析

不脱发的程序猿

嵌入式 汽车电子 自动驾驶汽车芯片

《欧拉开源操作系统行业应用案例集》2023年案例集征集开始!

openEuler

Linux 操作系统 openEuler

RocketMQ源码-NameServer架构设计及启动流程

小小怪下士

Java 源码 程序员 RocketMQ

应用部署初探:3个主要阶段、4种常见模式

SEAL安全

应用部署

从零到一,臻于至善|网易邮箱基于StarRocks 开发大数据平台的实践

StarRocks

数据库

2022年12月视频行业用户洞察:世界杯后半程看球热度不减,优质IP创新与开发助力用户留存

易观分析

音视频 视频

架构实战 5 -微博评论高性能高可用计算架构

架构实战营 「架构实战营」

瑞萨RH850 FCL、FDL和EEL库的配置和使用

不脱发的程序猿

嵌入式 汽车电子 RH850 ​瑞萨

全球首个面向遥感任务设计的亿级视觉Transformer大模型

京东科技开发者

CNN 遥感 遥感影像 企业号 2 月 PK 榜 深度视觉

运维百家讲坛第 1 期:井源 - 运维几何

巴辉特

架构训练营模块五作业

现在不学习马上变垃圾

架构训练营10期

2022年总结及2023年规划:新起点和新希望

不脱发的程序猿

程序人生 年度总结

OKR之剑·实战篇04:OKR执行过程优化的那些关键事

vivo互联网技术

团队管理 OKR

开发互动直播应用很简单:声网 Android Demo保姆级跑通教程

RTE开发者社区

android RTC RTE 教程分享

年度技术盘点:水稻、韦伯、脑机接口、AI预测及创作、快速充电

B Impact

MixMIM 创建混合图像,提出新型掩码方案

Zilliz

计算机视觉

分层次的电路设计方法

timerring

FPGA

windows命令窗口

MEImei

火山引擎DataTester:0代码也能实施A/B测试的实验平台

字节跳动数据平台

大数据 AB testing实战 企业号 2 月 PK 榜

在人间vpn搭建

阿呆

vpn

坚持技术or转做管理,我们该如何选择?

石云升

极客时间 1月月更 技术领导力实战笔记

比亚迪新能源汽车战略布局研究

不脱发的程序猿

汽车电子 比亚迪新能源汽车战略布局 比亚迪新能源汽车

想找个稳定的工作

MavenTalker

职业素养 职业发展 求职面试

比亚迪元EV汽车拆解报告

不脱发的程序猿

嵌入式 汽车电子 比亚迪元EV汽车拆解

架构实战营第 10 期 - 模块五:微博评论高性能高可用计算架构设计

kaizen

「架构实战营」

mockito入门

查拉图斯特拉说

后端 单元测试

2K字就能理解的async/await原理,还要拖多久?

梁木由

前端 前端开发 校招 前端入门

Cursor们疯狂生码,引爆无限软件危机!Netflix大佬警告:氛围编程正把我们带向灾难,程序员得动脑子_AI&大模型_褚杏娟_InfoQ精选文章