ITは芸術だ RSSフィード Twitter

2011-10-07

FizzBuzz問題を使って社内プログラミングコンテストを開催してみた

はじめに

先日、社内で初めてプログラミングコンテストを開催しました。

お題はかの有名なFizzBuzz問題です。

全員楽勝で解答するだろうと思いきや・・・結果はいかに!?


ちょっと長いエントリですが、このコンテストの顛末をお楽しみください。


開催の動機と経緯
  • メンバーの向上心を刺激するために、なにか面白くて技術的に意味のあるイベントを開きたかった。
  • 以前からFizzBuzz問題を全員で解いてみたかった。
  • FizzBuzz問題プログラマなら解けて当たり前、というようなWeb記事をよく見かけていた。
  • これぐらいなら誰でも解けるだろうと自分も思っていたが、実際にやってみないとわからない。
  • そこで社内プログラミングコンテストを開き、みんなでFizzBuzz問題を解いてみたいと思った。
  • マネージャーに話を持ちかけたところ、すぐに賛同してくれた。
  • FizzBuzz問題以外の追加問題も作成したが、第1回は様子見でFizzBuzz問題だけを出題することになった。
    • というか、マネージャーが「俺はFizzBuzz問題でも不安だ」なんて言い出すから。。。もう、そんなバカな☆
    • FizzBuzz問題あえて選んだ理由については、最後に詳しく書いています。

コンテストの目的
  • 自分のスキルを相対的に評価する。
    • 課題が同じなので相対的な評価が可能。
    • 実務では同じ課題に同時に取り組むことはない。
  • 他人のロジックから新しい発見を得る。
    • 良いコード、悪いコード(?)に関する知識を深める。
  • 良い意味でメンバー内の競争心をあおる。
    • チームワークがいい= みんな同じスキル、ではない。
    • 負けたくない、一番になりたいという気持ちも大事!
実施方法
  • 真の実力を試すために完全に抜き打ちで実施。
  • 設問は1問。制限時間は30分。
  • 言語は業務でよく使うC#Perlの好きな方を選択。
  • インターネット検索や技術書の利用はOK。
    • ただし、標準ライブラリや構文の確認等に限定する。FizzBuzzの解答例を検索するのは禁止。
  • 全員がすぐにプログラムを書き始められる状態にしてから、一斉に回答を開始する。

評価方法
  • 全員の前でプログラムの動作確認とロジックの解説を行う。
  • 解説時間は一人5分。
  • 各自がメンバーの中からイケてるプログラマ3人を選出。
    • 1位=3ポイント、2位=2ポイント、3位=3ポイント。
    • なぜそのプログラマが良いと思ったのかという理由も記述。
    • 投票は投票用紙を使い、無記名で行う。
    • 自分で自分に投票しても良い。
  • 早く終わった3人にはボーナスで3ポイント加算。
  • 合計点が一番多い人が優勝。
    • 優勝するとマネージャーから粗品がもらえる(予定)。
  • 開票もその場でオープンに実施。
    • 「1位xxさん、2位yyさん、3位zzさん、(次の紙)1位aaさん・・・」というように、記入された順位と氏名を順に読み上げていく形式。
    • なので、1位の人から最下位の人まで、全員の順位がその場で明らかになる。
  • 評価基準は基本的に個人の自由だが、参考までに以下のような評価ポイントを提示しておいた。
    • バグのなさ
    • 可読性
    • 保守性
    • 実行効率
    • エラー処理

出題者(つまりおいら)の立場
  • 回答者としてプログラムを書く。投票にも参加する。
  • ただし、出題者なので評価対象にはしない(順位はつかない)。

マネージャーの立場

当日の参加人数
  • プログラマは自分を含めて10人。(マネージャーを入れると11人)
    • 下は20代後半、上は50前後。
    • 業務経験年数は全員5年以上。
    • 当日は欠席者が3人いた。(できたら全員参加してほしかった)

問題の詳細
  • 仕様 *1
  • 引数
    • [0]=終端の値
  • 実行例
    • [0]=16
    • 1 2 Fizz 4 Buzz Fizz 7 8 Fizz Buzz 11 Fizz 13 14 FizzBuzz 16
    • 実際にはスペースではなく、改行して区切る。

実施時間
  • 合計2時間
    • 概要説明 10min
    • 開発環境準備 10min
    • 解答 30min
    • 休憩 10min
    • 動作確認&プログラム解説 5min × 10人 = 50min
    • 投票 5min
    • 開票&順位発表 5min

で、実際にやってみた

