見出し画像

クオリティを爆上げろ!! ~Cyclomatic complexity とは!?

こんにちは。ゼバ会です。
皆さん Cyclomatic complexity はご存知でしょうか。
ただ知っているだけでプログラムのクオリティが上がるという、非常に便利な考え方です。
知ってる方、知らない方、名前だけ知ってる方、いろいろだと思いますので、今回はこれを説明したいと思います。

こいつは本当にすごい考え方で、練習とか何にもいらないんです。
「サイクロマティックコンプレキシー」と読み、本当にただ知っているだけでプログラムがワンランク上の出来栄えになるんです。
メソッド1つあたりの複雑さを示す概念で、日本語では「サイクロマティック複雑度」「循環的複雑度」などと呼ばれたりもします。
(当ブログでは英語表記 Cyclomatic complexity で統一します)

当たり前ですけど、プログラムって、造りがシンプルな方がいいですよね。
なぜって、シンプルな方がバグも出にくいし、メンテナンスもしやすいからです。

でもプログラムの複雑さなんてどうやって計ります?
行数? つまり行の長いメソッドは複雑ってことにする? するってぇと、何もしてないけど改行を無駄にたくさん挟んだコードは「複雑」って扱いになっちゃう?
逆に意図的に改行を全消ししたらシンプルなコード?

それとも、文字数?
つまり説明をていねいにすごくいっぱいコメントで書いたコードは複雑?

コメントを複雑さに含めたくないなら命令数にする?
でも命令が縦に10個並んでるだけのメソッドより、if 分岐がいくつも大量にあるメソッドの方がバグは出やすいですよね。

こんな感じで、メソッドの複雑さを数値で推し量るのは意外と面倒なのです。
そこで登場したのが Cyclomatic complexity というわけ。
これは「プログラムの複雑さの要因になる要素が登場したら1カウント」とする考え方です。
具体的には、

・分岐条件式1つを 1 と数える
・分岐処理を 1 と数える
・命令1つを 1 と数える

この条件で数を数えていって、最終的に数が少ないメソッドを「よりシンプル」とします。
たとえば上記のルールで数えるとき、以下のプログラムの Cyclomatic complexity は12です。

function findCharactor(string text, char search)
{
3 if (text == null || length(text) == 0) {
1   return -1;
  }
4 for ( int idx = 0; idx < length(text); idx++ ) {
2   if ( text.charAt(idx) == search ) {
1     return idx;
    }
  }
1 return -1;
}

行頭の数値が1行あたりの複雑度となり、その合計が Cyclomatic complexity です。
なので if 文が2つ、if の中の条件式が計3つ、return 命令が3つで、for 文は 初期化・判定・増分・ループ の4つのステートメントを固定的に消費するので合計12となります。

あ、ちなみにですけど、細かい数え方は実は場合によってバラバラだったりします。( if とループの数だけをカウントする方法とか)
でもそこは問題ありません。
なぜって、自分達のプログラムのクオリティを上げることが重要なのであって、世の中のスタンダードに従うことは目的じゃないからです。
なのでお使いのフォーマッターがたまたま私と違う数え方をしていたとしても、そこには何の誤解も問題もありません。

大事なのは、この Cyclomatic complexity という数値は、例外エラーが出る確率と正確に比例することです。
なぜなら、より複雑なメソッドは、場合によりバグが出うることを目検で確認することが困難になり、必要なテストケースの数も肥大化することになるからです。
(もちろんだいたいざっくりですけど)理論上では絶対万全といえる単体テストケースの数は、おおむね Cyclomatic complexity の2乗になります。
ゆえに、テストケースの数が少なくて済むシステムの方が、万全を謳いやすいのは数学上の必然というわけです。

ですので、プログラマーの能力が一定であれば、Cyclomatic complexity が低い方が例外エラーが出にくいと、少なくとも統計的には言いきれてしまうのです。

ゆえに、少なくとも「例外エラーをとにかく減らす」という目的においては、この数を減らすこと自体が直接の目標値になります。
世の中的には、この Cyclomatic complexity を10以下にするのが望ましいとされているようです。

