Windows
Unity
静的コード解析
SourceMonitor

【Unity C#】静的コード解析ツールを使ってみた

はじめに

静的コード解析ツールSourceMonitorを使ってみました。

静的コード解析ツール?

ソースコードの汚さを解析できます。

参考: 静的コード解析 - Wikipedia

なぜ使うのか

ソースコードが読みやすいのか、読みにくいのかといった事はソースコードを読むまで分かりません。
静的解析を使うことで、ソースコードを読まなくてもソースコードがキレイなのかどうかがある程度分かるようになります。

採用例

ゲーム会社のフロム・ソフトウェアあたりでも静的解析が使われているみたいです。
【CEDEC2013】静的解析で開発現場はどう変わったのか――自動化が変えたソフトウェア品質

C#ソースコードの準備

解析を行う前の準備としてNewBehaviourScript.csが1つだけ追加されたUnityプロジェクトを用意しておきます。

UnityのバージョンはなんでもOKです。 ソースコードがあればOK

NewBehaviourScript.cs
using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class NewBehaviourScript : MonoBehaviour {

    // Use this for initialization
    void Start () {

    }

    // Update is called once per frame
    void Update () {

    }
}

SourceMonitorを使ってみる

0. ダウンロード

下記URLからSourceMonitorをダウンロードしてインストールしてください
http://www.campwoodsw.com/sourcemonitor.html

image.png

1. SourceMonitorを起動

image.png

2. 新規プロジェクト作成

image.png

3. 解析したい言語を選ぶ

今回はUnity C#のソースコードを解析したいので、 C#を選択します。
image.png

4. ソースコードのディレクトリの指定

プロジェクト名とソースコードが置いてある場所のディレクトリを入力します。
image.png

5. サブディレクトリの指定

image.png

6. プロジェクト設定

今回は何も触らずに "次へ"を押します。
image.png

"次へ"を押します。
image.png

7. チェックポイント設定

Allow parsing of UTF-8 filesのチェックが外れていると、
UTF-8形式のソースコードが解析できないのでチェックを入れておきます。
image.png

8.完了

image.png

完了ボタンを押すとプロジェクト作成は完了です。

9.さっそく解析してみる

プロジェクト作成が完了すると、チェックポイント特定ウィンドウが表示されます。
image.png

閉じてしまった場合はメニューのNew Checkpoint...からチェックポイント特定ウィンドウを開くことができます。
image.png

10.解析結果

OKボタンを押すと、解析結果が表示されます
image.png

解析結果

出来たてのNewBahaviourScript.csを1個解析した結果は以下のようになりました。

項目名 意味 数値
Files ファイル数 1
Lines 空行も含んだコード行数 16
Statements 空行を除いたコード行数 6
Mathods/Class クラス1個あたりのメソッド数 2.00
Calls/Method メソッド呼び出し回数をメソッドの個数で割った値 0.00
Max Complexity 複雑度の最大値 1
Max Depth ネストの最大値 1
Avg Depth ネストの平均値 0.33
Avg Complexity 複雑度の平均値 1.00

結局どういう値なの?

解析結果の数値が分かりましたが、これらの数値がどのような意味を持つかがいまいちピンと来ない
そこで、ソースコードを一部変更してもう一度解析してみます。

検証1: if文を追加して静的コード解析してみる

NewBehaviourScript.csを以下のように編集してみます。

NewBehaviourScript.cs
using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class NewBehaviourScript : MonoBehaviour {

    // Use this for initialization
    void Start () {

    }

    // Update is called once per frame
    void Update () {

        if (Time.time > 1f)
        {
            Debug.Log("ゲーム開始から1秒以上経過しました");
        }

    }
}

ゲーム開始から1秒以上経過した場合にデバッグログを出すようにスクリプトを変更してみました。

検証1: 解析結果

このNewBehaviourScript.csを解析した結果は以下のようになりました

image.png

項目名 意味 数値
Files ファイル数 1
Lines 空行も含んだコード行数 21
Statements 空行を除いたコード行数 8
Mathods/Class クラス1個あたりのメソッド数 2.00
Calls/Method メソッド呼び出し回数をメソッドの個数で割った値 0.50
Max Complexity 複雑度の最大値 2
Max Depth ネストの最大値 3
Avg Depth ネストの平均値 0.88
Avg Complexity 複雑度の平均値 1.50

解析結果を比較してみる

