軟體危機的無限迴圈:簡單(Simple)與容易(Easy)的距離


講者 Jake Nations ( Staff Software Engineer at Netflix) https://www.linkedin.com/in/jakenations/

本篇為閱讀 AI Engineer Youtube channel 的摘要


1) 開場:我們都「交付過自己不懂的程式碼」

演講一開始,講者先做了一個近乎坦白式的告白:他曾經交付(ship)了自己其實不理解的程式碼——那段程式碼是他產生的、測試過的、也部署了,但他無法解釋它到底怎麼運作

而他接著說了一句帶點「大家都懂」的判斷:他願意打賭,在場每一位也都做過同樣的事。既然這件事已經變成某種集體現象,他想帶大家走一段旅程,理解這種狀況是怎麼出現的。

他為接下來的內容先立了三個路標:

  1. 回顧歷史:歷史往往會重演。
  2. 我們掉進一個陷阱:把「容易(easy)」跟「簡單(simple)」混為一談。
  3. 確實有修正方法:但不是把思考外包出去。

2) AI 的加速是真實的,但大型系統的失敗也同樣真實

講者提到自己在 Netflix 的經驗:他過去幾年在 Netflix 推動 AI 工具的採用,而他強調——加速是絕對真實的

  • 過去要做「好幾天」的 backlog 項目,現在可能「幾小時」就能完成。
  • 過去在待辦清單上拖了「好幾年」的大型重構(refactors),現在終於開始被做完。

但他立刻補上一個現實的提醒:大型的生產系統(large production systems)總是會以意想不到的方式失敗。他用「Cloudflare 最近發生的事」作為例子:當這些系統出狀況時,你必須真正理解你正在除錯的程式碼

問題在於:現在我們用 AI 生成程式碼的速度與量都太大,理解(understanding)很難跟上。他承認自己也有過這種狀況:生成了一大段程式碼,看著它想「我根本不知道這在幹嘛」,但測試過了、能跑、於是就交付了。

他強調:這其實不是新問題。每一代軟體工程師,最後都會撞上一面牆——當軟體複雜度超過人類可管理能力時,就會出現「軟體危機」。差別在於:
我們不是第一個面對軟體危機的人,但我們是第一個面對「無限規模(infinite scale)的生成」的人。


3) 回到起點:軟體危機一直循環出現(只是規模變了)

講者把時間拉回到 1960 年代末、1970 年代初:當時一群頂尖的電腦科學家聚在一起,說出「我們正處於軟體危機」。

危機的樣貌是:

  • 社會對軟體的需求很大
  • 但供應跟不上
  • 專案進度太慢、交付很慢
  • 整體「做得不夠好」

他引用(並說自己是意譯/轉述)Dijkstra 的一段著名觀點:
當我們只有幾台很弱的電腦時,寫程式只是「溫和的問題」;
現在我們有巨大的電腦,寫程式成了「巨大的問題」。

他的解釋是:當硬體能力成長了 1000 倍,社會對軟體的想要程度也等比例成長;於是「程式設計師」被迫要在「需求(ways)」與「手段(means)」之間找出路:我們怎麼支撐更多、更大的軟體?

接著他用一段清楚的年代編年史,描述這種危機如何一再透過新工具/新方法被「暫時緩解」,但也不斷引入新的複雜度:

  • 1970s:C 語言 → 讓我們能寫更大的系統
  • 1980s:個人電腦普及 → 人人都能寫軟體
  • 1990s:物件導向(OOP) → 然後出現「地獄般的繼承階層」(Java)
  • 2000s:Agile → 有衝刺(sprints)、Scrum master 來指揮;不再走瀑布式(waterfall)
  • 2010s:雲端、行動、DevOps… → 軟體真正「吃掉了世界」
  • 今天:AI 工具(Copilot、Cursor、Claude、Codex、Gemini…「你說得出的都有」) → 我們能用描述的速度生成程式碼

他總結:模式(pattern)一直延續,但規模已經改變——現在是「無限」了。


4) Brooks 的提醒:沒有銀彈,因為真正難的不是「打字」

講者引出 Fred Brooks(《The Mythical Man-Month》作者),以及 Brooks 在 1986 年的論文 〈No Silver Bullet〉

Brooks 的核心主張是:不會有任何單一創新,能讓軟體生產力「提高一個數量級(order of magnitude)」。

