Pythonの静的解析ツールVultureを使ってみよう
Vultureについて
Vulture は Python プログラムの中の使われていないコードを見つけます。これは、大規模なコードベースでのエラーのクリーンアップや発見に役立ちます。Vultureをライブラリとテストスイートの両方で実行すれば、テストされていないコードを見つけることができます。
Pythonは動的な性質を持っているため、Vultureのような静的コードアナライザは死んだコードを見逃してしまう可能性があります。また、暗黙のうちにしか呼ばれないコードが使われていないと報告されることもあります。それでも、Vultureはより高いコード品質のための非常に有用なツールとなります。
次のような機能があります。
高速: 静的コード解析を採用し、高速に処理されます。
テスト済み:自分自身をテストし、完全なテストカバレッジを持ちます。
pyflakesを補完し、同じ構文で出力します。
未使用のクラスや関数をサイズ別にソートすることができます。
Python 3.6以降をサポート
インストール
Vulture は pip コマンドでインストールすることができます。
code: bash
$ pip install vulture
使用方法
vulture に引数としてPythonのファイルやディレクトリを与えて実行します。ディレクトリを与えた場合は、各ディレクトリに含まれるすべての *.py ファイルを解析します。
code: bash
$ vulture myscript.py
$ python3 -m vulture myscript.py
$ vulture myscript.py mypackage/
$ vulture myscript.py --min-confidence 100 # 信頼度100%デッドコードだけをレポート
Vultureは死んだコードの各チャンクに信頼度の値を割り当てます。信頼度の値が100%であれば、そのコードは決して実行されないことを意味します。100%以下の値は、コードが使用されていない可能性の非常に大まかな推定値(コードチャンクの種類に基づく)です。
使われていないコードを見つけて削除した後、再度Vultureを実行すると、さらに使われていないコードが見つかる可能性があります。
偽陽性の処理
Vultureが誤ってコードチャンクを未使用と報告した場合、誤検出を抑制するためのいくつかのオプションがあります。誤検出を修正することで他のユーザーにもメリットがある場合は、課題レポートを提出してください。
ホワイトリスト
推奨されるオプションは、使われていないと報告されたコードを Python モジュールに追加して、スキャンされたパスのリストに追加することです。このようなホワイトリストを自動的に取得するには、Vulture に --make-whitelist を渡します。
code: bash
$ vulture mydir --make-whitelist > whitelist.py
$ vulture mydir whitelist.py
結果として得られる whitelist.py ファイルは有効な Python 構文を含んでいますが、Python がそれを実行できるようにするためには、通常、いくつかの修正を行う必要があることに注意してください。
一般的なPythonモジュールやパッケージのホワイトリストは vulture/whitelists/ に集められています。
ファイルの無視
ファイルやディレクトリ全体を無視したい場合は、--excludeパラメータを使います(例:--exclude *settings.py,docs/)。
Flake8のnoqaコメント
flake8との互換性のために、Vultureは未使用のインポート(# noqa: F401)と未使用のローカル変数(# noqa: F841)を無視するためのF401とF841のエラーコードをサポートしています。ただし、noqaコメントはコードに視覚的なノイズを加えて読みにくくなるため、noqaコメントの代わりにホワイトリストを使用することをお勧めします。
名前を無視する
--ignore-names foo*,ba[rz] を使うと、Vultureはfooで始まるすべての名前と、barとbazという名前を無視することができます。また、--ignore-decorators オプションを使うと、指定されたデコレーターで装飾された関数を無視することができます。これは、例えばFlaskプロジェクトでは、--ignore-decorators "@app.route" を使用して、@app.route デコレーターを持つすべての関数を無視することができます。
可能な限り --ignore-name や --ignore-decorators の代わりにホワイトリストを使うことをお勧めします。ホワイトリストは Vulture に渡されたときに自動的に構文が正しいかどうかチェックされ、多くの場合、Python インタープリタに渡して、ホワイトリストに載っているコードが実際にプロジェクト内に存在するかどうかをチェックさせることもできます。
未使用の変数のマーク
タプルの代入や関数のシグネチャなど、使われていない変数を削除することができない状況があります。Vultureはこれらの変数がアンダースコアで始まる場合は無視します(例:_x, y = get_pos() or def my_method(self, widget, **_kwargs)))。
最小限の信頼性
--min-confidence フラグを使用すると、未使用として報告されるコードの最小信頼度を設定できます。--min-confidence 100 を使用すると、解析したファイル内で未使用であることが保証されているコードのみを報告します。
到達できないコード
Vultureがif False:のようなコードに文句を言う場合、ブールフラグ debug=False を使い、代わりに `if debug: と書くことができます。これにより、コードが読みやすくなり、Vultureを黙らせることができます。
型アノテーションの前方参照
#216 を参照してください。例えば、def foo(arg: "Sequence"): ... の代わりに、以下のように記述することを推奨します。Python 3.7以降を使用している場合に限ります。
code: python
from __future__ import annotations
def foo(arg: Sequence):
...
設定方法
コマンドライン引数は、pyproject.toml の tool.vulture セクションに格納することもできます。最初のダッシュを取り除き、残りのダッシュをすべてアンダースコアに置き換えるだけです。
コマンドラインで指定されたオプションは、pyproject.toml 内のオプションよりも優先されます。
設定例
code: pyproject.toml
make_whitelist = true
min_confidence = 80
sort_by_size = true
verbose = tru
バージョンコントロールとの連携
pre-commitフックを使って、各コミットの前にVultureを実行することができます。そのためには、pre-commitをインストールして、リポジトリの.pre-commit-config.yamlファイルに以下を追加します。
code: .pre-commit-config.yaml
repos:
rev: 'v2.3' # Vulture 2.3以降
hooks:
- id: vulture
次にpre-commit installを実行します。最後に、リポジトリに pyproject.toml ファイルを作成し、[tool.vulture] --> paths にVultureがチェックすべきファイルをすべて指定します(上記参照)。
どのような仕組みになっているのですか?
Vultureはastモジュールを使用して、指定されたすべてのファイルの抽象的な構文木を構築します。すべてのシンタックスツリーをトラバースする間に、定義されたオブジェクトや使用されたオブジェクトの名前を記録します。その後、定義されているが使用されていないオブジェクトを報告します。この分析では、スコープを無視し、オブジェクト名のみを考慮に入れます。
また、Vultureはreturn、break、continue、raise文の後のコードを探したり、満足できないif-やwhile-条件を探したりすることで、到達できないコードを検出します。
サイズによるソート
-sort-by-size オプションを使用すると、使用されていないコードを行数でソートします。これにより、使用されていないコードを探す際に、優先順位をつけることができます。
例
次のPythonスクリプトを考えてみましょう。
code: dead_code.py
import os
class Greeter:
def greet(self):
print("Hi")
def hello_world():
message = "Hello, world!"
greeter = Greeter()
greet_func = getattr(greeter, "greet")
greet_func()
if __name__ == "__main__":
hello_world()
実行してみましょう。
code: bash
$ vulture dead_code.py
次のような出力になります。
code: bash
dead_code.py:1: unused import 'os' (90% confidence)
dead_code.py:4: unused function 'greet' (60% confidence)
dead_code.py:8: unused variable 'message' (60% confidence)
Vultureは "os" と "message" が使われていないと正しく報告しますが、"greet" が実際に使われていることを検出できません。このような誤検出に対処するためには、ホワイトリストのPythonファイルを作成することが推奨されます。
ホワイトリストの準備
ホワイトリストでは、変数や属性などの使われ方をシミュレートします。上のプログラムの場合、ホワイトリストは次のようになります。
code: python
# whitelist_dead_code.py
from dead_code import Greeter
Greeter.greet
また、Vultureに --make-whitelist を渡せば、自動的に生成されたホワイトリストを得ることができます。
オリジナルのプログラムとホワイトリストの両方をVultureに渡すと、Vultureがgreetメソッドを無視するようになります。
code: bash
$ vulture dead_code.py whitelist_dead_code.py
code: bash
dead_code.py:1: unused import 'os' (90% confidence)
dead_code.py:8: unused variable 'message' (60% confidence)
終了コード
table: 終了コード
終了コード 説明
0 デッドコードがみつからない
1 デッドコードがみつかった
1 無効な入力(ファイルが見つからない, SyntaxError, 間違ったエンコーディング)
2 コマンドライン引数の間違い
類似ツール
pyflakes:多くのプログラム上のエラーに加えて、使用されていないインポートや使用されていないローカル変数を検出するツールです。 coverage:Vulture よりも確実に使われていないコードを見つけますが、コードのすべての分岐を実際に実行する必要があります。 uncalled: Vultureのように抽象構文木、正規表現、またはその両方を使って、デッドコードを見つけます。 dead: Vultureのように抽象構文木を使用してデッドコードを検出します。