iT邦幫忙

2026 iThome 鐵人賽

DAY 28
0
Software Development

AI時代下的軟體工程系列 第 28 篇

Day28: 階段三回顧:沒有標準答案,只有取捨

  • 分享至 

  • xImage
  •  

Day23 到 Day27,我們完成了系列的第三個階段。和前兩次一樣,先回頭看看這個階段想解決什麼、做到了哪裡。

階段三想解決什麼:Choose

階段二談的是如何建立 boundary,並用工具檢查它有沒有被遵守。但 boundary 即使被遵守,位置仍然可能是錯的,而且沒有任何工具會提醒我們。所以階段三問的是:boundary 該放在哪裡?又值得為未來的 change,先花多少成本?

這個問題困難的地方在於,未來的需求沒有人知道。boundary 太少,change 發生時要改很多地方;boundary 太多,平常就得為還沒發生的 change 多維護許多東西。

這五天,談的是五個需要做選擇的地方:

主題 要選擇的是 好處 代價
Cohesion(Day23) 什麼該放在一起 同一個理由的 change,只需要改一個地方 切得越細,一個 change 需要修改的 boundary 越多
Information hiding(Day24) 什麼該留在 boundary 裡 decision 改變時,client 不必跟著改 client 能使用的東西變少
DRY(Day25) 什麼該合成一份 同一份 knowledge,只需要改一次 所有 client 共用同一份,之後必須一起改
OCP 與 YAGNI(Day26) 什麼時候該開擴充點 新增一種做法時,不必修改舊的 code 多一層 abstraction,要多讀、多維護
Dependency 的形狀(Day27) 兩個 boundary 之間的 dependency 該是什麼形狀 另一邊的 change 不會影響到這一邊 多一層,更難從 code 追到實際執行的地方

前三天談的是已經在 repo 裡的東西:什麼放在一起、什麼留在 boundary 裡、什麼合成一份。第四天談還沒出現的東西:該不該在第二種做法出現之前,就先開好擴充點。最後一天談兩個 boundary 之間的 dependency:它無法消除,能決定的只有它的形狀。

從表上可以看到,每一種設計都有代價。沒有一種結構能讓所有的 change 都變容易;而結構本身也有成本,不管預期的 change 有沒有發生,都需要有人閱讀與維護。

原則是 heuristic

這五天都刻意停在比較高的層次:遇到某一種情境時,有哪些做法可以選、各自會犧牲什麼,並且盡量從根本的角度解釋為什麼。沒有任何一篇是「遇到 A,就做 B」這樣的規則。

原因是每個專案都有它的獨特性。要解決的問題、要支援的東西、需求的來源與變動的頻率,都不盡相同。同一層 abstraction,在一個專案裡經常用得到,在另一個專案裡卻只是多一層要讀的 code。直接套用別人的做法,常常只增加了成本,卻得不到好處。

這幾天出現過不少有名字的原則:SRP、DRY、OCP、YAGNI、DIP,其中三個出自 SOLID。它們都是 heuristic:從過去的經驗整理出來、在某些情況下通常有效的做法,並非任何時候都必須遵守的規定。

原則之間也會互相衝突:

  • 要把獨立的 change 分開,又不能切得太細。
  • 要把同一份 knowledge 合成一份,又不能把碰巧一樣的東西合在一起。
  • 要讓新的做法容易加入,又不能為了猜想中的需求先做。

如果把原則當成規定,兩個原則衝突時就沒有辦法決定。把它們當成 heuristic,衝突時要做的就是依照專案的狀況取捨。

取捨時,有三個問題在任何專案都用得上:

  • 這樣設計,哪一種 change 會變得容易?
  • 需要多花什麼成本?
  • 有什麼根據認為這種 change 會發生?已經發生過的 change,比已知的需求來源可靠;已知的需求來源,又比想像中的 change 可靠。

因此,只說「這違反了某某原則」並不夠,還要能說明:哪一種 change 會因此變得難改,以及為什麼認為它會發生。

這種判斷沒有公式,需要看過夠多的 change 才會有感覺。這五天整理的,只是比較不容易出錯的起點。

Refactoring 的成本變低了

既然是取捨,就會有選錯的時候。選錯之後要做的事是 refactoring(重構):程式的行為不變,只改變它的結構。

