2019年8月4日 星期日

提升程式碼品質:使用 Pre-Commit (Git Hooks)

沒有留言:
 
圖:Pre-commit framework

在團隊協作開發中,程式碼品質是一個非常需要重視的問題,除了比較進階的重構以外,有沒有什麼自動檢查的方法可以提升程式碼品質呢?答案是有的,就是今天要討論的 pre-commit。

在程式碼 commit 進 branch 之前,如果能在 local 端阻擋一些低級失誤,就可以大大提升開發品質,在專案中使用 pre-commit 有以下好處:

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

這次示範的 pre-commit 設定是基於 python 的 pre-commit framework:pre-commit。市面上的 pre-commit framework 其實不少,例如 js 的 Husky,或是自己撰寫 git hook 的 script 也是可以的。那就廢話不多說,開始介紹吧!




一、Pre-commit 基本使用


以 framework 設置 pre-commit 非常簡單,官方文件淺顯易懂,只需要 pre-commit 的基本程式,再加上一個 yaml 設定檔即可完成!


1. Pre-commit 安裝


  • 安裝工具:
$ pip3 install pre-commit  # pip 安裝 pre-commit
$ pre-commit install       # 安裝 pre-commit 至 .git/hooks
  • 撰寫配置文件 .pre-commit-config.yaml
  • 產生一個新 commit
    • 需要的套件安裝會和第一次 commit 時的檢查一起完成。


2. Pre-commit 設定檔與使用流程


需要設定的只有 .pre-commit-config.yaml 這個 config 檔,將它置於專案下(也就是和 .git 同一層)即可,他的基礎格式如下:
repos:
-   repo: https://github.com/pre-commit/pre-commit-hooks
    rev: v1.2.3
    hooks:
    -   ...

一個實際使用的 .pre-commit-config.yaml 範例:
repos:
-   repo: https://github.com/pre-commit/pre-commit-hooks
    rev: v2.2.3
    hooks:
    -   id: flake8
    -   id: detect-aws-credentials
    -   id: detect-private-key
    -   id: check-added-large-files
    -   id: check-merge-conflict
    -   id: check-json
    -   id: check-yaml
-   repo: https://github.com/ambv/black
    rev: stable
    hooks:
    - id: black
      language_version: python3.6
-   repo: https://github.com/Lucas-C/pre-commit-hooks-bandit
    rev: v1.0.4
    hooks:
    -   id: python-bandit-vulnerability-check
        args: [-l, --recursive, -x, tests]
        files: .py$
-   repo: https://github.com/asottile/reorder_python_imports
    rev: v1.6.1
    hooks:
    -   id: reorder-python-imports

使用這個設定檔 commit 時的檢查訊息:


修改後再次 commit:


成功 commit!


[用心去感覺] 有一些網路上的設定檔是用 sha 而不是 rev?
在官方文件有提到:rev is the revision or tag to clone at. new in 1.7.0 previously called sha,也就是這個欄位要放所指定的 repo 的版號(以前是 commit sha)。




二、常用的 Git Hooks


Pre-commit 設置的本質上就是加上一個個 Git hooks,而市面上有很多 hooks 可以挑選,以目的來區分大抵上有幾種類別:

  • 程式碼規範檢查:
    • black:自動格式化 (formatter)
    • flake8:檢查程式碼不符合規範的地方 (checker, style & linting)。
  • 安全檢查:
    • detect-aws-credentials
    • detect-private-key
  • 格式檢查:
    • check-json
    • check-yaml
  • 其他檢查:
    • check-added-large-files
    • check-merge-conflict
    • ...

而每一個 hook 也可以針對各個 hook 需要的設定檔再做設定,例如 flake8 的客製化:

in .flake8 file:
[flake8]
ignore = E203, E266, E501, W503, F403, F401
max-line-length = 79
max-complexity = 18
select = B,C,E,F,W,T4,B9
exclude =
    .git,
    __pycache__,
    build

另一個常見的用法是用 hook 啟動 unit test,不過要當 test case 質量和數量到達一定程度時比較建議使用,不然測試常常壞掉,然後不得已常常下 --no-verify 參數繞過檢查,反而得不償失。




三、Pre-commit 的注意事項


1. Pre-commit 是一個用於自律的輕量本地端開發工具。

  • Pre-commit 不是強制性的,不是用來強硬擋下不符規範的 commit;其主要目的是更早的發現問題,有助於維持開發流程的健康狀態
    • 方法論設計理念上,pre-commit hooks 的作用域僅限 local,其他人不應該有權限限制你 local 的 git 指令,如強硬限制你必須使用的 hooks。
    • 急於 commit 可以使用 $ git commit --no-verify 繞過檢查,雖然這種作法有點不健康。
  • 如果要強硬擋下的話應該使用 server 端的 pre-receive hook。
    • Server 端的檢查雖然有強制性,但也有速度較慢、可能無法重現等缺點。

2. 在開發團隊中維護 hooks,在大量 repositories 中設定 pre-commit 的方法?
  • Git 2.9 之後有 "global" hooks 的功能,但還是不大適合用於所有 repositories 的設定。
  • 由於存放 Git hooks 的 .git/hooks 目錄不會隨著專案複製,比較適合的做法還是另外開一個 repository 分享存放設定檔,讓團隊中的成員可以自行拷貝或連結進行設定,也比較方便追蹤 hooks 的更動。




補記:Git Hooks


圖:Git Hooks for CI

Git hooks 就是當某些事件發生時,觸發自定義的 script。主要分成用戶端和伺服器端的 hooks,以下是幾個常見 的 hooks 種類:

  • Client-side hooks
    • pre-commit
    • prepare-commit-msg
    • commit-msg
    • post-commit
    • post-checkout
    • pre-rebase
  • Server-side hooks
    • pre-receive
    • update
    • post-receive

其中,pre-* 代表在某個階段之前的動作, post-* 則只能用於通知。這些 hooks 使用時通常有一些既定的目的:

  • (Client-side, Pre-*):coding style checking
  • (Client-side, Post-*):check branch status for safety
  • (Server-side, Pre-*):protect master
  • (Server-side, Post-*):notification

其設定範例可以在 .git/hooks 中找到,記得把檔名中的 .sample 刪去即可使用:
applypatch-msg.sample       pre-push.sample
commit-msg.sample           pre-rebase.sample
post-update.sample          prepare-commit-msg.sample
pre-applypatch.sample       update.sample
pre-commit.sample





References


Official - pre-commit
https://pre-commit.com/

Lj Miranda - Automate Python workflow using pre-commits: black and flake8 https://ljvmiranda921.github.io/notebook/2018/06/21/precommits-using-black-and-flake8/

代碼質量和 Pre commit
https://www.jianshu.com/p/7e2d7b3d65cf

Dev Life - Pre-commit is awesome
https://blog.jerrycodes.com/pre-commit-is-awesome/

atlassian CI/CD - 3 Git hooks for continuous integration https://www.atlassian.com/fi/continuous-delivery/continuous-integration/git-hooks

使用 git hook 在 commit 前進行 unittest
https://yodalee.blogspot.com/2016/12/git-hook-unittest.html




沒有留言:

張貼留言

技術提供:Blogger.