Qlty
Qlty は Code Climate Quality の次世代版のオープンソースの静的解析コードツールセットで、各種プログラミング言語に対応している。
ここでは、コード品質の指標を試してみる。
指標は以下の通り。
-
classes: クラス数
-
funcs: 関数(メソッド)の数
-
fields: クラスのフィールド (メンバ変数・プロパティ) の数
-
cyclo (Cyclomatic Complexity): 循環複雑度
- if、for などの制御構文による分岐の数
-
cognitive (Cognitive Complexity): 認知複雑度
- if、for などのネストの深さ
-
LCOM (Lackoh of Cohesion in Metrics): 凝集度の欠如
- クラス内のメソッドとフィールドの関係性の薄さを表す指標で、クラス内の各メソッドがどれくらい同じフィールド(変数)をしようしているか
-
lines (Total Lines): プログラムの全行数
-
LOC (Lines of Code)
- 空行やコメント行を除いた、実行可能な実質的なコードの行数
インストール
curl -fsSL https://qlty.sh | bash
source ~/.bashrc
使い方
qlty は docker コンテナ内の各種プログラミング言語の静的アナライザを呼び出す。
そのため、qlty を実行するには、まず docker を起動する。
sudo systemctl start docker
初期化
ソースコードがあるディレクトリに移動する。
cd test1
qlty は git リポジトリ内での動作を前提としているため、非 git ディレクトリでは動作しない。
ここでは qlty の動作検証を行うために git init でリポジトリ化する。
git init
qlty init を実行すると、qlty で解析を行えるようになる。
qlty init
指標の出力
qlty metrics で指標を表示することができる。
qlty metrics -a
ここでは、以下のプログラム (test1.py ) を対象とする。
def main():
print('test')
return 0
if __name__ == '__main__':
main()
解析結果は以下の通り。
name | classes | funcs | fields | cyclo | complex | LCOM | lines | LOC
----------+---------+-------+--------+-------+---------+------+-------+-----
test1.py | 0 | 1 | 0 | 2 | 1 | 0 | 6 | 5
TOTAL | 0 | 1 | 0 | 2 | 1 | 0 | 6 | 5
比較
プログラムを変更して、各指標がどのように変化するかを比較してみる。
比較1. classes/fields
以下のプログラム (test2.py) ではクラスを定義している。
test1.py と評価結果を比較してみる。
class Test:
def __init__(self, msg1, msg2):
self.msg1 = msg1
self.msg2 = msg2
def show_3(self):
print(self.msg1, self.msg2)
print(self.msg1, self.msg2)
print(self.msg1, self.msg2)
if __name__ == '__main__':
test = Test('hello')
test.show_3()
name | classes | funcs | fields | cyclo | complex | LCOM | lines | LOC
----------+---------+-------+--------+-------+---------+------+-------+-----
test1.py | 0 | 1 | 0 | 2 | 1 | 0 | 6 | 5
test2.py | 1 | 2 | 2 | 2 | 1 | 1 | 13 | 11
test1.py では classes=0 だったが、test2.py ではクラスを定義したたため、classes=1 となっている。
また、fields はメンバ変数の数のため、test1.py では fields=0 だが、test2.py では fields=2 となっている。
比較2. cyclo (循環複雑度)
以下のプログラム (test3.py) では for 文を使用している。
test2.py と評価結果を比較してみる。
class Test:
def __init__(self, msg):
self.msg = msg
def show_n(self, n):
for i in n:
print(self.msg)
if __name__ == '__main__':
test = Test('hello')
test.show_n(10)
name | classes | funcs | fields | cyclo | complex | LCOM | lines | LOC
----------+---------+-------+--------+-------+---------+------+-------+-----
test2.py | 1 | 2 | 2 | 2 | 1 | 1 | 13 | 11
test3.py | 1 | 2 | 1 | 3 | 2 | 1 | 11 | 9
test3.py では for を使用したため、cyclo の値が 1 増えている。
比較3. complex
complex を比較するために、if 文を並列に実行する場合 (test4.py)と、if 文をネストさせた場合 (test5.py) を比べてみる。
class Test:
def __init__(self, msg):
self.msg = msg
def show_n(self, n):
for i in n:
if i % 2 == 0:
print(self.msg)
if i % 3 == 0:
print(self.msg)
if __name__ == '__main__':
test = Test('hello')
test.show_n(10)
class Test:
def __init__(self, msg):
self.msg = msg
def show_n(self, n):
for i in n:
if i % 2 == 0:
print(self.msg)
if i % 3 == 0:
print(self.msg)
if __name__ == '__main__':
test = Test('hello')
test.show_n(10)
指標は以下の通り。
name | classes | funcs | fields | cyclo | complex | LCOM | lines | LOC
----------+---------+-------+--------+-------+---------+------+-------+-----
test3.py | 1 | 2 | 1 | 3 | 2 | 1 | 11 | 9
test4.py | 1 | 2 | 1 | 5 | 6 | 1 | 14 | 12
test5.py | 1 | 2 | 1 | 5 | 7 | 1 | 14 | 12
test3.py と比べて test4.py、test5.py は if 文が 2 つ増えたため、cyclo が 3 から 5 に増えている。
一方、complex は test4.py では complex=6、test5.py では complex=7 となっていて、ネストしている方が complex の値が大きくなっていることがわかる。
qlty metrics のオプション
-
-a/--all: ディレクトリ配下の全プログラムを対象として指標を出力する。
-
--functions: 関数単位で指標を出力する。cognitive が出力される。
qlty metrics --functions test5.py function | fields | cyclo | cognitive | lines | loc ----------+--------+-------+-----------+-------+----- __init__ | 1 | 1 | 0 | 2 | 2 show_n | 1 | 4 | 6 | 6 | 6 -
--sort: ソートのキー。例えば、--sort lines で行数の降順にソート。
qlty metrics -a --sort lines name | classes | funcs | fields | cyclo | complex | LCOM | lines | LOC ----------+---------+-------+--------+-------+---------+------+-------+----- test5.py | 1 | 2 | 1 | 5 | 7 | 1 | 14 | 12 test4.py | 1 | 2 | 1 | 5 | 6 | 1 | 14 | 12 test2.py | 1 | 2 | 2 | 2 | 1 | 1 | 13 | 11 test3.py | 1 | 2 | 1 | 3 | 2 | 1 | 11 | 9 test1.py | 0 | 1 | 0 | 2 | 1 | 0 | 6 | 5 TOTAL | 4 | 9 | 5 | 17 | 17 | 4 | 58 | 49 -
--exclude-tests: テストコードを除外。