核心概念#

還記得老式黑白戰爭電影中穿越地雷區的士兵嗎?他用刺刀戳地面,沒有爆炸就繼續前進——最後被炸成碎片。他的初始探測沒有發現地雷,但那只是幸運,他因此得出錯誤的結論。

作為開發者,我們也在地雷區工作。每天有數百個陷阱等著我們。記住士兵的故事,我們應該避免靠巧合寫程式——不依賴運氣和偶然的成功——而是要刻意地編程(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,待在安全的地方