本章圍繞誓言中涉及**正直(Integrity)**的數個承諾展開,涵蓋小循環、持續改善、以及維持高生產力三大主題。

Small Cycles(小循環)#

Promise 4. I will make frequent, small releases so that I do not impede the progress of others.

「做小發布」意味著每次發布只變動少量程式碼。系統可以很大,但增量變更必須小。

原始碼控制的歷史#

作者以原始碼控制系統的演進歷程,說明業界長期以來一直在縮短循環週期

Figure 13.1: Punch card

  • 1950-60 年代(打卡片時代):原始碼就是你手上的那疊卡片,控制系統就是你的抽屜。沒有人需要「控制」原始碼,因為你實際擁有
  • 1970 年代(磁帶時代):原始碼存在磁帶上,編輯時產生新帶。為了防止合併問題,你必須在完成編輯前持有主帶
  • 模組化時代:系統變大、程式設計師增多,霸佔主帶太久會拖慢所有人。於是開始將程式拆成模組,並使用圖釘公告板來標記誰在編輯哪個模組

Figure 13.2: A bulletin board

圖釘系統之所以有效,是因為團隊成員彼此認識、在同一辦公室工作、隨時溝通。Pin 只是提醒——大家實際上都知道誰在做什麼。

年份系統說明
1972SCCSMarc Rochkind 以 SNOBOL 撰寫,後改寫為 C。第一個原始碼控制程式,支援檔案層級的悲觀鎖定(Pessimistic Locking)
1982RCSRevision Control System,取代 SCCS 成為主流
1986CVSConcurrent Versions System,引入樂觀鎖定(Optimistic Locking),允許兩人同時編輯同一檔案,系統嘗試自動合併
2000Subversion大幅改善 CVS,推動業界徹底走向樂觀鎖定;也是第一個在雲端使用的版控系統

Git#

  • 2005 年:Git 消除了主帶的概念。每台機器都保存完整歷史紀錄,可以在本地端提交、建立分支、查看舊版本,無需連接中央伺服器
  • Git 是**點對點(Peer-to-Peer)**的架構,最終的權威版本只是另一個可以 push/pull 的節點
  • 結果:你可以自由地做極小的提交——每 30 秒提交一次,甚至每通過一個單元測試就提交一次
timeline
    title 版控系統演進
    1950-60s : 打卡片時代(手動管理)
    1970s : 磁帶時代(圖釘系統)
    1972 : SCCS(檔案級悲觀鎖定)
    1982 : RCS(改進的檔案級鎖定)
    1986 : CVS(樂觀鎖定,合併機制)
    2000 : Subversion(CVS 的改進)
    2005 : Git(分散式,消除主帶概念)

Short Cycles(縮短循環)#

回顧整個演進軌跡,可以發現版控系統的發展始終被一個底層的驅動力推動:縮短循環週期

時代簽出週期
打卡片時代整個專案期間
圖釘公告板整個專案期間
SCCS/RCS(悲觀鎖定)整個專案期間
CVS(樂觀鎖定)大幅縮短
Git + CI可以每小時甚至更頻繁

Continuous Integration(持續整合)#

到 2000 年左右,即使使用 Subversion,業界已開始教導每幾分鐘就提交一次的紀律:

  • 提交越頻繁,遇到合併的機率越低;即使需要合併,也是瑣碎的
  • 持續整合嚴重依賴可靠的單元測試套件——沒有好的測試,合併錯誤很容易破壞別人的程式碼
  • 持續整合與 TDD 密不可分

長循環阻礙團隊的進度。提交間隔越長,其他人等待你的機會就越大。這不只是關於正式發布——它關乎每一個循環:迭代、衝刺、編輯/編譯/測試循環,以及提交之間的間隔。

Branches vs. Toggles(分支 vs. 開關)#

作者曾是嚴格的「反分支派」,在 CVS/Subversion 時代拒絕讓團隊成員建立分支。轉用 Git 後,他理解到 Git 中的分支其實只是開發者在兩次 push 之間的提交紀錄,並非真正的長期分支。