〇 シンプルなメソッドの作り方にはコツがある

ちなみに読者の中で、仕事中の息抜きに当ブログを読んでる方はいますでしょうか。
その方の中には、「さっそくやってみよう!」って、挑戦しようとした方もいらっしゃると思います。

ですが、、、
それらの方々のうち約半数は、やろうとして手が止まってしまったのではないでしょうか?
Cyclomatic complexity を10以下にするって、スッゲー難しいんですよね。
私が上記にサンプルとして載せたコードですら、すでに12です。
通常であれば、今まで1度もやったことがない人が、すぐにできるわけがないのです。

なぜなら「すでに頭の中でイメージが出来上がってる」ものを、さらに再分割しながらプログラミングするのですから、そりゃあ難しいです。
それはプログラミングではなくリプログラミングです。

もちろんあっさりできちゃった方は問題ありません。
それらの方々には、「これから後輩指導よろしくね!」と言わせていただきます。
ですがそれ以外の多くの方には、「処理手順をイメージする」という訓練が必要です。

〇 大事なのは処理手順を先に明確化すること

当たり前ですが、物事には順序というものがあり、とりわけプログラムは順序を考えずして作ることはできません。
ですから、まず頭の中で手順を作ってから、次にそれをプログラムコードとして打ち込むことになります。

その際、Cyclomatic complexity がやたら大きなプログラムを書く人(いわゆるコードがスパゲッティ化しやすい人)は、順序を考えるときに手順1つ1つの範囲が凄くざっくりしてて大きい傾向があります。
具体的には、「人間都合の手順書」(いわゆるビジネスロジック)だけで考える人は、メソッド1つ1つがすごく大きくなります。

たとえば「画面に名前を表示する」という手順があったとすると、画面に名前を表示したいのは人間の都合ですから、そのためにいくつもの手順が必要です。

1. データベースに接続する
2. 欲しい情報がデータベースのどこにあるか指示する
3. データを取得する
4. 画面に表示できるデータ形式に加工する
5. 名前を表示するための画面部品にアクセスする
6. 画面部品に名前をセットする

場合にもよるでしょうが、パッと考えつくだけでもこれだけの手順が必要です。
プログラミングという行為自体に不慣れな初心者さんなどは、これらの手順を考えずに「まず画面に名前を表示する」という手順を全部ひとまとめに考えるので、必然的にこれらの処理が全て1つのメソッドに内包されやすくなり、メソッドが非常に巨大になります。
一般に、内包する処理の多いメソッドはそれだけ多くの入力データを必要とするのですから、入力される可能性のあるパターン数がその分だけ増えてテストケースが増えることになるのです。

また、世の中にはプログラミングしながらじゃないと、設計できない人もいるでしょう。
それは初心者であればしょうがないことですが、プロ(特に上級者)がいつもいつもそんなやり方をやっていてはダメです。
なぜなら、書く前にまず整理するプロセスを挟まないことによって、さらに良い方法論があることに気づくチャンスを失うからです。

一度書いてちゃんと動いたプログラムは、さらに良い方法が見つかっても消すのがもったいなくなります。
ならない人もいるでしょうが、通常多くの人は、がんばって一生懸命書いたコードほど、多少問題を内包していても消すのがもったいないと感じるはずです。
このような心理効果をコンコルド効果といいます。
そして、コンコルド効果によって延命されたコードは、早めに消していればいらなかった改修コストを生みます。
このコストのことをサンクコストといい、本当に言い訳もできないくらい完全な無駄です。

世の中に無駄なものはないといいますが、そんなことはありません。
サンクコストと贅肉だけは、あっても本当に何の得もないのです!!!
贅肉! 贅肉贅肉贅肉!!!! 特に贅肉を減らしたい!!!

まぁ、そうならないために、プログラムは書く前に、まず頭の中での整理が必要なのです。
それが「しっかり吟味する」ということです。

〇人間都合の手順書は4項目1組になるはず

また、頭の中で手順を整理するときは、その全てを縦にズラッと並べないことも重要です。

