10
7

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 3 years have passed since last update.

保守容易性指数(Maintainability Idex)の計算方法を理解する

Last updated at Posted at 2021-02-23

概要

最近、flutterでコードメトリクス1を計測したら、保守容易性指数(Maintainability Idex)という指標が出てきました。

image.png

ちょっと調べて見たところ、C#とかでは結構メジャーっぽい(?)。
どうやって改善するのかわからないので調査します。
(最近まで全然知らんかったんだけど、この指標みんな使ってるの?)

目次

保守容易性指数の目安

コードメトリックス-保守容易性インデックスの範囲と意味(Microsoft)によると、評価基準は以下の通りらしい。

  • 0-9 = 赤
  • 10-19 = 黄
  • 20-100 = 緑

ただし、コレはMicrosoftの2保守容易性指数(Maintainability Idex)なので、他のツールだとちょっと違うかも。
でもこの基準を参照すると、僕のflutterコードはめっちゃきれいですやん。

保守容易性指数の計算式

いろいろなところで計算式は紹介されていますが、概ね2以下のとおりです

保守容易性指数
= 171 - 5.2 * log(Halstead複雑度のVolume) - 0.23 * サイクロマチック複雑度 - 16.2 * log(コードのライン数)

まず、 マジックナンバー(171とか)については諦めます
流石に調査はキツイので、誰が知っている人がいたら教えてください...

したがって、このページでは'サイクロマチック複雑度'と'Halstead複雑度のVolume'の具体的な計算方法を紹介します。

サイクロマチック複雑度

概要

英語版のWikipediaを引用します。

ソースコードのサイクロマチック複雑度は対象のセクションにおける'線形独立'なパスの数です。
'線形独立'とは、他のパスに存在しないエッジが1つ以上存在することです。

具体例

以下の関数のサイクロマチック複雑度を計算します。

int test_function(int max_index){
  if (max_index == 0) return 0;
  
  for (int index = 0; index < max_index; ++index){
    // 処理A
    if (index == 2){
      // 処理B
    }
  }

  return 1;
}

この関数のCFD3は以下のようになります。(レイアウトのため横書きにしています)

base.drawio.png

では、Entry PointからExitまでの'線形独立'なパスのパターン数を数えます。

base.drawio.png

base.drawio.png

base.drawio.png

base.drawio.png

上図の通り、4通りのパスが存在するため、サイクロマチック複雑度は4です。
また、Wikipediaには以下のように人間にとって簡単な計算方法が紹介されています

サイクロマチック複雑度Mは以下のように計算されます
M = E - N + 2P
E = CFD3のグラフのエッジ数
N = CFD3のグラフのノード数
P = CFD3の連結グラフ4の数

連結グラフ4の定義を参照すると、1つの関数のCFDではP=1だと言うことがわかります。
したがって、

M = E - N + 2

平面においては面の数=エッジ数-ノード数+2であるため、1つの関数のサイクロマチック複雑度MはCFD3の面の数を数えれば良いことがわかります。

base.drawio.png

Halstead複雑度のVolume

英語版のWikipediaのHalstead複雑度の項では以下の3種類の指標が定義されています

  • Volume
  • Difficulty
  • Effort

ここでは、保守容易性指数(Maintainability Idex)の計算に必要なVolumeの概要と具体例を記述します。

概要

プログラムの長さ・大きさを表現する指標です。ざっくりいうと以下のように定義されています。

Halstead複雑度のVolume = 単語の数 * 1単語あたりのビット数

つまり、Halstead複雑度のVolumeとは プログラム全体をビット配列で表現したときの長さ のことです。

ココで言う'単語'はオペレータ5とオペランド5のことです。
しかしながら、単純にトークンのことだと考えても結果に大して違いはない(はず)です。

具体例

英語版Wikipediaの例をそのまま引用します。

main()
{
    int a, b, c, avg;
    scanf("%d %d %d", &a, &b, &c);
    avg = (a + b + c) / 3;
    printf("avg = %d", avg);
}

まず、1単語あたりのビット数を計算します。このプログラムには、以下の12種類の'単語'が出現します。

  1. main
  2. ()
  3. {}
  4. int
  5. scanf
  6. &
  7. =
  8. /
  9. printf
  10. ,
  11. ;
  12. a
  13. b
  14. c
  15. avg
  16. "%d %d %d"
  17. 3
  18. "avg = %d"

これをビット配列で表現するとき、必要なビット数はlog2(19)です。コレが'1単語あたりのビット数'になります。
次に、'単語の数'を数えると、27個存在することがわかります。

したがって、Halstead複雑度のVolume = 27 *log2(19)となります。

最後に

Halstead複雑度のVolumeは複雑な解説資料多いけど、ちゃんと資料読んだら概念はめっちゃ単純でした。
ここまで読むと、以下の式で算出される値を改善する方法もなんとなくわかってくると思います。

保守容易性指数
= 171 - 5.2 * log(Halstead複雑度のVolume) - 0.23 * サイクロマチック複雑度 - 16.2 * log(コードのライン数)

参考にした資料

  1. https://pub.dev/packages/dart_code_metrics

  2. 場所によって0-100の値に正規化していたり、コメント行数を考慮した式が追加されていたりします 2

  3. Control Flow Diagram。clangを使えば自動的に生成もできる 2 3 4 5

  4. 連結グラフ 2

  5. ググってくれ! 2

10
7
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
10
7

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?