原因是:
真正困難的部分不是寫程式的機械性工作——不是語法、不是打字、不是樣板碼(boilerplate);真正困難的是:

  • 理解問題本身
  • 設計解法

而且沒有工具能消除這種根本難度。到目前為止,我們發明的工具與技術,大多都只是讓「機械性」更容易;但「核心挑戰」——理解要做什麼、應該怎麼運作——仍然一樣難。

於是他反問:如果問題不在機械性,為什麼我們一直優化機械性?為什麼資深工程師也會寫出(或交付)自己不懂的程式碼?


5) 關鍵混淆:Simple vs Easy(我們把「容易」當成「簡單」)

他的答案收斂成兩個詞:simpleeasy。我們常把它們混用,但其實完全不同。

他提到自己在 speaker dinner 被「爆料」是 Clojure 派,因此很自然地引用 Rich Hickey(Clojure 創作者)在 2011 年的演講 〈Simple Made Easy〉 的定義:

5.1 什麼是 Simple

Rich Hickey 把 simple 定義為「one fold / one braid」:單一摺疊、單一編織,沒有糾纏(no entanglement)
意思是每個部分做一件事,且不與其他部分纏在一起。

Simple 重點是:結構(structure)

5.2 什麼是 Easy

Easy 的意思是「adjacent / within reach」:就在你伸手可及的地方、你不費力就能拿到的東西——複製、貼上、交付(copy‑paste ship)

Easy 重點是:距離與可得性(proximity)

5.3 為什麼這個差異致命

講者強調:你不能靠「希望」讓東西變 simple。
簡單需要思考、設計、解纏(untangling)。

但你永遠可以讓事情變 easy:
把東西放近一點就好——裝個套件、用 AI 生成、從 Stack Overflow 抄一段。

而人類天性就是走 easy 的路:

  • Stack Overflow 的解法就在那裡
  • 會「用魔法處理一切」的框架?裝了就走

easy ≠ simple

  • easy:你能很快把東西加進系統
  • simple:你能理解你做了什麼

他給出一個重要的代價公式:
每次選 easy,就是選擇「現在的速度」換「未來的複雜度」。

而且他承認:以前這個交易「其實很常划算」。因為複雜度累積得夠慢,我們還有時間重構、重新思考、必要時重建。

但他認為:AI 打破了這個平衡。因為 AI 是終極 easy 按鈕,它把 easy 的摩擦降到幾乎為零,導致我們甚至不再考慮 simple 的路:
「程式碼瞬間出現時,誰還想架構?」


6) 以「對話式迭代」為例:簡單任務如何演化成複雜泥沼

接著他用一個刻意簡化(contrived)的例子,展示「我們都愛的對話式介面」如何把簡單任務帶向複雜。

6.1 從「加登入」開始

假設你有個 app,你對 AI 說:加驗證(add auth)
於是得到一個乾淨的 auth.js

你再迭代幾次,可能到第 5 次訊息(message five),看起來都還不錯。

6.2 複雜度開始上升:加 OAuth、修 sessions、解 conflicts

接著你又說:我們也要 OAuth。
於是現在你有 auth.js + OAuth.js

你繼續迭代,然後發現 sessions 壞了,開始出現各種衝突(conflicts)。
等到第 20 回合(turn 20),你已經不是在「討論需求」了,你是在管理一個變得太複雜的上下文——複雜到你自己都不記得曾經加過哪些約束(constraints)。

6.3 典型後果:死碼、碎片化解法、被動修測試

這時候會出現一連串熟悉的症狀:

  • 被放棄方案留下來的死碼(dead code)
  • 測試不是被真正修好,而是被「弄到會過」(fixed by just making them work)
  • 同時存在三種解法的碎片(fragments of three different solutions),因為你會不斷說「等等,其實…」
  • 每一個新指令都在覆寫(overriding)架構模式:
    • 你說「讓 auth 在這裡能用」→ 它做到了
    • 你說「修這個 error」→ 它也做到了
      但整體架構沒有任何阻力去抵抗壞決策,程式碼只是為了滿足你最新的要求而變形(morph)

他把這件事的本質講得很直白:
每一次互動都是在選 easy 而不是 simple,而 easy 永遠意味著更多複雜度。
我們其實知道更好的做法,但當 easy 路徑簡單到這種程度時,我們就會走;而複雜度會一路複利式累積,直到太晚。