項目名 意味 最初 検証1
Files ファイル数 1 1
Lines 空行も含んだコード行数 16 21
Statements 空行を除いたコード行数 6 8
Mathods/Class クラス1個あたりのメソッド数 2.00 2.00
Calls/Method メソッド呼び出し回数をメソッドの個数で割った値 0.00 0.50
Max Complexity 複雑度の最大値 1 2
Max Depth ネストの最大値 1 3
Avg Depth ネストの平均値 0.33 0.88
Avg Complexity 複雑度の平均値 1.00 1.50

考察

以下のようなことが読み取れます。
・メソッド呼び出し回数が増加
・複雑度が増加

考察 - 複雑度の増加について

表にて"複雑度"というワードが出てきますが、これはサイクロマティック複雑度を表しているそうです。

参考: 数値で測るコード品質 - give IT a try

このサイクロマティック複雑度(循環的複雑度)ですが、
条件分岐が1回登場するたびに複雑度は1増加するそうです。

つまり、下記のif文が原因で複雑度の最大値が1から2へ増加していたようです。

NewBehaviourScript.cs
if (Time.time > 1f)
{
    Debug.Log("ゲーム開始から1秒以上経過しました");
}

参考URL

数値で測るコード品質
http://blog.jnito.com/entry/20101231/1293770272

循環的複雑度を活かしたバグ潜在リスクの軽減
http://szk-takanori.hatenablog.com/entry/20111219/p1

検証2: if文の条件を増やしてみる

if文の中身の条件式を増やして静的コード解析してみます。

        if (Time.time > 1f && Time.time < 2f)
        {
            Debug.Log("ゲーム開始から1秒以上経過しました");
        }

検証2: 解析結果

項目名 意味 数値
Files ファイル数 1
Lines 空行も含んだコード行数 24
Statements 空行を除いたコード行数 8
Mathods/Class クラス1個あたりのメソッド数 2.00
Calls/Method メソッド呼び出し回数をメソッドの個数で割った値 0.50
Max Complexity 複雑度の最大値 3
Max Depth ネストの最大値 3
Avg Depth ネストの平均値 0.88
Avg Complexity 複雑度の平均値 2.00

今度は複雑度が3になりました。
if内部の条件式が増えると複雑度も増える、ということが読み取れます。

検証3: ifを入れ子にしてみる

ifで判定している箇所を以下のように変更して、静的コード解析してみます。

NewBehaviourScript.cs
        if (Time.time > 1f)
        {
            if (Time.time < 2f)
            {
                Debug.Log("ゲーム開始から1秒以上経過しました");
            }
        }

検証3: 解析結果

項目名 意味 数値
Files ファイル数 1
Lines 空行も含んだコード行数 27
Statements 空行を除いたコード行数 9
Mathods/Class クラス1個あたりのメソッド数 2.00
Calls/Method メソッド呼び出し回数をメソッドの個数で割った値 0.50
Max Complexity 複雑度の最大値 3
Max Depth ネストの最大値 4
Avg Depth ネストの平均値 1.22
Avg Complexity 複雑度の平均値 2.00

検証2と検証3を比較してみる

ここで、検証2と検証3を比較して表にまとめてみました。

項目名 意味 検証2(ifに条件式二つ) 検証3(ifの入れ子)
Files ファイル数 1 1
Lines 空行も含んだコード行数 24 27
Statements 空行を除いたコード行数 8 9
Mathods/Class クラス1個あたりのメソッド数 2.00 2.00
Calls/Method メソッド呼び出し回数をメソッドの個数で割った値 0.50 0.50
Max Complexity 複雑度の最大値 3 3
Max Depth ネストの最大値 3 4
Avg Depth ネストの平均値 0.88 1.22
Avg Complexity 複雑度の平均値 2.00 2.00

入れ子を作ると解析結果のネストの深さも上がる、ということが読み取れます。
複雑度は変わっていないことも読み取れます。

ネストについて

深いネストを作るとソースコードが読みづらくなるとよく言われます。
(参考: ネストの深さは闇の深さ)

解析結果のDepthが大きい数値になるようであれば、ソースコードを見直したほうが良さそうです。

まとめ

Complexity は分岐の数。 この数値が大きいと設計問題がある可能性が高い

Depthはネストの深さ。 この数値が大きいと読みづらいソースコードに

参考URL

複雑度と単体テストケース数の相関関係
http://forza.cocolog-nifty.com/blog/2009/01/post-ca39.html

ネストの深さは闇の深さ
https://qiita.com/Mic-U/items/1ec901864d4ab11c8d6f

メトリクス計測 SourceMonitor 紹介
https://qiita.com/syougun360/items/f38981dba32d3afd2957