AI 時代的軟體開發依然沒有萬靈丹
AI 讓生成程式碼變得簡單,卻無法解決軟體開發的本質困難:「你不知道你不知道的事」。在大量使用 AI 的未來,了解背後原理、權衡取捨,並保持獨立思考,才是工程師真正的核心價值。
現在社群軟體上充斥著 AI 要取代軟體開發,要取代工程師的貼文,搭配矽谷各大巨頭不斷裁員的消息,貌似軟體工程師就真的要消失了,很多人都陷入了焦慮和恐慌。
首先我認為巨頭裁員是和疫情期間過度招募有關係,而不是因為 AI 真的在實務上快速地取代軟體工程師,但這是另外一個話題,可以改天再聊。
為什麼『 依然 』沒有萬靈丹?
Frederick Brooks 在 1986 年提出的論文 No Silver Bullet – Essence and Accident in Software Engineering 放在 40 年後的現在還是依然通用,論文中指出軟體開發中的偶然性困難 (Accidental Difficulties) 以及本質性困難 (Essential Difficulties)。
這兩種困難不同在哪?
本質性困難是軟體本質上固有的困難,源自於你試圖解決的實際問題的根本難度,這涉及到你如何建立、設計和測試軟體的抽象概念,簡單來說,它就是你理解你要解決的問題,並在這個概念上提出解決方案。
而偶然性困難則是在軟體產生過程中而產生的副作用,不是問題本身的,像是語法錯誤、框架的耦合、套件的不兼容進而加入各式各樣的 workaround 或是任何防禦式程式碼都是為了解決偶然性困難而誕生的。
對我來說 AI 依然是在解決偶然性困難的問題,它再次地讓寫程式變得更簡單,甚至可以在不安裝 IDE 只用 ClaudeCode 的情況下不看一行程式碼開發出一個本地可以使用的 Todo List 工具,但這不是在解決問題,這只是更聰明的 copy paste。
那 AI 現在為什麼還不能解決本質性困難?以我的工作來說,困難在於理解需求 (這可能就佔了 80% 的工作時間),和 PO 協調找到折衷的方案,刪除一些不可能發生的使用者案例,AI 是否可以在這中間給出建議?可以的,完全沒有問題,但它會遺漏掉許多沒有被考慮到的事情,我的體感是他會嘗試給出最棒的解法,但最棒的解法往往不適合現在這個混亂的架構。
舉例來說,要在系統中引入即時的通知系統,AI 大概率會給你 websocket 的提議,你會告訴它,這只是內部系統,引入 websocket server 會提升複雜度,它會再告訴你 SSE 的做法,接著你試著在本地讓 AI 快速地產生做法,發現可以耶!然後你嘗試寫成一份文件讓大家 review 這份 tech design,大家提出 JWT token 怎麼處理的問題,接著你才發現原來本地繞過 token 檢查為了方便開發,而後你又跑回去問 AI,它給出了用某些套件可以帶上 JWT token 的方案,超棒的!接著你更新文件,大家又提出一個疑問,為什麼不用 long polling?接著你算了一下後台的 RPS,然後和 platform team 討論了一下發現這樣的 request 量級根本就是小兒科,進而採用 long polling 的做法,還可以不需要考量 authorization 的問題。
上述的案例一定有很多地方可以吐槽,但它就是在解決本質性困難的問題,你要是理解系統的那個人,才有辦法提出一個對目前系統最優的解決方案,否則讓 AI 跑下去就是做出一個一定可以運作的 websocket 架構,但多出了營運和維護的成本,以及不必要的複雜性。
Simple 以及 Easy 的差別
在這篇 Youtube 的演講中,讓我印象深刻地就是 Jake Nations 引用了 Rich Hickey simple made easy 的概念進而衍伸出 simple vs easy 的想法。
容易(Easy):只要開口要求,AI 就能瞬間給你程式碼,讓你快速擴充系統。
簡單(Simple):代表系統結構清晰、職責單一、沒有過度糾纏,這需要工程師花時間去思考、設計與解開糾纏。
那我們是不是可以透過叫 AI 永遠用最 simple 的方式去執行來避免過度糾纏呢?這其實是行不通的,系統最終依然會走向混亂。
Jake Nations 在演講中有指出:We can't make something simple by wishing it,這是什麼意思?下面是目前 AI 還存在的問題:
- AI 無法分辨 本質邏輯 與 技術債:當 AI 去分析專案時,它會把每一行程式碼都視為必須保留的模式 (pattern)。對 AI 來說,技術債並不算是債,它就只是更多的程式碼而已。因此,即使你要求它 simple,它依然會試圖去迎合並保留那些過去遺留下來的偶然性困難帶來的副作用,例如幾年前奇怪的 workaround 或是早該被 refactor 的程式碼。
- AI 看不見系統的接縫:在真實的專案中,本質性的商業邏輯與附屬的機制往往緊密糾纏在一起。Jake Nations 在重構系統時發現,即使給予 AI 完美的資訊,AI 也看不見系統的接縫,它無法辨識出商業邏輯的邊界在哪裡,也分不清哪裡是驗證邏輯的起點。當偶然性複雜度過於糾纏時,AI 無法幫你梳理出一條乾淨的路徑,它往往只會在舊系統上再疊加更多新的抽象層,或者直接陷入混亂而放棄。
- 簡單需要來自人類失敗的經驗與脈絡:能夠一眼看出某個架構太複雜或有危險,通常來自工程師過去的痛苦經驗(例如下班時間到了但還是要開的 P0 會議)。AI 只會生成你要求的東西,它無法編寫這些來自過去失敗的教訓。只有人類擁有足夠的歷史脈絡,能判斷哪些邏輯是「本質上」必須存在的,哪些只是幾年前某個工程師留下的權宜之計。
這也是我認為 code review 變得比以前重要很多的原因,我們已經不需要花費太多時間寫程式碼了,主要的工作就是避免 AI 不小心產生的垃圾進入到 codebase,所以 code review 應該變得比以往更嚴格,相信大家也已經開始在公司內部看到某些 PR 內的 changes 明顯就是引用錯誤的 pattern,或是早就 deprecated 的語法,也可以很明顯感受到很多人甚至連看完自己的 PR 才 mention review 都沒有做到。
Jake Nations 提出的三階段法
Research
把你所知道的上下文丟給 AI (架構圖、confluence 文件以及 slack 的討論串等等)。然後讓 AI 分析 codebase,描繪出依賴關係的草圖 (ASCII 很醜我知道)
這一步應該做的是不斷地收斂和刪除 AI 的想法,進而產生出一份清晰的研究文件,內容應該提及現在有什麼、彼此如何連接以及更動會影響什麼?
我不認為在這一階段 AI 應該給出程式碼,它應該更像是 pre-planning 階段,我知道大部分的人應該都是直接開 planning mode,這是因為你的腦子已經 research 結束了,所以你很清楚你要做什麼,那可以直接跳到 planning,但對於完全不熟悉的 codebase,這一步就是盡可能地和 AI 聊天,不斷迭代你的疑問,挑戰 AI 給出的解決方案,收斂到你覺得合理的程度。
Human Checkpoint
得到 researched 完整的文件後,要做得就是核實這些概念的可行性,你可以再開另一個 session 讓 AI 快速地去執行這份文件,你會有超級無敵亂的程式碼,不重要,反正要確保上一步的研究是可以運作的,而不是天馬行空的產物。
Planning
核實可行性後,接著就是和 AI agent 去共同建立一份極度詳細的實作計劃,內容需要包含真實的程式碼,型別定義和資料流,我們需要做的就是確保邊界清晰,避免不必要的耦合。
Jake Nations 推薦在這一步應該要做到讓一個 Junior Engineer 照著每一行去寫都能夠順利運作的程度。
Implementation
Yep,AI 在這裡實在是太優秀了,在本質性困難被人類消化和處理後,完整的脈絡和計劃會讓 AI 在一個可控的道路上前進,就像是小時候打 gameboy 的賽車遊戲,反正就按著加速就好,它撞到護欄車子也不會壞掉,有些甚至不會減速,反而是那些想要甩尾的人會慢一大拍。
AI 改變了寫程式的方式,但沒有改變軟體可能失敗的原因
在這個只要開口就能生成千萬行程式碼的時代,本質還是沒有改變,真正的挑戰一直都不是敲鍵盤輸入語法,而是要知道『該輸入什麼』。
遙想當年在五倍紅寶石學習程式語言時,高見龍 ( 龍哥 ) 就一直提倡一個觀念 你不知道你不知道的事情,現在這句話對我來說又更清晰了,AI 對我來說還是沒有解決本質性困難,當你不知道問題是什麼,系統的接縫是什麼的時候,AI 或許可以給出一個 workaround,但未來 AI 會一直針對這個 workaround 不斷地疊加複雜度和可能的錯誤,直到有一天你花費了一整天和 AI 去討論並理解一個最底層也可能是最基礎的錯誤時,才會意識到『知道』自己在做什麼還是最重要的事情。
未來是否還要使用 AI 已經不是一個問題了,你應該大量地使用,但你還是必須搞懂他為什麼要這樣使用?以及是否有更好的做法?取捨是什麼?最重要的是理解和不要放棄思考。
討論