カメヲラボ

主にプログラミングとお勉強全般について書いてます

10,000人のコードを読んだ話~さらばCodeIQ~

10,000人のコードを読んだ話

◆はじめに

本稿にはCodeIQで過去に出題された採用問題の採点内容に関する記述が含まれますが、問題に挑戦したユーザの皆様の具体的な解答に関しては、一切の記述はありません。これは、解答の権利がユーザに帰属するということと、そもそも私の手元には採点が終わった時点でデータが破棄されていて2度と閲覧することができないためです。

その代わり、ユーザの皆様が当時気になっていたのではないかと思われることを、採点に関するメモ・過去のメール・私の記憶を頼りにまとめたものを書き残しておこうと思います。

※注意
採点に関してちょっと突っ込んだ内容になっていますので、問題が生じた場合は修正を加えるか、最悪予告なしで削除します。予めご了承ください。

◆採点者として

2013年3月から、CodeIQの出題者としていろいろと出題させていただいていました。同年8月頃、「採点者やってみない?(要約)」というお話をいただき、それから2016年2月頃まで採用問題の採点者をしていました。おおよその延べ人数で言うと、採用問題が8,000名(うちコード銀行2,000名)、Ozy名義5,500名(ゴルフ系問題で自動処理したもの3,000件は含まない)で合計13,500名程度です。

◆採点について

・採点の目的

企業側が解答者の解答内容を閲覧することができるというのがCodeIQの特長で、実際にスカウトするかどうかは100%企業側の判断によります。また、どのようなコードがスカウト対象になるかは企業によって全く異なります。では、採点者は何をするかというと、大雑把な分類です。企業の採用担当者がコードを見るとは言っても、毎月500件前後の解答が寄せられるため、すべての解答に目を通すことは非常に難しいのです。そこで採点者は大体5種類程度にグループ分けします。

  • (A)超優秀だが業務内容に対してオーバースペックの可能性アリ
  • (B)おそらくどんな業務でも対応できる
  • (C)業務内容によってはピッタリの人がいるかも
  • (D)簡単な業務であればたぶん大丈夫
  • (E)さすがにあまりおすすめはできない

のような感じです。(C)以上の解答を閲覧するとか、(A)の中でとにかく頭の良さそうな解答だけ探すとか、(C)(D)あたりでコメントの書き方を見て真面目そうな人を探すとか、企業によって使い方はさまざまです。

次は基本的な分類方法を書いていきます。

・実行テストによる分類

プログラムを実行し、正しい出力が得られる解答は基本的に(A)か(B)に分類されます。いくつかのテストケースで正しい実行が得られない場合は(C)か(D)に分類されます。サンプル入力に対する出力すら間違っていたり、明らかに動かないとわかるコードの場合は(E)に分類されます(ただしコピペミスかな?と思われる場合で手直しすれば一応動くというレベルであれば(D)にする場合もあります)。

(A)と(B)の分類は、非公開のデータを用いてさらにテストを行います(後述)。(C)と(D)に関しては、問題毎に基準が変わりますが、基本的にはサンプルデータでしか正しく動作しないものを(D)として、テストデータで正しい出力が得られないものを(C)としていました。ここからさらに細かい加点・減点をしていきます。

・実行環境

当時(自動採点前)は解答コードに言語名や実行環境を明記してもらい、必要な環境は全部自前で用意していました(あまりにも高価なソフトを使用していて自前ではどうしても無理という場合だけ運営側にお願いしましたが)。WindowsMacが入ったマシンをそれぞれ1台ずつ用意し、Windowsの方でLinux系の仮想環境を用意して、週末になると実行テストを行いました。採点件数が多かったり、実行環境の構築に時間がかかる解答が含まれる週は、夜通し作業を行うこともありました。

アルゴリズムとプログラムの実行速度

Ozy名義で出題した問題に関しては、コードの書き方、アルゴリズム、プログラムの実行速度すべてにおいて、かなり入念にチェックしていました。採用問題については、実行テスト時に私が独自に用意したテストケースを用いて実行速度を測定していました(時間テスト)。独自にとは言っても大したものではなく、単に実行時間を計測しやすいようにデータのサイズを大きくしたものがほとんどです。

時間テストを通ったコードは間違いなく最高評価です。通らなかったコードは(1)アルゴリズムに問題があるか、(2)実装技術に問題があると判定します。ここで一つ大きな問題がありました。採点ミスはできるだけ抑えなければならないものの、採点作業に費やす時間は限られています。そこで(1)と(2)は、点数的な評価を全く同じにしました。もし(1)と(2)の判別に誤りがあったとしても、最終的な評価(点数)を同じにしておけば、解答の分類というゴールは達成できているからです。

