良く見るプログラムの課題、FizzBuzz。問題の内容は以下の通り。
- 3で割り切れる数字の場合Fizzと言う
- 5で割り切れる数字の場合Buzzと言う
- 3でも5でも割り切れる数字の場合Fizz Buzzと言う
- 上記以外(3でも5でも割り切れない)であれば数値を出力
さて、このプログラムを素直に書いてみると
string fizz_buzzer(int value)
{
string result;
if(value % 3 == 0) result = "Fizz";
if(value % 5 == 0) result = "Buzz";
if(value % 3 == 0 && value % 5 == 0) result = "FizzBuzz";
if(value % 3 != 0 && value % 5 != 0) result = lexical_cast<string>(value);
return result;
}
こんな感じでしょうか。
「でもちょっと無駄があるなぁ…」
こう考えてしまうのが性。ぱっと見思い浮かぶのが
5で割り切れる時Buzzを代入するのではなく連結すれば1行減らせる
です。するとプログラムはこうなります。
string fizz_buzzer(int value)
{
string result;
if(value % 3 == 0) result = "Fizz";
if(value % 5 == 0) result += "Buzz";
if(value % 3 != 0 && value % 5 != 0) result = lexical_cast<string>(value);
return result;
}
しかし、一度判定している3もしくは5で割った余りを再度計算するのが面白くないです。
「そうだ、フラグを追加しよう」
するとプログラムはこうなります。
string fizz_buzzer(int value)
{
string result;
bool fizz_or_buzz = false;
if(value % 3 == 0) { result = "Fizz"; fizz_or_buzz = true; }
if(value % 5 == 0) { result += "Buzz"; fizz_or_buzz = true; };
if(!fizz_or_buzz) result = lexical_cast<string>(value);
return result;
}
冗長な判定が無くなりいい感じになりました。しかし、フラグ用の変数を追加したのが面白くない。
そこで次の点に着目します。
「FizzでもBuzzでもなければresultの長さは0で数値代入になるじゃないか」
するとプログラムはこうなります。
string fizz_buzzer(int value)
{
string result;
if(value % 3 == 0) result = "Fizz";
if(value % 5 == 0) result += "Buzz";
if(result.size() == 0) result = lexical_cast<string>(value);
return result;
}
「短くなって良かった。最初のプログラムと違いFizz/Buzzの条件が変わっても修正個所は1か所で済むし。」
そう思っていたんです。しかし、ある人の言葉でこれにも問題があることに気付きました。
「このプログラムは命題通りのプログラムになっているか?」
与えられた値に対する出力は要求通りです。しかし、このプログラムを見て最初の要求がわかるか?と言われれば確かにわかりにくいです。
FizzBuzzのように単純なプログラムであれば問題は無いですが、仕様をプログラマが脳内で最適化してコードに落とすと結果は合っていてもプログラムを見た時にその要求を満たしているかすぐわからないという問題が起きうるんですね。