天天看點

做一個有品位的程式員

參見百湖教育訓練之前,華為的一個小夥伴發現了Git實作的一個 Bug,給我發了一個 Pull Request,讓我稽核以及代發到 Git 社群。不用看代碼,隻看 Pull Request 的說明,我相信大家就可以聞到這是一個好代碼,寫代碼的人有品味。

參見:

https://github.com/jiangxin/git/pull/25

—— 問:“能夠寫出正确代碼的程式員就是有品味的程式員麼?”

—— 答:“還不夠。品味來自于每一個細節,有品位的程式員會把每一次送出做小、做對、做好,讓人看懂,且無可挑剔,這樣才夠逼格,才可以稱為有品位。”

熟練使用 Git,會讓程式員更有品味。

送出做小

寫小送出就是将問題解耦:“Do one thing and do it well”。開源項目的送出通常都很小,每個送出隻修改一個到幾個檔案,每次隻修改幾行到幾十行。

找一個你熟悉的開源項目,在倉庫中執行下面的指令,可以很容易地統計出來每個送出的修改量。

$ git log --no-merges --pretty= --shortstat
2 files changed, 25 insertions(+), 4 deletions(-)
1 file changed, 4 insertions(+), 12 deletions(-)
2 files changed, 30 insertions(+), 1 deletion(-)
3 files changed, 15 insertions(+), 5 deletions(-)
           

而我看到很多公司内的項目則不是這樣,一個送出動辄修改成百上千的檔案,涉及成千上萬行的源代碼。試問這樣的送出能 Show 出來給人看麼?

可是在開發過程中,程式員一旦進入狀态,往往才思如泉湧,不經意間就寫出一個大送出。比如我又一次向 Git 貢獻代碼時,

送出還不算太大,就被 Git 的維護者 Junio 吐槽,要我拆分送出,便于評審:

TODO(連結尋找中。。。)
           

那麼如何将送出拆分為若幹個小送出呢?

拆分目前送出(松耦合)

先以拆分最新的送出為例,可以如下操作:

  1. 将目前送出撤銷,重置到上一次送出。撤銷送出的改動保留在工作區中。
    $ git reset HEAD^
               
  2. 通過更新檔塊揀選方式選擇要送出的修改。Git 會逐一顯示工作區更改,如果确認此處改動要送出,輸入“y“。
    $ git add -p
               
  3. 以撤銷送出的送出說明為藍本,撰寫新的送出。
    $ git commit -e -C HEAD@{1}
               
  4. 對于工作區其餘的修改進行送出,完成一個送出拆分為兩個的操作。
    $ git add -u
        $ git commit
               

拆分目前送出(緊耦合)

如果要拆分的送出,不同的實作邏輯耦合在一起,難以通過更新檔塊揀選(

git add -p

)的方式修改送出,怎麼辦?這時可以

直接編輯檔案,删除要剝離出此次送出的修改,然後執行:

$ git commit --amend
           

然後執行下面的指令,還原原有的檔案修改,然後再送出。如下:

$ git checkout HEAD@{1} -- .
$ git commit
           

同樣完成了一個送出拆分為兩個送出的操作。

拆分曆史某個送出

如果要拆分的是曆史送出(如送出 54321),而非目前送出,則可以執行互動式變基(

git rebase -i

),如下:

$ git rebase -i 54321^
           

Git 會自動将參與變基的送出寫在一個動作檔案中,還會自動打開編輯器(比如 vi 編輯器)。在編輯器中顯示内容示例如下:

pick 54321 要拆分的送出
pick ...   其他參與變基的送出
           

将要拆分的送出 54321 前面的關鍵字

pick

修改為

edit

,儲存并退出。變基操作随即開始執行。

首先會在送出 54321 處停下來,這時要拆分的送出成為了目前送出,參照前面“拆分目前送出”的方法對送出 54321 進行拆分。拆分結束再執行

git rebase --continue

完成整個變基操作。

送出做對

“好的文章不是寫出來的,而是改出來的。” 代碼送出也是如此。

程式員寫完代碼,往往迫不及待地敲下:

git commit

,然後發現送出中少了一個檔案,或者送出了多餘的檔案,或者發現送出中包含錯誤無法編譯,或者送出說明中出現了錯别字。

Git 提供了一個修改目前送出的快捷指令:

git commit --amend

,相信很多人都用過,不再贅述。

如果你發現錯誤出現在上一個送出或其他曆史送出中怎麼辦呢?我有一個小竅門,在《Git權威指南》裡我沒有寫到哦。

比如發現曆史送出

54321

