克服對程式碼的恐懼#

上一章的結論是「程式碼是負債」,這很容易讓人變得害怕寫程式碼。再加上對「完美程式碼」的追求——效能、架構、抽象層次、可用性、可維護性、正確性、安全性——同時把這些全塞進腦袋裡是不可能的任務。

作者自述:在接受正式電腦科學教育後,了解了程式碼可以出錯的所有方式,反而讓他的生產力驟降。面對任務會先沉思數小時甚至數天才寫第一行程式碼。這種「coding stage fright」需要被辨識並主動對抗。

接受不確定性:進入危險地帶#

在恐懼中無法有效工作。軟體開發的核心是學習領域知識並將其編碼,而學習最有效的方式是實驗——這需要勇氣。Google 的大型研究發現,團隊生產力最大的預測因子是心理安全感(psychological safety)

即興劇場有個概念叫 enter the danger:我們天生會迴避不舒服的情境,但最好的作品往往來自正面迎擊這些情境。

如果某件事令人恐懼,就更常做——直到它不再可怕為止。作者的策略是:到新團隊的第一天就部署東西到正式環境,立刻消除恐懼與焦慮。

用 Spike 克服「做錯東西」的恐懼#

當「做錯東西」的恐懼大於「做得不好」的恐懼時,人們傾向在動手前無止盡地討論、設計、思考。這是危險信號。

建議的工作流程從 spike 開始:spike 產出的程式碼不一定會進入 main,所以不完美也無所謂——恐懼因此消散。

Figure 10.1: Recommended development workflow

Spike 帶來的是知識,讓我們能更有信心地做出第一個正式版本。但要維持這個優勢,必須嚴格遵守紀律:

  • 絕不讓 spike 程式碼進入正式環境
  • 向利害關係人傳達:產品是知識,不是程式碼
  • 可以將 spike 的成果整理成投影片或白皮書,方便展示與分享

用固定比例克服「浪費」或「風險」的恐懼#

另一個恐懼的症狀是:支援工具比正式程式碼還複雜。在寫第一行商業邏輯前,就花大量時間建置測試環境、分支策略、feature toggle 系統、前端框架、自動化部署。

這些工具本身沒有錯,但在沒有程式碼的時候,既沒有風險也沒有浪費——花時間在上面只是拖延

最糟的情況是維護和開發這些工具本身就佔據了所有時間,讓團隊無法交付真正重要的東西。

解法:採用 80:20 比例——80% 時間在正式程式碼,20% 在非功能性需求(含工具維護)。作者見過最成功的實作是每週五為非工單工作日,用於實驗、大型重構或自動化開發任務。一整天足以做有意義的事,也常成為日常工單工作的調劑。

擁抱漸進式改善,克服「不完美」的恐懼#

**冒名頂替症候群(Imposter Syndrome)**在軟體業很普遍:擔心自己不夠格,怕被人揭穿。為了自保,我們試圖讓程式碼完美——但對非瑣碎問題而言,完美幾乎不可能達成。

作者已不再相信完美程式碼的存在。效能、易用性、可擴展性、穩定性——每一項都需要時間,而且無法同時最佳化所有指標

最有用的單一最佳化目標是:開發者生命(developer life)——從接到任務到完成的時間越短越好。這最大化了練習次數,也最小化了獲得回饋的時間。快速的回饋循環是提升品質的關鍵。

這也是 Dan North 的 spike and stabilize 背後的哲學:先不管品質快速產出,加入監控,六週後看是否被使用。被使用才正式實作,沒被使用就刪除。

複製程式碼如何影響變更速度#

共享程式碼 vs. 複製程式碼有兩個重要的權衡:

策略優點缺點
共享增加全域行為變更速度(改一處影響所有使用處)增加脆弱性(fragility)——每個呼叫端可能有不同的局部不變量,改共享程式碼可能破壞其中任何一個
複製增加局部行為變更速度(改一處不影響其他)全域變更需要逐一更新

在 spike 期間,應盡量大膽複製——這是快速測試假設的捷徑。等程式碼穩定後,再回頭問:「這段程式碼應該跟來源耦合嗎?當這裡改變時,來源也應該改變嗎?」如果答案是否定的,就維持分離。

