生産性を向上させるメタオブジェクト技術

■この記事で取り上げているトピックハイライト■

  • なぜ、メタオブジェクトを自分自身で使いこなせるようになると、日常のプログラミング生産性が大きく向上するのか?
  • なぜ、メタオブジェクト技術を使うと、分散オブジェクト、Rails、DI、ORマッピングWebサービスなどの、大きく生産性を向上させる仕組み自体を自分でつくれるのか?
  • C#のどのメタオブジェクト機能をどのように使えば、簡単に「C# on Rails」を作れるのか?
  • なぜ「Ruby on Rails」は陳腐化してしまう運命にあるのか?
  • Ruby on Rails」を陳腐化させるアーキテクチャとはどのようなものなのか?


■構成■
まず、Ruby on Railsと同様のフレームワークを、C#で作ったとしたら、どのようになるのかという例題を通して、メタオブジェクト機能、つまり、リフレクション、カスタム属性、CodeDOM、パーサジェネレータ、ダイナミックロードがどのように役に立つものなのかを見ていきます。
次に、メタオブジェクト機能の応用である、言語仕様のカスタマイズや拡張、ドメインスペシフィックな言語の作成、日常のプログラミングの生産性向上、メタなプログラミングスタイルなど、メタオブジェクト機能を使いこなすことの意味と効用について解説します。
最後に、メタオブジェクト機能を使って何か根本的なものを作るときの注意点について解説します。

■目次■

  • 例題: Ruby on RailsC#版を作る方法
  • メタオブジェクトを使いこなすと手に入れられる力
  • メタオブジェクトを使ったフレームワークの宿命

■例題: Ruby on RailsC#版を作る方法■
Ruby on Rails」のチュートリアルを斜め読みしてみました。
とりあえず、Rubyでなきゃいけない必然性は見つかりませんでした。別にC#でもJavaでも同様のものは、簡単に作れると思います。
たとえば、C# on Railsの場合、C#が持っているメタオブジェクト機能、つまり、リフレクション、カスタム属性、CodeDOM、パーサジェネレータ、ダイナミックロードを組み合わせれば、作るのは、それなりに手間はかかるけど、ものすごい手間というほどでもないでしょう。
また、C# on Railsを作ったとしたら、そこでのアプリ開発生産性は、Ruby on Railsの場合と、ほとんど同じだと思います。Rubyだからこそ、生産性が高くなる部分というのは、ほとんどない。ないことはないけど、枝葉末節の部分じゃないかと。
たとえば、Ruby on Railsチュートリアルにあるお料理のレシピ集をみんなで投稿して共有するWebアプリの場合、そのModelで、has-a関係を表現するには、C#の場合、カスタム属性を使って次のように記述すればいい。

public class Recipe : RailsModel{
	public int id;
 	public String title;
 	public String instruction;
 	public DateTime date;
 	[BelongsTo("Category")] public int category_id;
 	うんちゃらかんちゃら。。。
}
[HasMany("Recipe")] public class Category : RailsModel{
	public int id;
 	public String name;
}

上記のソースコードを見て分かるように、HasMany属性はクラス属性でBelongsTo属性は、フィールド属性です。
C# on Rails」は、このHasManyとBelongsToという二つのカスタム属性で示された関係を、リフレクションAPIを使って調べ、必要に応じて、二つのRDBテーブルをJOINするようなSQL文を生成します。*1
C#onRailsは、RDBからとってきたデータを、リフレクションを使って、上記のC#のRecipeオブジェクトのリストに変換します。対応づけは、リフレクションでとってきたクラス、フィールド、メソッドの名前の文字列と、SQL文でとってきたテーブル名やデータ項目の名前文字列の命名規則でやります。とーぜん設定ファイルはいりません。
また、むしろ、上記のC#のモデルクラスから、リフレクションでとってきた文字列をつかって、CREATE TABLE命令を自動生成するのもすぐできますから、RDBのテーブル定義も自動生成できるんじゃないですか? むしろ、チュートリアルを読んだかぎりでは、「Ruby on Rails」では、モデルのRubyコードとテーブル定義の両方をプログラマが手で設定するようになっていて、二度手間のように見えましたけど。
それから、コントローラの方は、こんな感じ。