處理半完成功能的兩種策略:

  • 分支策略:在新分支上開發功能,完成後合併回主線。如果分支存在數天或數週,可能面臨大規模合併
  • 開關策略(Toggles):在主線上開發,使用 Command Pattern、Decorator Pattern、Factory Pattern 等確保未完成功能不會在正式環境中執行。最常見的做法是——如果按鈕不在網頁上,使用者就無法執行該功能

多數情況下,新功能會在當前迭代內完成,或至少在下次正式發布之前完成,因此不需要任何開關。只有在功能尚未完成就需要發布到正式環境時,才需要開關。

flowchart TD
    Start["半完成功能"] --> Branch["分支策略"]
    Start --> Toggle["開關策略"]

    Branch --> B1["建立功能分支"]
    B1 --> B2["在分支上開發"]
    B2 --> B3["分支存在數天/數週"]
    B3 --> B4["大規模合併風險"]

    Toggle --> T1["在主線上開發"]
    T1 --> T2["用 Pattern 隱藏功能"]
    T2 --> T3["持續整合"]
    T3 --> T4["低風險"]

Continuous Deployment(持續部署)#

  • 作者希望你能每天甚至每天數次地發布到正式環境,在每次 push 時都能放心部署
  • 這依賴於自動化測試:程式設計師撰寫的單元測試覆蓋每一行程式碼,業務分析師和 QA 撰寫的驗收測試覆蓋每個預期行為
  • 判斷測試是否足夠的標準:測試通過時,你是否感到安心可以部署?如果不是,測試就不夠好
  • 最終目標是持續、安全、無儀式的部署——部署應該盡可能接近「無事發生」

Continuous Build(持續建置)#

  • 如果要持續部署,就必須能持續建置
  • 使用 Jenkins、Buildbot、Travis 等工具,在每次 push 時啟動建置
  • 建置失敗是紅色警報——它是緊急事件,所有人必須停下手邊工作立即處理

絕不允許建置持續失敗。如果你習慣了失敗,就會開始忽略它。如果你開始忽略失敗,就會被誘惑去關閉失敗的測試——「以後再修」。那些測試就變成了謊言

Relentless Improvement(持續改善)#

Promise 5. I will fearlessly and relentlessly improve my creations at every opportunity. I will never degrade them.

這個承諾源自童子軍創辦人 Robert Baden Powell 的遺訓:讓世界比你發現時更美好。作者由此衍生出童子軍規則:每次簽入程式碼時,都要讓它比簽出時更乾淨。

Test Coverage(測試覆蓋率)#

  • 使用覆蓋率工具(多數 IDE 內建)衡量程式碼被測試覆蓋的程度
  • 不要將覆蓋率變成管理指標,不要因覆蓋率太低而讓建置失敗——這會製造作弊的扭曲誘因(例如移除 assertion 就能提高覆蓋率數字,但測試變得無用)
  • 覆蓋率應作為開發者工具,幫助自己有意義地提升覆蓋率
  • 100% 覆蓋率始終是目標,但也是一個漸近目標——大多數系統永遠達不到,但不應因此停止追求

Mutation Testing(突變測試)#

  • 突變測試工具執行測試套件後,進入迴圈:以語意方式突變程式碼(如將 > 改為 <== 改為 !=、賦值改為 null),然後重新執行測試套件
  • 預期每個突變都會使測試失敗;未能使測試失敗的突變稱為存活突變(Surviving Mutations)
  • 執行時間可能很長(數小時),適合在週末或月底執行
  • 作者表示每次執行突變測試都會發現令人印象深刻的問題

Semantic Stability(語意穩定性)#

  • 測試覆蓋率和突變測試的目標是建立一個確保語意穩定性的測試套件
  • 語意穩定的測試套件在任何必需行為被破壞時都會失敗,消除了重構的恐懼
  • TDD 是良好的起點,但需要覆蓋率、突變測試和驗收測試來補充

Cleaning(清理)#

