はじめに
先日、社内で初めてプログラミングコンテストを開催しました。
お題はかの有名なFizzBuzz問題です。
全員楽勝で解答するだろうと思いきや・・・結果はいかに!?
ちょっと長いエントリですが、このコンテストの顛末をお楽しみください。
開催の動機と経緯
- メンバーの向上心を刺激するために、なにか面白くて技術的に意味のあるイベントを開きたかった。
- 以前からFizzBuzz問題を全員で解いてみたかった。
- FizzBuzz問題はプログラマなら解けて当たり前、というようなWeb記事をよく見かけていた。
- これぐらいなら誰でも解けるだろうと自分も思っていたが、実際にやってみないとわからない。
- そこで社内プログラミングコンテストを開き、みんなでFizzBuzz問題を解いてみたいと思った。
- マネージャーに話を持ちかけたところ、すぐに賛同してくれた。
- FizzBuzz問題以外の追加問題も作成したが、第1回は様子見でFizzBuzz問題だけを出題することになった。
- というか、マネージャーが「俺はFizzBuzz問題でも不安だ」なんて言い出すから。。。もう、そんなバカな☆
- FizzBuzz問題をあえて選んだ理由については、最後に詳しく書いています。
コンテストの目的
- 自分のスキルを相対的に評価する。
- 課題が同じなので相対的な評価が可能。
- 実務では同じ課題に同時に取り組むことはない。
- 他人のロジックから新しい発見を得る。
- 良いコード、悪いコード(?)に関する知識を深める。
- 良い意味でメンバー内の競争心をあおる。
- チームワークがいい= みんな同じスキル、ではない。
- 負けたくない、一番になりたいという気持ちも大事!
実施方法
- 真の実力を試すために完全に抜き打ちで実施。
- 設問は1問。制限時間は30分。
- 言語は業務でよく使うC#かPerlの好きな方を選択。
- 単純なコマンドプロンプト/ターミナル用プログラムが出題されることを理解してもらうために、本番の問題に近い例題とサンプルプログラムを提示。
- どうしても他の言語を使いたい場合は要相談。
- ところが、10人中8人がPerlを選択したので結局全員がPerlで書くことになった。
- インターネット検索や技術書の利用はOK。
- ただし、標準ライブラリや構文の確認等に限定する。FizzBuzzの解答例を検索するのは禁止。
- 全員がすぐにプログラムを書き始められる状態にしてから、一斉に回答を開始する。
評価方法
- 全員の前でプログラムの動作確認とロジックの解説を行う。
- 解説時間は一人5分。
- ライトニングトークのように質疑応答はなし、5分で完全に打ち切り。
- 各自がメンバーの中からイケてるプログラマ3人を選出。
- 1位=3ポイント、2位=2ポイント、3位=1ポイント。
- なぜそのプログラマが良いと思ったのかという理由も記述。
- 投票は投票用紙を使い、無記名で行う。
- 自分で自分に投票しても良い。
- 早く終わった3人にはボーナスで3ポイント加算。
- 合計点が一番多い人が優勝。
- 優勝するとマネージャーから粗品がもらえる(予定)。
- 開票もその場でオープンに実施。
- 「1位xxさん、2位yyさん、3位zzさん、(次の紙)1位aaさん・・・」というように、記入された順位と氏名を順に読み上げていく形式。
- なので、1位の人から最下位の人まで、全員の順位がその場で明らかになる。
- 評価基準は基本的に個人の自由だが、参考までに以下のような評価ポイントを提示しておいた。
- バグのなさ
- 可読性
- 保守性
- 実行効率
- エラー処理
出題者(つまりおいら)の立場
- 回答者としてプログラムを書く。投票にも参加する。
- ただし、出題者なので評価対象にはしない(順位はつかない)。
マネージャーの立場
- 元プログラマだが、プログラムは書かない。
- ただし、投票には参加する。
当日の参加人数
- プログラマは自分を含めて10人。(マネージャーを入れると11人)
- 下は20代後半、上は50前後。
- 業務経験年数は全員5年以上。
- 当日は都合により出席でないメンバーが3人いた。(できたら全員でやりたかった)
問題の詳細
- 仕様 *1
- 1から順番に数をコマンドプロンプト/ターミナルに表示する。
- その数が3で割り切れるなら"Fizz“
- 5で割り切れるなら"Buzz“
- 両方で割り切れるなら"FizzBuzz"と表示する。
- 引数
- [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のようなシンプルな問題でも、プログラムにはその人のキャラクターが現れている気がした。(自分のプログラムも含めて)
- 引数のバリデーション(フォーマットチェックや範囲チェック、引数の個数チェック等)を実装している人はほとんどいなかった。
- 0や-1、abc等を引数に与えたら全く何も起きない、またはPerlデフォルトのエラーメッセージが出るというプログラムが多かった。
- 仕様には書いていなくても、もうちょっと気にする人がいてほしかった。
- テスト駆動開発で開発する人も皆無だった。
- おいらはC#だったらNUnitを使うつもりだったんだけど、Perlのテストフレームワークにはほとんどなじみがなくて・・・と言い訳。
投票&開票結果
全員の動作確認とプログラム解説が終わった後に、各自がベストプログラマ3人を投票しました。
それからドキドキの開票となったわけですが・・・結果はほとんど全員が同じメンバーを1位、2位、3位に選んでいました。
2位と3位は多少バラつきましたが、1位はほぼ全員が同じメンバーに投票していました。
みんなシンプルでわかりやすいプログラムを「良いプログラム」と評価するようです。
まとめ
初めての社内プログラミングコンテストは色々と興味深い発見があり、面白かったです。
全員楽勝だろうというおいらの予想は見事に外れました。そして反対にマネージャーの予想が的中してます。(それは面白いというより困った事態ですが・・・。)
メンバー各自はそれぞれ異なるロジックを書いたものの、「良いプログラム」に対する考え方は全員同じベクトルを向いていました。
それにしてもFizzBuzz問題を解けなかったメンバーがいたことは結構ショックでした。
確かに、彼らはそれほどPerlに使い慣れているわけではありません。
とはいえ、
- 事前に似たような例題を示した。
- どうしてもPerlが苦手であれば、得意な言語を選択することもできた。
- インターネットや技術書は自由に使えた。
- 全員新人ではなく、それなりの経験年数を持ったプログラマだった。
- おいらを含めて、Perlがあまり得意ではないメンバーは他にもいたが、なんとか問題を解いた。
- FizzBuzz問題の仕様は特定の言語に依存するようなロジックを要求しない。
等々考えると、あまり弁護の余地がないのでは・・・などとつい考えてしまいます。
自分の同僚なんで、心苦しいんですけど(> <)。
そして、実際にプログラムを書いてもらうことは、その人のスキルやプログラムのセンスを如実にさらけだすものだと思いました。
職務経歴書を読んだり、「Perlができます」「Javaができます」なんて話を聞いたりするより、ずっと確実にその人のスキルが測れます。
マネージャーも今回のプログラミングコンテストの成果に満足していたようです。
できれば少し問題のレベルと形式を変えて、第2回社内プログラミングコンテストを開催したいと思います。
解けなかった理由はなんだろう?(2011.10.13 8:45追記)
おいらの個人的な推測ではなく、メンバーに実際理由を聞いてみました。
その内容は別エントリにまとめてあります。
あえて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追記)
社内用なのでかなり適当に作っていますが、何となく当日の雰囲気やノリが伝わるかもしれません。。。
あわせて読みたい
FizzBuzz問題が解けなかった理由を聞いてみた - give IT a try
→このコンテストの後日談です。FizzBuzz問題が解けなかった理由や今後の改善点について考察しています。
コードレビューコンテスト&LIVEリファクタリング - give IT a try
→社内勉強会の一環として開催したコードレビューコンテストのお話です。
どうしてプログラマに・・・プログラムが書けないのか?
→FizzBuzz問題は色々ところで言及されていますが、結構有名なお話はこちらではないでしょうか?
「ソフトウェア設計とは何か?」がすごい - Lism.in * blog - nekoya (id:studio-m)
→仕様は全く同じなのに、全員が違うプログラムを書いた。これはやはりプログラミングが製造ではなく、設計であることを証明していると思います。
プログラマの評価は手を動かしてこそなんぼ、という話は以下の本でも言及されています。
- 作者: トム・デマルコ,ティモシー・リスター,松原友夫,山浦恒央
- 出版社/メーカー: 日経BP社
- 発売日: 2001/11/26
- メディア: 単行本
- 購入: 26人 クリック: 339回
- この商品を含むブログ (197件) を見る
- 作者: Joel Spolsky,青木靖
- 出版社/メーカー: オーム社
- 発売日: 2005/12/01
- メディア: 単行本
- 購入: 18人 クリック: 371回
- この商品を含むブログ (451件) を見る
*1:このサイトを参考にしています。 http://vipprog.net/wiki/exercise.html#t52e5a48