How Do I Know That I’m Not Breaking Anything?#
程式碼是一種奇特的建築材料。金屬、木頭、塑膠等材料會因使用而疲勞損壞,但程式碼不同——如果你不去動它,它永遠不會壞。程式碼唯一出錯的方式,就是有人去編輯它。
這對開發者來說是一個沉重的責任。我們不僅是軟體中引入錯誤的主要代理人,而且引入錯誤也相當容易。任何人都可以打開文字編輯器,輸入最荒謬的內容。程式碼是非常脆弱的材料。
本章討論了幾種在編輯時降低風險的方式。有些是機械性的,有些是心理層面的,但在我們打破 legacy code 的依賴並建立測試時,關注這些技巧非常重要。
Hyperaware Editing#
當我們編輯程式碼時,我們到底在做什麼?我們通常有大目標——新增功能或修復 bug。但我們如何將這些目標轉化為行動?
坐在鍵盤前,我們敲下的每一個按鍵都可以分為兩類:改變軟體行為或不改變行為。在註解中輸入文字不會改變行為;在從未被呼叫的 string literal 中輸入文字,大多數時候也不會改變行為。但完成一個使用該 string literal 的 method call 的那個按鍵——那就改變了行為。
這就是程式設計的核心——確切知道我們的每一個按鍵在做什麼。這不是說我們必須全知全能,而是任何能幫助我們了解自己如何影響軟體的事物,都能幫助我們減少 bug。
Test-driven development 在這方面非常強大。當你能把程式碼放入 test harness 並在不到一秒內執行測試,你就能在需要時隨時執行測試,真正知道每次修改的效果。
測試促進了 hyperaware editing,Pair programming 也是如此。Hyperaware editing 聽起來會很累嗎?太多的任何事情都會令人疲憊。關鍵在於它並不令人沮喪。Hyperaware editing 是一種 flow state——你可以屏蔽外界,敏感地與程式碼互動。這其實可以非常令人振奮。
Single-Goal Editing#
“Programming is the art of doing one thing at a time.”
這句話是作者在工作時不斷重複的小咒語。
你是否有過這樣的經歷?你開始做一件事,然後想「嗯,我應該順便整理一下這段程式碼」,於是你停下來開始重構,接著又想著程式碼應該長什麼樣子,然後暫停去做那件事。你回到原來的位置,發現需要呼叫另一個方法,跳過去看那個方法,又發現那個方法需要修改——所有這些都在原始修改還未完成的情況下發生。
有些團隊就是這樣工作的——一段精彩的程式設計冒險,但最後四分之三的時間都在修復之前搞壞的程式碼。
更好的方式是:一次只做一件事。
當你需要修改一個方法時,你在 test harness 中有這個類別,開始修改。但突然想到「我需要改這邊的另一個方法」,這時你停下來,把那個方法的名字寫在紙上,回去完成當前的編輯。執行測試,確認全部通過。然後去看那個方法,寫測試,再做修改。
當你配對程式設計時,可以讓夥伴問你「你在做什麼?」如果你回答了不只一件事,就選一個。坦白說,一次一件事其實更快。如果你每次都試圖做太大一塊的修改,最終會陷入反覆修補的泥沼。
Preserve Signatures#
編輯程式碼時有很多犯錯的方式——拼錯、使用錯誤的資料型別、用一個變數名代替另一個。Refactoring 特別容易出錯,因為它涉及大量搬移程式碼、建立新的 class 和 method,規模遠大於新增一行程式碼。
通常處理方式是寫測試。但在許多系統中,我們需要先做一些 refactoring 才能讓系統可測試。這些初始的 refactoring(第 25 章的 dependency-breaking techniques)是設計為不需要測試就能安全執行的,因此必須特別保守。
Preserve Signatures 的核心概念:盡可能避免更改 method signatures。透過 cut/copy/paste 整個參數列表,可以將錯誤機會降到最低。
例如,要將一個方法的本體提取為 static method 時:
- 將整個參數列表複製到剪貼簿
- 輸入新方法的宣告
- 將參數列表貼到新方法的宣告中
- 輸入對新方法的呼叫
- 將參數列表貼到呼叫中
- 最後刪除型別,只留下參數名稱
當你反覆做這些動作,它們會變成自動化的。你可以更有信心地進行修改,並專注於其他可能導致錯誤的問題。
Lean on the Compiler#
Compiler 的主要目的是將原始碼轉譯為其他形式,但在靜態型別語言中,你可以利用它做更多事情。你可以利用型別檢查來識別需要修改的地方。作者稱這種做法為 leaning on the compiler。
Lean on the Compiler 包含兩個步驟:
- 修改宣告以引發編譯錯誤
- 導航到這些錯誤並進行修改
例如,在 C++ 程式中有幾個全域變數,你想用 Encapsulate Global References 技巧把它們包進一個 class 中。你可以在宣告周圍寫一個 class,然後編譯——所有找不到原始變數名的地方都會報錯,引導你做出必要的修改。
Lean on the Compiler 有其限制。 最容易出問題的語言特性是繼承。例如,如果你註解掉一個
getX()method,但 superclass 中有同名的 concrete method,編譯不會報錯——它會直接使用 superclass 的版本。類似的情況也可能發生在變數遮蔽上。
Lean on the compiler 並不總是實用的。如果你的 build 時間很長,直接搜尋可能更有效率。但當你能使用它時,它是一個很有用的做法——只是要小心,不要被虛假的信心所蒙蔽。
Pair Programming#
你很可能已經聽過 Pair Programming。如果你使用 Extreme Programming (XP) 作為流程,你可能已經在實踐它了。它是一種卓越的提升品質和團隊知識分享的方式。
作者特別建議在使用本書描述的 dependency-breaking techniques 時進行 pair programming。犯錯很容易,而且你可能完全不知道自己已經破壞了軟體。第二雙眼睛絕對有幫助。
面對現實吧,在 legacy code 中工作就像是手術,而醫生從來不會獨自動刀。