この決定は正しかったと思います。なぜなら、私自身が何度かこのミスを犯していたからです。私のミスのせいで本来スカウトされるべき人がスカウトされないという事態はなんとか避けられていたと思います。ただ、コード銀行のフィードバックで的外れなコメント(2~3回くらいは自覚があります)を受け取った方には申し訳なく思っています。ホントごめんなさい

システムの仕様上、一度フィードバックが送信されると取り消しや修正が不可能でした。だからこそ二重・三重のチェック体制が必要だったのですが、その辺は甘かったと思います。特にコード銀行あたりからは、できるだけ時間短縮のため半自動処理になっていました。そのため、自分のスクリプトで「たぶんこの分類」というところに入った解答の評価に対してかなりのバイアスがかかっていたように思います(特にO(n2)とO(kn)、あるいはO(kn)とO(nlog n)の判別に失敗することが多かったと記憶しています)。その辺りもミスが出た原因です。

しつこいようですが、点数の評価で不利益になる状態にはしていないということは繰り返し申しあげておきます。

・コード自体の評価

解答コード自体の評価は、かなりゆる~くやっていました。というのは、コードの書き方というのはプログラマの宗教観がある程度含まれているからです。たとえば、私の個人的な好みで言えば「超短い」か「超速い」がすべてで、コメントとか書き方は本当にどうでもいいのですが、企業の採用担当者がそれを良しとするかというとそうではない可能性が高いです。

コードの評価については、特に2013年後半~2014年はかなり入念に打ち合わせをしながら進めており、企業の採用担当者の中にはあまりプログラムを書かない方も含まれることを知っていました。自分でプログラムを書かない人間は、わかりやすいコメントが付いていたり機能単位でコンパクトに書かれたコードを好む傾向があります。けれどもエンジニアから見れば、無駄の多いコードに見えるかもしれませんし、自分の美学に反した記述かもしれません。その辺りのことを考慮して、加点は積極的に行い、できるだけ減点は行わないように努めました。

明確に減点評価を行った事項で私の記憶に残っているのは、定数の埋め込みです。たとえばCのコードで、

for(i=0; i<10; ++i) 何かの処理

のようなコードがあった場合に、「10」という数値が含まれています。これ自体で減点することはありません(ただし例外あり(「相対評価」の節で後述))が、

for(i=0; i<10; ++i) 何かの処理

何かの処理

for(i=0; i<10; ++i) 何かの処理

のように、「10」が複数現れた場合は減点の対象としていました。理由はパッと見たときにそれぞれの「10」が同じ意味で使われているのか判別しづらいことと、数値の書き換えを行う必要が出た場合に何らかのミスを犯しやすいということです。

相対評価

これまで評価方法について説明してきましたが、すべての問題に対して常に同じレベルで評価しているわけではありません。最初にも述べた通り、採点の目的は『企業の採用担当者がスカウト対象を絞り込みやすくするため』です。ですので、やたらと挑戦者数の多い問題、たとえば「素数の数を数えてください」や、コード銀行で言えば「階段ピョンピョン1・2・3!」みたいな問題の場合はある程度相対的な評価を取り入れざるを得ません。

コードに数値を埋め込んだ場合でも複数箇所でなければ許容すると述べたところですが、仮に正しい実行結果が得られる解答が100人分あったとして、100人のうち70人がconst変数や定数と定義された意味のある文字列を使っていて、30人が数値を直接埋め込んでいたとすると両者に少し差をつけることで特定の評価に偏るのを防いだりすることができます。

コンパイラ言語であれば、ソースに入力データが書いてあるコードとそうでないコードは実行テストの手間が全く変わってきます。そのようなコードも、挑戦者が少ない(おそらく問題自体が難しい)場合は評価に影響しにくいですが、挑戦者が多く、かつ評価の偏りがある場合はいくらか相対的な評価を取り入れていました。

◆コード銀行の話

・コード銀行とは

コード銀行の問題に挑戦すると、出題者からフィードバックが得られます。通常の問題は、解答後に企業がスカウトしようと思わない限りは何の反応もありませんが、コード銀行の場合は必ずコメントを貰うことができます。コメントの内容から、実際にスカウトされそうなのかどうかということと、スカウトされるのが難しそうな場合は具体的にどういう点を改善すべきかを知ることができる(しかも無料で!)という夢のようなサービスでした。

・コード銀行の問題

コード銀行の問題は、基本的には良く知られた問題をベースにしたもので、すべて私が作成しました(ですのでサービス終了後もこのblogで問題を保存しておきます。解説も書く予定です)。完全にオリジナルの問題が少ないのは、2つの理由がありました。

(1)問題の不備が発生するリスク
企業との取引が関わる問題で不備が発生した場合、責任を取るのは私ではなくCodeIQの運営責任者になります。「だから何でもいいやー」とは思えませんでした、自分で責任を取れないからこそ、出題のリスクは最低限にしたいと思いました。結果的には大きなトラブルは起こらず、この判断も間違っていなかったと思います。

(2)競技プログラマとの区別
よく知られた問題を出題した場合は元ネタを知っている人とそうでない人の解答内容に、わりとはっきりと差がでます。問題を見る!反射的にコピペ!送信!みたいな解答はCodeIQの知名度が上がるにつれて、競技プログラミング的な速解き解答が目につくようになりました。それはそれで好まれるケースもありますので、それほど悪い評価にはしていませんが、無駄なコードコピペが多いものや何の説明もないもの、取って付けたようなコメントが書かれているものは減点していたように思います。

・なりきり作文

少し脱線しますが、私は中学生に作文指導をすることがあります。参考書とか問題集に「作文の書き方」みたいなことが書いてあったりしますが、作文が全く書けない子というのは、その手の書き方を教えてもやっぱり書けないんですよね。そんなときは『なりきり作文』です。ただし、やるのは私自身です。その生徒になりきって、その場で作文します。もし生徒が2~3行でも書いていれば、それを元に20行くらいにまで膨らませます。1文字も書けない生徒はしばらく話をして言葉を引き出して、全部書いてやります。これを1~2回して、あとは見本になる作文をいくつか読ませて、それらを真似て作文させます。こうすると結構手間はかかるのですが、ほとんどの生徒は劇的に作文力が上がります。

・なりきりコメント

コード銀行のフィードバックコメントは、(運営のチェックや修正が必ず入りますが)基本的に私が書いていました。一人で書き続けるのは、個人的な嗜好による内容的の偏りやモチベーションの維持に注意しなければなりませんでした。特にモチベーションの維持は難しく、個人の出題と違ってオフィシャルな出題の場合は挑発的な解答や運営者を子馬鹿にした解答が(少量ではありますが)含まれます。人間が人間を評価する以上、100%避けることはできないのである程度は仕方ないとわかっているものの、精神衛生的には最悪でした。そこで解決策の1つとして、なりきり作文をしていました。架空の採点者を設定して、自分の人格とは切り離してコメントを書くのです。プログラミングにあまり馴染みのない人事担当者になって書いてみたり、パフォーマンス重視のエンジニアになったつもりで書いてみたり、とにかく褒めちぎる営業寄りの担当者になってみたり、問題毎である程度性格付けをしてから採点作業に臨みました。

これは自分自身のメンタリティを維持するのに効果的でした。たくさんコメントを書いていると、Twitter2chに否定的なコメントがどうしても出てきてしまいます。見ないようにするのも1つの対処方法ですが、私の場合はできるだけその手のコメントを収集していました。自分が直接言われているのではないと思い込むことで、ネット上で得られる発言はすべて前向きに捉えることにしました。

ちなみに当時のフィードバックコメントは、同じような内容であっても言い方を変えたり、意図的に表記の揺れを持たせたり句点の位置を変えたりしていましたので、ネット上でコメントをそのまま貼り付けたりしたものがあれば、容易に個人を特定することができました。結局メモはしたものの、運営側には特に報告していませんし、この文章を書いている時点ではその辺のメモもすべて破棄していますので、心当たりのある人はご安心を( ̄ー ̄)ニヤリ。

・なぜコード銀行が終わったのか

コード銀行は、「終わります!」と宣言されて終わったわけではなく、自然消滅に近い形で終わりました。きっかけは2015年4月以降の自動採点を含むシステムのリニューアルでした。リニューアル時は皆かなりバタバタしていましたし、自動採点が始まってからはCodeIQ初のプロコンのような新たな企画は打ち出されたものの、何かとトラブル続きでクオリティを高める余裕がなかった気がします。

2016年の7月頃にコード銀行(というより手動採点)再開のお話もいただいていたのですが、私自身がスケジュールに全く余裕のない時期で、熱意はまだ残っていたものの、どうにもならない状況でした。きちんと人員を確保して採点者を教育する仕組みが必要でしたが、結局再開することのないまま話は立ち消えることになりました。残念で仕方ありません。

◆最後に

最後に個人的な感想を書いて終わりにしておきます。

・手動採点

