JavaScript
FizzBuzz

FizzBuzzは複雑か?

まとめ

FizzBuzzは複雑度1で書くことが出来る簡単なプログラム。
よく見るFizzBuzzは複雑度6でベタ書きに比べると6倍複雑なプログラム。

複雑なプログラムと複雑でないプログラム両方書けるか確認するため、通常のFizzBuzzに以下の要求を加え2通りのFizzBuzzを書いてもらうとよいのでは?

  • なるべく複雑度が低いFizzBuzzを書いてください
  • なるべく複雑度が高いFizzBuzzを書いてください

FizzBuzzとは

面接で書くプログラム、いいプログラマなら2分で書ける。

Most good programmers should be able to write out on paper a program which does this in a under a couple of minutes.
https://blog.codinghorror.com/why-cant-programmers-program/

複雑さを測る: Cyclomatic complexity

今回はFizzBuzzの複雑さをESLintのcomplexityを使って測定します。使用する言語はJavaScriptです。

Cyclomatic complexity measures the number of linearly independent paths through a program’s source code.
https://eslint.org/docs/rules/complexity

1. ベタ書きFizzBuzz

ベタ書きFizzBuzzのcomplexityは1です。複雑ではないです。複雑なプログラムを書きたくない場合はベタ書きしましょう。

fizzbuzz.js
/* global console */
/* eslint complexity: ["error", 1] */

function fizzbuzz() {
  console.log(1);
  console.log(2);
  console.log("Fizz");
  console.log(4);
  console.log("Buzz");
  console.log("Fizz");
  console.log(7);
  console.log(8);
  console.log("Fizz");
  console.log("Buzz");
  console.log(11);
  console.log("Fizz");
  console.log(13);
  console.log(14);
  console.log("FizzBuzz");
  console.log(16);
  console.log(17);
  console.log("Fizz");
  console.log(19);
  console.log("Buzz");
  console.log("Fizz");
  console.log(22);
  console.log(23);
  console.log("Fizz");
  console.log("Buzz");
  console.log(26);
  console.log("Fizz");
  console.log(28);
  console.log(29);
  console.log("FizzBuzz");
}

fizzbuzz();

ESLintの様子

2. Generator FizzBuzz

Generatorを使ったFizzBuzzのcomplexityは2です。ベタ書きFizzBuzzに比べると2倍複雑になってしまいました。
ただしこちらは上限の数を任意に設定できるので、ベタ書きがつらくなるまでFizzBuzzを出力する場合に便利です。

fizzbuzz.js
/* global console */
/* eslint complexity: ["error", 2] */

function fizzbuzz(max) {
  const gen = function*() {
    let n = 1;
    while (true) {
      yield n++;
      yield n++;
      yield "Fizz"; n++;
      yield n++;
      yield "Buzz"; n++;
      yield "Fizz"; n++;
      yield n++;
      yield n++;
      yield "Fizz"; n++;
      yield "Buzz"; n++;
      yield n++;
      yield "Fizz"; n++;
      yield n++;
      yield n++;
      yield "FizzBuzz"; n++;
    }
  }();

  for (let n = 1; n <= max; n++) {
    console.log(gen.next().value);
  }
}

fizzbuzz(100);

ESLintの様子

2. 無名FizzBuzz

complexityは2です。Generator FizzBuzzと同じ複雑さですね。

/* global console */
/* eslint complexity: ["error", 2] */

!function() {
  (M => ((f => (p => f(a => (p(p))(a))) (p => f(a => (p(p))(a))))(f => n => (t => n > 0 && t())(() => (() => (str => console.log(str || n))((str => n % 5 ? str : `${str}Buzz`) ((str => n % 3 ? str : `${str}Fizz`)(""))))(f(n - 1)))))(M))(100);
}();

ESLintの様子

6. FizzBuzz(並)

よく見るFizzBuzzを書いてみました。一気にcomplexityが6になっています。ベタ書きFizzBuzzの6倍、Generator FizzBuzzや無名FizzBuzzの3倍複雑になってしまいました。

fizzbuzz.js
/* global console */
/* eslint complexity: ["error", 6] */

function fizzbuzz(max) {
  for (let n = 1; n <= max; n++) {
    if (n % 3 == 0 && n % 5 == 0) {
      console.log("FizzBuzz");
    } else if (n % 3 == 0) {
      console.log("Fizz");
    } else if (n % 5 == 0) {
      console.log("Buzz");
    } else {
      console.log(n);
    }
  }
}

fizzbuzz(100);

ESLint

結果

4通りの書き方でFizzBuzzを書きESLintを用いて複雑度を計測しました。
ベタ書きやGeneratorや無名関数を用いてFizzBuzzを複雑度1〜2で書けると分かりました。
よく見るFizzBuzzは今回試した4通りの書き方の中では最も複雑度が高いFizzBuzzの書き方だと分かりました。

よく見るFizzBuzzとは

よく見るFizzBuzzが書けるプログラマーはベタ書きFizzBuzzが書けるプログラマーに比べて6倍複雑なプログラムを書けると言えそうです。しかし、複雑度1〜2で書けるプログラムを3〜6倍無駄に複雑に書いているとも言えそうです。

複雑度6は複雑なのか

非常に良い構造!!!

循環的複雑度の目安

循環的複雑度 複雑さの状態 バグ混入確率
10以下 非常に良い構造 25%
30以上 構造的なリスクあり 40%
50以上 テスト不可能 70%
75以上 いかなる変更も誤修正を生む 98%

https://jp.mathworks.com/discovery/cyclomatic-complexity.html