作者認為最有效的「善行」就是簡單的清理——以改善為目標的重構:

  • 改善命名、結構、組織——即使其他人可能不會注意到這些變化
  • 清理的過程讓你學習程式碼,提升對程式碼的熟悉度和舒適感
  • 清理也在檢驗程式碼的靈活性——如果一個小清理很困難,就發現了一個僵化的區域

軟體應該是柔軟的(Soft)。如何確認它是柔軟的?定期做小清理、小改善,感受這些變更有多容易或困難。

Creations(創造物)#

Promise 5 中使用的詞是「creations」——程式碼不是程式設計師唯一的創造物。設計、文件、排程、計畫都是應該持續改善的創造物。

Maintain High Productivity(維持高生產力)#

Promise 6. I will do all that I can to keep the productivity of myself and others as high as possible. I will do nothing that decreases that productivity.

作者認為過去七十年來軟體業學到的最重要教訓是:唯一快速的方法是做好。

保持生產力的直接方法有三個面向:

1. Viscosity(黏滯性)——保持開發環境的效率#

程式設計師往往過度聚焦於寫程式碼的速度,但寫程式碼只是整體流程的一小部分。需要加速的環節包括:

環節改善方式
Building(建置)在二十一世紀,建置不應超過一兩分鐘。把加速建置當成設計挑戰
Testing(測試)現代筆電每秒可執行約 100 億條指令。如果整個系統不到 100 億條指令,理論上應能在一秒內測試完畢。問題在於重複——login 只需測試一次,資料庫查詢只需執行一次。用 mock 排除周邊設備的緩慢影響
Debugging(除錯)如果你在實踐 TDD、撰寫驗收測試、使用覆蓋率工具和突變測試,除錯時間可以降低到微不足道
Deployment(部署)部署是一個程序——自動化它! 為部署腳本撰寫測試。每次部署應該只需一鍵完成

2. Managing Distractions(管理干擾)#

會議(Meetings)

  • 當會議變得無聊時,離開。等待對話中的間隙,禮貌地說你的意見不再需要
  • 拒絕大多數會議邀請。不要被「害怕錯過(FOMO)」所誘惑
  • 如果你是團隊領導或主管,你的主要職責之一就是保護團隊的生產力,讓他們遠離會議

音樂(Music)

  • 作者曾經邊聽音樂邊寫程式,但發現音樂分散注意力
  • 有一次回顧一年前的程式碼,發現註解中散布著當時在聽的歌詞
  • 作者的理論:程式設計與音樂都是透過序列、選擇、迭代來組織元素——它們可能使用大腦的相同部分

情緒(Mood)

  • 情緒壓力可以摧毀你寫程式碼的能力
  • 解決方法:行動。不要試圖用音樂或會議掩蓋情緒,而是採取行動解決情緒的根源
  • 你不需要真正解決問題——只需要說服自己已經採取了足夠的行動

心流(The Flow)

  • 許多程式設計師享受那種超集中、隧道視野的「心流」狀態
  • 作者發現在心流中產出的程式碼往往品質較差,不夠周全
  • 結對程式設計是避免心流的好方法——溝通和協作會干擾心流

3. Time Management(時間管理)——番茄工作法#

作者推薦 Pomodoro Technique(番茄工作法)

  • 設定計時器 25 分鐘(15-45 分鐘皆合理,但一旦選定就不要更改)
  • 工作直到計時器響起,然後休息 5 分鐘
  • 處理打斷的規則:保衛番茄! 告訴打斷你的人,你會在 25 分鐘內回覆他們
  • 在番茄之間的休息時間處理打斷事項
  • 一天結束時,計算完成的番茄數量作為生產力的衡量指標
  • 熟練後可以開始以番茄為單位規劃工作日估算任務
flowchart TD
    A["設定計時器 25 分鐘"] --> B["專注工作"]
    B --> C{"被打斷?"}
    C -->|否| B
    C -->|是| D["延後到休息時處理"]
    D --> B
    B --> E["計時器響"]
    E --> F["休息 5 分鐘"]
    F --> G["處理打斷事項"]
    G --> A