CI/CD Server Note 04

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-msgupdate:

  • 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檔案的reporevhooksid以外還有其他可添加的元素,所代表的意義為何可以參考官方的文檔,下面介紹範例中使用到的元素:

  • 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所檢查的檔案只有放到暫存區的檔案,也就是經過git add的檔案,但如果使用--all-files參數則真的為所有檔案。

跳過/停用

如果要跳過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

可以看到比起前面的寫法多了excludeargs兩個參數:

  • exclude : 指定需要排除檢查的檔案或資料夾。
  • args : 要傳遞給hook的附加參數清單。

這裡排除了.venv/資料夾,且傳入了max-line-length=100ignore=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

前面所介紹的Flake8yapf分別是一種python的Linter跟Formatter,用來統一程式碼風格的工具,使用步驟通常是:

  1. 先用Linter來檢查程式碼中的潛在問題、風格指南違規和錯誤,它會提供有關代碼中可能存在的問題的警告或錯誤消息,如未定義的變數、未使用的變數、代碼風格不符合慣例等。
  2. 再用Formatter自動調整程式碼的格式,以符合指定的風格指南和慣例,它會自動糾正縮進、空格、換行等風格問題,以確保代碼的一致性。

常見用於python的Linter有下面這幾種:

  1. Pylint
  2. Flake8
  3. pycodestyle (以前的名稱是PEP8)

常見用於python的Formatter有下面這幾種:

  1. autopep8
  2. yapf (google版本在這)
  3. Black

總的來說,你應該首先運行 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