保持小而頻繁的 commit

一年半前我有個疑問, 究竟要寫成多個小 commit, 還是一個個完整子工作的大 commit。實際運作一陣子後, 我發覺答案很明顯, 小的 commit 才有用處。我現在甚至不明白為啥當時會為這問題困惑...

小的 commit 有以下的好處 (指令用 mercurial 表示):
  • 發覺某個功能掛掉時, 可以用二元搜尋法快速找到改爛的那版, 接著因為 commit 很小, 很快就能找出是那段 (甚至是那行) 程式造成問題。我已用這招在五分鐘內找出兩個數十、數百版前的錯誤。看到原因後發覺, 若不用版本管理系統, 我大概花很久都不見得能找到問題。
  • 當同伴有時間時, 他們可以容易地 code review, 每個修改都很簡單易懂。
  • 當同伴沒時間時, 他們可以容易地先用 log 濾出需要仔細看的 commit, 並只看一小部份和他相關的程式 (hgtk log 超方便的!)。
  • 寫程式難免會被中斷或忽然亂了方寸, 每做完一件小事就 commit, 確保自己一直都能掌握狀況。真的亂掉不知如何除錯時, 可以先將目前更新存起來 (hg shelve 或 hg revert), 再一個個加回去, 釐清目前狀況。這招數度解救我於混亂之中。
  • 隨時能放心地嘗試, 一看 diff 就知道目前做了那些事。commit 前看 diff 也能很快找出漏砍的除錯碼 (hgtk commit 是大家的好朋友!)。
另外, 用 DVCS (如 git、mercurial) 再附帶一些好處:
  • 不用擔心自己頻繁的小 commit 造成 build fail, 讓大家共用的 repository 毀了。做個一陣子確定告一個小段落且不會炸到別人, 再 push 這段時間做的一系列小 commits。當然, 每個 commit 本來就該是完整的工作, 且可以正常編譯和通過所有測試。但考量到效率, 小 commit 可以先用較粗略的測試 (如只跑 smoke test suite, 或目前修改部份的 unit test)。push 前再跑完整測試。
  • DAG 可以輕易看出大家開發的支線。但像 SVN 那樣一直線的記錄, 就無法看出各個人開發的脈絡。可能自己的 commit 變 11、14、16 成一個完整的工作, 中間卻搜了別人的 12、13、14。
  • 任何人都可以輕易地在自己的 repository 上做實驗以熟悉版本管理系統。只要打個 hg init, 立即擁有自己的世界!
不過 DVCS 也不是什麼都好, 像 DVCS 就沒有 lock。若成員眾多, 又會存圖片、音樂這種無法合併 (merge) 的檔案格式, 沒有 lock 就沒辦法避免兩個人在改同一份檔案。
要能完整地發揮小 commit 的功用, 需要一些好工具的協助。我直到有這樣的需求後, 才明白為什麼大家會需要這些指令:
  • hg shelve: 將目前 working directory 的東西存到暫存的 patch 裡。於是 working directory 就和 repository 內的資料一致。自己做到一部份, 和同事討論需要拿他的程式時, 就可以 hg shelve; hg fetch; hg unshelve。就算 unshelve 失敗, 看一下產生的 reject 檔 (也是 diff 格式), 很快就能將它塞回正確位置。頻繁地 fetch / shelve, 可以減少 merge conflict。另外自己也常做到一半發覺得先改另一個東西, 這時就先 hg shelve。改完另一個東西並 commit 後, 再 hg unshelve。若習慣重構再加功能的話, 這招超好用的。
  • hunk selection: 我是用 hgtk 做 hunk selection。一個 hunk 是指一個修改的檔案裡的一段 diff。hunk selection 就是只 commit 目前更新的部份結果。通常寫寫會手賤順手改一些和目前項目無關的小東西, 像是命之前漏改的小錯、加個共用的 helper function。改了都改了, 為了保持小的 commit 還要還原再分多次一一 commit 太累了。這時就能用 hunk selection 將同性質的部份修改 commit, 就能保有小的 commit 又不用改變原本開發流程。
另外為了方便追蹤 log, 以下幾件事務必切成獨立的 commit:
  • 搬函式、類別的位置
  • 修正程式縮排
  • 改名稱
這些修改一次會動到一堆程式。若又混到其它修改, 看的人會很難分辨那些是必須看的修改。千萬不要又縮排又改名字, 或是搬位置順便加幾行新功能。反之, 拆成獨立的 commit, 一看註解 "Refactoring: indent X.", 大概瞄一下覺得沒問題就不用細看了。
以上的討論漏了如何寫好的  commit log。目前我只知道要先寫一行摘要, 若有必要, 再空一行, 接著寫完整描述。這個第一行寫得好, 其他人就能快速地過跟上進度。還在摸索中, 現在覺得像 mercurial 那樣開頭加個 X: 或 (X) 表示和 X 元件相關挺不錯的。
最後附上《Coding Horror: Check In Early, Check In Often》, 這篇幫我解決一些疑惑, 強化一些觀念。

留言

這個網誌中的熱門文章

(C/C++ ) 如何在 Linux 上使用自行編譯的第三方函式庫

熟悉系統工具好處多多

virtualbox 使用 USB 裝置