MNTSQ Techブログ

リーガルテック・カンパニー「MNTSQ(モンテスキュー)」のTechブログです。

Refurbを使ったPythonの静的コードレビュー

MNTSQ Tech Blog TOP > 記事一覧 > Refurbを使ったPythonの静的コードレビュー

はじめに

Pythonを対象とした静的型チェックツールとしてmypyはよく知られています。静的型チェックを通じてプログラマーはより安全にコードを記述でき、安全にコードが記述できることで最終的にはソフトウェア開発の効率をより高めることができます。もちろん初期的な導入工数とCIパイプライン等での追加の時間的コストは発生するものの、それ以上の複利的な効果が期待できます。

静的型チェックよりも更に込み入った構文上の改良点や、未使用変数などの抽出を行ってくれるツールとしてLinterがあります。pylintflake8もLinterの一種としてカテゴライズされます。

mypyは静的解析の過程でAST(Abstract syntax tree: 抽象構文木)を吐きますが、このASTを利用したlintを行うツールとして今回はRefurbを取り上げます。

github.com

Refurb本体の説明としては

Lots of static analysis tools already exist, but none of them seem to be focused on making code more elegant, more readable, or more modern. That is where Refurb comes in.

とあり、既存のLinterよりもよりCode-readabilityや、モダンな書き方にフォーカスしたツールのようです。

Refurbの動作環境はPython3.10以降を前提にしていますが、解析対象はPython3.6以上のコードになります。

動作サンプル

pip経由でのインストールが可能です

$ pip3 install refurb

早速ですが動作を確かめるため次のサンプルコードに対してrefurbを回してみます。

# main.py

for filename in ["file1.txt", "file2.txt"]:
    with open(filename) as f:
        contents = f.read()

    lines = contents.splitlines()

    for line in lines:
        if not line or line.startswith("# ") or line.startswith("// "):
            continue

        for word in line.split():
            print(f"[{word}]", end="")

        print("")
$ refurb main.py
main.py:6:17 [FURB109]: Replace `in [x, y, z]` with `in (x, y, z)`
main.py:7:5 [FURB101]: Use `y = Path(x).read_text()` instead of `with open(x) as f: y = f.read()`
main.py:13:40 [FURB102]: Replace `x.startswith(y) or x.startswith(z)` with `x.startswith((y, z))`
main.py:19:9 [FURB105]: Use `print() instead of `print("")`
Run `refurb --explain ERR` to further explain an error. Use `--quiet` to silence this message

各行にエラーの内容が出ています。[FURB101]: Use `y = Path(x).read_text()` instead of `with open(x) as f: y = f.read()` などはファイルハンドリングの詳細まで踏み込んだメッセージで、pylint, flake8には見られないものです。

エラーの詳細について

--explain フラグを使ってエラーコードを指定することで、エラーの詳細が表示されます。

$ refurb –explain FURB101

When you just want to save the contents of a file to a variable, using a
`with` block is a bit overkill. A simpler alternative is to use pathlib's
`read_text()` function:

Bad:

"""
with open(filename) as f:
    contents = f.read()
"""

Good:

"""
contents = Path(filename).read_text()
"""

エラーの一覧

エラー一覧を確認できるコマンドオプションは提供されておらず、エラーの一覧を確認したい場合はテストデータのディレクトリを通じて調査できます。比較的新しいライブラリなのでこのあたりは後の改善に期待したいところです。

カスタムプラグインの作成

カスタムで自作エラーも定義できます。ただし静的解析結果をいじるため、mypyのASTシンボルをある程度漁って既存モジュールの動作を確認しつつ開発をする必要があります。

github.com

簡易エラー検出器の作成

カスタムプラグインを作成するほどのニーズではないが、一時的なエラー検出器を利用したいニーズに対しては refurb gen コマンドがあります。 これはCUIのプロンプトのガイドに沿ってASTのノード種別と検出器のファイルパス(.pyファイル)を指定すると、指定箇所にノード種別に対するエラー検出器サンプルを作成してくれるというものです。

作成した検出器をロードしてrefurbを利用する際には refurb file.py --load 検出器モジュールパス で利用できます。

その他機能

他にも追加の機能として

  • ローカルでgitのcommit前にチェックするツールpre-commitとの連携
  • 設定ファイルによる一部エラー、対象解析モジュールの無効化

ができるようです。

この記事を書いた人

f:id:mntsq:20201223123239j:plain

yad

ビリヤニ食べたい