from Pixabayfrom Pixabay

相關文章:Python 開發:Ruff Linter、Formatter 介紹 + 設定教學


有關 Python「Code Formatting」(Linter、Formatter 等)這一主題的文章,之前已寫了不少:

  1. VS Code 設定 Python Linter、Formatter 教學
  2. Python isort 擴充套件介紹與簡易設定教學
  3. Python Flake8 與 Black Formatter 擴充套件快速上手

這些文章都是我在工作中,實際使用這些工具的心得。

不過,為什麼要寫這麼多?還是那句老話:「我認為這些工具的重要性,再怎麼強調也不為過——更別說它們常常被低估了。

我曾在上述文章中的開頭或結尾處,不厭其煩地表達自己對它們的重視,這裡就不再贅述,直接進入重點。

本文主旨

本文將講述如何透過 pre-commit 這個工具設定 Git Hooks,並以 Flake8、isort、yapf 等 Python 開發常用套件為例,進行演示,同時提醒一些注意事項。

如同 Poetry,pre-commit 也是我在工作上,強烈希望導入的工具——畢竟它可以有效降低 code review 時血壓升高的次數☺️

本文架構

我們會先講一下什麼是 Git Hooks 和 pre-commit,因為不是論述上的重點,所以只需簡單帶過,讀者心中有個輪廓即可。

接著探討「為什麼要使用 pre-commit 工具?」。和我過去的許多文章一樣,對動機與場景將有一定程度的著墨,我們會探討 pre-commit 的價值所在,包括我從團隊使用經驗得到的看法。

最後是具體設定——這或許是最不重要的部分,因為它很簡單,但還是有一些值得留意的部分。以 Flake8、isort、yapf 為例,則是為了貫徹前述所有文章的一貫精神:有了 pre-commit,這一切才算圓滿。

附帶一提,考慮到「pre-commit」這個主題已經有數篇文章珠玉在前(參考文章我會放在文末),我下筆前都已拜讀完。本文的論述重心會盡可能與這些文章錯開,或乾脆直接引用,以降低不必要的重複感。


Git Hooks 介紹

Git Hooks 是 Git 提供的一個功能,允許開發者在特定的 Git 事件(如 commit、push 等)觸發時,自動執行自定義的腳本,這樣可以在提交程式碼之前進行一些檢查,以確保程式碼的交付品質。

Git Hooks 有很多種,不同的 Git Hooks 可以幫助開發者在不同的場景下進行自動化操作,這裡我們只要知曉最常用的 pre-commit hook 已足。

顧名思義,pre-commit hook 會在你使用git commit命令時觸發,讓機器依照你的 hook 腳本內容,在 commit 前對程式碼進行檢查。

事實上,你可以用任何語言來撰寫 hook 腳本,只要編寫的腳本能夠在系統上被執行,而且符合 Git hook 的腳本格式,就可以使用。

開發者只需要在.git/hooks目錄下建立相對應的 hook(命名必須遵守規則),依照 Git Hooks 的 API 規範,編寫自己需要的處理邏輯即可。

但我們才不會這麼做!因為我們有工具嘛!

Git Hooks 管理工具介紹

即使你會寫 shell script,也不一定想自己手刻一個 hook 腳本,所幸這麼常見的需求,自然有許多開源的工具可以使用,而 pre-commit 就是其中的佼佼者——只能說這套件的命名非常簡單暴力!🤣

pre-commit 是由 Python 所寫成的 Git Hooks 管理工具,但可以適用於各類程式語言專案,包括 Python、JavaScript、Ruby、Java、Go 等。使用者可以根據自己的需求,選擇要使用的 hook。

類似的工具還有 huskyovercommit 等等。不過作為 Python 開發者,我們當然還是選擇 pre-commit 囉!

雖然叫 pre-commit,但實際上它可以管理全部種類的 Git Hooks,比如pre-pushpost-merge等等,管理 pre-commit hook 只是其中最常見的用途而已,我想這應該不至於造成誤會。


為什麼要使用 pre-commit?

這個問題可以分為兩個層次

  1. 為什麼要使用 pre-commit hook?
  2. 為什麼要使用 Git Hooks 自動化工具?