使い慣れてるC#なら楽勝♪と余裕綽々なおいらでしたが、土壇場でPerlを使うことになり、他のメンバーと同じスタートラインに引き戻された気分でした。


あれ?起動時引数はどうやって取得するんだったっけ?

ループ処理と条件分岐の書き方は??

サブルーチンの定義方法は???


と、グーグル先生のお世話になりっぱなしでした。


一番速いメンバーは10分程度で終了しました。

それからしばらくして、ぽつぽつと他のメンバーも終了しました。

おいらは15分ぐらいで大体ロジックを完成させましたが、すぐに終了せず、細かい部分のリファクタリングを行いました。

25分ぐらいで手を止めて、5番目に終了宣言しました。


そして回答時間が終了。

ホワイトボードに書かれた終了宣言済みメンバーの名前を見てみたら・・・あれ?7人しか名前がない。


もしかして、できなかった人がいる!?


そんなバカなと思いながら、10分間の休憩タイムに入りました。


動作確認&プログラム解説

動作確認とプログラム解説は座っている席の順番で回していきました。

で、その結果と感想は・・・。

  • 終了宣言をしていない3人は本当に完成していなかった。(!!)
    • うち二人はほとんどロジックらしいロジックが書けていなかった。
    • 確かに3人ともPerlを業務でバリバリ使っている人ではなかったが、インターネット検索も許可している条件でこの結果はどうしたものか・・・。
    • てか、マネージャーの予想が的中(T T)。
  • みんな大体同じようなロジックになるんじゃないかと思いきや、かなりバラつきがあった。
    • なんか妙にトリッキーだったり、明らかに不要なコードをあえて残しているようなロジックがあった。
    • また、微妙に仕様をアレンジしてくる人もいた。
    • 社内で比較的プログラミング能力が高いと思われている人は、やはりシンプルで分かりやすいロジックを書いていた。
    • 全体としてFizzBuzzのようなシンプルな問題でも、プログラムにはその人のキャラクターが現れている気がした。(自分のプログラムも含めて)
  • 引数のバリデーション(フォーマットチェックや範囲チェック、引数の個数チェック等)を実装している人はほとんどいなかった。
  • テスト駆動開発で開発する人も皆無だった。
    • おいらはC#だったらNUnitを使うつもりだったんだけど、Perlのテストフレームワークにはほとんどなじみがなくて・・・と言い訳。

投票&開票結果

全員の動作確認とプログラム解説が終わった後に、各自がベストプログラマ3人を投票しました。

それからドキドキの開票となったわけですが・・・結果はほとんど全員が同じメンバーを1位、2位、3位に選んでいました。

2位と3位は多少バラつきましたが、1位はほぼ全員が同じメンバーに投票していました。

みんなシンプルでわかりやすいプログラムを「良いプログラム」と評価するようです。


まとめ

初めての社内プログラミングコンテストは色々と興味深い発見があり、面白かったです。

全員楽勝だろうというおいらの予想は見事に外れました。そして反対にマネージャーの予想が的中してます。(それは面白いというより困った事態ですが・・・。)

メンバー各自はそれぞれ異なるロジックを書いたものの、「良いプログラム」に対する考え方は全員同じベクトルを向いていました。


それにしてもFizzBuzz問題を解けなかったメンバーがいたことは結構ショックでした。

確かに、彼らはそれほどPerlに使い慣れているわけではありません。

とはいえ、

  • 事前に似たような例題を示した。
  • どうしてもPerlが苦手であれば、得意な言語を選択することもできた。
  • インターネット技術書は自由に使えた。
  • 全員新人ではなく、それなりの経験年数を持ったプログラマだった。
  • おいらを含めて、Perlがあまり得意ではないメンバーは他にもいたが、なんとか問題を解いた。
  • FizzBuzz問題の仕様は特定の言語に依存するようなロジックを要求しない。

等々考えると、あまり弁護の余地がないのでは・・・などとつい考えてしまいます。

自分の同僚なんで、心苦しいんですけど(> <)。


そして、実際にプログラムを書いてもらうことは、その人のスキルやプログラムのセンスを如実にさらけだすものだと思いました。

職務経歴書を読んだり、「Perlができます」「Javaができます」なんて話を聞いたりするより、ずっと確実にその人のスキルが測れます。


マネージャーも今回のプログラミングコンテストの成果に満足していたようです。

できれば少し問題のレベルと形式を変えて、第2回社内プログラミングコンテストを開催したいと思います。


解けなかった理由はなんだろう?(2011.10.7 19:15追記)

