プログラムが理解できない

 プログラムを理解するということは、プログラムに必要な機能を把握でき、実際のプログラムがどの様に実行されるのかを頭の中でイメージできるということです。

症状

  • おまじないとしか言えない命令があったり、必要な機能とは無関係の命令がある。
  • 念のためといって、save() 関数などを2, 3回呼び出す。
  • バグがある関数を直す際、その関数の出力を操作して直す。
  • いちいち別の型にキャストして変数の操作を行う。例えば、小数を文字列にして、文字列分割で整数部を取り出し、整数にキャストしたりする。
  • 再利用性を考えずに、既存のプログラムを無駄にサブルーチン化する。

処置

 この様なプログラマーは、開発環境のデバッガーを利用して一行ずつステップインし、プログラムを理解する訓練が必要です。例えば Visual Studio なら、プログラムの初めの方にブレークポイントを設定させ、F11 を使って一つ一つ丁寧にステップインさせ、プログラムが一体何を行っているのか理解できる様になるまで、変数の内容の変化を理解させると良いでしょう。万が一ステップインといった機能の無い環境でプログラミングをしている場合は、すぐに他の環境で訓練させるべきでしょう。

プログラミングパラダイムを理解できない

 プログラミングパラダイムとはプログラムの見方を与えるものです。例えば、オブジェクト指向は近代のプログラミングパラダイムの代表的な例です。他には関数型や手続き型、宣言型や命令型といったパラダイムがあります。アセンブラや GOTO を使用する言語と、それらのパラダイムが大きく違うことは明確ですが、個々のパラダイムもそれぞれ大きく違います。また、普及している近代的なプログラミング言語の多くがオブジェクト指向を取り入れていますが、オブジェクト指向でも個々の言語で、リスト内包表記 (”List Comprehensions”) や、総称型 (”Generics”)、ダックタイピング (”Duck-typing”) といった独自の拡張がなされています。

症状

  • 禁じ手を使ってパラダイムから抜け出し、残りのプログラムを命令型や手続き型のプログラミングを行う。
  • オブジェクト指向において、非スタティックな関数をインスタンス化していないクラス自身から呼び出そうしてコンパイルエラーを起こすが、なぜエラーになるのかわからない。クラスとインスタンスの概念を理解していない。
  • オブジェクト指向において、あるクラスのオブジェクトを操作する為のメソッドを “xxxxxManager” といったクラスを定義してしまい、肝心のオブジェクトには何も定義しない。
  • リレーショナルデータベースに、データベースネイティブな値で保存せずに、不用意にシリアル化したオブジェクトを保存する。
  • 関数型プログラミングにおいて、同じアルゴリズムの関数を引数の違いの為に何個も実装してしまう。
  • 決定的関数 (”Deterministic Functions”) の結果を自前でキャッシュする。
  • 純粋な関数型プログラミングにおいて、入出力やモナドの実装になると、他のプログラムからコピペをする。
  • 宣言型プログラミングにおいて、複数の値をデータバインドで設定せずに、一つ一つ命令を使って設定する。

処置

 教育を受けていないことが原因でこの様な症状が出ている場合は、適切な教育を受けさせるか、実際のプログラミングを通じて学ばせると良いでしょう。実際のプログラミングを通じて新しいパラダイムを身につけさせるには、プロジェクトに参加させるなどして、とにかく本人をそのパラダイムに集中させる他ありません。また、パラダイムの特徴を理解できるまでは、簡単な言葉で順を追って理解できる様にすると良いでしょう。

オブジェクト指向の例

  • オブジェクト指向は、レコードとメソッドの集まりである。
  • メソッドは、独自のグローバル変数を持つ細分化されたプログラム中の関数にすぎない。
  • それらのグローバル変数はフィールドと呼ばれ、必要であれば外部から隠すことができる。
  • プライベート宣言とパブリック宣言の目的は、外部からプログラムの実装を隠し、実装を意識しないで外部から利用できる様にするインターフェースを提供することである。これをカプセル化と呼ぶ。
  • カプセル化の結果、ビジネスロジックがプログラムの実装に影響されなくなる。

 最後のカプセル化の結果は、ほとんどのオブジェクト指向言語で共通しています。オブジェクト指向の主たる目的は、プログラマーがある機能を利用する際に、その機能の実装を意識しないで済む様にすることだからです。

関数型プログラミングの例

  • 関数型プログラミングは、複数の決定的関数を組み合わせである。
  • もし関数が決定的な場合、結果が必要になるまで実行される必要は無く、必要な回数だけ実行すれば良い。これらは、消極的評価 (”Lazy Evaluation”) と、部分評価 (”Partial Evaluation”) と呼ばれる。
  • 消極的評価と部分評価を利用するためには、渡されたひとつの引数をどの様に変更するか、どう他の関数に渡すべきなのかを関数で定義しなければならない。これをカリー化 (”Currying”) と呼ぶ。
  • 関数がカリー化された場合、コンパイラーは制約解決 (”Constraint Solver”) を通じて最適なプログラム実行方法を選ぶことができる。
  • 制約解決に通すことで、どう引数を渡してもらうかではなく、どんな引数が欲しいかを記述することに集中できる。

調べことができない・慢性的にプラットフォームを知らない

 近代的なプログラミング言語やフレームワークは、非常に幅が広くかつ深い命令と機能が提供されており、先進的なフレームワークである Java や、.NET、Cocoa では、才能のあるプログラマーでも学ぶのに最低でも一年はかかります。才能のあるプログラマーは、事前に必要な命令を探してからプログラミングを始め、特に優れたプログラマーは、目標を達成するために必要なプロセスを細分化し、既存のフレームワークやデザインパターン、さらにモデルやプログラミング言語を選び、実際にプログラミングを始めます。

