メルカリインターンでGoの関数の複雑度を測るツールを作った話
こんにちは.先週、メルカリのオンラインインターンに参加してきました!
インターンの概要はここに載っています. mercan.mercari.com
最近いつも研究がやばいと言いながら、参加したい気持ちを抑えられませんでした…
概要
5日間でGoの静的解析を学び、ツールを作るという内容のオンラインインターンでした.
内2日はGoの静的解析についての講義、残りの3日間は開発というスケジュールです.
講義資料はここで公開されており、講義の録画も後日公開される予定みたいです.
講義も分かりやすいく、そもそもGo自体に静的解析周りの環境が充実していたこともあり、実装に集中できたと思います.
skeletonという静的解析のコードの雛形を作ってくれるツールはとても便利です.
開発はたまたま最初のビデオチャットで同じ部屋になった大学の友達のsff1019とチーム開発することにしました.
複雑度計測ツール
例えば、プルリクエスト(PR)をレビューしてる際に、そのソースコードの難易度がどれくらいなのかの目安を知りたいと思ったことはありませんか?
ソースコードの難易度については様々なメトリクスがありますが、今回はその中でもCyclomatic Complexity (循環的複雑度)とHalstead Complexity (日本語訳分からん) を計測する静的解析ツールを作りました. (複雑度の詳細については後述)
また、実際にGithub Actionsを利用することでPRの際に静的解析を走らせ、reviewdogというツールを使うことでPRのdiffに対しアノテーションをつける機能も実装しました.
リポジトリは以下です (ツール自体とGithub Actionsのaction).
使い方
インストール
$ go get github.com/shoooooman/go-complexity-analysis/cmd/complexity
実行
$ go vet -vettool=$(which complexity) .
Github Actionsとの連携
PRに対し実行したい場合は、Githubのリポジトリの .github/workflows/
以下に下のようなyamlファイルを作成してください.
on: pull_request jobs: reviewdog: runs-on: ubuntu-latest name: complexity analysis steps: - uses: actions/checkout@v2 - uses: shoooooman/go-complexity-analysis-action@v1 with: github_token: ${{ secrets.GITHUB_TOKEN }}
これでPRの際にactionが実行されアノテーションが表示されるはずです.
デフォルトでは Cyclomatic Complexity > 10
の関数のみに出力される設定になっていますが、これは変更することができます.
詳細な説明はリポジトリのREADMEをお読みください.
複雑度とは
そもそもCyclomatic ComplexityとかHalstead Complexityとか何?という話です.
Cyclomatic Complexity (循環的複雑度)
Wikipediaによるとプログラムの可読性に関係する指標で、以下のように定義されます.
M = E − N + 2P ただし、 M = 循環的複雑度 E = グラフのエッジ数 N = グラフのノード数 P = 連結されたコンポーネントの数
こちらの記事も分かりやすかったです.
gocycloを使ってgo言語のプロダクトをシンプルに維持する|moli9ma|note
しかし、実際にソースコードの循環的複雑度を計測する場合には if, if else, for, switch
などの分岐の数を数え計算することが多いようです.
今回の実装も関数定義の初期値を1とし、ブロック中の if, else if, for, case, ||, &&
の数を足すといった方法をとりました.
例えば以下のコードの場合、Cyclomatic Complexityは3になります.
func f(v int) { if v == 0 { // 1つ目 ... } else if v == 1 // 2つ目 ... } else { ... } }
循環的複雑度 | 複雑さの状態 | バグ混入確率 |
---|---|---|
10以下 | 非常に良い構造 | 25% |
30以上 | 構造的なリスクあり | 40% |
50以上 | テスト不可能 | 70% |
75以上 | いかなる変更も誤修正を生む | 98% |
Halstead Complexity
こちらはソースコードの難易度を表す指標です.
オペランドとオペレーターの数や種類によって計算されます.こちらのページを参考にしました.
Halsteadの計測の実装はチームメンバーのsff1019がやってくれました.
Maintainability Index (おまけ)
複雑度とは少し違いますが、保守性を表すMaintainability Indexというものが、上のCyclomatic ComplexityとHalstead Complexityを用いて計算できます.
せっかくなので、ツールにはこちらの指標も出力するように実装しました.
オリジナルの計算式をMicrosoftが正規化したものを使用しました.
Normalized Maintainability Index = MAX(0,(171 - 5.2 * ln(Halstead Volume) - 0.23 * (Cyclomatic Complexity) - 16.2 * ln(Lines of Code))*100 / 171)
Lines of Code (コードの行数) もコードの静的解析を行うことで取得できます.
改善すべき点
現時点ではPRを出した際に、diffのあるファイルに含まれる全ての関数についてCyclomatic Complexityを表示します.
本来は、diffを含む関数のみに出力すべきですが、範囲が広くなってしまっています.
diffの位置を取得しそれを静的解析ツールに渡せれば、その中でdiffを含む関数を調べ、その定義部分に出力するといったことができるのですが、良い方法が分かっていません.
何か良い案があれば教えていただけると嬉しいです.
ちなみにアノテーションではなくコメントで複雑度を表示しようとすると、Githubの仕様上、diffの前後数行までしかコメントをつけることができないため、厳しいです.
感想
5日間という短い間でしたが、インターン前と比べ静的解析やGoの仕様への (加えてGithub Actionsも) 理解が深まったと思います.
そして何より静的解析が楽しかったです.インターン後も時間があるときに何かツールを開発してみようと考えています.
CI連携について詰まった部分について話すと長くなりそうなので、また別の記事にでも書ければと思います.
関係者のみなさん、ありがとうございました!