オブジェクト指向プログラミング (OOP) の学習がつらい理由
今C# でObject-Oriented Programming (OOP) の学習をしていて、第一段階のコア技術を理解した。そしてなぜOOPの学習がつらいのか、分かった気がするのでそのメモ。今回はC# で学習開始だったがおそらく他の言語を選んでも感想は変わらないだろう。
(約 4,200文字の記事です。)
日本語でなじみのない未知の単語がたくさん出てくる
まずはプログラミング言語としてではなくて、概念を理解するための単語が突然たくさん出てくる。しかも日本語に翻訳してもなじみのない概念がたくさん出てくる。なので結局はカタカナ英語も半分くらい出てくる。
また無理矢理微妙に日本語に翻訳しても中途半端になる。
以下は順不同でOOP学習時にUpNoteにメモした内容。英単語のメモもあるがここでは全部カタカナ英語に統一して書き出した。
コンストラクタ
イニシャライザー
プロシージャー、プロシージャル
モジュラー、モジュール
エントリポイント
コンクリート・オブジェクト
クラス、メソッド
インスタンス
ポインタ
ディスパッチ、ディスパッチャ
インヘリット、インヘリタンス、デライブ
ポリモーフィズム
ディス・リファレンス
バーチャル・ファンクション、バーチャル・テーブル
もうカタカナ英語だらけ😭。そして漏れなく全部理解できてようやくOOPをスタートできる。どこか一つでも「ナニコレ?」になればObject-Oriented なコードになっていないはずだ。
そして上記のカタカナ英語の一部は日本語に翻訳されている。だが逆に中途半端な日本語だと理解が難しくなる。
継承
多態性
仮想関数、仮想テーブル
日本語になってもサッパリ意味不明。そのまま英単語またはカタカナ英語で理解した方が楽。だって、継承?承継?多態性?多様性?仮想?疑似?日本語にすると逆に混乱する。カタカナ英語の方がいい。
言葉が多く出てくると言うことは、つまり概念としてかなり複雑なものだと分かると思う。そう、直感的ではないのだ。その理由も分かった。
非OOPのハローワールドの理解が必要だが、それをさらに上書きしなければならない
だれでもシンプルな Hello World この理解から始めると思う。そしてOOPを理解するためにはまずは、シンプルな、順番に関数を実行する「プロシージャル」なプログラミングを学ぶ。
問題はここからだ。
OOPは「プロシージャル」ではない。オブジェクト指向と書いているくらいなので、順番指向・手順指向ではない。だから今まで学んできた「関数」という概念が全部リセットされることになる。そして「オブジェクト、クラス、メソッド」という単語が登場する。
なぜこんな面倒な事をするのかというと、プロシージャル・プログラミングの限界を超えるためだったのだ。ある程度規模が大きくなると改良の頭打ちが発生するのだ。特に変数の独立性の確保。その限界を超えるために、概念の異なる手法が必要になった。その結果生み出されたのがOOPだ。
上から順番に関数を実行するプロシージャルな考え方から、オブジェクトに含まれているメソッドを呼び出して使うという考え方。ここまでなら「単に記法の違いだけ?」と思うだろう。だが拡張性がまったく異なる。なんちゃってOOPで書くと、それはつまりは「見た目を整えたモジュラー・プログラミング」になっているだけで、本質的にはモジュールでコードを整理整頓したプロシージャル・プログラミングだったりする。要するにOOPになりきれていないパターン。
OOPはプロシージャルなコードを改良したものなので当然ながらプロシージャルなコードをOOPに書き換えることができる。今回の教材ではそれをまず最初に教えてくれた。そうすることでプロシージャル・プログラミングの弱点を、OOPで鮮やかに解決することができることがわかったし、メンテや拡張性が段違いだということも分かった。
(結論)プロシージャルとオブジェクト指向の2つを理解しないとOOPできないから
ハローワールドから始まったであろう、シンプルなプロシージャルなプログラミングを、OOP学習からはいったん全部捨てて、OOPの作法を学び直さなければならないから、つらい。しかもOOPの理解のためにはプロシージャルな部分の理解がないと、OOPの作法すら理解できない。二重トラップ。だから学習がつらい。
そしてカタカナ英語の嵐と、各概念をキチンと理解しないと、うろ覚えではすぐに詰まる。なので、
プロシージャル・プログラミングできるだけの最低限の言語の理解
モジュラープログラミングからオブジェクト主体のプログラミングに作法を切替えるための仕組み(OOPの作法)を理解する
その過程で出てくるたくさんのカタカナ英語のOOPの概念を理解しながら実装方法を学ぶ
この3つが必要になる。特に2と3が最大の難関。ただ単に「こういうときにはこう書く」という表面をなぞっただけの学習ではすぐに詰まる。コードの結果ではなくて、コードに書き起こす必要性を論理的に導き出せることが重要。
ほとんど同じで一部だけ変えられる強み
OOPはオブジェクト指向プログラミングなので、主体はオブジェクトなのだ。そのオブジェクトを具体化するためのひな形・テンプレとしてクラスがある。そして「そっくりさんのクラス」を簡単に作れる。
クラスのインヘリタンスと、承継されたデライブド・クラスにあるメソッドのオーバーライドの話ね。それらによってほとんど同じだがちょっとだけ違う動作のメソッドを実現させる、使い分けたり切替えられる。これがポリモーフィズムの実現、ということになる。
カタカナ多すぎ😭
そしてオーバーライドは、これまた単に記法の違いではなくて、コンパイラとメモリ空間の話と関連する。コンパイル時の挙動が通常とはことなる。ここらへんにvritualが関わってくる。コンパイル時にメモリアドレスが決まっていないので仮に決めておく。実行時に初めてアドレスを決める。これによって動的にメソッドを切替えられる。ディスパッチャのような動作のこの仕組みがポリモーフィズムのコア技術。
概念として複雑だが、OOPでメソッドを実装できれば、驚くほどメイン関数がシンプルに、スッキリさせられる。そしてメイン関数の実装内容は、かなり「普通の人が普通に思考したときの流れ」に近くなる。細かい処理はクラス内のメソッドに書かれているので、人の思考に近い流れで、必要なオブジェクト名やメソッド名が並ぶことになる。引数にしてもそう。
そして何よりも「変数」の扱い方が決定的に変わる点。モジュラープログラミングで悩みの種になる「どの関数がどの変数をいつ上書きしたか」というリスクを避けられる点。オブジェクトに含まれる(紐付けられる)変数へのアクセスをかなり制限できる点。
プログラマからみて「まさかそんなところで変数が変わっていたとは!」を回避できる。変数と関数(メソッド)の切り分けがオブジェクト単位で分けられることで、スパゲッティになりにくいと感じた。引数で受け渡しがメインのモジュラープログラミングでは、規模が大きくなるにつれてその変数の変化をトレースすることが大変になる。また機能拡張によってどんどんと自分が管理しきれない変数が増えていく。要するに「手に負えなくなる」のだ。
その点で言えばOOPは規模が大きくなっても基本的には「変更に関わる部分でしか影響が出ない」ことを確認しやすい。OOPでは一見「変数」に見えるものが全てオブジェクトだということ、これが一番の特徴だ。オブジェクトに干渉していない機能拡張は、他の部分を変化させない。
C# でOOPの学習継続中
ま、要するに、OOPというものは単に見た目をこぎれいにするために関数をモジュール化した「書き方の話」ではないし、引数の受け渡しメインのモジュラープログラミングでもない。変数の影響をモジュール単位で扱うのではなく、オブジェクト単位で変数も関数も扱うこと。そしてオブジェクトの派生形を簡単に作れることで「ほとんど同じだがちょっと違う動作のメソッド」も簡単に作れる点。
これらの実現手段として(通常は省略されるが)this. というポインタと、見えないけど存在するバーチャル・テーブルとバーチャル関数が必要。この3つはソースコードでは(ほぼ)見えないし意識しなくていいが、この3つがOOPそのものだということ。
今学習中の章はようやくC# のコーディングの話、Visual Studioを使っての実際演習に入ってきた。
それだけOOPの理解は難しい。私も1コースを3回見直して完全理解してから次に進むようにしている。講師がそう言っているので。OOPは一段飛ばしや速習は無理😭。しっかり理解して1段ずつ登らないと、いずれ砂漠の斜面を登ることになる。
けど、今までOOPで書かれたソースコードを見てわけわかめ😱だったものが少しずつ「読める、読めるぞ~!」になってきた。ソースコードを読んでいると言うよりも、ソースコードの示すObject-Oriented が示す「考え方」を理解している、と言うのが正しいだろう。
次の学習が楽しみ😊
今回の創作活動は約1時間15分(累積 約3,896時間)
(1,140回目のnote更新)
<2024/09/04 追記>
続きはこちら。