今の職場は――いや、今の職場も、新人教育にC言語を採用している。
実際の業務としてはC++も多い上に、C#、Dart、JavaScript/TypeScript、Kotlin(とごく稀にJava)、Python、Swift(と時々Objective-C)と色んな言語が飛び交う環境なのだが、歴史的事情によりC言語色の濃いC++のコードも多いし、C言語の仕事も無くはない。いにしえのObjective-CコードにてC言語的側面に直面することもある。
そんな訳でC言語なのだが。
しかし周知の通り、C言語は最近の他の高水準言語と比べると低水準である。平たく言えば、便利ではない。この点が、初級者向けのプログラミング教育の際に問題となる。
職場でのプログラミング教育においては、言語やらライブラリを覚えることは、課題を解く上ではごく一部の要素でしかない。実際には、(暗黙のうちに)次のようなことを学ぶ。
- 解くべき課題を分析・整理して、内容を把握する。
- 課題を解く方法――解決策を考える。
- 解決策を抽象機械*1向けにアレンジする。
- アレンジした解決策をプログラミング言語で記述する。
- コンパイルエラーや実行時エラーなどを中心に、処理系との付き合い方を身に着ける。
(現実には、複数の人が関わるプロダクトでは、プログラミング言語で記述する際にキレイに書けるか否かも重要になってくる。あとGitなどのツールの扱い方を覚えるとか、報連相を含めて「実際のプロジェクトでの作業の進め方」を体得するとか、プログラミング以外の要素も入ってくるものだが、ここでは割愛する)
言語やライブラリの知識は、上記で言う項目 (4) に直接関わる内容であり、間接的に (3) に影響を与える。しかし (1) や (2) には効果がない。
課題の難易度が上がるにつれて、課題を解く上で (1) や (2) の割合が大きくなる。また、課題の抽象度が高くなるため、それを抽象機械の水準にブレイクダウンする (3) の作業の負担も増加する。
プログラミング教育においてC言語を使う場合の問題は、比較的難易度の低い課題のころから (3) や (4) の負担が大きい上に、難易度が上がるとたちまち (3) の負担が上昇してしまうため、学習者が (3) や (4) の作業で手一杯になってしまうことだ。この影響で、なかなか (1) や (2) の教育にたどり着けない。
例えば、素のC言語でGUIプログラミングは無理ゲーなのでコンソールアプリを扱うにしても、1行分の入力文字列を安全かつ比較的確実に取得することすら難しいのがC言語だ。他の言語では、言語処理系の実装上の制約その他の要因があるにせよ、教育用課題の範囲においては、1行の長さを気にせずに文字列を取得することが比較的容易だ。しかしC言語の標準ライブラリではそれがままならない*2。何らかのルーチンを自作するか、便利なライブラリを探すしかないのだが、受講者はまだそれらの作業が可能なレベルからは程遠い「スタート地点」にいる。
このような傾向は、課題の抽象度が高くなるにつれて酷くなる。
(単純な入力に関しては、初期の段階では騙し騙しscanf(3)を使うものだが、課題の難易度上昇に伴いscanf(3)以外で何とかする必要が生じる。避けられない問題だ。というかそもそもscanf(3)を騙し騙し使うのだって、講師役のベテランは普段scanf(3)を使わないので、例えば変換指定の空白文字絡みの微妙な仕様に講師ともどもつまづくとか、そういう非生産的な光景が毎回繰り広げられるのである)
正直なところ、実際の業務の都合を無視して、教育の実施者と受講者双方のレベルを度外視するならば、プログラミング初級者にはPythonを使って『Python言語によるプログラミングイントロダクション』片手に教育するか、OCamlを使って『プログラミングの基礎』片手に教育した方が、中~長期的にはよいのではないか?
とは言いつつも、諸事情により現実にはC言語を使うしかない。
では、どうすればよいか?
最近思うのだが、自分が書いたプログラムが動いたときの楽しさとか無視しちゃって良いのなら:
- 予め、ちょっとしたアプリを書いておくが、ある関数のみ中身を空にしておく。
- その関数の仕様を明示した上で、受講者に中身を書かせる。
――こんなスタイルで、出題者がテーマに応じた空の関数を用意しておき、それを1つずつ解かせて、レビューして、再度解かせて……と繰り返していく方法ならば、やり方次第では先に挙げた項目 (1) や (2) に踏み込んだ教育も可能ではないだろうか?
例えば、文字列の内容を解析する関数なら、状態遷移図やジャクソン構造図を用いたデータ構造の解釈と、そこからプログラムに変換する方法に的を絞って教育できる。解析する内容次第だが、関数1つに収まるボリュームで済む。
解かせるのが関数1個ならば、例えばその関数にテストデータを突っ込んでテストする処理をmain関数に書いておくことで、IDE上で特別な設定無しでデバッグ実行するだけで、ブラックボックス的に関数の動作を検証して受講者に結果を見せることも可能だろう。また、そういう課題を用意する労力は、同じようなことをアプリ単体に適用する場合よりは少なく済むはずだ。
そういう、詰めプログラミング的な教育も、ある程度は有効ではないか? もちろん、全面的にそれを採用するわけにはいかないのだが……。
*1:抽象機械:要はコンピュータのこと。ただし、プログラミング言語という「色眼鏡(抽象化層)」を通して見たコンピュータの姿であり、実際のコンピュータその物とは差異がある。ちなみに、例えばJavaを使っているのにCOBOL向け抽象機械を念頭にコードを書いてしまうのが、コボルの暗黒面に落ちた悪いコボラーである。善きコボラーは、COBOL案件でCOBOL向きのコードをCOBOLで書いている。
*2:gets(3)を使ったらアカンのは当然として、しかしfgets(3)だって「バッファサイズを超える長さの入力があったらどうするか?」とか「改行コードをどうするか? 入力元がファイルの場合、ファイル末尾には改行コードが存在しない可能性があるから、注意しないと……」とか、色々あるのです。