透過可擴展性做到「以新增取代修改」#

如果某段程式碼經常需要變化,可以透過可擴展性將變化點推到獨立的類別中。新增一個變體可能只需要新增一個類別。

但不應過早引入擴展點——這會增加不代表領域本質的附帶複雜度(accidental complexity)。全書遵循的三步流程是:

  1. 複製程式碼
  2. 修改並適配
  3. 如果合理,再統一與來源程式碼

這與 Expand-Contract pattern 的概念一致:先新增(expand)、再遷移(migrate)、最後刪除舊版(contract)。

本書中兩個主要的可擴展性模式:

  • Replace Type Code with Classes:將 if/switch 的靜態控制流轉為 interface 上的動態方法呼叫
  • Introduce Strategy Pattern:統一兩份相似程式碼,允許動態新增新策略

以新增取代修改實現向後相容#

對外公開介面或 API 時,我們有責任保護使用者不受意外副作用影響。版本化(versioning)是標準解法,但更進一步的目標是向後相容(backward compatibility)——永遠不改變既有端點的行為,只新增新端點。

微軟對向後相容有著驚人的承諾——Windows 95 的程式碼至今仍在 Windows 10 中運行。

具體做法:

  1. 複製要修改的端點
  2. 在新端點上實作變更
  3. 統一新舊端點的共享部分
  4. 棄用舊端點,在舊端點加入監控,使用量歸零後移除
flowchart TD
    A["複製要修改的端點"] --> B["在新端點上實作變更"]
    B --> C["統一新舊端點的共享部分"]
    C --> D["棄用舊端點\n加入監控"]
    D --> E{"使用量歸零?"}
    E -->|否| F["繼續監控"]
    F --> E
    E -->|是| G["移除舊端點"]

版本命名要一致且直觀——PHP 中 mysql_escape_stringmysql_real_escape_stringmysqli_real_escape_string 就是反面教材。

Feature Toggle#

將部署(deploy)與發布(release)分離的關鍵技術。最簡單的版本:

  1. 建立 FeatureToggle 類別
  2. 為新功能新增一個回傳 false 的 static method(feature flag
  3. 在修改處加入 if(FeatureToggle.featureA()) { } else { 原有程式碼 }
  4. 將原有程式碼複製到 if 區塊,在 if 區塊中做修改
  5. 準備測試時,讓 flag 讀取環境變數

這樣就能安全地持續整合與部署,由環境變數控制功能是否對使用者可見。

Feature flag 必須有到期日。 Knight Capital 在 2012 年因為一個閒置七年的 flag 被新版本重新啟用,在 45 分鐘內虧損超過 4 億美元。每個 flag 都應排定最遲六週內移除。

Feature toggle 成熟後,可以進一步發展為:

  • 資料庫驅動的 toggle + 管理 UI
  • 漸進式發布(先 10% 使用者,逐步擴大)
  • A/B 測試:將 toggle 與指標掛鉤,自動淘汰表現較差的版本

Branch by Abstraction#

當一個功能需要在多個位置做修改時,多個 if/else 會讓不變量散佈各處。此時應使用 branch by abstraction

  • 將 feature flag 從回傳 boolean 改為回傳物件(new Version2()new Version1()
  • 將兩個版本的邏輯封裝在各自的類別中,實作同一個 interface
  • 呼叫端不再需要 if/else,直接呼叫物件的方法

移除 feature toggle 時的步驟:

  1. 刪除不要的版本類別
  2. 刪除 interface(因為只剩一個實作,遵循 No interface with only one implementation 規則)
  3. 將剩餘類別的方法 inline 到呼叫端
  4. 刪除剩餘類別

最終結果:程式碼中完全沒有 feature toggle 的痕跡

flowchart LR
    A["if/else +\nboolean flag"] -->|"重構為"| B["interface +\n兩個版本 class"]
    B -->|"移除 toggle"| C["刪除不要的\n版本 class"]
    C --> D["刪除 interface"]
    D --> E["inline 剩餘\nclass 的方法"]
    E --> F["刪除剩餘 class\n完全無痕跡"]