這兩個問題有先後關係,先有 1 才有 2。不過 2 的理由比較簡單,所以我們先講 2 再講 1。

為什麼要使用自動化工具來管理 Git Hooks?

除了和〈是時候同步你的 dotfiles 了——我選擇 yadm〉一樣:因為這個方式更加輕鬆以外。更關鍵的是,透過工具你可以像堆樂高積木般,輕鬆組合各式套件所提供的現成 hook,比如本文的 Flake8,這顯然比自己寫 shell 要高效得多。

所以,直接透過工具來管理 Git Hooks,應該是絕大多數人自然而然的選擇

為什麼要使用 pre-commit hook?

在〈提升程式碼品質:使用 Pre-Commit (Git Hooks)〉一文中,作者提出了三點,闡述為何要使用 pre-commit hook,理由充分且令人信服,請容我直接引用:

  • 自動化檢查程式碼排版規範快速又有效率(如 python PEP8)
  • 低級的問題不會進到 code review
    • 多一點時間檢查程式邏輯,而不是基本錯誤(如排版)。
    • 人工檢查程式碼的時間很寶貴,減少人工即是增進效率。
  • 低級的問題不會進到 CI/CD pipeline
    • pipeline 應該多一點綠勾勾,而不是滿滿 debug 的痕跡。

我的關注點

此外,我在臉書社團「Backend 台灣 (Backend Tw)」這篇文章的回應中,也表達了自己的看法,這裡只節錄最後兩段:

上面的情況其實都在說:「linter已經提醒有問題,但formatter無法正常格式化」,細心的人會停下來排除問題或討論,但人難免都有粗心的時候,偶爾還是忽略且commit了

負責 review 的人看到這種格式類的錯誤,心裡難免會有點無言,但又不好直接怪隊友,畢竟對方也不是「故意」的,此時有機器幫彼此擋一下,也算是團隊協作的潤化劑

是的,我最大的著眼點在於:「減少團隊協作時,不必要的磨擦(精神消耗)。

個人認為這真的很重要,畢竟我們不是機器,都有情緒,而情緒應該要用在更有價值的地方。

小結

總的來說,如果你問我「為何要使用 pre-commit?」從個人角度看,我已視之為理所當然,畢竟我都寫了多篇關於「如何落實 Code Formatting」的文章了。

而 pre-commit 則是我們的最後一塊拼圖,它更進一步確保了 linter 和 formatter 的檢查,在 commit 之際,能被完全遵守

而從團隊的角度,則是因為「這樣對大家都好!」


pre-commit 基本設定

安裝 pre-commit 應該不必解釋,poetry add pre-commitpip install pre-commit任君挑選。macOS 也可以用brew install pre-commit全域安裝,但如此一來它就不會在專案虛擬環境中。

.pre-commit-config.yaml

接著是關於.pre-commit-config.yaml的編輯,請將此檔案建立於專案的根目錄,pre-commit 就是透過這份設定檔,實現各項 hook 內容。

一般而言,.pre-commit-config.yaml會長這樣:

1
2
3
4
5
6
7
8
9
10
11
repos:
- repo: https://github.com/pre-commit/pre-commit-hooks
rev: v4.3.0
hooks:
- id: trailing-whitespace
- id: check-added-large-files

- repo: https://github.com/PyCQA/flake8
rev: 5.0.4
hooks:
- id: flake8

最上面是由 pre-commit 專案提供的基本 hooks。剩下的部分則是其它開源專案所提供的 hook。

yaml檔中的reporevhooksid等設定,直接看文件就知道意思了。

設定上重點

對於第一次接觸 pre-commit 的人而言,這裡會有的最大疑問應該是:「啊我怎麼知道我可以用哪些 hook?我又怎麼知道這些 hook 的 repo 網址?」

沒錯,這是關鍵所在,這個「Supported hooks」網頁有目前全部可用的 hook 清單,上面如果沒有列出你想用的工具,那就是暫不支援囉!

至於rev的具體版本號(總不能自己瞎編一個),repo 網頁的「tags」頁面就會有,比如這是 Flake8 的 tags 頁面

值得留意的是:有些 hook 的 tag 有「v」開頭,有些則否(上述 Flake8 就沒有)。這主要取決於各專案開發者的 tag 命名習慣,我們只要留意一下即可。

