改既有的程式跟寫新的程式是兩種活。寫新的時候形狀由動手的人決定;改既有的時候形狀已經定了,動它的前提是不弄壞任何人依賴的行為。這件事的難度不由程式碼的複雜度決定,由有沒有辦法知道自己弄壞了什麼決定——同一段程式,有測試時的改法跟沒測試時的改法完全不同。

三本書按這條線分:有測試時怎麼做(Fowler 的《Refactoring》)、沒測試時怎麼先弄出測試(Feathers 的《Working Effectively with Legacy Code》)、以及改動小到不值得開專案時怎麼決定要不要現在做(Beck 的《Tidy First?》)。

起點是 Refactoring

Martin Fowler 的《Refactoring》把這個主題編成一份目錄,第二版收了六十幾項具名重構,每一項都有動機、機制與範例——編成目錄這件事本身就是它涵蓋面最完整的原因,因為漏掉的手法在目錄裡是看得出來的。它同時定義了這個詞:重構是改變程式碼的內部結構而不改變其外部行為——這個定義比日常用法嚴格得多,而多數把「重構」用在大改寫上的溝通混亂都來自忽略後半句。

IDE 自動化之後這本書的價值在哪,是讀它之前最該想清楚的事。 目錄裡多數手法現在是一個快捷鍵:Extract Function、Rename、Move Method 都由工具代勞,而書裡那些「先做這步、再做這步、每步跑測試」的手動安全程序,實務上不會有人照著做。看到這裡就把整本書跳過是常見的誤判。

被自動化的是機械步驟,沒有被自動化的是判斷:這段程式碼該不該提取、提取到哪一層、什麼訊號代表現在該動它。目錄的用途因此從「怎麼安全地手動做」轉成一套共同詞彙——說得出「這裡要做的是 Replace Conditional with Polymorphism」的時候,審查討論的單位就從「我覺得這樣比較好」變成一個有明確前後狀態的動作。書中的程式碼異味清單(Code Smell)是這套詞彙的另一半,它命名的是動手的理由。

版本要選:第二版(2018)把範例從 Java 換成 JavaScript,並補了不用 class 的函式式範例。這對讀者是真的取捨——寫 Java 的人第一版的範例反而貼近,而第二版的目錄本身有更新。時效上,被工具取代的是機械步驟而不是判斷——目錄裡多數手法現在是 IDE 的一個快捷鍵,那一層已經過時;命名這些動作的詞彙、以及判斷「現在該不該動它」的異味清單不依賴任何工具世代。證據來源是跨客戶的顧問經驗(作者與貢獻者群的實務歸納),形式是目錄而非論證。

這本要有一段程式碼在手上才讀得動——自己想改、但不確定怎麼下手的那種。沒有那段程式碼在手上,翻目錄的動作會停在「原來這叫 Extract Function」,而目錄真正的用法是反過來——手上有一個改不動的地方,去查它叫什麼。

繁體中文版《重構(第二版):改善既有程式的設計》,碁峰出版、賴屹民譯。

手上沒有測試時讀 Working Effectively with Legacy Code

Michael Feathers 這本補的是前一本假設已經存在、而現實中經常沒有的東西。它對遺留程式碼的定義是:沒有測試的程式碼就是遺留程式碼,不論它上週才寫好。這個定義把問題從「這段程式碼有多老」換成「我改它的時候拿什麼確認自己沒弄壞」,而後者才是可以動手處理的。

於是全書的主軸變成一個先有雞還是先有蛋的問題:要安全地改,得先有測試;要寫測試,得先把依賴切開;而切開依賴本身就是在改程式碼。Feathers 的解法是接縫(seam)——程式裡那些可以在不編輯該處的前提下改變行為的位置,找到它就有了掛測試的著力點。書末的二十四項解依賴技術是這套方法的目錄。

它跟 Fowler 那本的關係是入場與正式作業:Feathers 負責把程式碼推到「已經進了測試控制」這個門檻,門檻之後 Fowler 的目錄才用得上。兩本沒有重疊。

時效上,書中的範例語言(C++、Java、C)與那個年代的工具鏈已經隔了一段距離,測試框架的部分也是;接縫這個概念與二十四項技術處理的是靜態依賴怎麼被切開,那件事不依賴語言世代。證據來源是跨客戶的顧問經驗,來自作者在多個遺留系統上的現場工作。

處境相容性上,二十四項技術預設那份程式碼是動得了的——編譯、執行、加測試、改建置設定都做得到。系統只能透過廠商窗口改、或本機建不起來時,其中一半用不上,那種處境要先取得建置環境才談得上入場技術。