7) AI 的「模式保留」:技術債不會被識別,只會被延續

講者說:AI 把 easy 推到了邏輯的極限:你決定要什麼,立刻得到程式碼。

但危險在於:生成程式碼會把你 codebase 裡的每個模式「一視同仁」。

當 agent 分析 codebase 時:

  • 第 47 行的 authentication check 是一種模式
  • 他在 2019 年寫過的那段「怪 gRPC 但又像 GraphQL」的程式,也是一種模式
  • 技術債不會被標示成『債』,它只會被視為「更多程式碼」,也就更會被保留與複製

8) 複雜度的定義,以及 Brooks 的兩種複雜度

他承認自己一直講 complexity,現在補上定義:理解 complexity 最好的方式,是把它當成 simplicity 的反面。Complexity 意味著「糾纏」——當事情變複雜時,每件事都碰到每件事,你改一個地方會影響十個地方。

回到 Brooks,Brooks 說每個系統裡都有兩種主要複雜度:

8.1 本質複雜度(Essential Complexity)

這是問題本身的難度,是系統存在的原因,例如:

  • 使用者要付費
  • 訂單要履約(orders must be fulfilled)

8.2 偶發/附帶複雜度(Accidental Complexity)

這是我們一路上加進去的所有其他東西:

  • workaround
  • 防禦性程式碼
  • 框架
  • 曾經合理、現在未必合理的抽象

在真實 codebase 裡,兩者到處都是,而且常常糾纏到必須靠「上下文、歷史、經驗」才能分開。
但生成式輸出不會做這種區分,所以每個模式都被保留,複雜度只會繼續累積。


9) Netflix 真實案例:授權系統重構,AI 卡在「看不到接縫」的糾纏

講者分享 Netflix 正在做的一個真實例子:

  • 系統裡有一層抽象(abstraction layer / shim (適配層)),放在
    • 五年前寫的舊授權(authorization)程式碼
    • 與新的集中式授權系統(new centralized auth system)
      之間
  • 當時沒有時間重建整個 app,所以用 shim 先頂著

現在有 AI 了,看似是個重構好機會:把 code 直接改用新的系統——聽起來像個簡單需求。

但實際上並不簡單,因為舊系統跟授權模式綁得太深:

  • permission checks 被織進 business logic
  • role 的假設被烙進 data models
  • auth 呼叫散落在上百個檔案

結果是:

  • agent 重構到一部分就遇到解不開的依賴,然後失控(spiral out of control)或放棄
  • 更糟的是,它可能會嘗試把舊邏輯「用新系統再造一遍」——他認為這也不好

他指出核心問題:AI 看不到接縫(couldn’t see the seams),分不清 business logic 在哪裡結束、auth logic 在哪裡開始。糾纏到這種程度時,就算 AI 有完美資訊,也找不到乾淨路徑。當 accidental complexity 糾纏成這樣,AI 不但幫不上忙,反而常是再疊一層

人類能分辨(至少在你願意慢下來思考時能):哪些模式是 essential,哪些只是幾年前某人當時的做法。這些脈絡 AI 無法憑空推論,所以在開始之前,人必須先花時間做出這些區分。


10) 面對超大 codebase:上下文塞不進去,只能「壓縮成規格」

接下來他談「怎麼做」:當你面對巨大 codebase,要怎麼分離 accidental 與 essential complexity?

他描述 Netflix 的 codebase 規模:

  • 100 萬行 Java
  • 其中主要服務(main service)上次他檢查約 500 萬 tokens
  • 他能用的任何 context window 都裝不下

他一開始也想過把大段程式碼複製進去,讓模型找模式、理解發生什麼事;但就像前面的授權重構一樣,輸出也會迷失在自己的複雜度裡。

於是他被迫換方法:他必須選擇要放進上下文的東西,例如:

  • 設計文件(design docs)
  • 架構圖(architecture diagrams)
  • 關鍵介面(key interfaces)
  • 以及其他你想得到的關鍵材料

並且花時間把「元件應該怎麼互動、應該遵循哪些模式」的需求寫清楚。
他說白了:他是在寫一份 spec(規格)

