- 山田修路
- 約 2,500 文字
- 1,200 View
目次
TIG コアテクノロジーユニットの山田です。ソースやドキュメントを解析してファクト分析する仕事をしています。
今回は循環的複雑度の計算を題材に、Roslynを用いてVisual Basic.NETに対する解析コード1をC#で書いてみました。本記事では、メソッド単位の循環的複雑度の計算を、クエリメソッドを用いて構文木を走査する方法とVisualBasicSyntaxWalkerを継承したクラスを用いて構文木を走査する方法の2通りの方法を紹介します。それぞれの方法の特徴は以下の通りです。
- クエリメソッドを用いる方法
- あるノードの子ノードや子孫ノード、祖先ノードをクエリメソッドにより列挙して処理する形になります
- いわゆるXMLに対するXPathやHTMLに対するCSSセレクタのようなインターフェースです
- VisualBasicSyntaxWalkerを継承したクラスを用いる方法
- どの型のノードを訪問した際にどんな処理をするかというのを記述する方式となります
- いわゆるVisitorパターンです
Roslynとは
Roslynとは.NET Compiler Platformのコードネームで、コード解析ツール構築のためのAPIを持つコンパイラです2 3。Roslynが提供しているAPIを用いることで簡単に静的解析ができます。Microsoft公式で開発されているため、安心感がありますね。
循環的複雑度について
循環的複雑度とはコードの品質を表す指標の1つで、循環的複雑度が高いほど複雑な構造であるといえます。4 5
循環的複雑度は制御フローグラフのノード数nとエッジ数eから e - n + 2 という形で計算できます。
制御フローグラフに分岐が1つもない場合、この値は1となり、分岐が増えるごとに値が増えていくため、分岐の数 + 1という形で簡単に求めることができます。
今回の記事では、If, ElseIf, For, For Each, While, Case, Catch の数を数えて循環的複雑度を算出6することにします。
なお、IIf は一見三項演算子のようですが、扱いとしてはただの関数なので今回は対象としませんでした。自前で算出すると自由に計算ロジックを変えられるので、プロジェクトのルールに応じてカスタマイズできますね。
具体的には以下のようにカウントします。
Public Class A |
環境構築
下記ツールをインストールします。
- .NET 6.0 SDK
- Visual Studio Code
- C# 拡張機能
プロジェクト作成
下記コマンドでプロジェクトを作成します(-oで指定しているのはプロジェクト名です)。
これによりカレントフォルダに RoslynBlog.csproj ファイルが作成されます。
dotnet new console -o RoslynBlog |
デバッガの設定
さて、ここまでで C# の開発環境とプロジェクトの作成が済みましたが、まだデバッガが使用出来ない状態です。続いてデバッガの設定をしていきましょう。
といっても手順は簡単で、Visual Studio Codeのデバッグパネルを開き、 create a launch.json file をクリックするだけです。
これにより、launch.jsonが作成され、このようにデバッグが可能となります。
これで無事にデバッグできるようになりました。
開発
パッケージ追加
まず今回使用するパッケージを追加します。
dotnet add package Microsoft.Build.Locator --version 1.4.1 |
RoslynBlog.csproj ファイルをエディタで開くことで、依存パッケージが追加されていることが確認できます。言語とビルドツールが統合されており便利ですね。
計算対象のプロジェクトの読み込み
MSBuildWorkspaceを用いてプロジェクトを読み込み、各ドキュメントの各メソッドごとの循環的複雑度を計算して返します。
static async Task Main() |
クエリメソッドによる循環的複雑度の計算
こちらで紹介されているクエリメソッドを用いて循環的複雑度を計算します。
循環的複雑度の加算対象となるノードは以下のように判定できます。
public static bool IsDecisionNode(SyntaxNode node) |
上記メソッドを用いて、メソッド毎の循環的複雑度は下記のように計算できます。
static Dictionary<string, int> CalcCyclomaticComplexityByQueryMethod(SyntaxTree syntaxTree) |
SyntaxWalkerによる循環的複雑度の計算
構文木を走査しながら循環的複雑度を計算するSyntaxWalkerクラスを作成します。
internal class CyclomaticComplexitySyntaxWalker : VisualBasicSyntaxWalker |
作成した CyclomaticComplexitySyntaxWalkerクラス用いて、下記のように循環的複雑度が計算できます。
static Dictionary<string, int> CalcCyclomaticComplexityBySyntaxWalker(SyntaxTree syntaxTree) |
まとめ
今回はRoslynのSyntax APIを使い、Visual Basic.NETのプロジェクトを解析し循環的複雑度の計算をしてみました。
Roslynを使うことで(Solutionや)Projectを簡単に読み込み、解析できることがわかりました。
C#の循環的複雑度もノードの型が違うだけで、ほぼ同じ形で作ることができます。
今回の記事とは関係ないですが、C#だと Scripting API により、C#のコードをevalできるのですが、VB.NETのScripting APIは開発中止になったようなので今後使える見込みはなさそうです。
- 1.vblang/spec at main · dotnet/vblang · GitHub でantlrのgrammarが配布されているのですが、これを使ってparseできないようでした。 ↩
- 2.GitHub - dotnet/roslyn: The Roslyn .NET compiler provides C# and Visual Basic languages with rich code analysis APIs. ↩
- 3.10分間で人に説明できるまで分かるCompiler as a Service“Roslyn” - Build Insider ↩
- 4.バグの出にくいコードを書く~サイクロマティック複雑度について~ | w2ソリューション株式会社 TECH Media ↩
- 5.コード メトリック - サイクロマティック複雑度 - Visual Studio (Windows) | Microsoft Docs ↩
- 6..NETの静的解析ツールであるNDependの場合はcontinue, gotoなどもカウントするようですが、今回は計算対象外としています。Understanding Cyclomatic Complexity -- NDepend ↩