這本要接手過一個沒有測試、而又被要求改動的系統才讀得出東西——沒有那個處境時,接縫會讀成一個過度講究的技巧。可以先知道它存在,接手那樣的系統時再拿出來。

繁體中文版《Working Effectively with Legacy Code 中文版:管理、修改、重構遺留程式碼的藝術》,博碩出版。

要決定「現在整理還是之後再說」時讀 Tidy First?

Kent Beck 的《Tidy First?》(2023)處理的是另外兩本沒有處理的那個決定:手上要改一個功能,而周圍的程式碼有點亂——先整理再改、改完再整理、還是不整理?這本書整本只回答這一個問題,很快就能讀完。它也是本篇門檻最低的一本,跟起點書不是同一本:起點取 Fowler 是因為涵蓋面,而 Fowler 那本要手上正有一段想改的程式碼才讀得出東西。要挑給一群程度不一的人共讀時取這本。

它的做法是把整理當成一筆投資來算。整理現在要付成本、之後改動變便宜,而「之後」有多遠、會不會真的再改到這裡,決定了這筆投資划不划算。Beck 用耦合與內聚解釋成本從哪來,再用選擇權的概念解釋為什麼在不確定性高的時候保留彈性本身有價值。這是這個主題裡唯一把「要不要現在做」寫成可以推導的決定、而不是憑手感的一本。

它跟另外兩本的分工是規模:Fowler 與 Feathers 處理的是動作本身怎麼做,這本處理的是動作要不要現在做、做多少。書中的整理項目刻意都很小(一次幾分鐘到一小時),因為它主張的正是小步——大改動需要批准、需要協調、會跟別人的工作衝突,而小到不需要開專案的改動可以隨時做。

證據來源是單一路徑的個人經驗,加上他自己標明的經驗主義立場——書名的問號與副標的 personal exercise 都在說這是他的實踐而非通則,這個自我標定在技藝書裡少見。

處境上,先整理再改這個取捨預設 commit 的節奏由動手的人自己控制。每個 commit 要對應一張工單、或整理型改動需要單獨送審的流程裡,整理的成本項要重算——書中算的是認知與風險成本,那些流程另外加上一筆固定的行政成本。時效上是這個主題最新的一本,尚無過時的部分。這本書幾乎不需要前置準備,但它預設讀者已經知道重構是什麼——完全沒有那個概念時,先讀 Fowler 的前幾章再回來。繁體中文版《先整理一下?|個人層面的軟體設計考量》,歐萊禮出版。

為什麼只收這幾本

手上有沒有測試,決定了哪一類技術用得上,所以這個主題的入口不只一個:有測試的走 Fowler 的目錄,沒有測試的走 Feathers 的入場技術,改動小到不值得開專案的走 Beck 的取捨算法。三本之間沒有重疊。

重構主題的書市有一大類是特定語言的重構手冊,它們把 Fowler 的目錄翻譯成某個語言的慣用寫法。那類屬於教學系列而非書單的責任範圍,而且它們的時效跟語言版本綁在一起。看目錄裡的手法名稱就分得出來——手法名稱本身是某個語言的語法特徵時,那本書會隨那個語法的演進失效。

Joshua Kerievsky 的《Refactoring to Patterns》把重構手法接到設計模式上,本書單未評估它。它的前提是讀者已經熟悉 GoF 設計模式,而那套模式在近年的適用性本身有討論——判斷這本書現在的價值要先處理那個前提,這裡沒有做。

讀不動長篇文字的讀者在別的主題可以改走公開課,這個主題走不了。課堂教材要可控、可評分、每屆重複得了,而本篇三本書處理的已經存在而且沒人想動的程式碼正好是這三項的反面——它的價值來自真實系統累積出來的歷史,那種材料課程取不到。所以這裡查不到課,而這是對缺席最合理的解釋、不是查證結果。整條線的供給狀況寫在 工程技藝書單 的公開課段。

這個主題接到哪裡

改動的技術要用在對的地方,靠的是設計判準——什麼樣的結構值得改成、什麼樣的複雜度是根本問題。那條路徑走 設計判準與日常實踐

要動的程式碼沒有測試而需要先補,測試該怎麼分層與設計看 Testing 測試策略;大規模重構怎麼分階段推進、途中的常見失誤與作用域回歸風險,看 Python 維護指南的重構章,那裡有一個跨版本重構的完整實作案例。

如果重構一直被排不進時程,那不是技藝問題——走 估算、承諾與決策偏誤組織結構與團隊設計