なんやかんやでかろうじて連載も4回目になりまして、モチベーションも維持できてよかったです。語学留学もあと一カ月で、最近、仲良くなった大学生にC++を教えています。といっても最初はポインタを教えていたのですが次にリファレンスを教えることになって、思わず、「混乱の元だからリファレンスは最初は無理して覚えなくてよくてポインタを先に覚えましょう。」と言ってしまった。もっとも「リファレンスの方が簡単だから軽く覚えてガッツリポインタを勉強しよう。」とも言ったが、この連載のターゲットは『オブジェクト指向しか知らない世代』としていますが、JavaやC#しか知らない方たちはポインタの概念が理解できているか怪しい限りです。興味深いのは、私の半径3メートルの範囲、クパチーノ近辺ではC++をマスターすることがステータスになっているようです。
という訳(?)で今回はメソッドと関数について話します。なぜか知らぬがネットの界隈ではどうも、
メソッド>>>どうしようもない壁>>>関数
という図式が成り立っているようです。でこのどうしようもない壁なんて無いというのが今回のテーマです。もっとも1回で説明するのは難しいので何回かに分けて説明します(しかも飛び飛びになるかも)。
先ず、最初に以下の2つの表現
object.function();
function(object);
ですが、C++でみると生成される機械語コードは同じになります。例外はfunctionが仮想関数(ややこしいので以下、オーバーライドメソッドと呼びます)の場合になります。オーバーライドメソッドを除いて、C++でみると上記の2つのコードは同じということになります。『カプセル化があるだろ』という突っ込みがあるかもしれませんが、それは他のキーワード(friend)を使うことによって同等にできます。
もし、あなたには『どうしようもない壁』が見えるというのならどうぞ具体例とともにコメントをください。
ということで、ADPでは上記の2つのコードは同じように解釈されます。というか上のコードは下のコードとして解釈されます。『同じなら一つの書き方で良いだろ!』とお叱りを受けそうですが、表記上の重要な違いがあります。つまりobject.function()の記述はメソッドチェーンを実現できます。
object.function1().function2().function3();
もしこれを関数の形式で書くと
function3(function2(function1(object)));
となりますが、どちらが良いかは一目瞭然だと思います。ただ、これはあくまでも表記上の話でもしこれがどうしようもない壁というのならオブジェクト指向というのは表記上の問題ということで話が付きますし、そもそもメソッドチェーンの見た目はもはやメッセージパラダイムとは異なるでしょう。
さてfunctionがオーバーライドメソッドの場合の話ですが、簡単にオーバーライドメソッドについて話ますと、呼び出されるメソッドが実行時に決定される点です。C言語の関数は呼び出される実体がコンパイル時に決まりますが、オーバーライドメソッドの場合は実行時にオブジェクトの型によって呼び出される実体が決まります。
object.add(1);
とあった場合、objectがInteger型ならIntegerクラスのaddが呼び出され、objectがFloat型ならFloatクラスのaddが呼び出されるということになります。これはポリモーフィズムと呼ばれるもので、オブジェクト指向病にかかっている人たちはまさにポリモーフィズムが手続き型言語との差別化を図っていると信じています。ポリモーフィズムについては連載の後の方で詳しく説明しますが、関数ポインタを使うとこによりCやアセンブリ言語でも同様の機能を実現できます。こう言うと『ならアセンブリ言語で全部書けや!』と意味不明な切り返しをされるのですが、それに答ますとLinuxの記述では主にCが使われているというのは有名な話ですが、でデバイスドライバの実装等ポリモーフィズムと同様のことが行われています。つまりCでもある程度はオブジェクト指向を実用レベルでシミュレートできるということです。ちなみにADPはC++で記述しておりポリモーフィズムもバリバリ使っていますがパフォーマンス上の理由からCでリライトしようかと思案中です。
さて、話が横道にそれましたので元に戻しますとaddの例ですが、何か変なことに気付かないでしょうか?例えば以下の例はどうでしょうか?
object1.add(object2); // ・・・・・(A)
このaddメソッドですが、object1に対してはポリモーフィズムが効きますがobject2に対してはどうでしょうか?JavaやC++ではobject2にはポリモーフィズムは働きません。まぁ関数の例でもそうでしたが引数にはポリモーフィズムは働かないと考えてもしようがない面もありますが、もし引数にポリモーフィズムが働かないのが当然だというのなら(A)は以下の例と動作が異なることになります。
object2.add(object1); // ・・・・・(B)
加算(add)というのは通常交換法則が成り立ちますのでもし(A)と(B)の動作が異なるということならその実装は不完全としか言いようがないですね。ので通常addがポリモーフィズムであるならそれはobject1,object2双方に対してポリモーフィズムでなければならないことになります。2つのオブジェクトに対してポリモーフィズムを働かせることを
ダブルディスパッチと呼びます。私はダブルディスパッチについて16年程前に『More Effective C++』で知ったのですが、以前、といっても2,3年程前にJavaが得意で自称オブジェクト指向をマスターしている彼が『オブジェクト指向をマスターしている人は日本にはほとんどいない。俺を除いて』と言っていたので『ダブルディスパッチって知っている?』と聞いたら『なにそれ?』と返されたことがある。ということで知ったかのJava厨の検出にはダブルディスパッチは今のところ効果的かと思われる。ちなみにC#4.0以降の場合、dynamicキーワードを使うことにより(A)の記述でもobject2に対してもポリモーフィズムを働かせることができるのでC#プログラマにダブルディスパッチの技を繰り出すのは返り討ちにあう可能性があるので注意したい。話を戻してC++やJavaの場合はかの有名なデザインパターンの一つビジターパターンがダブルディスパッチの実装方法の一つとなる。
ダブルディスパッチの使いどころですが、二項演算子でポリモーフィズムを使いたいとき(もっともパフォーマンス上の問題があり二項演算子でポリモーフィズムは使わない場合が多い)や2つの物体の衝突を計算する場合(More Effective C++)があるでしょう。ADPではユニフィケーションの実装にビジターパターンによるダブルディスパッチを使用しています(これがしたいが為にC++を使ったがもっと効率的な記述をしたいからCで組みなおそうかと考えている)。
つまりこういうことになる。
object.method()
と
function(object)
において表現上の違いやプログラミング言語のサポート状況による違いはあるが本質的には両者を同等物とみて構わないということである。つまりどうしようもない壁はないということになる。
長くなったので続きは来週に持ち越します。
前回の記事ですが、読者の方から洗脳を解くには弱いという指摘を受けたのでちょっと補足を考えてみました。実際にオブジェクト指向に洗脳されている方には何をいっても聞かないのですが、そもそも論として、なんでこんな記事を書くのか?ということを説明しましょう。
一般的な話になりますが、エンジニアの中には原理主義者よろしく自身の考えを曲げない人が確かにいます。その人の世界で頑張ってもらえればよいのですが原理主義者の中には妥協という言葉をしらないのか、他人のコードや考え方に口出しをする方もいらっしゃいます。たとえば『手続型はダメ。オブジェクト指向はよい』とかですね。彼・彼女が完成されたエンジニアなら口出しも立派なアドバイスとして成立するでしょうが、ある意味究極のセオリーだと私が考えるのは『コードに正解はない』ということです。あるとすれば『こちらの方がより適切かもしれない』ということと『動くコードはやっぱり最強』ということです。あるエンジニアに、その昔私が書いたコードをありがたく手術と称してリファクタリングしてもらいましたがテストをすると重要なエラーチェックが抜けていることに気づきました。質問すると「まだ書いていない」という返事をもらいました。つまりこういうことです。エラーチェックのコードは時として見栄えが悪くなります。まぁ通常処理に加えて異常系の処理を加える関係上仕方がないでしょう。そういうコードから異常系の処理を取り払えば確かに見栄えはよくなります。さて、見栄えは良いが重要な処理が抜けているコードと見栄えは悪いがきちんと処理が入っているコード、あなたはどちらを評価するでしょうか?見栄えを取るという人は将来失業しないように気を付けましょう。
オブジェクト指向に心酔するのは構わないのですが、結論から言いますと残念ながらオブジェクト指向技術は思ったほど役には立ちません。詳しくは連載を通して理解してもらえればよいのですが、非常に残念なことにオブジェクト指向に過剰に心酔する人がいるのも事実です。例えば、この記事です。
staticおじさん達に伝えたい、手続き指向とオブジェクト指向の再利用の考え方の違いについて
経験のあるエンジニアならこの記事が眉唾だと理解できるのですが経験の浅いエンジニアなら真に受けてしまうでしょう。少し技術的な点を突っ込んでおきます。記事中
アセンブリやCOBOLのような言語では、オブジェクト指向言語で一般的なカプセル化という考え方がきわめて弱いということがあります。変数は静的なグローバル変数が中心であり、
とありますが、アセンブリ言語でもローカル変数はあります。またいわゆるメンバ変数もシミュレートできます。もし仮にアセンブリでローカル変数がなかったりメンバ変数をシミュレートできないとすればJavaやC++のコードはどうやって実行されると考えているのでしょうか?OSはどんなふうに記述されていると考えているのでしょうか?JavaやC++はコンパイラを通して一旦機械語に変換されます。アセンブリとはその機械語に一対一に対応します。また、そもそも論としてアセンブリとCOBOLを同じテーブルに並べるところにもの凄い違和感を感じます。もしあなたが違和感を感じられないのならオブジェクト指向を勉強する前に他に勉強することがあるということになります。
さてこの方の約3年後の他の記事を見てみましょう。
開発チームにアーキテクトがいないなと感じてしまうような、残念なコードスメルの例
引用しますと、
最後に、最近になって気づいた自分の間違えについて書いておかなくてはなりませんね。以前であればこうした設計上の問題は日本のSI業界の構造が問題なのであるという話をしていたかもしれません(^_^;)が、ここで書いたような話は多少フィクションが入っているとはいえ、実際私がSI業界以外の今の会社で体験したことに基づいていると告白しなくてはなりません。言語の特性から、Javaで開発していると、こういった設計上の問題が起きやすいということがある可能性もありますが、こういった話はSIer以外でも、どこの国の開発チームでもあるのだなということですね。
この中にある
Javaで開発していると、こういった設計上の問題が起きやすいということがある可能性もあります
この感覚は実はオブジェクト指向症候群から脱する第一歩になります。多くの生き残っているエンジニアは大なり小なりこういう感覚を端緒とし、次に”Javaはダメだ○○は良い”となり、最後には”オブジェクト指向がダメなんだ”となります。もちろんですが私も20年程前にこういう感覚に襲われたことがあります。
まとめますと、オブジェクト指向症候群にかかると間違った知識をまき散らします。さらに病気から抜け出すと後でそれを修正しなければならないという二度手間が発生するわけです。できれば早く抜け出した方が本人の為でもありますし業界のためでもあります。
と長くなったのと月末でいろいろ忙しいので、今週はこれにて終了です。次回は、メソッドと関数についてもう少し突っ込んで話したいと思います。
先週に続きましてオブジェクト指向再考ということで特にオブジェクト指向に毒された方を対象に現実的な観点でプログラムできるように、プログラマとして社会復帰できるように、ヒントを出したいと思います(もちろん回復するかどうかは本人次第です)。
今週からはメッセージについて話たいと思います。世の中には、
メソッド(メッセージ)>>どうしようもない壁>>関数
と思っている方がいらっしゃるようですが、今後数回に分けて、この『どうしようもない壁』というのはオブジェクト指向病特有の幻覚であるということを示したいと思います。まぁ、厄介なことに患者さんには明確に壁が見えているのでなんとも言えないのですが、もし少しでも『これは幻覚かも?』と思った方はこの連載が助けになるかもしれません。ちなみに『この壁を感じれないやつはプログラマとして終わっている』とお思いの方はお帰り頂いて構いませんし、反論のコメントを書き込んで頂いても構いません。
前回の最後に示しましたが、オブジェクト指向のキーファクターとしてメッセージがあります。この考え方(パラダイム)はそれはそれで素晴らしいものです。分かり切った例をあげますと、マウスの入力(クリック)がメッセージとしてOSに伝わり、イベントハンドラ(コールバック関数)に制御が引き渡されます。当たり前のようですがこの仕組みはメッセージパラダイムを具現化したものになります。もしメッセージを使わなかったら、例えば定期的にマウスの位置を読み込み、クリックされたどうかを確認することになります。考えただけでも厄介ですよね。GUIプログラムがイベント駆動型と言われる所以ですね。
さて、ここでのキーポイントは、『世の中の事象は全てイベント駆動(メッセージ)で記述するのが良いことか?』ということになります。例えば、以下のプログラムを考えましょう。
ユーザがスタートボタンをクリックしたら画面に"Hello"と表示する。
間違いなく、『ユーザがスタートボタンをクリックしたら』、というところはイベント駆動と相性がいいですね。では次の
『画面に"Hello"と表示する。』・・・・・(A)
というのはどうでしょうか?この部分はイベント駆動またはメッセージで記述できるでしょうか?
よくオブジェクト指向の教科書に出てくるのですが、
『画面オブジェクトに、引数に"Hello"を指定して表示依頼のメッセージを送る。』・・・・・(B)
というのを見受けます。最初に指摘しますと(B)は(A)を劣化させたものだと言えます。
ポイントは(A)は手続き指向で書かれて、(B)はオブジェクト指向(メッセージ指向)で書かれています。そして重要な点ですが、この場合、(A)はプログラムの仕様(本当にやりたいこと)を表していて(B)はプログラムそのそのものの説明を行っている点ということになります。つまり、(B)は以下のようなコードの説明を行っているということになります。
label.setText("Hello");
ここで画面オブジェクトというのがlabelになり、setTextが表示依頼メッセージで"Hello"が表示対象の引数ということになります。
つまり(B)は上記のコードの説明を行っていることになり、(A)は上記のコードの意図を表しています。
もし上記のコードにコメントを付与する場合、(A)、(B)どちらが良いでしょうか?
label.setText("Hello"); // 画面に"Hello"と表示する。・・・・・(A)
label.setText("Hello"); // 画面オブジェクトに、引数に"Hello"を指定して表示依頼のメッセージを送る。・・・・・(B)
(B)の方がよいという考え方の人に質問したいのですが、ラベルオブジェクトに値をセットする方法にはプロパティを使うというものもあります。つまり以下のコードもありえます。
label.Text = "Hello";
この場合、(A)、(B)どちらのコメントの方が適切でしょうか?
label.Text = "Hello"; // 画面に"Hello"と表示する。・・・・・(A)
label.Text = "Hello"; // 画面オブジェクトに、引数に"Hello"を指定して表示依頼のメッセージを送る。・・・・・(B)
それとも以下のようにコメントするのでしょうか?
label.Text = "Hello"; // ラベルプロパティに"Hello"をセットする。・・・・・(C)
もっとも、(C)のような発想もオブジェクト指向から離れたと言えるでしょう。
さて、ある程度経験のあるエンジニアなら大なり小なりこのようなジレンマを抱えたことがあるかと思います。つまりメソッドの中身を書こうとしたときに『どうもオブジェクト指向していないな・・・』と感じることがあったかもしれません。しかしよく考えてみれば分かりますが、メッセージ指向というのはある種の手続きの上っ面(呼び出し方法)についてパラダイムであり中身をどうするかは別問題ということに気付くかと思います。
ここで重要なことは、
メッセージというのはプログラミングパラダイムの一つであり、メッセージで表現した方がよいもの(例えばイベント)もあれば、そうでないもの(手続き指向のもの)があるということで、プログラマは適宜適切なパラダイムを選択してプログラムを行えばよいです。多くのプログラマは、プログラミングパラダイムについて
オブジェクト指向(メッセージ指向)> 手続き指向
と考えているかもしれませんが、実はそれぞれ適材適所があり適宜使用すればよいということになります。つまりマルチパラダイムですね。さてオブジェクト指向言語しか知らない方は実は手続き指向という発想がないかもしれません。何かをステップバイステップで処理をするという発想が手続き指向になります。特段難しいとは思えないですが、もしピンとこない方がいらっしゃいましたらコメントを下さい。
イベント処理を手続き指向で考えるのは不適切ですし、能動的に画面に何か表示したいと思った時にいちいちメッセージ云々を考えるのも不適切ということになります。そう考えると以下の2つの記述方法のどちらかが適切か理解できるかと思います。
z = 3 * x * y + 4 * x + 6 * y + 2;
z = 3.multiply(x).multiply(y).add(4.multiply(x)).add(6.multiply(x)).add(2);
もう迷う必要はなく上の方が適切だと理解できるかと思います。数式も一つのパラダイム(というか表現方法の一種)だと理解すれば無用なパラドックスに悩まされる必要もなくなります。
次回は、メソッドと関数についてもう少し突っ込んで話したいと思います。
前回出した記事(
オブジェクト指向おじさん?)のあいださんのコメントで最も興味深いものが、オブジェクト指向しかしらないプログラマが増えてきている、というものである。実は私はそれまで『なんでこうオブジェクト指向信者が途絶えないのか?』と疑問に思っていたのだが冷静に考えれば解るとおり『良いも悪いもなくソレしか知らない。』(あいださんのコメント)世代が増えて来ているようだ。幸いにして私の周り半径3メールではそんな奴はいないので私の視野が狭くなっていたらしい。そういう意味では前回の記事をアップして良かったと思う。
また、元々、オブジェクト指向に関する記事を書こうかと思って対象読者を従来の手続き型言語に精通している人としようかと思っていたのだが方針を若干変更してメインターゲットを『オブジェクト指向しか知らない世代』にして、今後増えるであろう、オブジェクト指向症候群に掛かった患者への処方箋を今後数回に分けて書くことにする。
オブジェクト指向という言葉を聞くとみなさんはどんなイメージをもたれるでしょうか?『オブジェクト指向とは何か?』を素人に説明するとプログラミングパラダイムの事でつまりプログラムを開発する上での一つの考え方や一つの模範ということになる。実はこれ以上でもこれ以下でもないのですが、もしあなたが以下のどれか2つに当てはまるのならオブジェクト指向症候群に掛かっているかもしれないので、この連載は役に立つだろう。
1.関数という言葉に嫌悪感を感じる。または時代遅れの遺物だと感じる。
2.よく他のプログラマ・言語に対して『オブジェクト指向ではない』と言っている。
3.staticを使っている人をみるとプログラマとして終わっていると感じる。
4.過去にオブジェクト指向を批判した記事を読んだが書いている奴がオブジェクト指向を分かっていないだけだった。
5.C++が最高、他の言語はダメと思う人。
6.Javaが最高、他の言語はダメと思う人。
7.C#が最高、他の言語はダメと思う人。
8.と言いつつも、自分自身がオブジェクト指向というのが何か実は良く解っていない。
さて、こういうと『お前が解っていないだけだ』と批判を受けそうなので少し私自身について説明します。私は14歳の時からプログラミングを初めて今年で32年目になります。仕事で使った言語は、覚えた順からBasic,Assembly,C,C++,VB,SQL,Java,Perl,PHP,MDX,Ruby,VB.NETです。ちなみにAssemblyは複数のCPUのインストラクションセットを覚えたし、実際に20年前まで仕事で使っていました。メジャーな言語ではC#が抜けているがやったことがないだけです。オブジェクト指向についていうとこれまた20年以上の経験になり、いわゆる手続き型言語からオブジェクト指向言語へコンバートした人になります。批判される前に私のオブジェクト指向の経歴をいうと、十数年前にJavaの記事を書いたこともあるし、ADPというプログラミング言語のプロジェクトを持っているがこれはC++で書かれています。またADPもオブジェクト指向をサポートしてます。その関係で一般のプログラマよりもオブジェクト指向についてよく考えていると自負している。
さて、まず最初の処方箋を言うと、オブジェクト指向は過大評価されている点を挙げましょう。実際は全くそんなことはないのにも関わらず、『オブジェクト指向はプログラマが進む最終地点』と考えている人が多いでしょう。歴史的にみてオブジェクト指向が持てはやされたことがありそれに無意識に乗っかっている人々がいたということもあるが、実はオブジェクト指向自体になにかプログラマを引き付ける魅力があります。ある人はそれを『究極の一手』と表現し、またある人は『彼女(彼氏?)』と言ったり、私自身も『オブジェクト指向はプログラマが進む最終地点』と考えていた時があった。さてここまでで『私がオブジェクト指向を理解していない』と思ったあなたは充分オブジェクト指向病に冒されていますので、何かコメントをしよう考えたのなら少し我慢して以下を読んで頂いて反論を頂きたい。
誰でも知っていることだがオブジェクト指向というのはもともと分類学の技法をプログラミングに取り入れて複雑なプログラムに対応しようというパラダイムの一種で、クラスとか継承とか多態性という言葉は分類学から借りてきたものであるといえる。ここで冷静になって考えてみれば分かることですがそもそも世の中の全ての事柄を分類学でカバーできるでしょうか?世の中の学問を見渡せば分かるとおり分類学では全てはカバーできないことは理解できるでしょう。つまり継承とか多態性というのは一部の領域にのみカバーをすることが保障されているということであり、ここで代表例を挙げると、GUIシステムにはオブジェクト指向がうまく適合できたようで、他の例を挙げると私の経験上になるがプログラミング言語の処理系もうまくマッチすると思う(残念ながら全く問題がない訳ではないが)。その他、経験上言えることは、一連の法則性を持った多様なものを処理するにはオブジェクト指向が向いていると考えられる。ほかの例を挙げると、
実はオブジェクト指向ってしっくりこないんです!の記事にある、とりすけ さんのコメントで税金計算処理の適用事例があります。
以上、確かに適用できる事例はありますが、逆にいうと応用例はかなり限定されています。よく自称オブジェクト指向専門家に具体的な話を聞くと『息を吸うようにインタフェースを使っている』という分かったような分からないような返しをされますが、具体的な話ができないということはそもそも彼(彼女)も分かっていないということになるでしょう。本当に息を吸うようにインタフェースを使っているのなら具体例が多数出てくるでしょう。
また、オブジェクト指向の本質はメッセージだ!という人もいらっしゃいます。なるほどメッセージをやり取りすることにより複雑なシステムを簡単に構築しようということらしいです。では以下の2つのコードはどちらが理解しやすいでしょうか?
z = 3 * x * y + 4 * x + 6 * y + 2;
z = 3.multiply(x).multiply(y).add(4.multiply(x)).add(6.multiply(x)).add(2);
『下のコードの方が良い』という人はぜひリハビリを行ってください。少なくとも人前では上の方がよいと言いましょう。ちなみに当たり前ですが、これを持ってオブジェクト指向がダメだと言いたい訳ではなく、言いたいことは、『メッセージというある種のプログラムを抽象化する道具も乱用すると却って悪戯にプログラムを複雑にする』ということです。
次の回(おそらく来週?)このメッセージについて考察したいと思います。
早いもので、年が明けてから3月も中旬になりましたが、一応更新しておきます。
先ず、6年間にわたり私を苦しめてきた通訳案内士試験ですが、晴れて合格しました。
そういえば、2次試験のレポートも書いていませんでしたが、2次試験は昨年の12月13日にありました。英検と違いとにかく拘束時間が長く内心二度としたくないと思っていたところで今一つ自信がなく合格と知ったときは、うれしさ半分、ウソだろう?という気持ちが半分でした。何はともあれ合格できて良かったです。
語学留学の方は、02.16 5学期目からリーディング・ライティングをやるようになった。
その5学期も終わり今は6学期目に入っています。成績は以下のとおり
| 10.15 1学期目 | 11.15 2学期目 | 12.15 3学期目 | 01.16 4学期目 | 02.16 5学期目 |
Level | 106 | 107 | 108 | 109 | 110 |
GPA | 3.3 | 2.9 | 3.2 | 3.1 | 3.0 |
かろうじて3.0を確保したかという感じだが、まぁ、成績が今一つ振るわなかったのは花粉症になりファイナルテストの成績が今一つだったからだと思われる。
最近は、言葉が口をついて出るようにはなったが、文法が今一つで、評価シートにも"Work on accuracy in speaking"と書かれた。まぁ仕方がない。後7週間で語学留学も終了になるので悔いがないように頑張りたい。