初期の手動採点はプログラムの実行環境に何の制限もありませんでした。自動採点になってからはideone.comのプログラム実行環境で、言語の種類にも制限が加わりました。手動採点時に、「ideone.comを使って実行テストしますよー」というルールにしてもよかったのですが、Windowsプログラマにとっては不便なんですよね。 そういうレベルでユーザを篩(ふるい)に掛けたくなかったので、かなり面倒ではありましたが、ユーザの皆さんの良識を信じてすべて受け入れることにしました。「何でもこいや!!」というスタンスにした結果、クッソめんどくさかったわけではありますが、今となっては良い思い出です。全く割に合わない仕事ではありましたが、とにかく熱気が凄かった。とにかく新しいことをやろう、前に進もうとするエネルギーに満ち溢れていました

・AI採点

人間による手動採点は非常に価値の大きいものだと思いますが、実際に続けるにはかなり大変です。大きな問題点は2つ。1つは採点水準の安定性です。複数採点者であれば説明の必要もないと思いますが、仮に単一の採点者であっても集中力に限度があり継続的に続けるのは困難です。採点基準もある程度決めているものの、採点過程の情報は採点者自身への暗黙のフィードバックとなり、無意識に採点基準へ影響してしまいます。2つ目は、採点者と解答者両方にとってのメンタリティです。採点者に関してはこれまでいろいろと述べましたので省略するとして、解答者についても書いておきます。この記事を読んでいて、「実はOzyに採点されていた」と気付いたとき、どんな気持ちになりますか?私に個人的な好意を持ってくださっている方にとっては良いかもしれませんが、「ショートコーディングだのコードゴルフだの、わけのわからん奴に評価なんてされたくない!」と思うのは自然なことですよね。良い評価を与えるときにはあまり問題になりませんが、悪い評価を与えざるを得ない場合、必ず角が立ちます。その辺の処理をAIがやってくれるなら、「このクソAIがまたアホなこと言っとるわ(笑)」と思っておけば、誰も心をすり減らさなくて済みます。実際、大まかに解答を分類するだけであれば、何とかなるのではないかと思います。

・スーパー解答者

有能感が半端なく出ている解答というのがあります。理想的なアルゴリズム・データ構造で、コードが洗練されているのは勿論のこと、コード内外のコメントも無駄がなく、意図が非常にわかりやすい記述になっています。コメントに関して具体的言えば、(1)コードを読む速度を上げるためのコメント(記述が長い部分を要約するコメントであったり、そのような書き方にする理由が簡単に添えられていたり等)と、(2)プログラマ向けでない人間に対して理解と安心感を与える簡単な説明が添えられていたりします。

(1)と(2)を同時に書かれたりすると、自分がなりきりコメントしているのを見透かされている気分になります。そして、そういう解答者は大抵1度きりの採点になります。おそらくすぐに良いスカウトが来たか、我々が見捨てられたかのどちらかだろうと思います。

・たくさん採点した結果

ユニークな人数で言えば数千人の規模だと思うのですが、いろんな人のコードを読んでいると、「可読性が~」とかいう気持ちはほとんどなくなります。書き方のクセみたいなのがわかってくるので、最終的には全部読みやすいコードに感じます。もちろん、なりきりコメントする以上それっぽいコメントは付けるのですが。

コメントに関しては、その解答に対してどれだけ真剣に取り組んだのかが伝わります。雑なコメントというのは、コードを書きながら思い付きで書いたかのようにまばらに、そして一貫性のない記述になっていることが多いです。

一方、無駄なコメントがたくさん付いて、一見無能なプログラマが書いたんじゃないかと思えるような解答であっても、プログラムを書いた後で何度も読み返して、後から一生懸命慣れないコメントを書きまくった結果である場合もあります。そういう解答って、おそらく普段はコメント書かないタイプなのだけれども、「良いスカウトをゲットしたい!」という気持ちから無理してコメント書いているのだな、というのが解答全体からにじみ出ていて、読んでて愛おしくなってきます。

全くコメントの無いコードも、1つの表現の形に思えます。コードにコメントが全く書かれていないけれども、それ以外の記述事項(工夫した点等)はわかりやすく丁寧に書かれた解答も少なくありません。そんな解答からは、「コードで語る」心意気がひしひしと伝わってきます。

ループや条件分岐、変数や関数の命名規則などで一貫性のない記述はよく見受けられますが、これっておそらく試行錯誤の証なんですよね。書いてみたけれどもうまくいかず、書き直して、また書き直して…という作業を繰り返した結果生まれたもののように思われます。

スーパープログラマが築き上げた巨大なライブラリのソースコードを読むのとはまた違った趣があり、非常に有意義な採点者生活を送ることができたと思っています。

挑戦してくださった皆さん、本当にありがとうございました。

またどこかで会いましょう。