あらためて考えると結構重要な問題なので、推測で理由を考えるのを取り下げます。

事故調査委員会ではありませんが、どういう条件が揃っていたら解答できたのかを本人に直接確認してみるつもりです。


コード解説での彼らの弁明から理由を推測してみます。

ひとつの大きな理由はPerlに慣れていないから、だと思います。

彼らは


「起動時引数の取得方法が分からない」

「最初から最後までPerlの解説本と首っ引きになっていた」

「実務でコマンドプロンプト/コンソール向けのプログラムをゼロから書く機会はほとんどない」


といった理由で、ほとんど時間が過ぎてしまった感じでした。

しかし、その点は「Perlじゃなくてもいいよ?」と確認しましたし、ネットや解説本があればそこまで大きなハンデキャップにならないんじゃないかというのが、おいらの見解です。


それ以外の理由としては正直言って、本人のスキル不足じゃないかと思っています。(同僚も見ているのであまり書きたくありませんが)

「えっ、この人がまさか!?」というよりかは、「あ〜・・・わからなくもない」というのが正直な印象でした。(ごめんなさい)


ただ、次回コンテストを開催するときは彼らの意見も取り入れて、より平等な条件となるように工夫したいと思います。


あえてFizzBuzz問題を選んだ理由(2011.10.7 22:15追記)

