……春にして君を離れ……でも今は春じゃないわ、十一月じゃありませんか……アガサ・クリスティ『春にして君を離れ』
はじめに
この文章は、「競技プログラマーのためのコーディング面接、これだけ」というタイトルで書き始めたものだ。以前よりソフトウェアエンジニアの教育をしており、年によっては GAFA が揃うこともある。Google のソフトウェアエンジニアだけでも10年近く毎年入っており、教えられて入った人も教えるようになり、今年曾孫(4代目)の Google ソフトウェアエンジニアが誕生した。ただこの育成速度ではまったく十分ではない。年間一万人の桁で育てる必要があると考えるので教え方自体を広めることにした。書き進めていくうちにコーディング面接に有用なだけでなく、むしろ学習や仕事全般に共通する話になった。おそらく、汎用性がある話ではあるのだろう。外資系コンサルタントや外資系金融機関の人たちが時々見学に来て、職種が違っても同じ面接手法だとみな言っている。また10年以上前にスポットで家庭教師をして桜蔭や開成に入っていったことがあり、本質的に同じところがある。特に偏差値40台から開成中学に入る方は詳細を書いた。コーディングに関係がない人はここをクリックして「選別と教育」の章から読むといいだろう。
競技プログラマーのためのコーディング面接、これだけ
競技プログラミングでは普通に行われていることだけれどもコーディングインタビューでは本当にやらないほうがいいことがある。ここによくみるものを列挙しておく。これですべてとはいわないが、どうしてそれがまずいと判断されるのかについても丁寧に書いておいたので、これを丁寧に読むだけでレッドカード級のやらかしはほぼなくなるのではないかと思う。
全体を通して重要なポイントを3つあげる。まず、採用したいのは仕事ができる人であるということ。次に、なにか目的のために仕事をしていることを意識すること。そして、遠い帰結やリスクを予想できること。
それぞれについて簡単に説明しておくと、まず、仕事ができるというのは、一緒に働いたときに成果がより出るということだ。典型的な簡単な仕事は「巨大なコードベースと要望が与えられて、要望に従うように振る舞いを少し変えること」や「バグの原因を突き止めて修正すること」などである。それが十分にできるかを間接的に見るために採用活動をしている。次に、仕事の目的と現在の行動の整合性が取れているかは当然に重要だし、ここから外れた行動は違和感の形で見分けがつく。そして、遠い帰結を考慮していないと、痛い目にあったことがないのだなと判断される。
勘違いしやすいこととしては、最終的に出てくるコードの文面のみで判断されているわけではないということだ。あらゆる場面での感覚といったものまで見ている。特に、なんとなくこれは嫌だなという気持ちを共有できるかは大きなポイントになる。
ここからは具体的な例を書いておこう。
標準入出力からのやりとり
もちろん、オンラインで提出するなどで、標準入出力でやり取りが指定されていることがある。そういった場合はそれでいいのだが、面接官と対面していて、テキストエディターやホワイトボードなどで単にこういう感じのコードを書いてくれといわれてコードを書くときに、標準入力から受け取って、標準出力に出力するコードを書く人がいる。
仮に面接官が親切ならば次のように言うだろう。「関数で書いて欲しいです。ところで、関数呼び出しのオーバーヘッドはいくらでしょうか。また、標準入出力でデータのやりとりをするときのオーバーヘッドはいくらでしょうか。それぞれの、いい点悪い点を挙げてください。」
許容される範囲の答えの一例としては、「関数呼び出しのオーバーヘッドは1ナノ秒オーダー。一方で、標準出力でデータを受け取る場合はプロセスの立ち上げがあり、状況によって違いますが、たとえば1から10ミリ秒くらいを想定しますので、速度としては1000万倍くらい違います。一方で、シェルスクリプトなどから呼ぶ想定ならば、標準入出力が詰まったりすると自動的にプロセスを一時的に止めたりしてくれるいい点があります。」くらいになるだろう。言語によっては数字が変わる。言語間をまたいだやりとりとして FFI や RPC などを混ぜて比較してもいいだろう。
どういうことかというと、まず、標準入出力をインターフェースとしようというのは技術的なデシジョンである。技術的なデシジョンをしたというからには、なぜそれをしたかについての答えが返ってこないと恐ろしい。これは何も考えずに、10ミリ秒、ライブラリーの都合によっては1秒を超えるかもしれない水準の高いコストをかけることにしたということになるので、なぜそうしたのかを説明する必要がでてくる。
ただ、ここまで答えられたとしても、マイナスが消えても時間が経過した分だけ損で、より評価されるわけではないと個人的には感じる。
秒での判断
上の件でもそうだが、だいたい何がどれくらいの時間で実行されるかを見積もれることは大事だ。
プログラムを書くというのは、なにか目的があって、その目的を満たすために行うものである。その内容は様々で、たとえば、スーパーコンピューターを数ヶ月借りて数値計算を回して気象現象について知りたいのかもしれないし、リアルタイムで動画を生成したいのかもしれない。知りたいことは今の方法で間に合うのかである。つまり、スーパーコンピューターならば何ヶ月で終わるか、リアルタイムの動画生成ならば計算が10ミリ秒で終わるかである。正確に時間を見積もるのは難しいので、事前に終わるか分からない場合もあるだろう。その場合はちょうど難しいところなので計測してみるという話になるかもしれない。いずれにしても具体的に何秒なのかが見積もれないといけない。計算量はその計算をするための手段なので、計算量が分かるのに計算時間が見積もれないと驚く。計算量はあくまでも極限を取ったときの漸近的な振る舞いのことなので、それが直接何かの根拠になることはない。たとえば、社内研修を開く仕事を任されて計画を立てている。全員の弁当を手配することになった。その時に、弁当代は来場予定社員数に比例することしか情報が出てこないと動揺する。単価が500円でも3000円でも概算でいいので予算を提示して欲しい。これと同じことだ。熱伝導方程式の形から、火の通りやすさは厚さが半分になると4倍になるが、ある厚さの肉がどれくらいで焼けるかの目安がないと実践できない。これと同じことだ。
もちろん、分かっている人たちでも、時間内に収まることがあまりにも明らかな場合は明示的には言及しないことがあるのだが、そういう状況でも言及していないだけで見積もりはしている。見積もりのために必要な知識はそれほど多くない。どうせ桁くらいしか合わないものなので、言語ごとの倍率と記憶階層やネットワークなどに典型的にかかる時間を合わせて20も覚えればそれらしいことが言えるようになる。少し古いが Jeff Dean numbers で調べると出てくるものが代表的な資料だろう。そこにない数字では整数の割り算が遅くて20-50クロックくらいかかるのは知っておいてもいいかもしれない。言語ごとの倍率は、おおまかに3つに分けて考えている。コンパイルしてネイティブコードになるタイプ。これを1とすると、中間コードやガーベージコレクションのオーバーヘッドが入るグループが2〜5倍。動的型付けするインタープリタ型の言語は30〜100倍くらいで見積もっている。このあたりの数字は時代によって倍くらい平気で変わるのであくまでも目安である。
リアルタイムの動画生成の例でも少し書かれているが制約はプログラムの目的からある程度自然に出てくるものである。コーディング面接では入力の制約を質問すると評価されるものであると考えている人たちがいるようだがそうではない。プログラムの目的から制約を見積もること、見積もれないと判断できる場合には質問すること、確認して合意を取ること。そういったことがなされないと不自然に感じられる。これを表面的に真似すると制約を質問すると褒められているようにみえるということだ。
また、目的から制約がだいたい決まるとはいえ、制約は破られていくものであるので、ものによっては制約が変わっていくかもしれないと思って作るほうが好ましい。
一方、論理の方向が逆なので制約から想定解の計算量は推測できない。コストをかけて良い状況であればデータセンターを何ヶ月も占有することを想定しなくてはいけないこともあるからだ。コーディング面接はこういう状況のことを考えて欲しい場合がある。
計算量の使い方についても一応コメントをしておこう。プログラムはその目的から使用できるリソースに制限があり、実行前に大まかにその制限を満たすかどうかを見積もる必要がある。その見積もりの一部で計算量を使うということだった。このため上から抑えるのであり、どれくらい緩い評価をしていいかは、どれくらいリソースが逼迫しているかに依存する。あまり文句をいいたくないのだが、LeetCode などに載っている計算量評価は緩すぎて意味があるものになっていないことがある。また、計算量の評価だけでは間に合うか分からないときには計測する話になるだろう。
タイトな計算量の評価は難しくて期待されていない場合がある。真面目に評価すると計算量がカタラン数や分割数などの組み合わせ論的な数やフィボナッチ数などになる場合はそれなりの頻度で現れる。カタラン数は分岐の数が一定の二分木の総数、分割数は正の整数を順序を考慮せずに正の整数の和に表す方法の数を指す。そうなると、カタラン数や分割数の漸近評価がしたくなる。カタラン数の方はスターリングの公式から導出できる。分割数の方はハーディとラマヌジャンが発見したもので である。計算量に分割数が絡むコードは結構あるのだが、この漸近評価は競技プログラミングでも競技数学でも範囲外になっているようだ。国内外のメダルを持っている人たちにもそれなりに聞いたが、うろおぼえでも知っていたのは一人だった。近い分野を専攻したので知っているとのことで、競技関係では見たことがないとのことだった。導出に複素解析などが必要でただの知識問題にしかならないからだろう。このように高校生くらいでも理解できるものしか扱われていないことは意識しておいたほうがいい。一方で、分割数の漸近評価は1, 2%の面接の問題で絡むという印象がある。現実の問題は高校範囲を超えないように出題されたりはしない。そうなると、計算量の計算自体が難しいのでもっと緩い評価でなんとかするか実際に計測したいというので十分だろう。
計算量についてもう一つ。具体的な計算をすると となるような n を求めたくなることがあるだろう。腕を組んでじっと考え出す人たちがいるのだが、こういうときには手を動かして掛け算をはじめよう。どう考えても答えは10以上20以下である。十数回もかけ算をすれば分かるはずである。桁があえばいいので数字を丸めていっても構わない。6か7まではまじめにかけて、あとは10だと思ってもよい。
一応、四則演算ができれば十分時間の見積もりはできると思うが、それに加えてルートと指数対数を知っていると速いところもあるだろう。特に、常用対数で 2, 3, e あたりを覚えておくと会話がスムーズになることがある。たとえば、IEEE754の倍精度浮動小数点数は仮数部が52+1ビットだが、これは十進法では53*0.3010だから有効桁数が15,16桁だといえる。たとえば、上の分割数の漸近評価をすると、 なので
となる。意外に n が100でも億の桁である。そもそも分割数の漸近評価をすることはないだろうがこういうこともできる。実用的なところだと、万、億、兆、京が 4, 8, 12, 16 で、K, M, G, T が 3, 6, 9, 12、m, μ, n が -3, -6, -9 を覚えておいて足し算引き算をするくらいでも便利である。
速度の最適化
コードは多角的に評価されるもので、状況に応じて何が重視されるかが変わる。コードの複雑さを減らすことが比較的重視される事が多いが、ライブラリーなどでどういう使われ方をするか分からないので速度を重視することもある。速度の議論をするならば定量的にするべきである。典型的にかかる時間を覚えていてそこから概算し、それよりも細かい精度が必要ならば実際に測定してみる。
時間の議論では、ナノ秒から年(3*10^7秒強)単位まで広いスケールの時間が出てくる。3京倍違う。年単位のプロジェクトの話をしているときに、ナノ秒改善する方法について固執する奇妙さが分かるだろうか。地球から最も近い恒星系アルファ・ケンタウリまでの距離をメートルで表すと4京メートルくらいだから、約4.3光年先のアルファ・ケンタウリまでの旅行の計画中に、1メートル経路を短くする話をしているようなものである。1ナノ光秒が30cmほどだから桁感が同じになる。物理的な世界ではこれほどの差がつくことはまずない。コロナの初期に医療物資の輸入のため上海から東京のチャーター便を船便と比較したことがある。1000万円程度であったからこれくらい斬新なことをしても輸送費が1万倍変わらない。だが科学技術関係の見積もりでは大差がつくことがあるので局所的相対的な計算はあまり意味をなさない。つまり、たとえば全体の一万分の一の部分を百倍速にしても全体ではほとんど変わらないということだ。
多角的な評価は実際に大事で最近もそういう経験をした。有向グラフの上で7つ以下の辺で繋がっているノードの組を列挙する再帰関数が C# で与えられ、サーバーのメモリーを食い尽くしてクラッシュするので直して欲しいということだった。再帰関数の中で、長さが7以下のリストからハッシュセットを用いてループがないかを確認していたのだが、このハッシュセットを使うのをやめてリストの全要素を確認するようにすると速度もメモリー使用量も改善して動くようになった。そもそも長さが20以下程度ならば、線形に全要素を調べるほうが速いし、ハッシュセットはヒープ上に作られるのでガーベージコレクションへの負荷が大きく、ここが動くかどうかにクリティカルなのだ。
局所戦にこだわらない
多角的に評価で思い出すのは Python の文字列結合だ。
s = "" for x in a: s += x
というコードを書いたとしよう。
「文字列の追記は文字列の再構築が走るので、リストに append して join するほうがいいのではないか」というのがよくある面接官の反応だ。これに対して、「そうですね。」といって直したらそれほど大きな問題はない。だが、たまに抵抗する人がいる。「そう思っていたんですが実験した範囲では最適化が効くみたいです。」この返答はなかなかに困る。「いつでもその最適化は行われるのか。インタープリターのバージョンに依存しないのか、たとえば、バージョンアップで最適化がなくなることはないのか。もしも、最適化がされることが保証されていないならば、そのように書いておいたとして、どういった場合に最適化されないのか。そのような仕様が仮にあるのだとしたら、そのドキュメントへのリンクをコメントで書いておいて欲しい。また、Python のバージョンが変わったときには、その最適化がされることが保証されていないならば、そのドキュメントをもう一回見て、バージョンによって仕様が変わっていないかどうかを確認する仕組みが必ず走るようにして欲しい。」という疑問や欲求が自然に湧いてくる。ここまでの疑問にその場で答えられるならば、面接官は「なるほど、そうなんですね。」といって、Python に詳しい人だと思うだろう。そして、仕様レベルで最適化が保証されているのだとしても、最低限コメントとして、「このような場合には最適化されることが保証される。どこどこ参照。」と書いておいてほしいと頼むだろう。なぜならば、今後そのコードを読んでデバッグする人が、「むむ、もしかして、今回のタイムアウトの原因はここではないかな?」といって余計な実験をすることになりかねないからだ。それで、そうなのだとしたら join で書き直したほうがよいだろう。つまり、気にしているのは思ったように動くか、ではなくて、半年後に読んだ別の同僚が不安にならないか、環境の変化に対して頑健か、なのだ。
ちなみに、疑問への答えだが模擬面接のあと調べた。Python 2 の公式ドキュメントには CPython などでは文字列の追加の最適化が 2.4 から効く場合があることが書かれている(5. Built-in Types — Python 2.7.18 documentation)。Python 3 については公式ドキュメントには言及がないようだが、ソースコードを見る限り CPython では 3.3 あたりから最適化が効く場合があるようだ。
さて、ここまで来て、知識のレベルによりそれぞれ異なる理由で異なる判断をすることが分かった。1. += が文字列を再生成すると知らない人。2. += が文字列を作り直すと思っているので join で書き直す人。3. 経験上最適化が効くらしいから += で書く人。4. 最適化が効くかどうかは難解な話なので join で書く人。などだろう。ここで 3. が一番困るのだ。
試してみたところ動いているというのはそれはそれでひとつなのだが、特に情報セキュリティーでは賢い敵が間違いをつついてくるので、ランダムな入力で試してみて動いたからといって問題がないということにはならない。気になって調べるという動作をすると30分もかからずに 3. から 4. になるだろうから、3. であることは気になったことがないことを示している。また、仮に、4. であったとしても、聞かれなければ細かい議論には立ち入らないほうがよい。コミュニケーションとして仕事の目的に向かっておらず、詳しいことをアピールしようという行為だからタイムペナルティーにしかならない。
ここまでをまとめると仕事の目的と遠い帰結が見えていないために局所戦をしていてそれに気がついていないという状況に気をつけようということだ。
変数名・関数名
命名規則やスタイルは状況に依存して変わるものであるから、一人で開発しているならば自分なりの理由があってコンシステントであればいい。ただ、言語によっては標準的な指針があるので、それくらいは軽く見ておいてもいいかもしれない。それに加えて、様々な考え方がありチームで統一することの重要性は知っているので合わせることにやぶさかでない、という態度であることが大事だろう。大規模な開発においては適切な独裁が重要である。
例を少し挙げれば……
- Python: PEP 8 – Style Guide for Python Code | peps.python.org
- Go: Effective Go - The Go Programming Language
関数名のほうが、変数名よりも大事ではある。巨大なコードベースでソースコードを読んでいて、関数名から何が返ってくるか分からなければ、定義の中身を読んでから戻ってこなくてはいけなくなる。そこで、呼び出し元のコードを読んでいて、概ねこういうものが返ってくると分かるものが好まれる。これは、心の理論の問題だ。つまり、書いている自分が分かることを読む人が分かるか分かるかということだ。自分が書いたコードであっても数ヶ月も経てば覚えていない。よくある痛い目に遭うのひとつである。
class が作れるように
言語によっては struct かもしれないが、なんらかのデータ型を作れるようにしておくことは大事である。仕事でまず使うからだ。そして、できて欲しいことの典型例が「巨大なコードベースと要望が与えられて、要望に従うように振る舞いを少し変えること」や「バグの原因を突き止めて修正すること」であり、それができるかを確認するのがインタビューなので、こういったところを見ている。
C++ の分割コンパイルの方法などは一人で小さいコードを書いているとあまり使わないかもしれないが大規模開発ではまず使う。ヘッダーファイルの中で using namespace std; すると include したファイルがすべて影響を受けることになる。なんらかの行動をしたことによる遠い帰結を予期できるかという話である。
何ができるようになる必要があるかの一つの考え方として「社内のエンジニア全員が数年かけて自分の分身に徐々に置き換わったときに会社が回るか」というのはいいのではないだろうか。すべてができる人はほとんどいないだろう。一般に、人は誰かに頼って生きているので、自分が代われない仕事があるのは当然なのだが、それはそこで人の世話になっているということだ。道路があるのも、食事が食べられるのも、ウェブが見られるのも、誰かがあなたのために投げかけてくれたからである。得てして、人は自分ができない仕事を軽視ないし無視する。また、相手の仕事の内容を理解していたら、より相手の仕事がしやすいように仕事を頼むことができるだろう。
「必要があるときに調べて分かれば別によいのでは」という意見は、状況によっては正しいのだが、壊れているのを見つけたときには問題の切り分けが必要だ。「こういう原理でここが動いているから、こういう入力に対して、これが返ってきたということは、ここまでは正常に動いていて、ここからも正常に動いているから、おそらく、ここのチームの担当箇所が問題を起こしていると思われる」と特定して連絡をすることになる。「こういう問題があって、たぶん、こういう理由で君のところだと思うんだけど、もしかしたら私の問題かもしれない。ちょっと協力してくれないか。」みたいに。これができないと関係している全チームに対して連絡をすることになる。で、各チームの人たちが上の行動を取り、要は全体の仕事を増やすことになる。
グローバル変数を汚さない、二度呼べるか
たとえば、トポロジカルソートを書いて欲しいといわれたとしよう。
from collections import deque def dfs(G, v): seen[v] = True for next_v in G[v]: if seen[next_v]: continue dfs(G, next_v) sorted_list.appendleft(v) N, M = map(int, input().split()) G = [[] for _ in range(N)] for _ in range(M): a, b = map(int, input().split()) G[a].append(b) seen = [False] * N sorted_list = deque() for v in range(N): if seen[v]: continue dfs(G, v) print(*sorted_list)
だいたいこんな感じのコードを書く。このコードは、グローバル変数に結果を書き込んでいるので、二回呼べない。seen を初期化をすればいいがスレッド並列で呼ぶとどうなるのか。こういう風に色々と再利用できるかについて疑問を感じる。引数で渡す程度で回避できる問題であるのに対してバランスが取れていない。環境によっては変更ができるかもしれないが再帰の深さには限界がある。他にも、ループがある入力であった場合に何が返ってくるのが好ましいのか、なども考えるところかもしれない。変数名が相当変なことはもうすでに書いた。
つまり、面接の場では、動くか動かないかだけを気にしているだけではなく、コードを見たときに多くの人が感じることを感じるか考えることを考えるかをみている。
こういう書き方は、2005年前後に日本の ICPC のコミュニティーで見かけるようになったもので、(当時は C++ が多かった)どうせ使い捨ての関数なので再帰の引数を引き回すようなことはしないでいいという考えでなされているものだ。
ここで軽く競技プログラミングの歴史を説明しておこう。日本では、1990年代に ACM-ICPC 、スパコンコンテストや旧情報オリンピック(4回で終了)が開催されるようになった。その後、2004年の夏に ACM/ICPC OB有志の会(https://web.archive.org/web/20050206181855/http://acm-icpc.aitea.net/camp-2004/ 後の OB/OG 会)によって合同練習会が開かれ、大学を超えたつながりができてきた。このあたりの人たちが TopCoder も遊ぶようになる。
そして、卒業生が練習会を開くのにならい、在学生も練習会を開くようになった。2007年にできた東京大学競技プログラミング同好会もその一つである。2007年国内予選1位から3位を独占したこの練習会(FrontPage - 東京大学競技プログラミングクラブ)には他大学や高校生も参加するようになり、また、年に一度 ICPC に似たプログラミングコンテスト UTPC を開いたので、競技プログラミングという言葉が ICPC 型のプログラミングコンテストのことも指すようになった。要するに、OB/OG 会でプログラミングコンテストの街ができ、そこの道場の名前が競技プログラミング同好会だったのである。そして、道場の看板が種目名となったということだ。ラグビーやバドミントンがもともと学校名・邸宅名であるようなものである。
もともと道場の名前を指すので、種目名としての競技プログラミングという言葉が指す範囲は一定しない。狭義には「複数の問題が与えられ、問題ごとにコードを書き、時間内に入出力の一致した問題の数を競う」というタイプのコンテストだが、ICFP コンテストなどこの定義に当てはまらなくても競技プログラミングと呼ばれることがある。この道場の出身者がよく遊んでいるためである。最近はオンラインジャッジシステムのことを競技プログラミングと呼ぶことさえよくあるようだ。もっともこれは最近の用法ではなく、2008年には Project Euler を含む言葉として使われているのでむしろ古くからある用法である。(焼き鳥屋で刑法学者と意気投合してコインハイブ事件について書き出すだけの難儀なお仕事 - 白のカピバラの逆極限 S.144-3)
ここで歴史の話をしたのは、この記事で書かれているような内容は昔は先輩から教えられてきたが、競技プログラミング同好会が登場したあたりから継承されなくなったというように理解しているからだ。過去にどのようなことがあったかを追うと自然と考えが相対化され、その射程と限界が浮かび上がってくる。
本題に戻ると、グローバル変数を汚すとインタビューの文脈では、こういう道具を作ってくれといったらわざわざ一回使うと壊れるように作った、というようにみえる。もちろん、一回使うと壊れるように作ることも技術的なデシジョンであるから、なぜその技術的なデシジョンをしたのかについての説明がつけられることが求められる。
REP マクロ
#define REP(i,a) for(i=0;i<a;i++)
正確には覚えていないがこれも2005年頃にはあったマクロである。TopCoder や ICPC あたりで使われていたようだ。
- https://jag-icpc.org/?2007%2FTeams%2FKUMA
- https://www3.ntu.edu.sg/home/ehchua/programming/icpc/icpc_getting_started.html
このあたりで見つかる。私は好まなかったが、特に i, j で二重に回したときに i, j を打ち間違うことがあって、見かけが似ているので気が付きにくい、というので使っている人がいた。当時の C++ には範囲 for 文もなかった。
いまでも rep という名前が保存されて伝承されているというのが面白い。
1972年にできた C 言語のシンタックスは FORTRAN や COBOL などと違い記号を多用する。この学習が難しいのでマクロでなんとかしようとする流れが昔からある。いくつもの流儀があるが、たとえば C 言語を FORTRAN 風にするマクロというとこんな風になる。
#define BEGIN { #define END } #define IF(c) if (c) { #define ELSE } else { #define ENDIF } #define DO(i, from, to) for (int i = from; i <= to; i++) { #define ENDDO }
古くから今日まで伝わるものでは /bin/sh (1977年の Bourne shell) のソースコードのマクロが有名だ。これは Algol 68 の影響を受けている。
#define BEGIN { #define END } #define FOR for( #define WHILE while( #define DO ){ #define OD ;} #define REP do{ #define PER }while( #define DONE );
昔から定期的に取り上げられており Usenet の1980年代1990年代にも記録がある。
- 1983年 our friend "{"
- 1987年 PASCAL like C with no sweat
- 1992年 converting C 'for' to PASCAL 'FOR'
1986年には Pete Orlin と John Heath が Easy C という記事でこういったマクロを推奨して、大きな論争を引き起こしたらしい。
Pause for the bizarre dick hamming distance measuring code snippet. STAY FOR THE EPIC 🧵.
— Sean McBride (@bushidocodes) April 17, 2025
Easy C is a set of preprocessor macros circa 1986 that tries to make C look like Fortran/COBOL/Pascal. It spawned an 80s flamewar. Grab your 🍿 and LOCK IN. pic.twitter.com/a8DO7XQiKz
そして、毎回猛反対を受けているものである。学習を阻害し、技術的に予期しない動作をすることがあるという理由もあるが、可読性・保守性を下げるというのが大きい。
プログラムのソースコードは、人が機械に命令を伝えるものであると同時に、人同士の意思伝達の手段である。だから、書くことよりも読むことのほうがだいぶ重要である。変わったマクロを使ったコードを使うということは、読むことが大事だと考えていないか、ほとんどの人が知らないものでも自分が使い慣れているから他人も読みやすいと考えているということを示唆するので、コミュニケーションや社会的な関わりに独特な感性を持つと判断される。
オンラインジャッジで機械しか読まないとしても避けたほうがいい。採用すれば年単位で一緒に働くかもしれないわけだ。最終候補者が若干名残っていたら、ここまで出てきたものを全部精査することもあるだろう。その時に自分のコードやレジュメなどを読んでいる横までいって説明することはできないのだ。
二分探索を読めるか
二分探索が典型的なので二分探索と書いたが、アルゴリズム全般、書けるけれども自分が書いたものさえ読めない人がほとんどであるように感じている。
書く時は自分の書き方を固定してもいいが、読むときにはどんな書き方がされていても動くのか動かないのかも含めて読めないといけないので読むほうがだいぶ難しい。
よく、開区間、閉区間という表現を使っているのを見る。それ自体は別によいのだが、中身を聞いてみると自分でも何を言っているのかが分かっていないことも多い。典型的には開区間や閉区間は数学で実数についていうもので、位相空間論における開集合と閉集合は性質として大きく異なるものだ。だが、配列の範囲の話の際は、自然数という離散量なので大した意味がなく、単に表現する不等式にイコールがついているかないか程度の意味しかない。そして、このときに何の値と範囲の関係の話をしているのかが分かっていないことがある。
とりあえず意味が分かっていないと感じたときによくする話をしておく。プログラムというのは融通の効かない機械に対して作業をしてくれと頼むものだ。だから、そもそも自分で手作業でその仕事を実行することができないならば読み書きできるはずがない。また、プログラムを書けるくらい理解しているならば、一人での手作業よりは少し難しい方法として複数人でシフトを組んで実行することもできるはずだ。
典型的な二分探索の問題として、広義単調増加の配列とある数が与えられて未満と以上に分けることを考える。複数人でシフトを組むために少し物語風にしよう。標高5000mの広い高原の奥に塔が1万本立っていて、0から9999までの番号がついている。各塔は登って降りてくるのに一日かかる。塔の上には数字が書かれていてその数字は小さい順にならんでいる。塔の上には同じ数字があることもある。したいことは5000未満の書かれた塔と5000以上の書かれた塔に分けることだ。
一人の人が毎日連続で塔を登るのは大変だから、ベースキャンプを作ってシフトを組むことになるだろう。一度、どの塔に登るかの戦略は忘れて、ベースキャンプの情報共有の仕組みを整えよう。普通に考えると、登って降りてきた人にレポートを書いてもらうことになる。まず、レポートの書式だが「何月何日何番の塔に登った。いくつだった。責任者サイン。」くらいで十分な情報だろう。「3月5日5番塔に登った。10だった。太郎。」「5月7日15番塔に登った。50だった。次郎。」そういったものだ。これを書類入れにまとめていく。シフトの日にやってきた隊員は、書類入れの中身を一通り見てどの塔に登るかを決めて出かけていく。隊員が何も考えずにランダムに選んだとしても10万回(ランダムに出てくる商品が揃う期待値と同じなので n log(n) くらい)も登頂したらおおむねすべての塔の情報が集まってくるだろう。
これだとあまりに効率が悪いのでベースキャンプに書類の整理係でも用意しよう。毎朝、書類から登る塔の選定に重要な情報だけを抜き出してホワイトボードに書く係だ。情報がないときにはホワイトボードに何も書かないのが自然だが番兵を使うこともできる。つまり、存在していない-1の塔と10000の塔を仮想的に考えて、-1の塔には-∞、10000の塔には+∞と書かれていたというレポートを作っても結果が変わらないことを利用する考え方だ。さて、このとき、本質的な情報は2つのみだ。「3月5日5番塔に登った。10だった。太郎。」「5月7日15番塔に登った。50だった。次郎。」という2枚のレポートがあったとして、5番塔の情報は、15番塔が5000未満と分かったらいらないのだ。そういうわけで、小さい側と大きい側の情報の2つだけあればいい。それをホワイトボードに書くときに、いくつかの書き方がある。「小さいと分かっている塔の中で最大の番号」でもいいし「情報がないのはここから」でもいい。同じ状況を表すのにどのように表現するかの方法は何通りかある。ただ、表現された数字が何を意味しているのかについてこのチームの中では共通理解がないとまずい。あとで揉め事になりうる。
何を言っているかというと、二分探索で左だの右だの書いてあることがあるが、それが部隊内でどのような共通理解がなされているのかは必ず問題となるはずなのに、書いた本人もその理解がないまま書かれている場合がよくある。動くコードだとしても少し話をしてみるとなぜかここを理解せずに書いていることが分かるのだ。ここの了解があれば、あとは登る塔は必ず妥当か、必ず終了するのかと、終了時に欲しいものが出てくるのかくらいの問題でしかない。なぜか理解せずに覚えようとする人をよく見る。中学高校あたりで仕込まれるのだろうか。
また、読むときには上の部隊内の共通理解がなんなのかを読み取って辻褄があっているかを確認すればいいはずである。これも二分探索に限らない。
標準ライブラリーの実装を読んでみるのもいい練習だ。そこにあるコードは、典型的な解きたい問題に対して、その欲求を満たすためにエンジニアリングした結果、標準ライブラリーに収納されている。つまり、何をしたいのかという欲求や目的があり、それをどう表現するか、という順番で作られているので、表現から意味を読みとくことになる。
ついでなのでここに書いておくが、一般的にコードは書くよりも読むほうがずっと大切で重要である。読んで理解するためには書くよりもどうしてもある面では高い能力が必要になる。自分で書くときにはどう書くかは制限できるが、他人がどう書くかは読んで理解するまでは分からないからである。これは、AI で書くようになったとしても同じである。AI でコードが書ける時代になったのでコーディング能力が不要になるのでは、と聞かれることがあるが、あまりそうは思わない。私がときどき例え話で述べているのは『最高に優秀で信頼できる弁護士がいて、分厚い契約書を持ってきて「契約書の中を直接読ませてあげられないが、どういう内容かはいくらでも説明してあげる。だからサインして。」といったとしよう。そのときあなたはサインができるか。』ということである。ソースコードはその会社の中の仕事をこなす機械のためのマニュアルであるから、契約書のようにそのまま動いてしまう。説明を聞くよりも中身を読んだほうが楽で正確であるという状況が変わらないならば、これからも読むことの重要性はそう変わらない。特にレビューを人間がせざるを得ないとすると、ボトルネックは人間が読む速度になる。自動テストなどの仕組みが AI 向けにチューンアップされるとますます開発速度が上がっていき、それに比例してレビューで読む量が増えることになる。そういうわけで、コードを読むことの重要性、物事を理解することの重要性は変わらない。
しかし、AI のある環境が訓練の難易度を上げる側面があることは確かである。専門家はコードを見たときに「書いた人がこういう意図で書いていてこう動く」「バグがないか」などを読み取っている。ただ、いきなりそれを同じ精度でやれといわれても厳しいので、そのプログラムについて自分で書くなどして十分に分析や準備をしてから読むことで補助輪にする。そして、最終的には補助輪なしにできるようになるとよい、というように訓練するだろう。この訓練は一つ難しいところがある。自転車の走行は、物理的に外から見ても自転車が走ったか否かは分かるが、読めているかは心の中の話なので外から見ても基本的には分からない。だから、自分で訓練をどんどん骨抜きにしてしまうことになりやすいのだ。
面接官を見すぎない
技術面接において、教授からの試問のように対応する人がいるがそうではない。一番近いのは、家族から電話がかかってきて「今から20分で家に着くんだけどその後10分で出かけないといけない。この10分で食べられないと夜まで食べられないからなにか用意してくれないか。」と頼まれたような状況だ。返事は「いいよ。何したらいい。」ではない。分かったといいながら、自発的に冷蔵庫を開けたりしてプランを立てて欲しい。
また、まったく難しい問題を出しているつもりがない。難しい問題を出しても大した判断材料にならないからだ。
面接官が次のような状況だと思うといいかもしれない。「1年間準備してきたプロダクトもローンチまであと少しだ。そこで、小さな問題があるという連絡があった。ローンチ後に直してもいいができれば直したい。だが、忙しすぎる。この仕事は自分でやったら10分でコードは書けるがそのあとにプロダクションに乗せるまでに問い合わせが来たりで手一杯なのがさらに手一杯になる。隣りにいる候補者にちょっとお願いできないかしら。」これが何を言っているかというと、自分ならば10分でできることをお願いしているだけなので、ある程度自律的に動いて勝手に完成させて欲しいのだ。とはいえ、はじめのお願いからして十分ではないので判断に迷うところは聞いて欲しい。また、面接官の手間をかけすぎるならば、そもそも自分でやったほうが早いということになる。もちろん、面接は緊張状態にあるし、普段の仕事ならば共有している前提はもっとあるだろう。そういったところは割り引いて見てもらえるだろうが、とはいえ、本筋と何の関係があるのかよく分からないことを滔々と語られても、時間ペナルティーを自分に与えているくらいにしかならない。
クイックソートについて
アルゴリズムについて知っているというと、書けることと呼び出せることくらいができれば十分知っているといえると思われている気がするが、おそらく面接の場ではそれでは足りない。特にクイックソートは再帰の典型例であるから知っていて欲しいことが少し多めである。
インプレイスなクイックソートをナイーブに書くと次のようなものだろう。
int partition(std::vector<int>& arr, int low, int high) { int pivot = arr[low]; int i = low + 1; for (int j = low + 1; j <= high; ++j) { if (arr[j] < pivot) { std::swap(arr[i], arr[j]); ++i; } } std::swap(arr[low], arr[i - 1]); return i - 1; } void quickSort(std::vector<int>& arr, int low, int high) { if (low < high) { int pi = partition(arr, low, high); quickSort(arr, low, pi - 1); quickSort(arr, pi + 1, high); } }
とりあえず、クイックソートを書いて欲しいといわれたら、それは頭の中にある操作をコードの形で表現できるかという意味なので、これでも構わないと個人的には考えるが、このコード自体にはいくつか感じるところがある。
ピボット選択の方法として頭から取っているが、これはソート済みの配列に弱い。これに対して、乱択にする方法、頭と尻尾と真ん中の中央値を使う方法などがある。理論の文脈で最悪計算量が改善するメジアン of メジアンという手法が知られているが、これは現実的には遅く、知らなくてもいいのではないかと思う。
また、上のコードは同じ値がある時に弱く、特にすべて同じ値であったときに最悪計算量を引く。これに対しては、3値で分ける方法、ピボットと同じ値の半分を後ろに半分を前に回す方法などがある。
最後に、最悪計算量に近いときのスタックオーバーフローを起こさないようにするためには小さい方から先に再帰する必要がある。大きい方は末尾再帰除去最適化に任せるかループに書き換える。一般に関数呼び出しのスタックのサイズはそれほど大きくないからこういう工夫が必要になる。
Google のソフトウェアエンジニアに聞くとだいたい上のような内容が出てくる。アルゴリズムの本ではクイックソートが載っていることが多いと思うが、これくらいまでが載っていないと不十分だろう。面接ですべてが答えられる必要はないが、アルゴリズムに詳しいとアピールするならばこの程度は知っていてもよさそうだ。
データ構造やアルゴリズムの連想
何も分からなかったときに「これはセグメントツリーですか」というような質問が来ることがある。これは、データ構造やアルゴリズムを思いつけば解ける問題を解いてきているためである。
しかし、知りたいことは、手作業でできることを機械にお願いできるかなので、データ構造を思いつくと終わりというような単純な問題は好まれない。一番いいのは5つくらい解法が思いつくがどれも嫌なところがあるというものだ。その嫌なところを感じ取れる人ととは働きやすい。
それはともかく、解法が思いつかなければ、手作業でやるとしたらどうするのか、そしてそれをシフトを組んで複数人で行うとしたらどうするかを考えればよいだろう。
ちなみに、セグメントツリーは知らなくても別に評価の変わらないデータ構造だ。確かに私も Google の面接中に一度言及した記憶はあるが、言及したところで評価が一切変わらなかっただろうと思われる。数十行程度で書ける単純なもののわりに複雑な木の代用として使いやすいという特徴があるので過剰に意識されているデータ構造である。
コンピュータサイエンスの知識
コーディング面接から少し離れており会社や職種によっては聞かれないこともあるが知識についても簡単に書いておこう。詳しく知りたい場合はソフトウェアエンジニアリング協会にコンタクトを取ってほしい。
ソフトウェアエンジニアに限ると技術面接で聞かれる知識はかなり狭い。はじめにも書いたが、典型的な簡単な仕事は「巨大なコードベースと要望が与えられて、要望に従うように振る舞いを少し変えること」や「バグの原因を突き止めて修正すること」であるので、これに必要な知識が聞かれる。特にバグを修正するためには正常な動作を理解した上で異常な動作から原因を推測する必要がある。医学でいうところの生理学と病理学である。身の回りにあるソフトウェアがどのように作られているかと、それが問題を起こしたときにどう見分けるかということである。正常系は、ネットワーク、OS とアプリケーション、データと分散システム、言語処理系くらいであろう。そして、どこの部分が問題を起こしたかによって症状は異なる。
聞かれる場所は、重要で問題発生に直結しやすいところなので、たとえば、TCP ならばスリーウェイハンドシェイクと輻輳制御くらいである。それぞれ SYN flood やネットワークのスループットの低下などに関係している。他は、DNS、HTTP、ロードバランサー、プロセス、スレッド、ファイルディスクリプタ、仮想メモリ、ページング、システムコール、トランザクション、インデックス、ガーベージコレクションなどであろう。発生する問題を分類すると、応答性能の異常、リソース消費の異常、通信の異常、データの異常くらいである。
デバッグの方法は、基本的にはプログラムのデバッグと同じである。問題を再現して、発生箇所を特定、原因を分析して、修正案を作り、検証する。これだけである。また、いくつか難易度の高いデバッグ類型がある。複数のプロセスやスレッドの競合に関するもの、メモリーなどのリソース管理の失敗によるもの、不安定な分散システムとネットワークで起きるもの、バージョンや環境の変化に依存するバグなどである。
最後に、詳しいという自己評価は避けたほうがよい。たとえば、ガーベージコレクションを考えてみよう。今でも研究がなされている分野である。だからガーベージコレクションの研究者で主要な論文を毎週追っていて代表的な言語における実装を触っているのでもない限り深く知っているなどということは得策ではない。「参照カウンタ、マーク・アンド・スイープ、コピーGCが教科書的にはあり、実務ではさまざまな工夫がなされていることまた深い研究がなされていることは知っていますが、それほどよく知りません。ガーベージコレクションが問題を起こすととてもつらい目にあいますね。本当につらいです。」といっているほうがいいのである。ガーベージコレクションが問題を起こすと、メモリー使用量が徐々に増えていったり、突然一時的に固まったりする。直すのがなかなかに大変だ。試行錯誤なしにガーベージコレクションのどんな問題でも解決できるという自己評価の人がいれば、それは間違いなく大した問題に出会ったことがないだけである。何かに詳しいというのは悪いことではないが、採用したいのは「一緒に働くとより成果がでる人」である。
数の暴力に対する耐性
簡単なコードが書けるようになったとして、その後に、大きなコードベースを読めるか。そして、書けるか。それから、ソフトウェアエンジニアリングの範囲を超えた複雑性にどう対処するか、といった問題がある。そこらへんは応募している職によっては要求される。いずれにしても働き始めたら必要になるだろう。数十行程度のプログラムだけを扱っていると、数の暴力に対する耐性はなかなか身につかない。
このように学習が進むにつれて評価基準や要求水準が変わっていくのは、どのような分野でもよくある。高校までの歴史は歴史的な知識の有無を確認しているが、大学からは歴史的な史料を原語で読むようになる。そして、研究者となれば、史料批判を通じて過去の出来事や思想を理解し、それに基づいて新たな解釈や歴史像を構築する。数学でもそうだろう。数学者でもはじめは九九の暗記をしていたはずだが、大きな桁の掛け算を暗記するのが数学者の仕事ではない。
このことを認識しているだけでも振る舞いがよくなるように思う。
魔法が解ける
技術面接では難しいことを聞かない。なぜならば、仮に社員のほとんどができないことを聞いたら、入社してよいかの判断材料にならないからだ。ところが、実際に技術面接をしてみると予想外にできない。平均的な技術面接の通過率自体は5%以下で応募から1%以下0.1%以上くらいの感覚である。ただ、これは人為的に変えられる。十年ほど教えているという話を書いたが、特に初めの5年は一人あたりにかけていた時間は典型的には6時間から20時間程度だった。通常5%以下の通過率のところ教えた結果ほとんどが通過した。
技術面接では難しいことを聞かないことは体感しているし、少し考えれば分かることであると思うが、そうは考えていない人も少しいる。この活動を聞いた元同僚に「あの面接は天才しか通らないはずだ。」といわれた。そもそも選別がかかっているに違いないというのである。もちろん誰にでもできるようになるという気はないが、中学英語や高校化学程度の難易度であるのでそれほど大変ではない。文科省の公式発表では中学の英語の授業についてけるのは半数程度であるとされている。そういう話をした時に、近く受けるという人がいたので見学をしてもらった。「とても通るとは思えない。キャリブレーションがずれているのかフィードバックと準備で短期間に改善するのかどちらか。」というのが次に聞かれたことである。3回ほどの模擬面接を見てもらうと、通過しても不思議ではないところまできたという評価に変わり実際に通過していった。そして、模擬面接をしているのを見た人たちも自分で工夫して教えると教えられることが分かっていった。
それでは、教える時に何をしているのだろうか。表面的なその場しのぎでは無理だ。伝えた内容をできるだけ広い範囲に応用できるようにする必要がある。このためには、普遍的で抽象的な話から大枠を描いて、考え方を変えて行動の変容までつなげる。憑き物を落とす、魔法をかける、呪いを解く、現実を見せるなど、呼び方は何でもいいが、一番効率がよい方法が、何か致命的な行動をしたところで、なぜ問題があると感じるのかをかなり抽象的なところから具体的なところまでつながるようにすることだ。知識が足りなければ、その知識があったと仮定したらどのように振る舞うかなどを説明して知識の使い方を説明する。現実を見せるときに苦痛を与えてはいけない、というか効率が落ちるので、そこのあたりのさじ加減は、対面で相手の心の動きを見ながら行う。また、どのようなリカバリーが取れるかなどを説明する。人は言い間違えることが多々あるのでリカバリーは効くものである。面接は緊張状態なので普段の力がでないことが前提であるし、普段の仕事ならば資料を確認して当然な状況など面接の場ではできないことがある。ただ、何を気にしているかなどの価値観が普通ではないのは見て取れるので影響が大きい。
それから、振る舞いによって小さな傷を作らないようにするのは大事だ。チームマッチが難しいというが、2017年から2022年のハイアリングフリーズまでの5年半で、チームマッチに失敗したのは、枠のほとんどなかった一番はじめの一人とハイアリングフリーズに巻き込まれた一番最後の一人だけであった。これは面接のときの小さい傷がチームマッチに影響を与えるからであろう。
こういう内容であるから、オファーを貰ったあとで面接を頼んでくる人たちもいた。ソフトウェアエンジニアとして働くにおいて、本質的な考え方や技能を伝えているので、その魔法が面接以降も続くことを前提としている。
ところが、まれに面接が終わってオファーを貰った途端に魔法が解けることがある。短時間で変化したものは短時間で戻りうる。経験上、こうなるとあまりよいことにはならない。また、もちろん、もともと現実が見えないままオファーを貰ってしまう人もいる。本来のソフトウェアエンジニアの仕事のうちで簡単なものは「巨大なコードベースと要望が与えられて、要望に従うように振る舞いを少し変えること」や「バグの原因を突き止めて修正すること」などであり、仕事との関係で誰でもできる大したことを聞いていないのだが、技術面接を通った自分は優れた人物であると思うようになる。入社している人物は同じ基準をおおむね満たしているのだが、以前よりも難易度が格段に上がっているはずだ、などの根拠のない説明をつけていく。実際は容易になっている感覚さえあるのだが。こうなると以前からいる人たちは自分よりも愚かであるということになる。これは自分の技能と仕事の関係が分かっていない状態である。
これは原理上いたしかたない。2000年ごろまではソフトウェアの仕事は好まれていなかった。IT は新 3K や 7K「きつい、帰れない、給与が安い」「規則が厳しい、休暇がとれない、結婚できない、化粧が乗らない」などといわれていた仕事であった。学んでいる人はなんらかの強い動機があった。しかしながら、(事実かどうかはともかく)良い待遇の仕事であると考えられるようになると仕事の目的を認識せずにこの分野に入ってくる人たちが増えていくのだ。医学科も近いところがあると言われる。しかし、そういう学生でもベッドサイドに立って患者を見てからは目の色が変わる場合がほとんどだと上司の教授に昔聞かされた。ただ、情報系は医療に比べて不利な点がある。医療の場合は、自分が何かをした直後に容態が急変するという経験をする。多くの場合自分の責任ではないかもしれないが、しかし、事実として結果は変わらない。オペラント条件付けによればより強い刺激がより即時的に与えられるほどよく効くわけだが、情報系で何かを行ったとしてもそれが実際に悪い影響が出るまでには相応の時間がかかる。そういう意味で情報系は学習がしにくいのだ。そういうわけで、生得的な学習メカニズムを意識的に補助することが必要となる。だから、仮に働き始めたあともここで書かれているようなことが当たり前であるかを時折確認して欲しい。
最後に、教えたことを後悔することはないのか、などと聞かれることがあるのでコメントをしておく。別に迷いがないというわけではないが、原則論として、まず、教育が良かったか悪かったかは、教育を受けた人が老年になり一生を振り返ったときに決めることである。だから、外野ましてや教えた人物が教育自体が良かった悪かったなどというのは筋が違う。ただ、私自身の目標という意味では、情報系の能力を配ることであり、特に自分よりも効率的に教育する人物を生み出すことであるので、そのように教育する人物が生まれなかったならばそれは目標との関係では失敗である。
レジュメの書き方
コーディング面接とは少しずれるが、せっかくなので書いておく。STAR メソッドなど色々と形式的な話はあるが、それよりも前の方針の話がしたい。一番の根本は「私があなたたちと一緒に働いたら仕事の成果がより出る」ということが基本的なメッセージであるということである。
また、読む人は3種類と理解している。リクルーターがはじめ数秒見て会うかどうかを決める。エンジニアなどが面接前にみてどういう内容の質問をするかを決める。ハイアリングマネージャー(直属の上司になる人)が最後に採用するときに面接の内容とともに自分のチームにいると本当にありがたいかを決める。この3種類の特にリクルーターとマネージャーにどう受け取ってもらいたいかを考えて書くといいだろう。読んでいる横でこういう意図で書いたという説明はできないので、何も知らない人の気持ちになって読むことになる。
本当に書くことがないならば、コードが書けるだとか Git や Docker を使ったことがあるだとか書くしかないが、なんらかの人のために役に立った経験があるか、そしてそれを就職した後でもできるのか、ということが問われている。よって、インパクトと再現性を書くといいだろう。インパクトというのはどのように人の役に立ったのかということだ。再現性は、まぐれでインパクトが出たわけではないということなのでいろいろな要素がある。そのプロジェクトはそれなりの複雑性を持つものなのか、自分がいなかったら何が起きなかったのか、つまり自発性といったことだ。関わる人が多いことも複雑性に関するものだろう。『このことからも分かるように「私を雇って一緒に働くと、あなたのプロジェクトはもっと成果が出ます」』とつけて違和感がある行はおそらくずれている。仕事の経験である必要はない。自分や周りの人のために何か作った経験はないだろうか。エンジニアリングをするということは物や環境を構築して有用なものを作ることなので、その欲求があるならば自分の周りの環境を少しでも自発的に整備しているはずなのだ。高校の文化祭のときの経験でもよい。場合によってはソフトウェアを作ることで人の役に立ったのでなくてもよい。組み合わせて一緒に働くとより成果が出ると説得したい。
競技プログラミングのレートについては消すようにという指示まではしていないが、書くことが他にないので書いている程度の認識でいるべきだ。明らかに消したほうがいいと思うレジュメは見たことがある。レジュメスクリーニングにどこも通らないというので見たのだが、経験年数があり様々なプロジェクトで成果を出しているのに、レートを強く売り込んでいると歪んだ価値観を感じさせる。「私があなたたちと一緒に働いたら仕事の成果がより出る」というメッセージと繋がらないのだ。これは LeetCode のレートであったと記憶している。よほど書くことがなければコードの読み書きができるということを意味しているので、レジュメスクリーニングでプラスに効く場合があるかもしれないが、それくらいである。外資系企業は国内でレジュメスクリーニングをしていない場合があるためほとんどレートの書かれたレジュメは見ておらず、そういったレジュメが通りやすくなっていないと考えられる。一方で書かなくても問題はないようである。大学の授業や本の内容をなぞっただけの個人プロジェクトを書いているのもネガティブに働く。情報科学の技能を使って問題を解決したことではないことをアピールしているからだ。なんらかの現実の不便さを解決するために仕組みを考えたこととは異なる。
もう一つ、レジュメに書かれた内容は自己申告であるので本当かどうかは分からないということである。ただ、何を重要だと考えているかの価値観は突き抜けてくる。どういう人物は仕事の成果がより出ると考えているのかが分かるのだ。特に日本は儒教的であるため格の話をしがちである。
最後にレジュメについて言いたいこととしては、一種のセルフハンディキャッピングをしないということだ。「環境はひどいものだったが、私の能力が高かったので、結果的に差としてそこそこの成果になった。」という主張をする人が結構いる。どんなプロジェクトでもそれなりに面倒なことは起きる。「周りの仕事は不完全なこともあるが、そういうものである。そういう状況でも別に乗り越えていけるのだからどうということはない。でかいプロジェクトになれば当然ややこしいことも起こる。エンドユーザーが喜んでもらえるようなことができてよかった。」という態度でいて欲しい。他の類型としては、たとえば「このプログラミング言語を初めて使ったにもかかわらず、プロダクトが出せた。」というのがある。これは「プロダクトが出せて素晴らしいインパクトがあった。」とインパクトを差分ではなくて絶対的に書くべきだ。「習得が早く、初めて使う言語でも柔軟に扱えた。」というのを付言するという形ならば否定まではしないが、こちらのほうがだいぶ弱いシグナルだろう。高いプロ意識と成果を出す能力を持っているという主張をし、未来での貢献を予感させて欲しいのだ。
最後の後に追加しておくが、リクルーターによるスクリーニングの部分だけは少し異なる要素がある。まず、ここはあまり再現性がない。面接はコストがとても高いので面接できる人数は限られている。そこでリクルーターが通りそうだと感じる人を選んでいるがこの時点では情報があまりないのでどうしても安定しない。精神的なダメージを受けるだけ無駄である。同僚の経験談として、レジュメを出して応募し、リクルーターから「面接にぜひ来て欲しいのだが、手続き上の問題でここにレジュメを出し直してくれないか」と頼まれて従ったところリジェクトの通知を受け取ったという話がある。リクルーターに問い合わせたら面接に呼ばれたので同僚になったということだが、それくらい適当である。Google Code Jam のファイナリストや IMO メダリストでもレジュメで落とされた例を知っているのでコンテスト成績もあまり関係がなさそうである。学歴が効くと思っている人もいるようだが、ここで少し書いたように東大医学部医学科のレジュメがなかなか通らなかった。そういうわけで、上で書いてきたようにコンピュータを使ってエンジニアリングをした形跡がほぼ必須である。この形跡がなくても許されるのは、情報系か物理系に在学中の場合くらいで、ここはよくいる経歴の人たちなので単にレジュメを書くのが下手であると思って拾ってもらえることがあるようだ。こういうトリックがいまでも有効かは分からない。いずれにせよ、この条件を満たした後は、レジュメスクリーニングは抽選に当たるかどうかくらいのものだと思ったほうが精神衛生上もいいだろう。
そういえば、もう一つ。会社によっては新卒での応募だけレジュメスクリーニングがなされない場合がある。そもそも、新卒一括採用と中途採用が厳格な分かれ方をしているのは日本の流儀であり、アメリカでは一般的にはそうではない。通年で区別なく採用していてポジションによってスキルの要求が異なるという形だ。そして、日本のように入社の一年前に内定を出すというようなことは普通なくせいぜい数ヶ月前だ。ただ、それでは日本の新卒一括採用の文化に合わないので早めに受ける枠を用意しているのが新卒一括採用枠であるように見えているだけである。この枠は短期間に応募が多くレジュメスクリーニングを人手でするのが大変であるためオンラインでタスクを与える形式で代替することがある。そういうわけで採用基準が変わる訳では無いがレジュメスクリーニングを回避できる利点がある。特に理工系でない学生の場合は、卒業後にインパクトのあるプロジェクトの経験を集めることになるのでこの利点が大きい。よく「新卒でどこかの会社に行ってから、希望の会社に転職をしたい。どこの会社がいいか。」と聞かれるがこのような理由から新卒が一番入りやすい。あえていえば、レジュメスクリーニングの観点からは人の行き来がある会社であるとわずかによいかもしれないが誤差程度である。もっとも、受動的に在籍していることでソフトウェアエンジニアとしてのスキルが身につく環境というのが日本にあるとは思えないので、スキルについては自発的に習得して欲しい。それに、かえって職歴があるということによって自然に増える期待もある。たとえば、東京でタクシードライバーをしていたというならば、東京の道路やランドマーク、客や渋滞の分布を知っていることが期待される。横浜でしていた人に大阪のことを知っていることは期待しないが、大阪でしていたら大阪、名古屋なら名古屋のことを知っていることが期待される。つまり、仕事の適性、主体性や観察力が問われる範囲が広がるのだ。
選別と教育
この話もついでにしてしまおう。私にはいまいち分からないのだが、持って生まれた才能を重視する人達がいる。言い方を変えれば、選別か教育か、という話だ。
行動遺伝学によると、人間の能力は遺伝が5割、共有環境が1割、非共有環境が4割であるということになっている。これは双生児の研究から出てきた数字で、共有環境とは家庭内で兄弟姉妹で共有されている環境、非共有環境とはそれ以外のことである。割合は分散を考える。人間の能力が正規分布をするとして、その分散の寄与を回帰させると上の割合になるということだ。
ただ、私がまず言いたいことは、この考え方自体、教育が敗北している状況が前提でなされているということだ。
人間の身長はほぼ正規分布になるが、イヌの体長は正規分布にならない。これは何を言っているかというと、人間の身長に影響を与える要因が極めてたくさんあり、それらが独立に作用するのでおおむね正規分布のようになるということである。一方で、イヌの体長はヒトが人為的に交配をして品種を作っているので正規分布にはならない。仮に、人間が隕石に当たると身長が3倍に成長する生き物であったとしよう。一定の人数が隕石に当たるとすると、人間の身長は正規分布しなくなるだろう。
つまり、行動遺伝学の前提とする、ヒトの能力の分散に対しての寄与を遺伝、共有環境、非共有環境に分けるという考え方自体が、そもそも人為的な教育による介入が乱数程度にしかできないことを前提としているのだ。
念の為書いておくと、この遺伝の影響が半分というのはよい遺伝子があるということを意味するとは限らない。同じ家庭と異なる家庭で育てられた一卵性と二卵性の双子の能力から分散に説明をつけているだけなので、ある二卵性の双子のどちらの遺伝子が、ある環境にその尺度でよりフィットしていたかをいえても、その遺伝子が普遍的にすべての環境で優れているとはいえない。また、遺伝に支配されている割合は文脈依存性がある。つまり、たとえば、環境の要因は双子同士の育つことになった平均的な違いに依存するので状況に依存しているはずである。このように行動遺伝学の研究には様々な限界があり「遺伝が5割、共有環境が1割、非共有環境が4割」は標語程度の意味しか持たないものである。
もちろん、私は遺伝の影響がないという気はない。しかし、それ以上に教育による強い介入が可能であると考えている。しかしながら、教育が行われることはめったにないため、上のような分布になる。私が教育による強い介入が可能であると考えているのはいくつかの観察と経験からくるものだ。
たとえば、囲碁のプロ棋士には5代続けてプロ棋士、4代続けてプロ棋士の一族がいる。2022年のワールドカップサッカー日本代表は、26人中6人が川崎市の西部と横浜市の北西部の狭い地域で小学校時代を過ごしている。湯川秀樹(旧姓:小川)の兄弟は、戦死した末弟を除いて4人全員が東大または京大の教授で、父と次男の息子も東大か京大の教授であった。全員分野も違う。親子兄弟姉妹は遺伝子を半分しか共有されていないので、逆説的だが、親子は25%、兄弟は共有環境を加えて35%しか能力を共有していないというのが定説からの計算になるが、それではこの割合は説明がつかないだろう。つまり、隕石が当たるような稀な事象を一定割合で人為的に起こすことがある家系や地域や家庭で可能であったということだ。
私が学生時代に学部ごとに誕生日の分布を調べたことがある。結果は面白いものだった。法学部や経営学部などは4月生まれが2割前後多いが、理系はほぼ偏りがない。つまり、誕生日が大学の専攻に影響を与えるということだ。この原理を考えるとさらに不思議なことを意味する。幼少時に周りを見渡してなんとなく自分はこういう人物だと思い込む効果が、誕生日によって確率的に異なるために、統計的に専攻の分布が変化するということだ。論理的飛躍を恐れず、私はここからこう結論づけた。わずかな思い込みによって専攻が強制されており、そこの制限は精神をいじれれば外すことができるはずだ。
それでは、強い介入とはどのようになされるものなのだろうか。私もそのメカニズムが完全に分かっているわけではないが、私はそれを暗黙の前提を直接的に特定・修正・再構築する行為と考えている。いくつか具体的な話から説明しよう。
これは私の話ではないが私を教えてくれた人の話である。ある日、小学校二年生の子が宿題をしていた。「1デシリットルの水が入ったコップが12個あります。これは、1リットルのやかん□個と1デシリットルのコップが□個です。」というような問題が並んでおり、その子はいろいろな数字を書いてみるが答えと合わない。それを見ていたある人が、いくつか質問をしていって、次のような発見をした。この子は「せんべいを3枚持っています。空のお盆の上に置きました。お盆の上にせんべいはいくつあるでしょうか。」という問題には答えられるが、「やかんの中に水が2リットル入っています。空のバケツに移しました。バケツには水が何リットル入っているでしょうか。」という問題には答えられない。つまり、はじめの言動を引き起こしていた欠如していた知識は「液体には量があり、それは移動によって保存する。そして、その単位はリットルである。」であったということだ。
そこで、「バケツの中に3リットルの水が入っています。なべに移しました。何リットルでしょうか。」「5?」「それがねえ。なんと3リットルなんだよ。」「水槽の中に2リットルの水が入っています。ビニールプールに移しました。何リットルでしょうか。」「3?」「それがねえ。なんと2リットルなんだよ。」というやりとりを繰り返していると、なぜか「液体には量があり、それは移動によって保存する。そして、その単位はリットルである。」ということを理解するようになる。
何を簡単なことの発見を重大なことのように話していると思われたなら心外である。この質問に対して、どれほど暗黙の知識を使っているかはちょっと変えてみたら分かる。「デュワーの中に液体窒素が50センチメートル入っています。ざるに移しました。何センチメートルでしょうか。」これは50センチメートルではないだろう。ところで、この状況は暗黙の前提を置くと身の危険を感じる。
水に話を戻そう。特にこの場合、発達段階の議論では一般に液体の量の保存が分かるようになるのが7歳前後とされているから、量の保存概念がない可能性は十分ある。量の保存の概念がない状態ではリットルとデシリットルの換算は意味が分からない。また、この子が当てずっぽうにでも数字を言い続けてくれるのが解析の大きな手助けになっている。そして、この子は量の保存の概念を分かった後、そのことがあまりにも嬉しくて周囲のあらゆる人にリットルとデシリットルについてのクイズを出し続けた。
子供は特にこういった概念を理解しないために躓き自発的に解決できないことがよくあるが、これは大人にもあてはまる。大人でも誤った学習によって袋小路に陥っていることは多々あるし、修正がどれくらい自発的にできるかは人によって異なる。たとえば、経理で理由を求められたときには意図を書くのだが、原因を書く人がいる。法律上の扱いは意図に依存し、意図は本人しか知りようがないので聞かれているのだが、特に自然科学の教育は意図を排除して記述させる。このため話が噛み合わなくなる。たとえば、約束に対価があるかで無意識に人は異なる扱いをするがこれに気がついていない人がいた。人間関係の観察だけでこれが体得できる人はいいができない人にいかに説明するかは問題になる。コモンローでは Consideration (約因)という概念がある。約因というのは、要するに契約の対価ということで対価がない契約は英米法では認められない。アメリカで CEO が給与を1ドルにすることがあるのはこれである。コモンローは慣習から発見されるという建前をとるから、おそらくこれも約束と契約の差異が制度化されたものであろう。日本では取締役の報酬は0円にできるが、書面によらない贈与は解除できるから、対価のない契約に一定の特殊性が認められているといえるだろう。このように大人でも誤った学習が残っていることがよくある。それが問題を起こしていなければいいのだが、ときには大きな障害となる。こういった問題は、得てして、その根本的な原因からは遠い形で表出しているので、問題が起きる最小のケースを探し特定する必要がある。この際、もちろん見つける側にもスキルが必要だが、見つけてもらう側も見つけてもらいやすくするスキルがある。
他にも問題が解けていても意味が分からないまま習った儀式を繰り返しているケースがあり、こういった場合は一瞥して違和感を感じるのだが紙の試験では点数が引くことが難しい。ただ、口頭試問では容易に確認できる。
教える経験から得た理解もある。ソフトウェアエンジニアを育てることもそうだが、中学受験のアドバイスをしたこともある。数時間話をしただけで変わり、偏差値40台から半年で開成、30台から二年で桜蔭に入っていった。小さな障害を取り除いた程度の話であり、自分の貢献であるという気はない。小学生まではおかしな癖がついていないので特に容易である。会ってそれぞれ20個程度、40個程度の改善点を書き出しただけである。開成の子については半年と時間がなかったこともあり、気がついたことの半分は保護者に向けてであった。
たとえば、保護者に聞いたことは偏差値をどのようなものだと思っているかである。学校の偏差値は定義は一意ではないようだが、典型的には、ある模試を受けた集団を点数で輪切りにしたときに、その学校に80%以上の確率で合格すると推定される切片のうち一番低い点数を正規化したものである。だから、倍率が1.25倍より大きく模試の成績と無相関に採用している学校は偏差値が無限大に発散する。つまり、模試の質が下がれば、学校の偏差値は上がるのである。ここでいう質というのはその学校の難易度を測る尺度としての適切さという意味である。こういったことを理解していないことが広くどのような問題を引き起こすのかはこの章の最後に譲ろう。
このように、私は観察や経験から強い介入が行われた形跡があると考えている。しかも、プロ棋士やサッカー選手を育てるには長期的に強い介入が必要なのかもしれないが、開成・桜蔭・ソフトウェアエンジニアを育てる程度ならば、経験上、短時間で強い介入を行えば十分なのである。短時間の接触で済んでいるのは自発的または家庭内で「暗黙の前提を直接的に特定・修正・再構築する行為」を再現できるようにできたからであろう。
そして、これまで行ってきた強い介入をどのように起こしたかをおぼろげながらにしても説明したつもりだ。一人で再現することは難しいかもしれないが、複数人で議論しながら読み込めばおそらく再現できるようになるだろう。強い介入は「隕石が当たるような稀な事象」であってはならない。故意に引き起こせるものにしなくてはいけない。
ただ、強い介入を受ける側にもそれなりの用意がいる。小学生くらいのうちは強い介入を素直に受け入れられるのだが、高校生くらいになってくると余計な自己判断が出てくる。この強い介入がなされたとしてもある種のホメオスタシスによって元に戻っていく。これで正しいのか自信がないなどの理由をつけてやらない理由を発見する。
この際、一番問題になるのは報酬系である。多くの場合、何かをできるようになるのは退屈なのである。どのような分野の専門家であれ、共通言語基盤となっている範囲を満遍なく押さえないといけない。しかし、既にできるところを伸ばすことは容易であるが、できない部分を伸ばすのは退屈である。たとえば語学では徐々に映画や小説などが楽しめるようになるが、ある水準まで到達するまでは意味が分からず退屈である。たとえば楽器で曲の練習をしていて一音だけ間違えたからといって、その音だけ後から出しても一曲通しで弾いたことにはならない。多くの場合、その音の前に本来の問題があって、そこを見つけ出して修正をし、さらに安定してできるようになって初めて一曲通しで弾いたことになる。こういった解析は自分の失敗について考える作業であるので退屈である。こういう場合、報酬が支払われるまでの時間がどうしても長くなる。少年漫画的な世界観だと「窮地に追い込まれて苦痛を感じ絶望したときに相転移を起こし劇的成長を遂げカタルシスを感じる」が現実にはそうはならない。はじめは単に退屈で味がしないが、淡々とこなしているとかすかに味を感じるようになって、そこから味がどんどんするようになってそれが普通になる。高校生にもなるとこれが難しくなってくる。この点、上に出てきた小学校2年生の子は恐ろしく強い。辛抱強く滅茶苦茶な数字を言い続け、液体の量の保存とリットルが分かったというかすかな報酬だけでずっと喜んでいられた。
この報酬系の問題は、強い介入を受ける上で大きな抵抗を引き起こしやすい。強い介入が「暗黙の前提を直接的に特定・修正・再構築する行為」であるから、それは必然的に退屈な分析や思い込みを修正する作業を含む。そして、長期間、強い介入を続けられない状況では、個人または家庭内でその介入の効果を継続させるようにせざるを得ない。対面で話をする場合は、まだ強い介入に持続性をもたせることがやさしめで、退屈などものともせずに全力で走り出すことがあるが、そうでもないとある種の意思の力が介入を受ける側に求められる。
素質や偏差値への信仰も強い介入に対しての抵抗となる。生まれつきの素質によってできることの多くが決まっており、偏差値という単一の尺度によってそれが測れるという考え方だ。「遺伝が5割」という考え方も基本的に軌を一にしている。この理解は、強い介入を受けたときに自身の制限を正当化する考えをもたらす。遺伝や素質の影響が、環境や介入によって劇的に変動するものであるという認識があれば、強い介入を受けいれる余地が生まれる。
ここまで、強い介入が「暗黙の前提を直接的に特定・修正・再構築する行為」であるという推測と、それがどういう状況で効果的になるのかを書いてきた。この考えからは、プロ棋士、小川一族やサッカー日本代表に見られる「才能の集中」は強い介入が特定の家系や地域で継承されていたということだと解釈できる。隕石を当てることは人為的にできるのである。
継続的に強い介入が起きる場所があるとして短時間で行う場合はどうだろうか。短時間で大きな効果をもたらすためには強い介入の効果が継続する必要があると上に書いた。自発的に発生し続けるようにするということは、つまり、介入を受けた人が自ら介入できるようにするということである。だから、強い介入に関してメタ的な認識をすること自体が強い介入の最も重要な部分である。短時間で行う場合は憑き物落としに近い感覚を持っている。そういうわけで、この章がここに設けられたのである。この文章全体で書かれた内容の実践にこの章の内容が役に立つことを願っている。「Twitter で医師を拾ってきて Google のソフトウェアエンジニアにするだけの簡単なお仕事」「人生のチュートリアルを終わらせるたったひとつの冴えたやりかた」も参考になるだろう。
さて。この章の最後に学生時代に中学受験生の男の子を教えたときの話を書いておこう。実は私は教育をするのがそれほど好みではないので、小学生を教えたことは2, 3度しかない。桜蔭の子が1人目でこの子が2人目だ。この部分はまったく別の文脈で何年も前に私信として書いたものであるが、色々な人に私的に見せるととても評判がいいのでそのままここに置いておく。具体的なアドバイス部分は省いているので万人の役に立つ部分であろう。これを参考にしようとする人が万が一現れたときのために追加で3点書いておく。まず、理解できない反応が出てきているときほど黙って観察する時間がより必要になる。多くの場合先生役が話しすぎている。次に、保護者が伴走し塾も使う場合、仮に塾の先生に疑いを抱いたとしてもそれを子供に伝えてはいけない。判断材料を集め先生を変えると判断をするならば黙って変えるべきだ。さもないと習えと疑えという両立しがたいメッセージを出し続けることになる。最後に、これは何年も経った後に書いているので、伝えている内容はある程度正確にしても、表情や話し方などが書かれていないため雰囲気はおそらく読んで得られる印象とは著しく異なるだろう。会ってお母さんはとても明るい気持ちになったということだった。
ある日、友人から、そのさらに友人から次のような相談を受けたので、ちょっと見てきてくれないか、といわれました。その家庭には、小学生の息子がおり、成績が平均以下と振るわないので志望校を変えることも視野に入れているというのです。私も友人の頼みですので、紅茶とケーキくらいごちそうしてくれれば見に行くと申し上げ、最近の模試の問題用紙と解答用紙を用意してくれるように頼みました。
席につくと、お母さんは、まず、成績表を見せて偏差値をこれくらいにしたい、という話をされました。それに対して「偏差値をそれくらいにするには何点とればいいのか計算できますか」と聞きました。分からないという答えをもらい、そのあと少しやり取りをしたあとにこのようにまとめました。
「要するに、偏差値が思ったような値でない、将来に悪いことが起きるのではないかと感じて不安になる、不安になるが子供にその重大さが通じていない気がするのでいかに不安かを子供に伝える、という理屈で行動していますね。それで私が呼び出されたと理解しています。ただ、思ったような成績が取れないことは本人にとっても十分に嫌なことであり追撃を入れる意味はないと私は思うんですよ。」
「子供への指示は常に具体的に肯定形で。何かをするなという否定文での命令は読解が必要になるので難しいです。」
「私が何を言っているかというと、子供をサンドバッグにするな、ということです。子供の受験で不安になるのは自然ですが、それを子供に言っても状況はおそらくよくならないでしょう。だから不安をぶつけてもいい人を用意します。精神的に安定した大人、普通は他の家族とかになります。ああ、私も家族にサンドバッグになってくれと明示的に頼まれてサンドバッグ役をやっていたことがありますよ。あと、もちろん、今の話が納得できなくて、本人に不安をぶつけたほうが成績がよくなると思うならば、もちろんそうすればいいのですが、ここでもう一つ言いたいことは、なにか行動を取る前にできればそれの帰結が何かを考えたいということです。」
国語を見るとまったく違うことをしているので、そう伝えました。「自分の感情や願望の話をしていますが、何が書いてあるかが聞かれています。求められていることが分かっていないです。サッカーとバスケットの区別がついていないレベル。算数でいうと、時速何キロで歩いているかを割り算ではなくて、自分の普段歩く速度で答えるレベル。」そして要求されていることを説明しました。「将来、あなたがなにをするのであれ文章を読むことになりますが、そのとき相手がどう考えているかを理解することが求められます。あなたがどう思うか、などどうでもいいのです。」
その後、算数の問題を4問ほど、息子さんと同時に解きました。
終わると、私は息子さんの答えを覗き込み「どっちが違うかは分からないけれども一致しないやつがあるなあ。」といってから自分の解答の確認に入りました。
すると、お母さんが、模範解答を見て横からどの問題が間違っているかを指摘し、すぐに息子さんは修正しました。お母さんは「ミスが多いのですが、ミスだから大丈夫だと息子がいうんです。」といいました。
「まず、お母さんが模範解答を見てどれが間違っているかを指摘したのはよくありません。試験会場についていけないのだから、本人が見つけないといけません。あと、息子さんが間違っていると聞いてすぐに直せたのもよくありません。基本的に自分のやったことに疑いを持っていないということだからです。」
「息子さんのよくいうというミスだから大丈夫である、という発言が何を意味しているのか、考えてみましょう。ミスであるから本質的には理解している、よって、自分は悪くない、自分の知性に傷はついていない、という話をしていますね。もうちょっと踏み込むと、知性に傷がついていないので、親に愛される資格がある、といっていますかね。」
「一般にミスというのは、罪が重いものです。あなたは将来なんらかの重要な仕事につくでしょう。そうですね、分かりやすいところで医師になったとしましょう。たとえば、投薬量の計算で小数点を打つ場所を間違えて事故で患者さんが死に至ったとしましょう。そのときに、ミスだから大丈夫、といったら遺族の方々は「ああ、よかった、ミスだったか」といってスタンディングオベーションをして帰っていくと思いますか。よほど、「全力で、あらゆる手立てを尽くしましたが、力が及びませんでした。」といわれたほうがまだ納得できるでしょう。ああ、これはどんなことでも一緒ですよ。もちろん、人は誰でもミスをするのですよ。ミスはなくならないものです。しかし、ミスをしたならばそれと向き合わないといけません。正直、塾の模試なんてどうでもいいのです。できてもできなくても、行きたい学校に入学できたならば、どうでもいいでしょう。ミスをしたのがそういうどうでもいいところで、とても幸運だったわけです。入試も行く学校が変わるだけですから、大して大事なわけでもないです。ただ、次のミスがそういう幸運なものかは分かりませんよ。」
「それから、あなたが悪いか悪くないかにも、あなたの知性にも、私は興味がありません。」
「最後に、親に愛される資格、というのに引っかかります。あなたは親よりも長生きするであろうし、そうしなくてはいけないです。そう考えると、あなたは10歳を過ぎたのだから、そろそろ周りから与えられる価値判断から離れ、自分の判断を持ちはじめてよいのではないかと思います。私は、あなたが例えば試験で満点を取ったとしても、自分を許せなくて怒り狂う事があってもよいと思うし、仮に零点であったとしても、自分はよくやったのだと満足することがあってもよいと思うのです。」
「いや、まあ、明日からあらゆる価値判断を自分でし始めたらそれはそれで困ると思うので、これから10年かけて自分の考えを確立していって欲しいと思いますけどね。」
というようなことを楽しそうに話していると、平均を切っていた子供が次の模試でクラスが4つ上がり、最終的に開成に入りました。たしか、今は医者になっているんじゃないですかね。
もちろん、この他に具体的なアドバイスもたくさんしておりますが、一番大きいのは親子関係の変更、次が科目によってはまったく違うゲームで戦っているので、ゲームのルールの理解。あとは、現実の認識です。認知行動療法みたいなものですね。
最後に
コーディング面接であれば、だいたい上のような内容を読めばかなりの部分が解決するだろう。コーディング自体の習得もそれほど難しいものではない。2023年の年末からコーディングの練習会を開いており、速いと2, 3ヶ月もあれば書けるようになる。
コーディング練習会参加マニュアル(一般社団法人ソフトウェアエンジニアリング協会) - Google ドキュメント
プログラミングコンテストの能力とはほとんど関係がない。Google だけでも10年近く毎年送り込んできたが、半分はコンテストに出たことがなく、残りも緑、水色、青が同じくらいずつだ。緑より上は、ゆるく下がる印象があるがこの記事の内容を理解すればほぼ差が消えるだろう。茶色下位でながらく停滞していた人も少し教えればできるようになった。
練習するときには何をできるようにするのかを気にしなくてはならない。だいたいの場合基準をハックをすることは簡単なので、自分でハードルをどんどん下げていってしまう。
また、この考え方が非常に広い応用範囲をもっていることも分かるのではないだろうか。たとえば、物理学の研究者になりたいならば、何も見ずに一次元調和振動子のシュレディンガー方程式くらいは解けて欲しいといったことに相当している。学部の演習問題というのは、専門家集団にとっては突然聞かれても解ける常識程度の問題しか出ていないのであるから、知的な挑戦として解いてはいけないのだ。
就職活動に際しての面接は当たり前だが職種によって違いがある。機械学習エンジニアなどであると、典型的な仕事としてモデルを作ったり改良したりするので、そういったことが聞かれる。モデルの改良はこうすれば必ずこうなるということがあまり言えないが、状況に応じてどういう手立てがあるかがある程度網羅的に出てくることが求められているように思う。
ここまで競技プログラマーという言葉を使ってきたが、競技プログラミング同好会の影響を直接的または間接的に受けた人くらいの意図である。競技プログラミング同好会と関係がない人物が競技プログラマーを名乗るべきではないという考え方もあるが、同好会の影響下にあると自認しているならばいいだろう。今でも道場で育った人たちがプログラミングコンテストの街で活躍しているから実際のところほとんどすべての人が同好会の影響下にある。それは悪いことではないが、それはともかく私の世代が能力の継承を一部途絶えさせたのだろう。ここまで書いてきた内容の多くは競技プログラミングという道場ができる前のプログラミングコンテストの街で私が身につけたことだった。ただ、競技プログラミングをしていたと申告して私のところにやってくる人たちを教えていると、身につけたことの中には今となってはあまり見られない知識や能力があるのでそれを戻す目的でこの文章を書いている。代わりに当時はたとえば Dynamic Programming が分類されておらず一種類しかなかった。その後、分類を細分化してパターンを増やしてコードが読めなくても書けるように整備されていった。鶴亀算などをたくさん知っていれば、一次方程式と行列を知らなくてもよいというようなものである。
私は誰が何の仕事をしようがどうでもいい。ただ、日本の経済の立て直しのためにしなくてはならないことの中に、コーディング能力を広めることがあると考えている。そして、それを前提としたソフトウェアエンジニアの大量の育成とソフトウェア産業に関連する研究者の養成がある。特に、ソフトウェアエンジニアというのが「機械に指示を出せる文系総合職」に近い職業であるから人口のかなりの割合にソフトウェアエンジニアとしての能力を配らなくてはいけない。これには少し注釈が必要かもしれない。先日、内閣府から IT システムを作るよい方法はないのかと聞かれた。答えたのはだいたいこういうことだ。50年前を考えて欲しい。少々、人手がかかるかもしれないが紙とペンと電話と FAX で事務の仕事はできるはずである。会社の各部署に文系総合職が散らばり、それぞれが自分の部署がうまく回るように全力で考え、人に指示を出しながら綱引きをすると回る仕組みである。仕組みの変更をするときには、文系総合職同士が抗議をしたり飲んだりと調整をしていく。この仕組みの一部を電子機器に置き換えていったものが現在の業務のワークフローのはずだ。人は指示を出しておいても勝手に指示を変更していったりするが、電子機器は指示を出しておけば同じことを繰り返してくれる。一方で融通が効くわけではない。だから、文系総合職の役割のうち保守などの部分は減るが、むしろ、業務フローが回るか、それを束ねたときに回るか、仕事を業務フローに翻訳する仕事、業務フローを改善する仕事などの部分は大変になっているはずだ。そして、予想できない状況は必ず残るので会社だと部長や社長などの決裁でかたをつける。こういう風な業務フローと機械の場合はデータの管理方法・溜め込み方を考え、変更で起きる問題を洗い出す仕事は電子化によって減るどころか増えているはずである。これをするためには業務と機械の特性を理解した人物が必要である。そういったことを説明した。しかし、日本にはそういった人たちが足りない。ソフトウェア産業に関連する研究者の育成はもっと大変である。日本の大学の仕組みがもともと戦前にできたものであり、研究分野が硬直的になっているため日本では原理的に十分なソフトウェア産業に関連する研究者を生み出すことができない。だからおそらく海外の大学院に進学する人を増やす必要がある。この話は別に書こう。
(2025/11/30 追記: 書きました。)
情報系の分野について、中国語では非常に正確な情報が飛び交っているが日本語では情報が十分でないため、圧倒的な差がついてしまった。その結果、世界の AI 研究者の半分が中国系で、Google などのビッグテックのソフトウェアエンジニアの2, 3割が中国系だ。言い方を変えれば、きちんとした訓練方法が日本で知られていないために起きているだけであるともいえる。
なぜ日本では必要な人たちに情報が届かないのか。この文章を読んだあなたもたとえば小学生の子供を持つ親にこの文章を共有しようと思わないだろう。それは情報を発信して周りを助けることが当たり前になっていないからだ。そうすると、小銭稼ぎのためのマーケティングによって誤情報が流れ続け、社会全体が損をする。毎年数万人の規模でソフトウェアを扱える人を増やすにはこれも何とかする必要があるだろう。
幸いなことに若者が力のある分野である。正しく養成をすると10年あれば追いつけるはずだ。
追記(2025/12/5): ソフトウェアエンジニアリングの習得で、上の考え方の実践をするのに助けが必要ならば、下のリンクが参考になるであろう。
ソフトウェアエンジニアリング協会: 業界関係者で集まっている団体。オンライン・オフラインで授業や練習会を開いている。
ソフトウェアエンジニアリング協会のイベント情報の購読: 大学などで定期的に集まっている。今年は、東京大学、東京国際工科専門職大学、水道橋内海、大阪キリスト教短期大学で開かれる。
https://x.com/ainsophyao: 著者の X。DM などでの連絡もどうぞ。
docs.google.comこの記事と同じ考え方によって開かれているコーディングの練習会の考え方についてまとめたもの。
docs.google.com練習会に参加している人に向けてしたアドバイス集。一部は Discord 上であるが、上の協会にコンタクトするなどで参加すれば全文が読める。
docs.google.comなぜか定期的にバズっている学習についてまとめた スライド。
nuc.hatenadiary.org教える側から見た体験談と学習のための書籍など。