在過去,refactoring 是成本很高的行為。工程師大多知道 codebase 越乾淨越好:可讀性高,bug 也比較少。但 refactoring 需要時間,帶來的好處卻很難衡量,畢竟做完之後,程式的行為和原本一樣,沒有多出任何 feature。對只關注結果的人來說,程式能跑就好,這些時間不如拿去開發新功能。因此結構上的問題,常常是大家都知道,卻一直沒有人去改。

有了 coding agent 之後,情況不一樣了。改名、搬移 code、拆分 module、更新每一個呼叫的地方,這些過去需要人一步一步動手的工作,現在大多可以交給 agent。前兩個階段的工具在這裡也有幫助:test 與 type checker 能確認行為沒有改變,boundary 則讓一次修改不會影響太多地方。

這帶來兩個改變。

一、不必一開始就選對。 過去修改結構的成本很高,所以會希望第一天就把設計做完整,連還沒出現的需求也先準備好。現在可以先選最簡單的做法,等根據足夠了再調整。

二、更難得的,是知道怎麼使用 coding agent。 動手修改變容易了,但要往哪個方向改、改成什麼樣子,agent 並不知道。它可以把 code 改成任何樣子,也可能越改越亂,所以需要有人告訴它方向。

要注意的是,成本變低的只有 repo 裡的 code。已經儲存的資料、已經公開給別人使用的 interface,修改起來仍然不容易。

人的角色:腦中對 repo 的想像

階段三提供了一些想法,幫助我們設計好的 boundary。它帶來的好處可以這樣串起來:

好的 boundary → module 之間的耦合降低 → codebase 更好讀、更乾淨
             → agent 讀到的 context 更好 → 產出的品質更好

對 coding agent 而言,repo 就是它的 context。boundary 設計得好,它完成一次 change 需要讀的東西就比較少,讀到的也大多和任務有關。

此外,agent 傾向延續 repo 裡既有的寫法。結構好,它新增的 code 也會維持好的結構;結構亂,它也會跟著亂下去。所以這是一個循環:結構越好,agent 的產出越好,之後要調整也越容易。

要讓這個循環往好的方向發展,需要兩種 agent 沒有的資訊:

  • 現況的全貌:repo 裡有哪些 boundary、各自負責什麼、彼此如何連接。agent 每次只會讀到 repo 的一部分;人不必讀懂每一行,但要說得出全貌。
  • 未來的方向:需求從哪裡來、哪些已經確定、哪些只是猜測、結構接下來應該怎麼調整。這些資訊都不在 repo 裡。

兩者合起來,就是人腦中對 repo 的想像:它現在是什麼樣子,以及它應該變成什麼樣子。

這也是階段三無法交給工具的原因。工具能檢查一條 boundary 有沒有被遵守,卻無法判斷它值不值得存在,因為這需要的正是上面兩種資訊。

因此,階段三中人的角色是:運用腦中對 repo 的想像,引導 coding agent 選出最適合這個專案的設計。

  • 交代任務時:除了要做什麼,也說明它應該放在哪裡、哪些地方是刻意不改的。
  • 檢查結果時:除了確認結果正確,也確認 repo 有沒有更接近我們想像中的樣子。

想像越清楚,來回修正的次數就越少,迭代越快,也越容易達成目標。反過來說,如果腦中沒有這樣的想像,就只能接受 agent 交出來的結果。

三個階段,放在一起看

階段一:Control 階段二:Contain 階段三:Choose
無法消除的限制 程式可能做錯 系統大到無法一次理解 未來的需求無法預測
我們做的事 用可以執行的檢查確認對錯 把一次 change 的範圍限制在 boundary 內 決定 change 的成本要花在哪裡
由誰判斷 工具 工具 + 人 人

階段一與階段二,最後大多能交給工具。階段三則需要由人來判斷:coding agent 讓寫 code 與改 code 的成本都變低了,但方向仍然要有人決定。

每一次 change 都有成本,而設計,就是決定這些成本要花在哪裡。


上一篇
Day27: Dependency topology:dependency 可以怎麼設計?
下一篇
Day29: AGENTS.md:什麼值得預先告訴 agent?
系列文
AI時代下的軟體工程 共 30 篇
圖片
  熱門推薦
圖片
{{ item.channelVendor }} | {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言