知道這些,基本上就能設定好.pre-commit-config.yaml了。

pre-commit 設定 Flake8、isort、yapf

這裡我們以 Flake8、isort、yapf 為例,這三個工具都是 Python 開發常用的套件,而且我們在之前的文章中也有介紹過,所以就不再贅述。

Flake8

直接看設定檔:

1
2
3
4
5
6
7
8
9
10
11
repos:
...

- repo: https://github.com/PyCQA/flake8
rev: 5.0.4
hooks:
- id: flake8
exclude: migrations/
args:
- --max-line-length=100
- --ignore=E131,E126,E402

hooks設定下所有可以用的子項目,上述的頁面已有說明。

這只需要提醒一件事:那就是args的設定內容務必和編輯器(比如 VS Code)的設定一致。

換句話說,我 VS Code 的settings.py也有一樣的設定:

1
2
3
4
"flake8.args": [
"--max-line-length=100",
"--ignore=E131,E126,E402"
],

如果兩者的設定有衝突,會造成適用上的混淆,這是我們不想看到的。

isort

1
2
3
4
5
6
7
8
9
10
repos:
...

- repo: https://github.com/PyCQA/isort
rev: 5.11.5
hooks:
- id: isort
exclude: migrations/
args:
- --line-length=100

一樣特別注意args,尤其像 isort、yapf 這類「formatter」,設定不一致會直接造成「在編輯器中格式化符合規範,但 commit 時卻被 hook 擋下來」的窘境

yapf

1
2
3
4
5
6
7
8
9
10
11
repos:
...

- repo: https://github.com/pre-commit/mirrors-yapf
rev: v0.32.0
hooks:
- id: yapf
exclude: migrations/
args:
- column_limit = 100
additional_dependencies: [toml]

yapf 有一個很常見的問題,之前在〈Python 開發:pyproject.toml 介紹 + 使用教學〉中已提過。

簡言之,只要你的專案中有pyproject.toml,無論裡面是否有關於 yapf 的設定項,yapf 都會試圖去解析(parse)它,此時toml套件就成了必不可少的一環。如果沒有toml,將進一步導致 yapf 無法正常運作。

這個問題在 pre-commit 同樣會發生,所以如果專案中有pyproject.toml,就要額外設定additional_dependencies: [toml],方能功德圓滿🙏


最後一哩

附上完整的.pre-commit-config.yaml供參:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
repos:
- repo: https://github.com/pre-commit/pre-commit-hooks
rev: v4.3.0
hooks:
- id: trailing-whitespace
- id: check-added-large-files
- id: check-ast
- id: check-case-conflict
- id: check-json
- id: check-toml
- id: check-yaml
- id: end-of-file-fixer

- repo: https://github.com/PyCQA/flake8
rev: 5.0.4
hooks:
- id: flake8
exclude: migrations/
args:
- --max-line-length=100
- --ignore=E131

- repo: https://github.com/PyCQA/isort
rev: 5.11.5
hooks:
- id: isort
exclude: migrations/
args:
- --line-length=100

- repo: https://github.com/pre-commit/mirrors-yapf
rev: v0.32.0
hooks:
- id: yapf
exclude: migrations/
args:
- column_limit = 100
additional_dependencies: [toml]

還有一件想當然爾但依舊必須提醒的事,即 hook 使用的rev版本,要和你專案裡使用的工具的版本一致。比如上述設定檔中我的 Flake8 是5.0.4版,那虛擬環境裡安裝的flake8自然也是5.0.4,而不會是其它版本。

安裝完 pre-commit 並建立好.pre-commit-config.yaml後,事情還沒有結束!此時專案中的.git/hooks目錄下,依舊是預設時的模樣:只有一大堆 sample 檔。

pre-commit install

我們還差一個動作——將.pre-commit-config.yaml轉譯為 hook 腳本,才能真正發揮它的功用。當然,這會由工具代勞,我們只需要下指令即可:

1
pre-commit install

下指令前請先確定虛擬環境已經開啟,不然可能會找不到pre-commit這個執行檔——除非你是全域安裝。

這個指令還有相關參數可以使用,有興趣可參考文件。不過在絕大多數情況下,這樣就已經足夠。

參考文章