← 所有文章

理解程式碼,是為了更好地生成程式碼

做 FiboCode 的過程中,我越來越在意自己能否看懂專案的架構和功能流程。這篇文章記錄了我對自然語言程式設計、模糊需求,以及如何長期用 AI 修改程式碼的一些思考。

我不喜歡看不懂自己專案的感覺。

需求是我提的,功能也是我想要的,AI 生成以後,我也確認過它能正常運行。但如果有人問,這個功能經過了哪些模組,為什麼要這樣實作,再改一個地方會影響什麼,我卻未必能回答清楚。

做 FiboCode 的過程中,我越來越在意這件事。

我當然希望 AI 多幫我寫一些程式碼。可專案是要一直做下去的,今天加完功能,明天還會有新的想法。我不希望每次修改,都得先祈禱它別把其他地方弄壞。

修好了 A,B 又出問題。再去修 B,A 又出現了新的 Bug。碰到這種情況,我們很容易繼續把現象發給 AI,等它給出下一份解釋和修復方案。

但這樣反復幾輪,心裡其實會越來越沒底。每一次修改看上去都有道理,合在一起,系統到底變成了什麼樣子?

我想弄清楚這個問題。也是沿著這個問題,我開始覺得,理解程式碼和生成程式碼之間的關係,比我最初想的要緊密。

自然語言程式設計,讓我想到了一件事

從機器語言到匯編,再到高級語言,我們一直在做抽象。

很多過去需要自己處理的細節,後來都交給了工具。我們可以用更少的程式碼表達更複雜的功能,也不必每次都從底層重新開始。

現在又向上走了一步。我們用自然語言描述需求,讓 AI 去生成程式碼。

乍看起來,好像只是換了一種表達方式。但我覺得,這中間有個變化不能忽略。

匯編器、編譯器按照明確的規則轉換程式碼。程序當然可能有 Bug,但轉換的過程不需要猜測我究竟想幹什麼。

模型需要猜。

它根據我說的話、看到的程式碼和上下文,推斷我想要什麼,再選擇一種實作。同一句話,可能得到不同的結果;即使生成結果相同,也可能一直在實作一個我沒有表達清楚的需求。

後來讀到 Birgitta Böckeler 的一篇文章,她也談到了這個區別:大語言模型依靠推斷生成結果,不能直接當成自然語言的編譯器。這和我在想的問題很接近。

那我們能做什麼?

把需求寫清楚,把規範留下來,讓測試檢查關鍵行為,應該都能減少一部分偏差。如果設計里已經說清了模組之間怎麼協作,涉及哪些類和方法,AI 實作時也會多一些依據。

不過,文件得和程式碼對得上。寫得很詳細,卻描述著一個早就改掉的設計,可能比沒有文件還麻煩。

我也不覺得這些辦法能徹底消除生成過程的不確定性。但至少可以少留一些讓模型臨時猜測的東西。

可我真的知道自己想改什麼嗎?

這件事想下去,又會碰到另一個問題:人也很難把需求說清楚。

尤其是在不瞭解程式碼的時候。

我可能很清楚介面上想多一個按鈕,或者某個操作應該方便一點。至於它應該放進現有的哪個模組,會經過哪些處理,又有什麼舊行為需要保留,我未必知道。

這時候,就算我反復告訴 AI“別影響其他功能”,它又該怎樣理解這句話?

比如,“把這裡統一一下”,可能只想統一展示,也可能是要合併數據處理邏輯。這兩種理解,最後涉及的修改範圍會差很多。

如果連我自己都說不清範圍,模型就只能根據它看到的內容補全。它可能做得比我預想的多,也可能繞過了某個我根本不知道的約定。

改完以後發現問題,再讓它修。修改越積越多,最初的要求、中途加上的限制和程式碼里原有的聯繫,未必都還能完整地留在上下文里。

我擔心,最後會堆出這樣一份程式碼:功能局部能用,到處都是為瞭解決上一個問題留下的補丁。看上去只有 AI 願意繼續改,人已經不太敢碰了。

這種程式碼讓我想到 AI slop。更麻煩的是,就算繼續交給 AI,它也未必真正掌握了整個系統。它還能寫出解釋,卻不代表我們終於理解了它。

所以我越來越覺得,想把需求說得準確,還是得先知道專案大致是什麼樣子。