たとえば手作りラーメンを土から作ろうとするとき、その手順はかなり膨大なものになるでしょう。
ですが、だからといって100個も200個もある手順書を、1から順に理解しようとする人はいません。

悪い手順書の例)
1. 休眠農地を探す
2. 不動産屋と契約をする
3. ホームセンターへ行く
4. 小麦の苗を買う
5. 植える
6. 小麦を育てる
7. 雑草を刈り取る
  :
  :

こんなの誰が見ても悪い手順書です。
おそらくこの下にはもっと膨大な手順があるのでしょうが、どのタイミングで、どういうことを、なぜやるのかさっぱり分かりません。

なぜなら、人間は手順が5個以上ある手順書を「複雑だ」と感じるようにできているからです。
コンピューターは手順が何億個あっても順番に実行するだけですが、人間は「ありとあらゆる手順書」を4ステップで執筆しなければならないのです。
それが嫌だと言う人は、電脳化手術を受けてサイボーグになるか、でなければヴァイエンススーツを購入していただくしかありません。

たとえば土から手作りラーメンを作る手順書は、分かりやすく整理するとこうなります。

良い手順書の例)
1. 小麦を用意する
2. ラーメン生地を作る
3. スープを作る
4. 調理する

ほら。こんなにシンプルになった。
で、大項目としては4つしかなくても、当然ながら「1. 小麦を用意する」の中にはさらに細かな手順があるはずです。

小麦粉を用意するための手順書の例)
1.1. 入手方法を検討する
1.2. 購入する

要は、手順書ってのは「手順が縦にズラッと並ぶ」から混乱するのであって、「手順1つの中に子手順が4つだけ入ってる」分には、人間は混乱することはないわけです。
もちろん、子手順4つそれぞれの中に孫手順がそれぞれ4つずつ入ってるかもしれませんが、それも同じ。
こんな感じで手順書をどんどん細かくしていく作業を、一般に「作業の落とし込み」と呼びます。
徐々に段々落ちていくから落とし込み。

昨今ではウォーターフォール型開発モデルは徐々に否定されつつあります。
みんながアジャイルに飛びついてから、この「落とし込み」がちゃんとできる人は、多少ですが少なくなってしまいました。
(もちろんできる人はいつの時代もちゃんとできるんだけど、ちゃんとできる人が育ちにくくなったということ)

手順を人間にとって分かりやすい形に落とし込めば、それは必ず以下のようなツリー構造になるはずです。

土からラーメンを手作りする手順書の例
1. 小麦を用意する
  1.1. 入手方法を検討する
    1.1.1. 近所のスーパーで買うか検討
    1.1.2. 地方の評判のいい農家から買うことを検討
    1.1.3. 自力で栽培することを検討
  1.2. 購入する
    1.2.1. 販売元に購入を希望する
    1.2.2. 購入する
    1.2.3. 代金を支払う
2. ラーメン生地を作る
  ……
  ……
3. スープを作る
  ……
4. 調理する
  ……

で、理論上は、この手順書の一番下の階層(上記の例だと3段目)の1つ1つが「1メソッド」になります。
(もちろん実際にはサービス全体の手順とメソッド単位の手順が両方同時に書かれた書類なんてないだろうし、仮にあっても巨大すぎて人の手には負えないでしょうが、作り手の頭の中では手順書がキチンと描かれていることが大事ということです)

プログラムを作る前にこのような手順書をまとめることで、Cyclomatic complexity が規定値を超えないかどうかがコードを組む前にイメージできるわけです。
だって、書けそうになかったら手順書をまとめなおせばよいだけのことですから。
現物のプログラムはまだ存在しないので、整理しなおしは簡単です。

ただし、以下のような失敗をしてしまうとせっかく手順書をまとめても無駄になってしまうので、要点に気をつけながら手順がまとめられるようになるまでに多少の慣れ(練習)は必要です。

〇 Cyclomatic complexity のイメージが不明瞭となる失敗例

1. 親項目、子項目、孫項目 の粒度がバラバラ

