簡易式サイクロマティック複雑度
藤原:「ほな、今日はおまえらに「簡易式サイクロマティック複雑度」を学んでもらうで~」
浜田:「なんやねんそれ?」
藤原:「まずは、田中!これなんやと思う?」
松本:「藤原はホンマにいっつも台本通りやな」
田中:「えっ、僕ですか?え~っと、なんか渦巻いてるやつですか?」
藤原:「それは『サイクロン』や。お前の髪はよう渦巻いとるな。ほんなら、七崎。これなんやと思う?」
山崎:「七崎ちゃいますよ、山崎です、ヤ・マ・ザ・キ。え~っと、なんかこう、あれでしょ?難しいやつですか?」
藤原:「もっとボケろ~(蹴り) ほな、まずはサイクロマティック複雑度の説明を見てもらうで~」
サイクロマティック複雑度とは?
ソフトウェア測定法の一つでソースコードがどれぐらい複雑であるかをメソッド単位で数値にして表す指標です。
具体的には、プログラム内の独立した経路(パス)の数を表します。主にコードの保守性やテストの必要性を評価するために使用されます。
サイクロマティック複雑度の計算は、次のように行います:
- ノード:プログラムの各命令(処理の単位)をノードとみなします。
- エッジ:ノード間の制御フローをエッジとして考えます。
- 複雑度の式:複雑度は以下の式で求められます。
V(G)=E−N+2P
- V(G): サイクロマティック複雑度
- E: エッジ(制御フローのリンクの数)
- N: ノード(命令の数)
- P: プログラムの連結成分の数(通常は 1)
ほ~ん、で?
藤原:「これがサイクロマティック複雑度の説明や~。浜田、わかったか~?」
浜田:「わっけわからん」
松本:「そらモンゴルから来た留学生には難しいわな」
藤原:「見てもろたとおり、めっちゃむずいし実用的やない。やから、おまえらにもわかるように、今日はつぎのように計算しよか~」
簡易式サイクロマティック複雑度の公式
- 関数のハッピーパス: 1
- 条件分岐の数: x
サイクロマティック複雑度 = 1 + x
これなら
藤原:「これは簡易的にしてるから、実際の公式とは違う答えになることもあるけど、目安としては役に立つで~」
浜田:「これならなんとなくわかるな」
藤原:「ほんなら、遠藤!条件の数が 2 やとしたら、サイクロマティック複雑度はなんぼや?」
遠藤:「え~・・・っと、2 やから、4?」
藤原:「ほんま男前やな~。正解は 3 や」
山崎:「それで、これから何するんですか?」
藤原:「これから~、えーっと・・・」
藤原:「あっ。え~、人間の脳はな、短期記憶ってものがあって、そこに入るのは限られてるねん。知ってたか~?」
松本:「ほんまアドリブ効かへんな~」
藤原:「認知科学的にな、人間が短期で覚えられるのは 7 ± 2
って言われてるねん。やから、おまえらには、この 7 って数字だけを覚えてもらえばええからな。ほな、これからおまえらに問題を解いてもらうで~」
藤原:「それぞれの問題は簡単な typescript で書いてるから、おまえらでもわかると思うで~」
問題 ①
function gakinotsukai(member: string) {
if (member === '山崎') {
return 'へたれ';
}
return 'スベる';
}
藤原:「ガキのメンバーの関数を作ってみたで~。簡単やな。ほな浜田!このサイクロマティック複雑度はなんぼや?」
浜田:「これは簡単やろ。山ちゃんで一回分岐して、他はスベってるだけやから、1+1 で 2 やろ?」
藤原:「正解や。さすがリーダーやな。浜田さんのスゴさは。南半球を駆け巡るで」
問題 ②
function gakinotsukai(member: string) {
if (member === '山崎') {
if (isChouno) {
return 'ビンタ';
}
return 'へたれ';
}
return 'スベる';
}
藤原:「次は、isChouno
を追加したで。この関数の実装の詳細は追わなくてええから、gakinotsukai
の関数だけでサイクロマティック複雑度をはかってみよか~。ほな松本!これはなんぼや?」
松本:「これは、あれやろ?蝶野が出てくるってことは、蝶野ビンタの条件が追加されとるわけやから、条件は 2 つになって、1+2 で 3 やな」
藤原:「正解や。さすが、ボケの天才やで。松本さんのスゴさは北半球を駆け巡るで」
問題 ③
function gakinotsukai(member: string) {
if (member === '山崎') {
if (isChouno) {
return 'ビンタ';
}
return 'へたれ';
}
for (const i = 0; i < 7; i++) {
hamadaShoutLoudly(
'第一回チキチキ「簡易式サイクロマティック複雑度」選手権!'
);
}
return 'スベる';
}
藤原:「こっからちょっと難しくなるで~。for 文を追加してみたんやけど、遠藤!このサイクロマティック複雑度はなんぼや?hamadaShoutLoudly
の実装の詳細は気にせんでええからな~」
遠藤:「浜田さんが 2 で、松本さんが 3 やろ?ほんなら、4 ・・・?」
藤原:「一応正解や~、ホンマ男前やな。補足すると for 文自体が分岐になってるんやけど、その中の実行処理自体は分岐してないから、1 ってカウントしてるんや。中で分岐したら、それもカウントせなあかんで~」
問題 ④
function gakinotsukai(member: string) {
if (member === '山崎') {
if (isChouno) {
return 'ビンタ';
}
return 'へたれ';
}
switch (member) {
case '松本':
return 'おもろい';
case '遠藤':
return '男前';
default:
console.log('豊中のハゲカッパ');
}
for (let i = 0; i < 7; i++) {
hamadaShoutLoudly('第一回チキチキ「サイクロマティック複雑度」選手権!');
}
return 'スベる';
}
藤原:「さらに複雑なってきたな~。switch 文を追加してみたで。田中!これはなんぼや?」
田中:「そうですね。switch 文で条件(case + default)が 3 つ追加されてるんで、条件は全部で 6 つあるから、1+6 で 7 ですか?」
藤原:「正解や。さすがやで。田中のスゴさは、南九州を駆け巡るで」
田中:「僕だけ規模小さくないですか?あと、僕だけ gakinotsukai
関数で名前出てないんですけど」
藤原:「これが、簡易的なサイクロマティック複雑度が 7 の実装や。ちょっとややこしなってきたやろ~?人間の脳は、7 からキツなってくるで~」
問題 ⑤
function gakinotsukai(member: string) {
if (member === '山崎') {
if (isChouno) {
return 'ビンタ';
}
return 'へたれ';
}
switch (member) {
case '松本':
return 'おもろい';
case '遠藤':
return '男前';
default:
console.log('豊中のハゲカッパ');
}
for (let i = 0; i < 7; i++) {
hamadaShoutLoudly('第一回チキチキ「サイクロマティック複雑度」選手権!');
}
return 'スベる';
}
藤原:「ほなこれで最後や。山崎!これはなんぼや?」
山崎:「これ、田中のときと一緒でしょ?騙されませんよ!1+6 で 7 でしょ?」
藤原:「不正解や。よ~見てみ?引数の member が null も取るようなっとるやろ?ほんで、最後の return で null 合体しとるやろ?null 合体も 1 つの条件分岐として考えられるから、これは 8 や~。まあ、ちょっと難しかったな」
ほんで?
藤原:「さて、お前らには問題を解いてもろたけど、どうや?特に山崎、どう思った?」
山崎:「上から読んでいったんですけど、null 合体を見逃しましたね」
藤原:「せやろ?最初に言ったんやけど、人間の脳はだいたい 7 つまでのことしか同時に処理できひんねん。山崎が平均やとしたら、間違うのは別に不思議やないんや」
山崎:「でも、これくらいのコードなんて、結構あるんとちゃいます?」
藤原:「せや。やから、簡易式サイクロマティック複雑度が 7 を超えたからって悪いわけやないねん。やけど、人間の脳には収まりにくいコードになってるって、一つに指標にはなるで~」
浜田:「ほんなら、どないしたらええねん」
藤原:「さっきのコードやったら、条件分岐をカプセル化して、null の処理を早めにもってきたら、もっと読みやすいで~」
リファクタしてみるで~
function gakinotsukai(member: string | null) {
// null の場合は早期リターン
if (member === null) return 'スベる';
// 松本・遠藤の場合の判定処理をカプセル化
if (isMatsumoto(member)) return 'おもろい';
if (isEndou(member)) return '男前';
// 山崎の場合の処理は参考演算子で表現
if (member === '山崎') {
return isChouno ? 'ビンタ' : 'へたれ';
}
console.log('豊中のハゲカッパ');
// forループの処理を関数で切り出してシンプルに
Array.from({ length: 7 }).forEach(() => {
hamadaShoutLoudly('第一回チキチキ「サイクロマティック複雑度」選手権!');
});
// 最後の戻り値
return member;
}
藤原:「ちょっとリファクタしてみたで~。ほんならこれの簡易式サイクロマティック複雑度はなんぼや?浜田、答えてもらえるか~?」
浜田:「if が 4 つやけど、中に三項演算子が 1 つあって、あとは、forEach が 1 つあるから、1+6 で 7 か?」
藤原:「正解や!こうすれば、人間でも読みやすくて、ぎりぎり理解できるコードになるってことやな~。これ以上、条件が増えたりするなら、実装の詳細をカプセル化したり、関数自体を分けることを検討したほうがええで~」
まとめ
藤原:「今日はおまえらに簡易式サイクロマティック複雑度を学んでもろたけど、これはあくまでも指標や。でも。条件が 7 を超えそうなときは、リファクタリングを検討するいいタイミングやと思うで。あと、変数の数が 7 を超えへんようにするのもええな~」
浜田:「でも、なんでガキのメンバーでこんなことやったん?」
藤原:「ごっつええ質問やな~。浜田、ガキ使のメンバーって何人や?」
浜田:「5 人やろ?」
藤原:「そうや。ほんで、司会進行の僕を入れたら、なんぼや?」
松本:「6 人やな」
藤原:「最後に、ガキとしての全体をハッピーパスと考えると?」
全員:「 7 や!」
藤原:「せやから、ガキ使は宇宙一おもろいし、人間の脳に収まりやすい設計になっとるんやろな~。知らんけど」
おしまい。
あとがき
簡易式と実際のサイクロマティック複雑度の計算は合わないことが多いです。
あくまでもそろそろリファクタリングせなあかんな~、って感じの参考にしてもらうくらいが、ちょうどええかな~って思います。
実際の計算のほうが正しいのですが、実務上で計算するには中々大変なので、Eslint や Jest + Code Coverage で計測して運用するのが良さそうですね。
Eslint では以下のように設定できます。error にしておくと、CI で検知できるのでいいですね。
export default defineConfig([
//
{
rules: {
complexity: ['error', { max: 7 }],
},
},
]);
ちなみに、一番好きな企画は「ハイテンション・ザ・ベストテン」です。よろしくお願いいたします。
宣伝1
文字列や配列の分割、地味に毎回書いてへん?
そこで、divider
っていう文字列と配列を分割するユーティリティライブラリを作ってみたから、みんな寄って見てって~!
気に入ったら ☆ してな!
🔗 GitHub: nyaomaru/divider
🎮 Playground: divider-docs.vercel.app
divider の Playground もあるから、よかったら遊んでってな~
宣伝2
Zenn でも記事書いてるから、よかったら見てってな~