中包含錯誤,直接在目前工作區中針對這個錯誤進行修改,然後執行下面指令。

git commit --fixup 54321
           

你會發現使用了

--fixup

參數的送出指令,不再詢問你送出說明怎麼寫,而是直接把錯誤送出

54321

的送出說明的第一行拿來,在前面增加一個字首“fixup!”,如下:

fixup! 原送出說明
           

如果一次沒有改對,還可以再接着改,甚至你還可以針對這個修正送出進行 fixup,産生如下格式的送出說明:

fixup! fixup! 原送出說明
           

當開發工作完成後,待推送/評審的送出中出現大量的包含“fixup!”字首的送出該如何處理呢?

如果你執行過一次下面的指令,即針對錯誤送出 54321 及其後面所有送出執行互動式變基(注意其中的

--autosquash

參數),你就會驚歎 Git 設計的是這麼巧妙:

$ git rebase -i --autosquash 54321^
           

互動式變基彈出的編輯器内自動對送出進行排序,将送出 54321 連同它的所有修正送出壓縮為一個送出。

Tips:執行

git config --global rebase.autoSquash true

指令設定配置變量

rebase.autosquash

,執行

git rebase -i

指令會自動帶上

--autosquash

參數。

對于“送出做對”,很多開源項目還通過單元測試用例提供保障。對于這樣的項目,在送出代碼時往往要求提供相應的測試用例。

Git 項目本身就對測試用例有着很高的要求,其測試架構也非常有意思。我曾經針對Git的單元測試架構寫過部落格,參見:

複用 git.git 測試架構

Tips:Git 的測試架構代碼經過重構,已經成為一個單獨的項目:

Sharness

,更加友善重用了。我曾經的幾個項目都使用了這個架構寫用例,後面有時間專題介紹。

送出做好

僅僅做到送出做小、送出做對,往往還不夠,還要通過撰寫詳細的送出說明讓評審者信服,這樣才能夠讓送出盡快通過評審合入項目倉庫中。

例如今年7月份在華為公司内部的 Git 伺服器上發現一個異常,最終将問題定位到 Git 工具本身。整個代碼改動隻有區區一行:

你能猜到送出說明寫了多少麼?寫了20多行!

receive-pack: crash when checking with non-exist HEAD

If HEAD of a repository points to a conflict reference, such as:

* There exist a reference named 'refs/heads/jx/feature1', but HEAD
  points to 'refs/heads/jx', or

* There exist a reference named 'refs/heads/feature', but HEAD points
  to 'refs/heads/feature/bad'.

When we push to delete a reference for this repo, such as:

        git push /path/to/bad-head-repo.git :some/good/reference

The git-receive-pack process will crash.

This is because if HEAD points to a conflict reference, the function
`resolve_refdup("HEAD", ...)` does not return a valid reference name,
but a null buffer.  Later matching the delete reference against the null
buffer will cause git-receive-pack crash.

Signed-off-by: Jiang Xin <[email protected]>
Signed-off-by: Junio C Hamano <[email protected]>
           

Git 對于送出說明的格式有着如下約定俗成的規定:

  • 送出主題

送出說明第一行是送出主題,是整個送出的概要性描述。可以在送出主題中添加所更改的子產品名稱作為字首(如:receive-pack:)。

送出主題(即送出說明的第一行)盡量保持在50位元組以内(Gerrit 的commit_log檢查插件似乎有着稍微寬泛一些的要求)。

這是因為對于像 Linux、Git 這樣的開源項目,是以郵件清單作為代碼評審的平台,送出主題要作為郵件的标題,而郵件标題本身有長度上的限制。

  • 送出主題後的空行

必須要在送出說明的第一行和後續的送出說明中間留一個空行!如果沒有這個空行,很多 Git 用戶端會将連續幾行的送出說明合在一起作為送出描述。這樣顯然太糟了。

  • 送出說明主體

送出主題之外的送出說明也有長度的限制,最好以72位元組為限,超過則斷行。因為 GitHub 在顯示送出說明時支援 Markdown 文法,

是以作為一個有品位的程式員學些 Markdown 的文法,讓你的送出說明的可讀性變得更強吧。

我總結過一個 Markdown 和其他文本标記語言的文法說明,可供參考:

  • 簽名區

在送出說明最後是簽名區。簽名區可以看出這個送出的參與者、評審記錄等等。

最後,讓我們一起學習成為一名有品位的程式員吧。并依靠你對代碼的品味,高品質嚴要求,守護你的項目吧。

Notes: 本文最早在 2015年底發表在我的部落格上:

http://www.worldhello.net/2015/12/23/taste-of-a-programmer.html