頭の中で手順をまとめるという作業にあまり慣れてない人がやってしまいがちなミス。
たとえばこういうヤツ。

1. 小麦を用意する
2. ラーメン生地を作る
3. スープを作る
4. 手袋をつける (←!?)
5. 調理する

上記の例は、一番大きな大項目となる手順の中に、一部細かい個人的なものが混じっている例です。
このようなパターンは、「たまたま思いついた順」に作業手順書を作ることによって発生します。
サービスデザインの実例でいうと、「お客さんと打ち合わせ」「仕様策定」みたいな大きな項目と同じ並びに「ヘッダーに模様をつける」なんてタスクが入ってくるような感じでしょうか。

これは、タスクの重要性の大小を、自分だけの価値基準で判断することで起こります。
あなた自身は料理をするとき手袋をつけるかもしれず、Web画面のヘッダーデザインだって十分に重要事項なのかもしれませんが、あなた以外の人はそんなふうには考えません
手順書というのは、そのシステムを末永く平和裏に保つことを目的とした資料ですので、あとから見た人が納得できなければいけないのです。

重要性の判断を誤ったまま組んだプログラムは、おそらく他人からはグチャグチャに見えます。
また、もしそういうの感覚的にピンとこないようであれば、上司や同僚に相談するのも大事です。

2. 詳細設計書と構造の異なるプログラムをしてしまう

詳細設計書に手順書を書く際に、本当は自分でも納得していない場合にやってしまいがちです。
あるいは、設計書通りにプログラムが書けないことに、プログラムを組み始めてから気づいた場合にもやってしまうかもしれません。
設計を変えたんなら設計書も変えなきゃいけないんだけど、面倒くさくてそのまま突っ走っちゃう。

そんなとき、とりわけ疲れてると「結果的に同じならいいでしょ」って思ってしまいがちだけど、ダメです。
Cyclomatic complexity を最低限にするとは、自分のプログラムがバグの出にくい構造であることを書面で証明するに等しいことです。
設計書と違うことをやると、それが保証できなくなってしまいます。

また、もしそのような「詳細設計書とプログラムが一致しない」現象がチーム内で慢性的に発生しているなら、個人的には DIC 法 でのプログラム記述に挑戦してみてほしいところです。
(DIC法でのコード記述それ自体は、そんなに身構えなくても、やりたいことを日本語で説明するという意識があればさほど難しくないと思います)

3. 使用するコンピューター言語で「できること」「できないこと」を理解しないまま手順書を書く

んで、上記の 2. を読んでいて「そんなこと言われてもなぁ」と思った方は、おそらく自分が使っている言語の仕様をまだ理解していないということでしょう。
プログラム言語は、十分に精通してから使い始めることも重要です。
最近は有料の言語は少なくなりましたから、次に入る現場の言語が未知のものなら個人的に使ってみるのもいいでしょう。

「おそらくできるだろう」では実際に書き始めてからパニクるので、使用する言語にあらじめ精通しておくことです。
まぁ、それがプロってものですからね。

もっとも、世の中にはクライアントとの人間関係の問題で無茶を言われて横車を押している方も多く、本当にどうしようもない方だっているとは思います。
そういう方は、、、、まぁ、戦略的撤退は逃げじゃないと思っていただければと。

〇次回予告

さて。
今回で「プログラムは分かりやすく組む」という初心者いじめのようなシリーズが終了します。
もちろん読者の皆さんの頭の中がスッキリシャッキリスーパークリアになったわけではないでしょうが、語ることは語り尽くした感じです。

頭の中をクリアにする能力は大事なので、今後も必要があれば書いていきますが、とりあえず次回はクリーンアーキテクチャーの話に戻ろうと思います。
第1回で「クリーンアーキテクチャーを中心に据える」と言っておきながら、実はまだあの本に書かれてるようなことを1回も書いていないんですよね。

てなわけで次回は、「アーキテクチャー設計の始め方 ~フレームワークは最新かどうかで選ぶな!」でお送りします!

この記事が気に入ったらサポートをしてみませんか?