class RecipeController : RailsController{
	public Recipe model;
	public void list(){
		recipes=model.find_all();
	}
	public List recipes;
}

modelへの参照は、C# on Railsがリフレクションを使って自動的に設定してくれる。対応関係は、命名規則で表現する。つまり、クラス名から、Controllerをとったものを、対応するmodelクラスとみなす。命名規則によってDependencyを記述している。だから設定ファイルはいらない。これは、Dependency Injectionの思想に反するものだ。Dependency記述を切り離して、別に定義するのが、Dependency Injectionの考え方だ。DIでは、それによって、二つのクラスの関係を独立させ、プラガブルにしている。しかし、このC# on Railsでは、命名規則という形で、Dependencyをそれぞれのクラスに組み込んでしまっている。つまり、C# on Railsの発想とは、各モジュールを独立化し、プラガブルにするという目標を、はなっから捨ててしまっている。ソフトウェア部品の再利用など、はなから念頭にないのである。*2

それから、テンプレートhtmlはこんな感じ。

<% foreach(recipe in recipes) %>
 <tr>
  <td><%= link_to(recipe.title), meth("show"), P["id"]=recipe.id %></td>
  <td><%= recipe.date =%></td>
 </tr>
<% end %>

C#のパーサジェネレータでASTをつくって、それをCodeDOMのASTに置き換えて、あとはCodeDOMに.NETコードを生成させれば、JITが使えて、パフォーマンス的にも、高速。当然、キャッシュさせるから、毎回コンパイルしたりしない。
また、C#では、このカスタム属性を、簡単にユーザが定義できるようになっていますから、これを実装するメタオブジェクトプロトコルを実装するのは簡単。たとえば、上記の二つの属性のソースコードは、次のようになっています。

public class HasManyAttribute : Attribute {
	public HasManyAttribute(string s) { this.className=s; }
	public string className;
}
public class BelongsToAttribute : Attribute {
	public BelongsToAttribute(string s) { this.className=s; }
	public string className;
}

C#の場合、ソースコードに、上記のように書いておくだけで、すぐにユーザ定義のカスタム属性が使えるようになります。
そして、これらの属性の定義をリフレクションAPIで取り出すRailsルーチンはどうなっているかというと、たとえばBelongsTo属性の場合、次のようになっています。

BelongsToAttribute attr =
	(BelongsToAttribute)Attribute.GetCustomAttribute(model_class, typeof(BelongsToAttribute));
string child_class_name=attr.className;
Type child_class=Type.GetType(child_class_name);

■メタオブジェクトを使いこなすと手に入れられる力■
で、本題は、こういうふうに「言語仕様をカスタマイズする」ということ。これは、何十年も前から行われてきたことで、取り立てて目新しいことでもなんでもないっす。ぼくが十数年前にヘビーに使い込んでいた言語処理系では、クラスのデフォルトの継承関係やメソッドディスパッチなんかもカスタマイズできました。つまり、クラス定義やメソッド定義の、定義方法自体を定義するメタクラスが定義できる言語処理系ですね。で、クラスやメソッドを宣言するときに、どのメタクラスインスタンスとして、そのクラスやメソッドを定義するのかを、指定するわけです。
それで、なにが嬉しいのかというと、たとえば、アスペクト指向という新しい概念(オイラ的には、あまり新しいと思えないのですが)がでてきたら、簡単にAspectJみたいな言語仕様にしてしまうことができるんです。たとえば、AspectJでは、既存のクラス定義やメソッド処理に変更を加えずに、メソッドが呼ばれるたびにログ記録をとるコードを挿入できたりしますけど、言語仕様をカスタマイズできるシステムだと、そもそも、メソッド実行それ自体をユーザ定義できますから、あるメソッドが実行される直前もしくは直後に、別のメソッドが実行されるようにするなんて、簡単です。
この、言語仕様をカスタマイズする機能のことを、メタオブジェクトプロトコルという人もいいます。このメタオブジェクトプロトコルの最大の利点は、ドメインスペシフィックな言語仕様もどきを簡単に作れるという点です。ドメインスペシフィックというのは、ある特定の業務分野のアプリを作るための専用言語ということです。たとえば、人工知能システムだったら、人工知能システムの記述に向いた言語仕様、3Dグラフィックシステムだったら、それでよくある問題解決の記述に向いた言語仕様、株取引だったら、それを記述するのに向いた言語仕様、というわけです。
この「C# on Rails」も、本質的には、メタオブジェクトプロトコルを使って、C#の言語仕様を拡張子し、DB-Webアプリという、特定のドメイン(問題領域)に特化した言語仕様を創り出しているわけです。もちろん、C#のカスタム属性とReflectionは、本格的なメタオブジェクトプロトコルに比べれば、おもちゃみたいなもので、できることもかなり制限されますが、その制限の範囲内でも、それなりに実用的な言語拡張ができるようになっているということです。
さらにいうなら、ここ十数年の間に、ソフト開発の現場に大きな影響を与えてきたソフトウェアモジュールは、たいていメタオブジェクトライブラリです。もしくは、メタオブジェクトフレームワークです。たとえば、シリアラザ、分散オブジェクト(RMIとか.NET Remotingとか)、ORマッピング(Hibernateとか)、Webサービス(SOAPとか)、DI(Springとか)は、どれも、ヘビーにリフレクションやモジュール自体のパラメータわたしやダイナミックロードなど、「プログラム自体をデータとして扱うプログラム」というメタオブジェクト機能を使って実装されています。
あと、ASP.NETに使われているメタオブジェクト系機能としては、CodeDOMがあると思います。これは、対象ドメインの問題解決を記述する言語として、既存のC#の文法を拡張するだけではぜんぜん不十分なケースに使えます。つまり、Syntax自体を根本からかえなきゃならない場合です。その場合、昔はそのたびに、その問題領域をいちばん自然に記述する専用言語を作ってきました。問題領域を記述するためのドメインスペシフィックな文法を持つ言語、JOB記述言語、シェルスクリプト言語、SQLEmacs Lisp秀丸など各種エディタのマクロ言語、正規表現BNFAWK、各種人工知能言語、XMLなどをイメージすれば、分かりやすいと思います。
ただ、それらの言語は、どれもインタープリタなので、実行速度の問題がでるわけです。もちろん、実行速度に問題ない場合は、それらのインタープリタが、リフレクションを使って.NETやJavaのオブジェクトにアクセスすれば、JavaC#のオブジェクトに自然に参照できるドメインスペシフィックな簡易言語をつくれますね。Spingなんかは、独自言語ではなく、XMLですけど、XML文の中からJavaのオブジェクトを指定するのに、やっぱりリフレクションを使ってますね。なので、実行速度の問題がでない場合、これで十分だと思います。
ただ、どうしても実行速度の問題がでちゃう場合というのがあって、その場合、インタープリタではなく、トランスレータコンパイラとして実装することになるんですけど、どちらも作るの、手間なんですよね。つまり、字句解析して構文解析して、Abstract Syntax Treeを作るところまでは、パーサジェネレータを使えば、まあ、簡単なんですけど、それを.NETのバイトコードもしくはC#ソースコードに変換するコード生成がめんどい。yaccのようなパーサジェネレータが、コンパイラを自動生成してくれる、というのは、幻想にすぎなくって、AST構築とコード生成の部分は、いままでは、依然としてハンドコーディングしてたわけですよ。ちょーメンドクサイ退屈人生の無駄遣い意味ない泥仕事なわけですよ。ところが、C#.NETのCodeDOMは、このコード生成をかなーり楽にしてくれる。ドメインスペシフィックな言語のASTを作ったら、CodeDOMを使って、それをC#のASTに置き換えてやるだけで、あとは、CodeDOMがコンパイルして、.NETコードをはきだしてくれるわけです。で、それをキャッシュしておけば、パフォーマンス問題は解決、と。
で、これらの、メタオブジェクト機能を、日常的に自分で使いこなせるようになると、プログラムの生産性が、けっこう上がるわけですよ。
たとえば、自分のアプリのソースコードを、メタオブジェクトルーチンとアプリルーチンとの複合体として、実装し、それら二つをレイヤ分けしてアプリ構造をつくると、とても生産性が高く、短く美しいソースコードで、問題を記述できる。また、一般に、通常のライブラリに比べ、メタオブジェクトライブラリ、もしくは、メタオブジェクト機能を組み込んだ一般のライブラリは、より汎用性が高く、使い回ししやすい傾向がある。
ただ、難点は、メタオブジェクト機能は、本質的に言語自体の拡張という性質を持つので、別の言語のように見えてしまい、同僚から、「おまえのソースコードは読みにくい」というクレームが来るということだ。ただ、こんなの、単なる慣れの問題なので、ようは、メタオブジェクトというカルチャーを、その職場が受け入れるかどうか、という、それだけの問題だと思う。しかし、こんな何十年たっても、一部のハッカーしか使っていないような機能が、今後普及するのかどうかは、ちょっと怪しいという気もする。
それから、少なくとも、自分で新しいプログラミングパラダイムや新しい潮流を起こそうとしたら、たぶん、メタオブジェクト機能を使いこなせないんじゃ、話にならんケースとか、けっこうあると思いますよ。だから、そういう野心を持つプログラマは、普段からそれを使いこなしておいた方がいいと思いますよ。
■メタオブジェクトを使ったフレームワークの宿命■
あ、それから、最後に、ちょっとだけ注意点。メタオブジェクトライブラリで、根本的に新しいフレームワークをつくる、というのは、じつは、プラットフォームの正常進化で簡単に陳腐化されることがが多いです。メタオブジェクトライブラリで実装したくなるようなものって、そもそも、プラットフォーム自体が、不完全なものであるために、その不完全さを補うため、その必要性がある、という構造になっていることが、たいていだからです。HORBなんかがいい例じゃないかな。また、Ruby on Railsについても同じで、あれも、同じ理由ですぐに陳腐化しますよ。そもそも、進んでいる方向は、むかし、Object Relational Databaseが目指した方向と同じなんですよ。つまり、もともと、オブジェクトというのは、パーシステントとリレーショナルという二つの性質をデフォルトで兼ね備えているべきなのに、いま、それが欠落しているので、その補完をメタオブジェクトライブラリでやってるだけですから。*3
つまり、SQL文法とC#文法が別々にあるのは、間違っていて、C#の文法でSQL的なことができなきゃならんわけですよ、本来は。つまり、オブジェクトAのリストとオブジェクトBのリストがあったら、それらの特定のフィールドをキーにして、それらをJOINしたリストを生成するような文法をC#は当然持っているべきで。で、それらはいちいち変換するとかじゃなく、プログラマから見ると、ある一つのC#オブジェクトが、RDBのネイティブオブジェクトでもあって、オブジェクトに対してコミットとかのメソッドを実行できると。そもそも、O-Rマッピングという発想は、対処療法なんですよ。そもそも同じようなオブジェクト定義が二つもあるのが間違っている。たとえば、以下のような二つのオブジェクト定義をするのは、本来冗長で、

CREATE TABLE RecipeT(
	title VARCHAR(255),
	instruction TEXT,
	うんちゃらかんちゃら。。。)
public class Recipe{
 	public String title;
 	public String instruction;
 	うんちゃらかんちゃら。。。
}

そもそも、マッピングとかじゃなく、一個の定義でできるようになってなきゃならないのだけど、レガシーとの互換性だのなんだののごちゃごちゃがあって、それらをはじめから一つのものとして定義できるようなインフラにするのに手間取っているだけで。で、当然SunもMSもこんなことはとっくに分かっていて、C#3.0とか見ても、MSがその方向に突き進んでいくことは、容易に見て取れるわけです。
また、先日、そもそも、本質的に依存しているオブジェクトや、本質的に同一のオブジェクトにDIを適用しようとしているダメダメな自称SEに先日会いましたけど、たとえば、本質的に同一のオブジェクトにDIを適用するのって、正規化されていないデータベースの辻褄を合わせるために、洗練されたアーキテクチャを構築するというような本末転倒意味不明のことをやっているようなもので。Ruby on Railsにしても、同じ話で、それが、どんなに洗練されたアーキテクチャであろうとも、そんなものは、そもそも必要ないかもしれんのですよ。
もちろん、、、、と、ありゃ、時間だ。
そんなわけで、そろそろ昼休みが終わるので、仕事に戻ります。

*1:べつに、リレーションを張らなくても、2つのSQL文でそれぞれとってきて、それをC#onRails側で合成するというやりかたでも同じ。片方だけの一覧をとってきて、それをリストボックスから選ばせるというのでもあり。そこは枝葉末節。

*2:そもそも、「ソフトウェアの再利用可能にしたりプラガブルに設計することで必ず生産性が向上する」という単純な思い込みが、諸悪の根源なのだ。つまり、この記事のように、単純に「同じことを2度しない(Only and Only OnceあるいはDRY:Don't Repeat Yourself)」と無条件で考えてしまうと、逆に生産性が大きく低下するケースがたくさんある。ソフトウェアを、再利用可能な形で設計したり、プラガブルに設計するコストが、そう設計することで得られるメリットを上回るというケースなど、いくらでもある。とく小規模のWebサイトを、さっと作る必要があるときなど、その傾向が強い。余分な工数をかけて再利用可能だのプラガブルだのに設計したところで、あとから状況が変われば、はじめに予見したプラガブルアーキテクチャの範囲内でしか設計変更できない。しかし、そもそも未来の十分な予見など無理な場合だっていっぱいあるのだ。十分な精度で予見するための、入念な検討にコストと時間を書けるなら、予見なんてざっくりでいいから、プラガブルアーキテクチャなど放棄して、さっさと作ってしまったほうが、トータルコストは安く、機動的で、柔軟性も大きいのだ。なにか、大きな変化が起きたら、そのときまた、書き直してしまえばいい。小規模のサイトの場合、プラガブルアーキテクチャなどにこだわらなければ開発コストは、ずっと小さくなるのだから。なによりおそろしいのは、プラガブルアーキテクチャという檻に思考が閉じ込められて、無意識のうちに、その枠内でしかビジネスを展開できなくなってしまうということだ。再利用可能な柔軟なフレームワークは、思考の牢獄なのだ。もちろん、DIでもEJBでもなんでも使って、プラガブルアーキテクチャにした方が、トータルコストは安くなる場合だってたくさんある。大規模なシステムなんて、たいていそうだ。だから、そういう場合は、C# on Railsは向いていない。そういう場合は、これを使わなければいいだけの話だ。もっというと、そもそもDIというのは、「本質的に独立」している二つのオブジェクトを切り離すためのデザインパターンであって、「本質的に依存」している二つのオブジェクト定義に使うと、単に複雑なだけで、DIのメリットが少しもでない設計になってしまうのだ。この問題については、まだまだ奥が深いが、話が本記事のテーマから遠ざかるので、それはまた別記事で。

*3:もちろん、すべてのオブジェクトをパーシステントにしたら、パフォーマンス上の問題がでるから、カスタム属性で、Persistentと指定したオブジェクトだけそうなる、というようなイメージです。