デジタルちんぶろぐ

デジタルな話題

ホーム 連絡をする 同期する ( RSS 2.0 ) Login
投稿数  268  : 記事  0  : コメント  4375  : トラックバック  79

ニュース


技術以外は
ちんぶろぐ

記事カテゴリ

書庫

日記カテゴリ

良く見るプログラムの課題、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のように単純なプログラムであれば問題は無いですが、仕様をプログラマが脳内で最適化してコードに落とすと結果は合っていてもプログラムを見た時にその要求を満たしているかすぐわからないという問題が起きうるんですね。

投稿日時 : 2010年10月19日 23:44

コメント

# re: FizzBuzzと貧乏性 2010/10/20 9:12 びーしむ
> プログラムを見た時にその要求を満たしているかすぐわからないという問題
これに対する1つの解が、テストファーストの開発手法なのでしょうね。
実装方法は、任せるから、仕様どおり出力してくれればOKと言うことです。

わかりやすさをあげるにはコメントだと思います。関数の先頭に
/// この関数は、以下の仕様で動作します。
って書くとか。

わかりやすい・わかりにくいは、同じコードでも読み手のレベルによってで変わるので、難しい基準ですねぇ。
例えば、説明の中で使っている 「lexical_cast<string> は、本当に考えているように動作するの?使った事ないからわかんないヨ。心配だなぁ」って、言う上司?のがいたらと考えると。。。ぞっとします。(汗)


あと「このプログラムは命題通りのプログラムになっているか?」の回答は、「3 と 5 で割り切れるときは、"Fizz Buzz" だから、間にスペースがない」だったりして。

# re: FizzBuzzと貧乏性 2010/10/20 10:40 あんどちん
> わかりやすさをあげるにはコメントだと思います。関数の先頭に
> /// この関数は、以下の仕様で動作します。
コメントに記述するなら仕様通り作った方がいいかなぁとか

> 例えば、説明の中で使っている 「lexical_cast<string> は、本当に考えているように動作するの?使った事ないからわかんないヨ。心配だなぁ」って、言う上司?のがいたらと考えると。。。ぞっとします。(汗)
それはsprintfだって同じ事ですけどね。ただ使ったことが無いもの、知らないものに対して不安になる人がいるのは事実ですね。
ちなみに僕はモメるくらいなら使わなきゃいいと思いますし、ソースを短くするために書いただけで、そこは主題では無いからlexical_castなんて使わなきゃいいと思います。
# もちろん僕のこのエントリもそういった部分を含んでいますが、しかし何故皆「プログラム=仕事->現場でどうこう」と考えるのでしょうね?
# だったら「現場の方針に従って書けばいい」で終わっちゃうんですが。
# この話も「レビューでオッケーもらえば問題ない」で終了しそうですね

> あと「このプログラムは命題通りのプログラムになっているか?」の回答は、「3 と 5 で割り切れるときは、"Fizz Buzz" だから、間にスペースがない」だったりして。
致命的な失敗をやらかしてましたね。"Fizz Buzz"だと上記のような最適化が出来なくなります^^;
# "Fizz "にすればいいけど、それは"Fizz"とは違うし


# re: FizzBuzzと貧乏性 2010/10/20 12:24 びーしむ
あんどんちんさんの意見に全面的に賛成です。
ソースを読んで、仕様がわかるのがよいですよね。

蛇足ですが、自分が気をつけている点をいくつかあげておきます。
・ if 文は、 { } を省略しない。
・ 論理演算式は、自明であっても ( ) をつける
そうすると、下記のようになります。
if((value % 3) == 0){
result = "Fizz";
}

理由
コードに作者の意図が追加されるので。
これがないと文法どおり動作するのが、正しいのか、間違っているのかが「わかりにくい」からです。

。。。といっても気休めにしかなりませんが orz

# re: FizzBuzzと貧乏性 2010/10/21 0:24 あんどちん
> ・ if 文は、 { } を省略しない。
> ・ 論理演算式は、自明であっても ( ) をつける
上記2の点に関して僕がこのエントリで上げたソースがそうなっていない理由は「長くなるから」の一点です。
仕事のソースならびーしむさんが書かれているのと同様に書きます。

ただ、僕はblogにソースを上げるとき「出来るだけコンパイルできるソース」を「出来るだけ短く」書くようにしています。
# 過去に上げているエントリでmainのあるものはコンパイル&実行できるようなものばかりになっています

今回は似たような関数を何個も上げているので出来るだけ短くするためにエントリのように書きました。



# re: FizzBuzzと貧乏性 2010/10/21 8:41 びーしむ
なるほどです。
わざわざ説明してもらってありがとうございます。

Post Feedback

タイトル
名前
Url:
コメント: