核心概念#
還記得老式黑白戰爭電影中穿越地雷區的士兵嗎?他用刺刀戳地面,沒有爆炸就繼續前進——最後被炸成碎片。他的初始探測沒有發現地雷,但那只是幸運,他因此得出錯誤的結論。
作為開發者,我們也在地雷區工作。每天有數百個陷阱等著我們。記住士兵的故事,我們應該避免靠巧合寫程式——不依賴運氣和偶然的成功——而是要刻意地編程(programming deliberately)。
Tip 62 - Don’t Program by Coincidence(不要靠巧合寫程式)
如何靠巧合寫程式#
Fred 收到一個程式任務,輸入一些程式碼,試了試,似乎能用。又加了一些程式碼,也似乎能用。幾週後程式突然壞了,花了數小時也修不好。Fred 不知道為什麼程式壞了,因為他從一開始就不知道它為什麼能用。
實作的意外#
實作的意外(Accidents of Implementation)是因為程式碼目前恰好以這種方式運作而產生的巧合。你依賴了未記錄的錯誤行為或邊界條件:
- 你用錯誤的資料呼叫某個函式,函式以某種方式回應,你基於此回應來編碼——但作者並未打算那樣運作
- 呼叫順序錯誤或在錯誤的上下文中呼叫,但似乎能用
- Fred 不願回頭清除多餘的呼叫:「它現在能用了,還是別動好了…」
這些「能用」的理由為什麼不可靠:
- 它可能並非真的能用——只是看起來像
- 你依賴的邊界條件可能只是意外,不同環境下可能不同
- 未記錄的行為可能在下一版函式庫中改變
- 多餘的呼叫讓程式碼變慢,也增加引入新 bug 的風險
差不多正確不等於正確#
作者曾在一個大型專案中,各硬體資料收集單元使用當地時間。由於時區和日光節約時間的衝突,結果幾乎總是差一(off by one)。開發者養成了加一或減一來取得正確答案的習慣。最終整個大型程式庫充斥著 +1 和 -1 的語句,全都不正確,專案最後被廢棄。
幻想的模式#
人類天生傾向在巧合中看到模式和因果關係。日誌每 1,000 個請求出現一次錯誤——可能是競爭條件、可能是普通 bug、也可能只是巧合。不要假設,要證明它。
上下文的意外#
你也可能遇到「上下文的意外」。你正在寫工具模組,只因為目前是在 GUI 環境中開發,模組就必須依賴 GUI 嗎?你是否依賴英語使用者?是否依賴網路速度?當你從網路上複製找到的第一個答案,你確定上下文相同嗎?還是你在建造「貨物崇拜」(cargo cult)程式碼?
如何刻意地編程#
| 原則 | 說明 |
|---|---|
| 永遠清楚你在做什麼 | Fred 讓事情慢慢失控,最後像煮青蛙一樣被煮熟 |
| 能向初級程式設計師詳細解釋程式碼嗎? | 如果不能,也許你在依賴巧合 |
| 不要在黑暗中編碼 | 如果你不確定為什麼它能用,你也不會知道為什麼它會壞 |
| 按照計畫行事 | 不管計畫在你腦中、在雞尾酒餐巾紙上還是白板上 |
| 只依賴可靠的東西 | 不要依賴假設,如果無法判斷,假設最壞情況 |
| 記錄你的假設 | 用 Design by Contract(Topic 23)幫助釐清假設 |
| 不只測試程式碼,也測試你的假設 | 用斷言(Topic 25)去驗證 |
| 把時間花在重要的地方 | 基礎和架構比華麗的功能更重要 |
| 不要當歷史的奴隸 | 所有程式碼都可以替換,準備好重構(Topic 40) |
相關章節#
- Topic 4,石頭湯與煮青蛙
- Topic 9,DRY——邪惡的重複
- Topic 23,契約式設計
- Topic 34,共享狀態是不正確的狀態
- Topic 43,待在安全的地方