結果是:500 萬 tokens 的原始世界,被他壓縮成大約 2000 字的規格文件。
再更進一步:從 spec 延伸出一份「要執行的精確步驟清單」——不是模糊指令,而是精準操作序列。

他發現這樣產出的程式碼更乾淨、更聚焦,而且他能理解,因為他是先定義、先規劃。

他曾把這方法叫做 context compression;你也可以叫它 context engineering,或他口中提到的另一個稱呼(逐字稿寫作 specter of development)。他強調:名字不重要,重要的是——思考與規劃變成工作的大多數


11) 三階段流程:研究 → 計畫 → 實作(用人類審核守住理解速度)

他接著用「實務上怎麼運作」來拆解三階段流程。

11.1 Phase 1:Research(研究)

這一階段的精神是:先把「相關的上下文」盡可能餵給它。

他列舉會放的材料:

  • 架構圖
  • 文件
  • Slack 討論串
  • 以及所有跟你要改的內容相關的東西(他說他前面講過很多次了)

接著讓 agent:

  • 分析 codebase
  • 畫出元件與相依關係(components and dependencies mapping)

他強調這不該一次 prompt 就結束(one shot)。他會用「探針式提問」反覆確認,例如:

  • 快取(caching)怎麼做?
  • 失敗(failures)怎麼處理?

當 agent 分析錯了,他就糾正;缺上下文就補。每一次迭代都會精煉分析。

這一階段的輸出是一份單一研究文件(single research document),內容包含:

  • 現況是什麼(what exists)
  • 哪些連到哪些(what connects to what)
  • 你的變更會影響什麼(what your change will affect)

效果是把「數小時的探索」壓縮成「幾分鐘閱讀」。

他提到(呼應早上 Dex 的分享):人類檢查點(human checkpoint)在這裡至關重要。這是你把分析對照現實、驗證正確性的時刻;也是整個流程中槓桿最高的一刻——在這裡抓到錯誤,可以避免之後的災難。


11.2 Phase 2:Implementation Plan(實作計畫)

有了有效研究後,第二階段要產出「詳細實作計畫」,包含:

  • 真正的 code structure
  • function signatures
  • type definitions
  • data flow

他希望這份計畫詳細到「任何開發者都能照著做」。他把它比喻成「按數字填色(paint by numbers)」:你可以把它交給最資淺的工程師,叫他照著一行一行抄,最後應該就會跑。

他指出:這一步是做出大量關鍵架構決策的地方,包括:

  • 複雜邏輯是否正確
  • 商業需求是否遵循良好實務
  • 服務邊界是否清楚
  • 是否乾淨分離、避免不必要耦合(unnecessary coupling)

他說人類能在這裡「先看到問題,避免它發生」,因為人類「真的經歷過」。AI 沒這選項,它會把每個模式當作需求。

他也點出這一步的「魔法」是審查速度:我們能在幾分鐘內審完計畫,並且清楚知道接下來會建出什麼。
而要跟上生成速度,我們也必須用同樣快的速度理解我們正在做什麼。


11.3 Phase 3:Implementation(實作)

最後才是寫碼與落地。他說:當你有清楚計畫、且以清楚研究為後盾,這一階段應該「相當簡單」——而這正是目標。

因為當 AI 有明確規格可遵循時,上下文會保持乾淨聚焦;你就能避免長對話造成的複雜度螺旋。

相比「50 則訊息的演化式程式碼」,你會得到「三個聚焦輸出」,而且每一步都在前進前被驗證:

  • 研究文件
  • 實作計畫
  • 實作結果

因此你不會得到:

  • 被放棄的方案殘骸
  • 互相衝突的模式
  • 「等等其實…」造成的死碼遍地

他認為另一個巨大回報是:你可以用背景 agent 來做大量工作。因為困難的思考已先完成,agent 只要照計畫實作;你可以去做別的事,回來快速審查。審查也更快,因為你是在核對是否符合計畫,而不是試圖理解它是否「自己發明了什麼」。

他把立場說得很清楚:我們不是用 AI 代替思考;我們是用 AI 加速機械性部分,同時維持理解能力。研究更快、規劃更周全、實作更乾淨;但思考、綜合(synthesis)、判斷仍由人負責。


12) 回到那個 AI 無法處理的授權重構:突破點其實是「先手工做一個」

他回到前面提過的授權重構:現在他們確實在推進,也有進展。

