CI/CD Server Note 04

pre-commit程式碼風格檢查
在多人協作開發軟體的情境下,程式碼品質是一個需要重視的議題,要確保程式碼的一致性、可讀性和可維護性,除了程式碼重構,還有一個極為實用的工具,那就是pre-commit
。
這個工具可以在程式碼提交之前自動執行程式碼格式化、語法檢查等等,確保在程式碼進入版本控制系統之前符合一定的品質標準和規範,有助於防止不良程式碼進入版本控制系統,減少在後續的CI/CD pipeline過程發生錯誤而需要做code review的次數,從而提升整個團隊的效率和程式碼的品質和效率。
Hooks
pre-commit
是透過Git的Git Hooks來實現的,當某些操作git的動作發生時,會觸發自定義的script,而pre-commit
如同字面上的意思,是在commit的時候會先觸發定義的script對程式碼進行檢查或是其他操作,再進行commit的動作。
Hooks大致上可分成pre-
跟post-
兩種,除了這兩種以外常用的還有commit-msg
跟update
:
pre-
: 代表在Git操作執行之前所要進行的動作,可以用來做執行代碼風格檢查、測試、驗證提交內容等操作。post-
: 則在Git操作執行之後要進行的動作,通常用來執行自動化部署、發送通知或日誌記錄等後處理操作。commit-msg
: 在提交訊息(commit message)被編輯後觸發,可用於檢查提交訊息的格式。update
: 在參考(reference)更新時觸發,可用於驗證參考的更新。
通常情況下,這些hook script通常需要搭配特定的檔案、工具和執行環境,以實現其功能,因此整包可能會變得相當龐大。當需要在其他專案中使用這個hook script時必須將這龐大的script複製到另個專案中,這個過程非常麻煩,而pre-commit
套件就是為了解決這個問題。
pre-commit基本設定
pre-commit
最初是為了Python設計的套件,不過也可以用於其他語言的項目,只要設計好要使用的語言相應的檢查、驗證腳本。目前專案中我都使用Poetry來管理python的套件,所以這裡使用poetry來安裝pre-commit
,使用pip來安裝也可以,由於pre-commit
希望只在開發環境使用,不會想要在測試或是產品上安裝他,所以要設定成--group main
:
poetry add pre-commit --group main
pip install pre-commit
設定檔
接下來要在專案的根目錄創建.pre-commit-config.yaml
檔案範例如下:
repos:
- repo: https://github.com/pre-commit/pre-commit-hooks
rev: v4.4.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: # 後略
其中yaml檔案的repo
、rev
、hooks
、id
以外還有其他可添加的元素,所代表的意義為何可以參考官方的文檔,下面介紹範例中使用到的元素:
repo
: 可以用git clone
的repository連結。rev
: rev為對應的版本號,可以在release的tag頁面看到。hooks
: 要使用哪些hook,這個頁面列出了可使用的變數。id
: 要使用存儲庫中的哪個hook。
上面這個範例使用的是由pre-commit
提供的基本hooks:
trailing-whitespace
: 檢查程式碼是否存在多餘的尾隨空格。check-added-large-files
: 檢查是否添加了過大的文件。check-ast
: 對程式碼進行抽象語法樹(AST)分析,以檢查淺在的問題。check-case-conflict
: 檢查檔案名的大小寫是否與版本控制系統中的文件衝突。check-json
: 驗證JSON文件的格式是否正確。check-toml
: 驗證TOML文件的格式是否正確。check-yaml
: 驗證YAML文件的格式是否正確。end-of-file-fixer
: 自動添加文件末尾的換行符號。
以上是列出pre-commit-hooks
自帶的一些常用hook,如果有需要添加新的hook可以去官方文檔中尋找。
使用
在下指令以前要確定已經進入了安裝了pre-commit
的python虛擬環境,不然可能會找不到指令,接著在專案根目錄以命令列執行pre-commit install
,將.pre-commit-config.yaml
轉譯為hook腳本存在專案下的.git/hooks
目錄中:
> pre-commit install
pre-commit installed at .git\hooks\pre-commit
到這個步驟後,執行git commit
就會執行.pre-commit-config.yaml
設定的各個hook,如果是在VSCode執行,並有不符合規定的會直接跳出警告,可以查看哪個環節出錯了:

按照官方的文檔說明,如果添加了新的hook建議執行以下指令 (或是你想要手動執行),讓所有的檔案都檢查過一次:
pre-commit run --all-files
除了使用所有的hook檢查所有的檔案,也可以指定hook-id
或特定的檔案去執行:
# 指定hook檢查所有檔案
pre-commit run [hook-id] -a
# 指定hook檢查特定檔案
pre-commit run [hook-id] --files <file_1> <file_2>
跳過/停用
如果要跳過pre-commit
直接commit的話,在git commit
後面加上--no-verify
又可以跳過檢查了:
git commit --no-verify -m "Commit Message"
如果要直接停用pre-commit
直接輸入以下指令就可以停用了:
pre-commit uninstal
其他hook
前面有提到可以到官方提供的可支援hooks列表中安裝需要的script,下面會介紹幾個目前在用的hook,可以讓剛上手的人有一個基本的設定可以使用。
Flake8
Flake8
是用來檢查python程式碼風格和找到程式碼潛在的品質問題以符合PEP8
,有助於提高程式碼的質量、可讀性和可維護性,關於Flake8
的介紹網路有很多了,這裡就直接上範例:
# 前略
- repo: https://github.com/PyCQA/flake8
rev: 6.1.0
hooks:
- id: flake8
exclude: .venv/
args:
- --max-line-length=100
- --ignore=E402
可以看到比起前面的寫法多了exclude
跟args
兩個參數:
exclude
: 指定需要排除檢查的檔案或資料夾。args
: 要傳遞給hook的附加參數清單。
這裡排除了.venv/
資料夾,且傳入了max-line-length=100
和ignore=E402
兩個參數給Flake8
,其中max-line-length=100
將限制長度從原本的79改為100;而ignore
則是要Flake8
忽略某些警告或是錯誤,這裡的E402
代表的是import python套件的位置不在檔案的頂部的錯誤,Flake8
的規則可以在這裡查到,可以根據團隊的規定或是不同的專案來配合需求。
isort
isort
主要是將import進python的套件按照一定的規則排序、分組,並按照字母順序排列,同樣isort
也符合PEP8
的規定,以下是使用isort
的範例:
# 前略
- repo: https://github.com/PyCQA/isort
rev: 5.12.0
hooks:
- id: isort
exclude: .venv/
args:
- --line-length=100
- --profile=black
同樣在這裡看到了line-length
,為了讓彼此共通的判斷規則一致,設定為100。
yapf
yapf (Yet Another Python Formatter) 是一個python程式碼格式化工具,它可以幫助開發者自動調整python程式碼的風格和格式,以確保程式碼的一致性和可讀性,YAPF根據PEP 8規範自動格式化代碼,使代碼風格一致,並減少程式碼審查中的風格問題。
按照pre-commit
支援的hook安裝的方式可以在.pre-commit-config.yaml上添加以下代碼:
# 前略
- repo: https://github.com/pre-commit/mirrors-yapf
rev: v0.32.0
hooks:
- id: yapf
exclude: .venv/
args:
- column_limit = 100
additional_dependencies: [toml]
或是使用google的yapf,它提供了許多可以客製化的參數設定,詳細的說明可以去github上查看:
# 前略
- repo: https://github.com/google/yapf
rev: v0.40.1
hooks:
- id: yapf
name: yapf
language: python
entry: yapf
args:
- -i
- COLUMN_LIMIT = 100
types: [python]
black
black跟yapf一樣是程式碼格式工具,不過black是以嚴格的規範出名,基本上沒有太多的客製化參數可以調整:
- repo: https://github.com/psf/black
rev: v23.9.1
hooks:
- id: black
args:
- --line-length=100
- --skip-string-normalization
大致上會使用的參數就這兩種,其他的參數可以參考這個網址:
--line-length
: 單行的長度限制,預設為88。--skip-string-normalization
: black會對所有字串使用雙引號並標準化字串。
Linter & Formatter
前面所介紹的Flake8
跟yapf
分別是一種python的Linter跟Formatter,用來統一程式碼風格的工具,使用步驟通常是:
- 先用Linter來檢查程式碼中的潛在問題、風格指南違規和錯誤,它會提供有關代碼中可能存在的問題的警告或錯誤消息,如未定義的變數、未使用的變數、代碼風格不符合慣例等。
- 再用Formatter自動調整程式碼的格式,以符合指定的風格指南和慣例,它會自動糾正縮進、空格、換行等風格問題,以確保代碼的一致性。
常見用於python的Linter有下面這幾種:
- Pylint
- Flake8
- pycodestyle (以前的名稱是PEP8)
常見用於python的Formatter有下面這幾種:
總的來說,你應該首先運行 Linter 來檢查代碼中的問題,然後再運行 Formatter 來確保代碼符合風格指南。這樣可以確保你的代碼既沒有錯誤,又符合一致的風格,提高了代碼的品質和可維護性。
使用的範例
要注意如果有彼此共通的判斷規則,那參數要記得設定一樣:
repos:
- repo: https://github.com/pre-commit/pre-commit-hooks
rev: v4.4.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
- --profile=black
# 選擇black或是yapf
- repo: https://github.com/google/yapf
rev: v0.40.1
hooks:
- id: yapf
name: yapf
language: python
entry: yapf
args:
- -i
- COLUMN_LIMIT = 100
types: [python]
- repo: https://github.com/psf/black
rev: 23.9.1
hooks:
- id: black
args:
- --line-length=100