はじめに
静的コード解析ツールSourceMonitorを使ってみました。
静的コード解析ツール?
ソースコードの汚さを解析できます。
なぜ使うのか
ソースコードが読みやすいのか、読みにくいのかといった事はソースコードを読むまで分かりません。
静的解析を使うことで、ソースコードを読まなくてもソースコードがキレイなのかどうかがある程度分かるようになります。
採用例
ゲーム会社のフロム・ソフトウェアあたりでも静的解析が使われているみたいです。
【CEDEC2013】静的解析で開発現場はどう変わったのか――自動化が変えたソフトウェア品質
C#ソースコードの準備
解析を行う前の準備としてNewBehaviourScript.csが1つだけ追加されたUnityプロジェクトを用意しておきます。
UnityのバージョンはなんでもOKです。 ソースコードがあればOK。
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
1. SourceMonitorを起動
2. 新規プロジェクト作成
3. 解析したい言語を選ぶ
今回はUnity C#のソースコードを解析したいので、 **C#**を選択します。
4. ソースコードのディレクトリの指定
プロジェクト名とソースコードが置いてある場所のディレクトリを入力します。
5. サブディレクトリの指定
6. プロジェクト設定
7. チェックポイント設定
Allow parsing of UTF-8 filesのチェックが外れていると、
UTF-8形式のソースコードが解析できないのでチェックを入れておきます。
8.完了
完了ボタンを押すとプロジェクト作成は完了です。
9.さっそく解析してみる
プロジェクト作成が完了すると、チェックポイント特定ウィンドウが表示されます。
閉じてしまった場合はメニューのNew Checkpoint...からチェックポイント特定ウィンドウを開くことができます。
10.解析結果
解析結果
出来たての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を以下のように編集してみます。
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を解析した結果は以下のようになりました
項目名 | 意味 | 数値 |
---|---|---|
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へ増加していたようです。
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で判定している箇所を以下のように変更して、静的コード解析してみます。
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