但進展不是因為更好的 prompts。反而是因為他們發現:他們甚至無法直接跳進 AI 的研究/規劃/實作流程。

他們必須先「自己手工做一次變更」:

  • 不用 AI
  • 直接讀程式
  • 理解依賴
  • 改改看、看什麼壞掉

他坦白:手工遷移很痛苦(a pain),但非常關鍵。因為它揭露了所有隱藏約束:

  • 哪些不變量(invariants)必須成立
  • auth 改動會讓哪些服務壞掉

而這些事情,不是做再多「產生程式碼式的分析」就能冒出來的。

接著,他們把那個「手工遷移的 pull request」餵進研究流程,作為後續研究的 seed。AI 因此能看到「乾淨遷移」長什麼樣子。

但事情仍不會一鍵完成:每個實體(entities)都稍有不同,他們要不斷追問:

  • 這個怎麼辦?
  • 有些東西有加密,有些沒有

他們必須在多次迭代中補上額外上下文,然後才可能生成一個「也許能一次成功」的計畫。
他強調「也許」才是重點:他們仍在驗證、仍在調整、仍在發現邊界案例(edge cases)。

因此他說:三階段方法不是魔法。它能運作,是因為他們先親手做過一次遷移,先「賺到」理解,才把它編進流程。

他再次呼應 No Silver Bullet:
他仍不相信銀彈存在——不是更好 prompts、不是更好模型、甚至不只是更好 specs;而是你必須深入理解系統到足以安全修改它的程度。


13) 為什麼不乾脆一直跟 AI 迭代到跑通?因為「能跑」不等於「能活」🧪

他提出一個很多人會想問的問題:為什麼要做這麼多前置工作?為什麼不乾脆跟 AI 一直迭代到它「work」?模型總會變強,最後不就都能自動搞定?

他給的回答是:「it works」不夠。因為有一條很重要的界線:

  • 通過測試的程式碼 vs 能在 production 活下來的程式碼
  • 今天能動的系統 vs 未來能被別人修改的系統

真正的問題是「知識落差(knowledge gap)」:
當 AI 能在幾秒內生成幾千行程式碼時,人類要理解它可能要好幾小時;複雜時可能要好幾天;糾纏更深時甚至可能永遠也理解不了。


14) 更深的風險:跳過思考不只是加速技術債,還會讓「品味與警覺」退化

他提出一個他認為很多人還沒怎麼討論的點:

每一次我們為了跟上生成速度而跳過思考,我們不只是加入了不懂的程式碼;我們還在失去一種能力:察覺問題的能力

那種直覺——「嘿,這變得很複雜了」——在你不理解系統時會萎縮(atrophies)。

他說,模式辨識來自經驗:
他能一眼看出危險架構,是因為他曾在凌晨三點被它叫醒去救火;
他會推動更簡單的方案,是因為他曾經維護過別人留下來的「不簡單替代方案」。

而 AI 只會生成你要求的東西,它不會自動帶著那些「過去失敗的教訓」。

因此三階段方法的價值在於:它把理解壓縮成可以審查的產物(artifacts),讓我們能用接近生成的速度去理解。沒有它,我們只是在用超過理解能力的速度累積複雜度。


15) 結尾:AI 改變寫碼方式,但沒改變軟體為何失敗;危機依舊是人的危機

他最後拉回更大的視角:

AI 改變了我們寫程式的方式,但他不認為 AI 改變了「軟體為什麼會失敗」。每一代都有自己的軟體危機:

  • Dijkstra 那一代,靠建立「軟體工程」這門紀律來應對
  • 我們這一代,面對的是「無限程式碼生成」的危機

他不認為解法是另一個工具或另一套方法論;而是記得我們一直都知道的事:軟體是人類的事業(human endeavor)。最難的從來不是打字,而是知道該打什麼。

因此,未來能茁壯的開發者,不會只是生成最多程式碼的人;而是:

  • 理解自己正在建什麼的人
  • 仍然看得出接縫(see the seams)的人
  • 能辨識自己正在解錯問題的人

他強調:那仍然是我們;也只會是我們。

最後他留下一個問題。他說問題不是「我們會不會用 AI」——那已經是既定事實,船已經開了。真正的問題是:
當 AI 寫下我們大多數程式碼時,我們是否仍能理解自己的系統?