不必一開始就懂所有細節,但至少要知道,自己提出的變化會落在哪裡。

我希望在架構這一層和 AI 對話

我並不主張把 AI 生成的程式碼一行行全部審查一遍。

專案複雜以後,人的精力很有限。我更希望先看清框架:有哪些核心模組,各自負責什麼,一個功能從哪裡進入,經過哪些處理,最後在哪裡改變數據和狀態。

也就是在高級語言的實作之上,再整理出一層對系統的認識。暫且叫它“架構層”吧,我還沒有特別在意這個名字。

關鍵是,這一層要能和實際程式碼對應起來。

我看到一個模組,能找到它的實作;看到一條功能流程,能知道它經過哪些類和方法;想調整一個行為,能順著關係找到需要關心的位置。

這樣,我跟 AI 討論修改時,就有了更具體的話可以說。

比如我知道某個操作本來就經過統一的工作佇列,那新增能力時,就可以討論它能否沿用這條流程。看到 AI 又寫了一套狀態管理,也可以追問:原來的為什麼不能用?這兩套狀態以後會不會互相影響?

如果這些問題始終沒想過,拿到一份能運行的程式碼,我可能就接受了。現在至少有機會提前看出,它是不是把事情做複雜了。

我覺得,程式碼水平也會在這種比較中提高。

一個功能有幾種做法,為什麼當前專案選擇了這一種?某個抽象看起來很漂亮,實際修改時卻要同時碰很多地方,它真的有幫助嗎?另一個模組寫得很樸素,幾次需求變化都沒有牽動其他部分,它又做對了什麼?

這些問題值得花時間琢磨。慢慢有了自己的判斷,就更容易告訴 AI 哪些地方應該調整,也能理解它給出的另一種方案。

當然,我理解的架構也可能是錯的。圖里連著的關係,需要回到程式碼里核對;我以為不會影響的功能,也需要通過測試和實際運行來檢查。遇到關鍵狀態、複雜邊界,再往具體實作里看。

我希望自己能逐漸做到:大部分時候知道該從哪裡看,也知道什麼地方還沒弄懂。

這也是我做 FiboCode 程式碼分析功能的原因。

我想讓程式碼、文件和關係圖聯繫起來,幫助人從陌生的實作里看清框架,沿著一個功能找到它的流程。有了這層認識,再去和 AI 討論怎麼改,心裡會踏實一些。

Unmesh Joshi 在《What Is Code?》里也談到,好的抽象、類型、測試和明確的規則,可以成為模型理解和修改程式碼的依據。我很認同這個方向:我們對系統的理解,應該盡量留在專案里,讓下一次修改還能用得上。

有些時間,我還是願意花在讀程式碼上

AI 寫得很快,會讓人忍不住把下一個任務也交給它。

功能一個接一個完成,專案看起來一直在前進。但我也會在意,做完這些以後,自己到底多懂了什麼。

如果只是不斷提出需求、接收結果,等到專案複雜得無法把控,再花很多時間修補,甚至推倒重來,那前面省下的時間,可能又花回去了。還有模型呼叫的費用,以及重新熟悉一套實作的精力。

我更願意拿其中一部分時間,靜下來看看程式碼。

不一定看很多。可以先弄懂一個模組為什麼這樣拆,一個功能為什麼要經過這些步驟,或者 AI 這次用了什麼我還不熟悉的辦法。看不明白就問,再去程式碼里找依據,試著改一點,看看自己的理解對不對。

我找資料時,看到 Anthropic 做過一個學習實驗。參與者借助 AI 完成陌生程式設計任務後,短期測驗的平均成績低於手寫組。這個小樣本實驗不能回答長期能力會怎樣變化,但它讓我更在意,任務完成以後,我是否真的學會了其中的東西。

我希望這些理解能一點點積累起來。以後遇到類似的設計,自己能看出門道;AI 給出方案時,也能提出有依據的問題。

FiboCode 能不能把這件事做好,還需要繼續摸索。但我想做程式碼分析的原因,確實很大一部分就在這裡。

我希望繼續用 AI 寫程式碼,也希望專案越做越大以後,自己還能說清它是怎麼工作的。


文中提到的思路,簡單畫出來是這樣:

另外一些相關資料