症状

 そのプラットフォームで長くプログラミングをしているにも関わらずこれらの症状が現れた場合、プラットフォームの習得が遅すぎることを意味しています。

  • イベントやハンドラー、正規表現など、フレームワークで提供されている機能を再実装する。
  • フレームワークで提供されているクラスを再実装する。タイマー、コレクション、ソートや検索アルゴリズムなど。
  • 掲示板で教えて君をする。
  • 簡単に済ませられる機能をやたらと遠回りなプログラムで実装する。小数を文字列に変換してまるめた後に、再度小数に戻したりなど。
  • 同じ機能でもより良い新しい実装方法があるのにも関わらず、既存の古い方法で実装する。例えば、ラムダを使わず名前付きデリゲート関数を未だに書いているなど。
  • 自分の知っているレベルでの「心地の良い」プログラミングに執着し、複雑な問題を単純な機能を使って非常に長いプログラムで解決する。

処置

 空き時間が無ければこの様なプラットフォームの知識はなかなか身に付かないし、現場ではとにかく実装することを急ぐのでなおさら身につきません。根本的な解決にはなりませんが、手元に便利なプラットフォームのリファレンスを置かせ、できる限り短時間で全体を把握できるようにすべきです。本でもいいですし、2画面にして片方に PDF や、ウェブサイトを開くのでもいいでしょう。リファレンスに目を通す癖を付けさせる為に、まずは既存のプログラムを 10:1 以上に縮めさると効果的です。

ポインターを理解できない

 ポインターを理解していないプログラマーは、複雑なデータ構造や効率的な API の設計ができず、限られた種類のプログラムしか書けないでしょう。マネージド言語では、ポインター演算を無くし、一方で自動開放などを追加した参照という機能が替わりに提供されていますが、これもポインターの概念が理解できなければ、値渡しや参照渡しの違いが理解できず、酷いデータ構造やバグを生み出してしまいます。

症状

  • 連結リストの実装ができない。連結リストで項目を追加、削除してもデータが失われない様に実装できない。
  • 連結リストなどの動的なデータ構造を使わず、不定量の大きな配列を割り当て、配列の長さを別の変数でカウントする。
  • ポインター演算によって起きているバグを見つけられない、修正できない。
  • ポインターの参照している値が関数外から変更される可能性を意識しない。
  • ポインターを複製した後に、参照が解放された元のポインターから値を変更しようとする。
  • ポインター自身をシリアル化して保存しようとする。
  • ポインターの配列をポインター自身の比較でソートする。

 山岡さんはホテルのどこかの部屋に泊まっています。どの部屋かはわかりません。しかし、友達の林さんの部屋はわかるので、そこまでいって山岡さんがどの部屋に泊まっているのか聞きました。すると、林さんは、どこに山岡さんがいるのかわからない様でしたが、山岡さんの別の友達である北島さんの部屋を教えてくれました。そこで北島さんの部屋に行き、山岡さんの部屋を教えてもらい、結果、山岡さんの部屋に行き、山岡さん自身に会うことができました。

 ポインターは様々な比喩を用いて説明することができ、データ構造は様々な類推を用いて説明することができます。上の例は連結リストの例で、プログラマーでなくとも思いつくデータ構造です。この様なポインターの説明は、理解できないものではありません。それでもなぜポインターを理解できない人がいるのかというと、コンピューターのメモリー内で一体何が起きているのかを理解する際に、よく似ている変数の概念と絡まってしまい混乱するからです。ポインターの概念が正確に理解できイメージできる様になるまでは、プログラムの内容をわかりやすい他の話にたとえるのがいいでしょう。

再帰関数の処理を把握できない

 再帰関数の概念は簡単に理解できますが、処理結果がイメージできなかったり、複雑な計算を単純な再帰関数で実装できるのに気がつかないことがあります。再帰の条件式を書く際にそうなってしまうと、非常にやっかいです。

症状

  • 再帰関数で簡単に処理できるにも関わらず、非常に複雑な反復処理を行う。
  • 再帰条件を、処理開始時と処理終了時に確認させる。
  • 再帰条件を確認しない関数を書く。
  • 末尾再帰 (”Tail recursion”) を使うべきところで、グローバル変数や持ち越される変数に再帰処理をまとめる。
  • 再帰関数の引数に何を渡して良いのかわからない。引数を操作しない再帰関数に何を渡して良いのかわからない。

 まずは一つの条件と、同じ渡された引数を操作しない一つの再帰関数を書かせましょう。何かふに落ちていない様子でも、そこでやめさせ、実際に実行させましょう。スタックオーバーフローが起きるはずですから、プログラムに戻らせ、渡された引数を操作して再帰関数に渡す様に変更させましょう。それでも、スタックオーバーフローや、永遠と出力が続く様であれば、再帰条件を書き換えるなど、適切な結果が出力されるまであれこれ実験させましょう。二つ以上の条件を持つ再帰関数は、完全に理解できるまでは書かせるべきではありません。

 目的は、再帰関数を扱うだけの自信を持たせることにあります。再帰処理内で、今どこにいるのかが正確にわからなくてもいいのです。自信が付き、実際に現場で実装する時が来たら、まずは単体テストを書かせ、同じような試行錯誤を繰り返させればいいのです。

きょうのはま

耳が痛いです。