FizzBuzz問題あえて選んだ理由を詳しく述べると以下のようになります。


  • 裏の目的として社内メンバーのプログラミングスキルを社外のプログラマとも比較したかった。
  • そのためにはおいらが独自に考えた問題よりも、すでに良く知られている定番問題の方が相対的な評価がしやすい。
  • その反面、「FizzBuzz問題なら知っている」とか「以前書いてみたことがある」というリスクは十分予測できた。
  • そのリスクはあえて覚悟した上で、FizzBuzz問題を選択した。
    • リスクを軽減させるために、プログラミングコンテストを開催することは全く伏せた上で、メンバーを集めた。
    • 事前にアナウンスすると定番のFizzBuzz問題を予習する人が出てくるかもしれないので。
  • もし、「知っている、書いたことがある」メンバーが数多くいたり、そのせいでコンテストの結果に大きな不公平感を残すことになれば、第1回プログラミングコンテストは失敗ケースとして、次回以降の反省材料にするつもりだった。
  • ただし、結果としてはそのような問題は今回発生しなかった。
  • 少なくとも、おいらの社内においては「FizzBuzzを知らない」もしくは「知っていても解いたことがない」メンバーがほとんどだったと思われる。
    • まあ、それはそれでちょっと悲しい話なんですが・・・。
    • そしておいらも実は「知っていたが解いたことはなかった人」の一人です(- -;

良いプログラム、良いプログラマの定義は自由(2011.10.8 23:30追記)

反響を見てると、「10分じゃ遅い」とか「バリデーションはやりすぎ」みたいなコメントを時々見かけます。

まあ、それはそれでいいと思います。


ただ、今回は速さを競うコンテストにしたわけではありません。

速いだけで良いなら、「一番先にバグのないプログラムを書いた人が優勝!」で終わりです。

しかし、今回のコンテストでは速い人3人に3ポイントは与えるものの、同僚の評価次第では遅くても優勝できる可能性を残しています。


つまり、速く仕上げたい人は速く仕上げれば良いし、じっくりと仕上げたい人はじっくりと仕上げても良いのです。

そして、同僚の中に「速い人ほどスキルが高い」と思う人がいれば、速い人に投票するでしょうし、速さ以外の工夫を評価する人がいれば、遅い人にも投票するわけです。

プログラムを書く側も解説タイムの中で「こんなに速く書けたのが自慢です」とか「私は入力値のバリデーションは欠かせないと思っています」とか、プログラマとしての自分のこだわりを自由にアピールできます。


たとえば、3番目に終了したメンバーは投票の結果、6位に終わりました。

速さを競うだけなら、彼は「社内第3位のプログラマ」になるはずでした。

しかし、他のメンバーからの投票は1ポイントも獲得できませんでした。おそらく、微妙に仕様を変えたり、必要以上に複雑なロジックにしてしまったのが、敗因だったんじゃないかと推測しています。


結局、おいらが見たかったのは、ここにいるメンバーがどういうプログラムやどういうプログラマを良いと評価するのか、ということです。

それゆえに、プログラムを書く人も、それを評価する人も基本的に考え方は自由ということにしています。

もしコメントを残してくれているあなたがこのコンテストに参加していれば、あなたの投票によって違う人が1位になっていたかもしれません。


良いプログラム、良いプログラマの定義は、あなた自身の思想やあなた自身がおかれているコンテキストによって変わってくる、そういうものなんじゃないかとおいらは考えます。


参考資料

1位の人が書いたプログラム

10分ぐらいでさらっと書き上げたプログラムです。全員これぐらいのプログラムを書いてくれるだろうと期待していたのですが・・・orz

my $endval = $ARGV[0];


my $fizz;
my $buzz;

foreach my $i (1 .. $endval) {


    $fizz = $i % 3;
    $buzz = $i % 5;

    if ($fizz == 0 and $buzz == 0) {
        print "FizzBuzz";
    }
    elsif ($fizz == 0) {
        print "Fizz";
    }
    elsif ($buzz == 0) {
        print "Buzz";
    }
    else {
        print "$i";
    }
    print "\n";

}


おいらが書いたプログラム

main関数がないと落ち着かない、サブルーチンを細かく分けたい、入力値のバリデーションをしないと不安・・・という、おいらの性格をよく表していると思います(笑)。

use strict;
use Carp;
use warnings;

main(@ARGV);

sub main {
    my @args = @_;
    
    validate_args(@args);

    my $arg = $args[0];

    for (my $i = 1; $i <= $arg; $i++) {
       my $val = fizz_buzz($i);
       print "$val\n";
    }
}

sub fizz_buzz {
    my ($val) = @_;
    
    if ($val % 15 == 0) {
        return "FizzBuzz";
    }
    
    if ($val % 3 == 0) {
        return "Fizz";
    }
    
    if ($val % 5 == 0) {
        return "Buzz";
    }
    
    return $val;
}

sub validate_args {
    my @args = @_;
    
    if(@args != 1){
        die "Argument count must be 1.\n";
    }
    
    my $num = $args[0];
    
    if(!($num =~ /^\d+$/)){
        die "Argument value must be integer.\n";
    }
    
    if($num <= 0){
        die "Argument value must be greater than 0.\n";
    }
}


お遊びで書いたF#版プログラム

たぶんみんな似たようなロジックになって面白くないんじゃないかと思い、前日の晩に「できるだけ関数型言語らしく書こう」と思いながら書いたプログラムです。関数型っぽいかどうかはあまり自信がありませんが。。。

let fizzBuzz n =
  if (n < 1) then
    failwith "Argument cannot be less than 1."

  // 参考サイト
  // http://www.slideshare.net/bleistift/cvbf
  let judge n =
    match n % 3, n % 5 with
    | 0, 0 -> "FizzBuzz"
    | 0, _ -> "Fizz"
    | _, 0 -> "Buzz"
    | _ -> string n

  [1 .. n]
  |> List.map judge
  |> List.iter (printfn "%s")

// 実行
fizzBuzz 100


当日使用したスライド(2011.10.8 22:30追記)

社内用なのでかなり適当に作っていますが、何となく当日の雰囲気やノリが伝わるかもしれません。。。


あわせて読みたい

コードレビューコンテスト&LIVEリファクタリング - ITは芸術だ

→社内勉強会の一環として開催したコードレビューコンテストのお話です。


どうしてプログラマに・・・プログラムが書けないのか?

FizzBuzz問題は色々ところで言及されていますが、結構有名なお話はこちらではないでしょうか?


「ソフトウェア設計とは何か?」がすごい - Lism.in * blog - nekoya (id:studio-m)

→仕様は全く同じなのに、全員が違うプログラムを書いた。これはやはりプログラミングが製造ではなく、設計であることを証明していると思います。


プログラマの評価は手を動かしてこそなんぼ、という話は以下の本でも言及されています。

Joel on Software

Joel on Software

*1:このサイトを参考にしています。 http://vipprog.net/wiki/exercise.html#t52e5a48

nminorunminoru 2011/10/07 18:20 問題の仕様通りに作るなら、終端 16 の場合の表示は
"1 2 3 Fizz 4 5 Buzz 6 Fizz 7 8 9 Fizz 10 Buzz 11 12 Fizz 13 14 15 FizzBuzz 16" か
"1 2 Fizz 3 4 Buzz 5 Fizz 6 7 8 Fizz 9 Buzz 10 11 Fizz 12 13 14 FizzBuzz 15 16" では。
「3または5で割り切れる場合に数字の代わりに文字を表示せよ」という仕様が入ってないのですから。

通りすがり通りすがり 2011/10/07 19:36 さすがにそれは重箱の隅ではなかろうか。
もしそれで悩んだとしても確認すれば即座に解決される話であるし、ロジック的な差異もほとんどない。

tatt61880tatt61880 2011/10/08 00:39 向上心を高めるためにこういう機会を設けるのは良いですね。

mainの処理がが終わったら全体の処理が終了するのかパッと見でわからないので、
main(@ARGV);
exit 0;
と書いておくか、mainで値を返すようにしておいて、exit main(@ARGV)としておいたほうが良い気がします。

あと、FizzBuzzでテスト駆動開発は流石にやり過ぎだと思いますw

yukizokinyukizokin 2011/10/08 01:22 はじめまして。
興味深く読ませて頂きました(私自身もプログラムしてみました)

最後の1位の方のソースを拝見して気になった点があります。
Fizz , Buzz , FizzBuzz の3つの処理になっているのが問題に感じます。
『プログラム』の回答としては3の倍数と5の倍数で処理をする、という単純化したアルゴリズムに置き換えるべきではないでしょうか?
例えば、「7の倍数の時はGessと表示せよ」というルールが追加されると7つの処理に増えます。
一つ仕様が増えるたびに約倍の処理を追加&テストする必要が生じます。
(2^n-1)に比例するわけです。
実践で仕様が追加されるたびに指数的に追加仕様やバグが膨らむのは、こいう配慮がないところに原因があると思います。

homararahomarara 2011/10/08 01:28 この記事見て使い慣れたJavaでやってみた。
↓完成までにやらかしたミス
(コンパイル時)JDKにパスが通ってない。
(コンパイル時)配列のlength取得時に()をつけた。
(コンパイル時)コピーした別ファイルを修正してるのに気付かず、修正しても変わらないエラーメッセージに困惑。
(実行時)引数チェックで0個以外の引数を弾いた。
(実行時)ループを添字0から始めた。
(実行時)ループの終了判定を小ナリでやった。
合計11分。屈辱だ・・・

gnetygnety 2011/10/08 02:22 仕様どおりなら、3つの処理に分けるのが正しいと思う。
3の倍数と5の倍数で処理をするのはたまたまうまくいくだけな気がする

quill3quill3 2011/10/08 09:38 昔、こんなこと自分でやってみたのを思い出しました。

http://d.hatena.ne.jp/quill3/20090118/p1

aa 2011/10/08 11:30 仕様書がおかしいです。はい。
123Fizz45FizzBazz ですね。
誰しもがこの問題を知っているという先入観がありますね。
知りませんよ、こんなの。

あ 2011/10/08 11:59 書きなれない言語で30分は厳しいな、言語仕様の確認だけでかなり時間取られちゃう

yoyo 2011/10/08 22:40 思ったことを書きます。
- C#とPerlって変わった会社だなあ。
- 10人中8人がPerlを選んだんだったら残りの2人はC#で書けばいいじゃん。全員Perlにした経緯が不明確。
- トップサブミッションが10分というのは遅いけど、残りの20分何すれば良いのか分かんないから、敢えて遅くしたと推測できる。
- 3位までに入れば1位と同じポイントが貰えるんだったら、トップが提出するのを見てからでも提出可能。
様子を見て全体的に遅くなった可能性は大いに考えられる。
- 例外処理は問題文で明文化すべき。今回は「やらなくて良い」といってあげるべき。本質から外れるから。
こういうコンテストで例外処理を考慮させるんだったら、それこそ本質の問題以上に仕様が増えることになりそう。
だったら「やらなくて良い」と割り切ったほうがいい。
- 仕様がどうこう言ってくるヤツに対応するためにも問題文でサンプルの出力を与えるべき。
今回は与えてるっぽいから「仕様が...」は跳ね返すことができる。
- FizzBuzzぐらい知っとけ。せめて名前ぐらいは。
- 3割できないのは酷すぎる。そいつらは仕事変えさせろ。
- 「できる人ができない人にできるハズって言うのはマズいんじゃないですか」
「どういう条件が揃っていたら解答できたのかを本人に直接確認」しちゃあマズいと思うんスよ。
もう「できない人」なんだから。さっさと別の仕事やってもらった方がいい。
- プログラムを書かせてスキルを見るのは賛成。FizzBuzzが良いかどうかは別として。
- FizzBuzzでテストドリブンは無い。慎重すぎる。
- FizzBuzz程度で良いコード悪いコード云々はどうでもいい気もするが、実際にやってみたら気づきはあるのかもしれない。
好きな漫画のセリフを引用しちゃいました。

スパム対策のためのダミーです。もし見えても何も入力しないでください
ゲスト


画像認証

おとなり日記