Cコンパイラ制作の夏期集中コースが思っていた以上にうまくいった話

2018年の夏に僕はセキュリティキャンプ(以下「セキュキャン」)というイベントでCコンパイラ作成コースの授業を行いました。授業はとてもうまくいったといってよいと思います。参加者は6人だったのですが、6人全員プログラミング技術がかなり飛躍的に向上したようですし、そのうち3人は期間中にセルフホスト(自分の書いているコンパイラで自分のコンパイラ自身をコンパイルできること)まで漕ぎ着けることができました。

この文章では、その授業をどのように僕が教えたのかということと、生徒にできるだけ多くのことを学んでもらって自信をつけてもらうために僕が何を気をつけていたのかという2つの点について説明します。

セキュキャンとは

セキュキャンは5日間の合宿イベントで、学生を対象としてコンピュータセキュリティやプログラミングについて教えるというものです。いくつものコースが用意されているのですが、僕が受け持ったのは「集中コース」というある特定のテーマに期間中集中して取り組むというものでした。僕は集中コースでCコンパイラ作成を教えることにしました。参加者は公募制で、願書を出してきた学生から、共同講師のhikaliumと僕とで参加者を選びました。 

目標設定

コンパイラ開発というものは終わりのない作業で、改良しようと思えばいくらでも続けることができます。とはいえコースという期限のある中で教える以上は、目安としてわかりやすい目標があるとよいと思って、セルフホストをコースの目標設定ということにしました。

テスト用のプログラムをコンパイルできるようにする、といった別の目標設定も可能でしたが、セルフホストはコンパイラ開発の深淵に触れることができるので、開発目標としてはとても面白いのです。セルフホストを行う場合、各参加者が書いたコードは、普通のプログラムのように実行されるコードであるのと同時に、そのコンパイラが読み込んでコンパイルする対象のデータにもなります。このコードとデータの二面性が深みがあって面白いのです。

学習の内容

僕のコースでは1ヶ月ほど前から授業を始めました。オンラインでハングアウトを使ってビデオミーティングをして初回の顔合わせを行いました。その後は僕が書いた開発のガイドラインに沿って生徒に開発を進めてもらうというという形をとることにしました。

開発のガイドラインではステップ・バイ・ステップの開発スタイルを強調しました。多くの人は、やや大規模なプログラムを0から書く方法に馴染みがなく、どこから手をつけていいのかよくわからないようです。特にコンパイラでは、コンパイラの教科書でコンパイラ内部がステージごとに分けて説明されているので、各ステージごとに作り上げようとする人が多いようですが、そのような作り方はまず間違いなく破綻します。コンパイラのあるステージが作成するデータ構造は、次のステージの入力になるわけですが、次のステージが存在しない段階では、今のステージがどういったことをやればよいのかよくわからないのです。

したがって僕らのコースでは、全てのステージを均等に作り上げていくというアプローチをとることにしました。例えば最初のコミットでは、数字1つだけを読み込んで、その数字でmain関数からリターンするようなコードにコンパイルする、というものを作ることにしました。これはCで10行くらいで書くことができます。これは簡単すぎてコンパイラとはいえないと思うかもしれませんが、定義ではこれは立派なコンパイラですし、この簡単なコンパイラを発展させていくと、驚くべきことに本物のCコンパイラに成長させることが可能なのです。

このようなやり方ではいったん書いたコードをひたすら書き換え続けることになりますが、それはむしろ望ましいことということにしました。

最初のコミットにはユニットテストとビルドファイル(Makefile)を含むようにしてもらいました。ユニットテストは趣味のプログラムでも欠かせないものです。自動テストがなければ結局テストを手で毎回行うしかないからです。とはいえ規模に合わせた適切な複雑さというものがあり、こういったプロジェクトでは大げさなテストフレームワークは不要です。したがって十数行のシェルスクリプトを書いて、それをユニットテストとして使ってもらうことにしました。

そのあとは次のような順番で機能を少しづつ追加してもらいました。

・加減算
・乗除算
・ローカル変数(変数宣言はなくて、初回使用で暗黙のうちに変数が定義されることにした)
・引数なしの関数呼び出し
・引数ありの関数呼び出し
・引数なしの関数定義(この段階までは全てが暗黙のうちにmain() { … }に入っていたが、明示的にそれを書くことにした)
・引数ありの関数定義
・グローバル変数
・配列
・ポインタ
・char型(この時点まで暗黙のうちに全ての値はint)
・文字列リテラル
・構造体
・#include

関数定義ができるようになったくらいの時点で、自分でもびっくりするくらい普通に複雑なプログラムがコンパイルできるようになります。今回のキャンプの参加者もそのことに驚いていたようです。

各ステップでは、その段階でリーズナブルな言語になっていることを重視して、極端に一気にC言語ぽいものを目指さないようにしました。たとえばポインタが導入されるまではすべての型はintなので、関数定義では返り値や引数の型を書かずに、"main(x, y) { … }"のように書くようにしました。仮に自分が1970年代に後の歴史において「C言語」と呼ばれる言語を作っているとしたらこうするであろう、という道筋をたどるようにしました。

各コミットにおいては、機能追加と、その機能をテストするテストコードを同時にコミットしてもらうことにしました。基本的に全てのテストが通る状態でコミットを行い、テストがない機能はコミットしないということを目指してもらいました。

このコースでは、コンパイラ作成の技法だけではなく、中規模〜大規模プログラムを0から作る方法や、物事を大げさにしすぎず適切な単純さ(素朴すぎて一見逆に初心者のやり方みたいにみえるような単純さ)で作るという、エンジニアリングのベストプラクティスを実践していく方法を学んでもらおうとも思っていました。その目標はどうやら達成できたようです。

教える側の姿勢

このコースでは生徒にエンジニアとしての自己肯定感と自信を大幅に増してもらうということを1つの目標にしていました。講師陣とチューターでその目標をまず共有し、そのために基本的にどんどん褒めていくようににしました。基本的には生徒がコードを書いている限りは褒める材料には事欠きませんし、仮に一つのことがよくないとしても、別のよいことを見つければよいので、褒めるのはそんなに難しくありません。生徒の良い点を見つけるというのは、無理にこじつけているといったことではなく、教える側としては当然のことです。

各参加者と毎週ビデオ会議をしていたのですが、そこでリアルタイムでコードレビューをして、良い点を指摘するようにしました。すっきりしていて心が洗われるようなキレイなコードでいいですね、とか、よい方向に向かって開発できているからこの調子でコードを書いていけば今後の機能もうまく追加していけるはず、といったことを言うようにしていました。生徒には、自分の書いているコードが良いものであるというように思ってもらって、気持ちよく開発してもらえるようにしようと思っていました。

褒めるためにはその生徒が相対的に他人より優れていなければいけないということはないですし、生徒も、自信を持つためには他人より優れていなければならないということはないと思います。もっと単純な、自分はエンジニアとして結構よいなとか、自分の今書いているコードはよいコードだろう、という気持ちを持ってもらうことが重要です。同じことをやっていてもまったく気分は違いますし、結局のところコンパイラ作成のようなやや難しいプロジェクトに初めて取り組む場合、「自分にはできる」という根拠のない自信が必要なのです。

コンパイラを書くこと自体も自己肯定感を大きく増やすことができるプロジェクトです。コンパイラという複雑なプログラムをある程度動くところまで作り上げることで、自分はすごいプログラマなんだという誇りを持つことができます。そのような自尊心は大切です。

また、僕ら講師陣は参加者を助けるためにここにいるのだから、疑問があるところはなんでも聞いてほしいということを強調するようにしました。質問を尻込みするようにならないように、初歩的な質問でも高度な質問でも、まったく同じ態度で、単に質問に対して答えを丁寧に返すように心がけました。

成果

上記のような手順で1ヶ月間開発に取り組んで、キャンプ本番になるわけですが、キャンプ終了までに6人中3人がセルフホストすることに成功しました。残りの3人もキャンプ後も開発を継続してくれているようです。

セルフホストは僕が設定したマイルストーンで、それは延々と続いていくなだらかな開発プロセスにおいては、特別ではないただのある1つの地点にすぎません。キャンプにおいて最も重要なことは、学んでもらうことです。これは僕の私見ですが、すべての参加者に、一生忘れられないレベルで多くのことを学んでもらえたと思います。その意味で僕のセキュキャンは大成功だったと思います。

179

Rui Ueyama

#エンジニア 系記事まとめ

noteに投稿されたエンジニア系の記事のまとめ。コーディングTIPSよりは、考察や意見などを中心に。
2つのマガジンに含まれています
コメントを投稿するには、 ログイン または 会員登録 をする必要があります。