InfoQ

InfoQ

Articles

マイブックマーク

ブックマークするためにログイン または 会員登録 する

ブックマークされました!

ブックマークがエラーになりました。もう一度お願いします。

オブジェクト・メタデータにシングルトンクラスを使う

作者 Werner Schuster , 翻訳者 編集部 投稿日 2008年4月30日

セクション
プロセス/プラクティス,
デベロップメント,
設計/アーキテクチャ
トピック
Object Oriented Design ,
コード分析 ,
動的言語 ,
Ruby ,
言語
タグ
OOP

オペレーションあるいはAPI呼び出しの結果、多数のオブジェクト、つまりオブジェクトグラフができてしまった。やるべきことは、データを解析し、解析結果をグラフのメタデータとして保存することである。

あまりにも抽象的すぎる?コンパイラがどう機能するか思い浮かべてみよう。パーサは、コードをツリー構造で表す構文木(あるいは抽象構文ツリー〔Abstract Syntax Tree=AST〕)の形で出力する。次に、記号テーブルで記号を集めたり、型推論したり、その型を使って型検査を行ったり、など、多数のアルゴリズムがパスの形でツリー上に広がる。

でも待って。最後の2パスに問題が示されている。型推論コードは、型検査が使うデータをどこに保管するのか? 必要とされる場所にデータを保管すれば、最も好都合だろう。すなわち式ノード(AST内)がFoo型を返すと分かれば、その情報を正にノードに保管しておくのが最良だろう。

ASTで機能するツールに注目して、ソリューションを説明しよう。コンパイラではないが、同じような要件のツールである。RubyではParseTree(サイト・英語)ライブラリがASTとしてRubyソースコードを返す。以下は例である。

[:vcall, :obj, :say_hello, [:array, [:lit, 42], [:lvar, :foo]]] 

これは ParseTree ASTとして表したRuby コードである。今回は記事でVMではないので[GLOVA1]、グラフとして整理したオブジェクトやオブジェクト参照の取り扱いが少々難しい。そのため、ParseTreeのS-exprツリー表現を使う。 S-exprは入れ子になったリストで、各リストがツリー内のノードを表し、最初の要素がノード型を指定している。サンプルでは、ノードが仮想メソッド(vcall)への呼び出しを表す。パラメータはレシーバ(呼び出されたメソッド内のそれ「自身」)、メソッド名、パラメータである。

ツール向けのアイデアは型検査であり、動的解析ツールあるいは自動リファクタリングになるだろう。そうした種類のツールが機能するための必要条件の1つが型推論で、変数あるいは式の型を決定する。例えば、次のASTはto_sメソッドへの呼び出しを表す。

[:vcall, :obj, :to_s] 

これに関して集めることができる情報は何で、その情報で何ができるのか? 例えば戻り値の型だ。その判断は難しいが、to_sはオブジェクトの文字列表現を返すメソッドなので、「文字列」を返すとだけ言っておこう。これは必ずしも真実ではなく、推測である。コード補完機能のようなツールでは十分だろうが、こうした事柄が100%正確というわけにはいかない。しかし場合によっては、もっとたくさんの情報を集めることもでき、そうなればアナライザはいっそう正確な情報を判断できるだろう。

アナライザはツリーに取り掛かり、そして決定したメタデータをASTにアノテートする。コードベースをモジュールにしておくための良いアイデアは、アナライザをメタデータ消費者から分離しておくことである。メタデータ消費者とはつまり、ASTを動かして[GLOVA2]メタデータに何らかの処置を施すコードのことである。例えば、Rubyのエディタは、スーパークラスで別のメソッドをオーバーライドするメソッドを強調表示することができる。

ソリューション

かいつまんで話すと、ParseTreeノードをアノテートするソリューションは以下のとおりである。

node = [:vcall, :obj, :to_s] 

def node.set_metadata(key, value)
 @_metadata ||= Hash.new
 @_metadata[key] = value
end
def node.metadata
@_metadata ||= {}
end

node.set_metadata(:type, :String)

一体何をやっているのか?

秘密のソース:シングルトンクラス

Rubyではあらゆるオブジェクトがクラスのインスタンスである。他の多数のOOP言語とは異なり、Rubyではオブジェクトのクラスを変更できる。これをオープンクラスと混同してはいけない。Rubyでは、ランタイム時にさえ、クラスの修正が可能である。シングルトンクラスは類似しているが、単一オブジェクトのクラスのみに影響を与える点で異なる。小さな相違に見えるかもしれないが、影響を受けるオブジェクトだけにクラス修正の効力を制限するという点で、大きな利点がある。それに比べてオープンクラスは、こうしたクラスの全コードと全オブジェクトのクラス定義を変更する。有用だが、他人のコードにも手を出すことになり、そして共通クラスのあまりにも多数のコードに変更が起これば、既存メソッドとの名称衝突が起こる可能性がある。ParseTreeノード(普通のRuby配列)のケースでは、ヒープ上のすべての配列ではなく、実際に使用された配列オブジェクトだけが影響を受けることを意味する。

別の見方をすると:シングルトンクラスを使えば、クラスを作成して使用するコードに局所的にクラス変更を加えることができ、外に見せる必要はまったくない。他方、オープンクラスはグローバルな変更である。クラス名はグローバル変数であり、すなわち「文字列」は文字列を表すクラスオブジェクトを指す。より適用範囲を絞った変数(ローカル変数、メンバー変数)がグローバル変数より望ましいのと同様に、オープンクラスよりシングルトンクラスが望ましいのである。

もちろん、どちらのソリューション(シングルトンもしくはオープンクラス)を選ぶかは個々の状況に左右される。 オープンクラスでは、クラスへの変更は1度で、その後は、そのクラスの全オブジェクトにメソッドが加わっている。シングルトンクラスでは、クラスへの変更(すなわち、シングルトンクラスの作成とそれを指し示すオブジェクトのクラスポインターの変更)は、すべての変更で発生する。

構文は前述したように単純である。

def object_variable.method_name()
# code
end

オブジェクトを指し示す変数はメソッド名のプレフィックスになっている。これよりも柔軟で、モジュール的な方法はMixinの使用である。そうすればモジュールでアスペクトを処理するメソッドをまとめて一括でミックスできる。例:

module Metadata 
def set_metadata(key, val)
@_metadata[key] = val
end
def metadata
@_metadata ||= {}
end
end
x = [:vcall, :obj, :to_s]
x.extend Metadata
x.set_metadata(:type, :String)

Mixinでは、モジュール内で定義された機能をクラス内へとミックスできる。このサンプルでは、ノードオブジェクトのシングルトンクラスである。もう一度言うが、このオブジェクトに限ってこうしたメソッドが存在することになる。

別の例 - 不要コードの削除

静的アナライザコードが抽象的過ぎるなら、別のツールを試してみよう。不要コードのリムーバだ。「不要コード」とは、単に何もしないコードであり、取り除いてもプログラムの動作は変わらない。たとえば、次のようなコードだ。

for x in [1,2,3] do
end

アノテーションのコンセプトでは、こういった種類のコードを探し、次のようにアノテートすることができる。:

node.metadata(:dead_code, :true) 

けれども、コードに不要と印をつけるのは、ほんの第一歩に過ぎず、今度は除去する必要がある。ここへ来て、解析と処置の分割が合理的であることが簡単に理解できるだろう。IDEは若干のコードを不要コードとして強調表示したいだけかもしれないが、簡単なコード削除方法(応急処置)を提供することもある。

このコードをどうすれば削除できるのか? もちろん、ASTからノードをダンプした後にRuby2Ruby(source)を使ってRubyソースを吐かせることは可能であろう。しかし、とても気持ちの良いソリューションとは言えない。Ruby2Ruby がParseTree ASTをRubyソースコードに変えるが、フォーマット(空白スペース)やコメントのなどの多数の有用情報を失ってしまう。こうした情報を無くしてしまうことは、IDEやリファクタリング/クリーンアップツールで容認できるものではない。

メタデータの助け船:ノードはソースの場所、つまり、この特定ノードが元々のソースコードで見つかる場所でアノテートすることができる。そしてそれがあれば、ことは簡単である。クリーンアップコードは単にコード中を走って、:dead_codeと印のついた全ノードを見つけ出し、ソース場所メタデータの文字オフセットを使ってソースファイルで印つきのノードを除去する(もちろん、ノードを除去すると、残存ノードのオフセットに狂いが生じる。これを解決するには、削除した文字数を覚えておいて、実際のオフセットから差し引けば良いだけである。こうすれば、ソースの再パーズと再解析は必要ない)。

結論

この記事の例は言語ツールに焦点を当てたが、ここで述べたアイデアはあらゆる種類のオブジェクトコレクションで利用できる。オブジェクトグラフにアノテーションが必要な場所なら、単独で書かれたアナライザによる複数のパスという可能性もあり、その場所がどこになってもノードと一緒にアノテーションを保管しておけば好都合である。オブジェクトグラフのクラスが開発者の制御下になく、他の場所で広範囲に使われるのなら(たとえば ParseTreeにおける配列クラス)、シングルトンクラスを選択すれば良いだろう。その他の状況では、オープンクラスがより良いソリューションとなるかもしれない。

ここで述べた特定ツールの実装に関して:ParseTreeは、Rubinius とJRuby(jparsetree)(サイト・英語)で、Ruby 1.8.x向けに利用可能である。JRuby版は個々のノードにもソースの場所を付加するので、ソースの修正がさらに容易になる。 ツールのアイデア出しやツール書きを楽しもう。Rubyでは簡単にできるのだから。

原文はこちらです:http://www.infoq.com/articles/prototypes-for-metadata
このArticleは2007年12月5日に原文が掲載されました)

特集コンテンツ一覧

GAE開発の落とし穴

Googleのクラウド環境をつかったGoogle App Engineによる開発するにあたり、初めての試みで苦悩する開発者達の経験をもとに、各開発フェーズにあわせて問題点やどう解決したかをご紹介します

イベントレポート:「Coqチュートリアル#1」

去る1月12日、定理証明支援系ツールCoqの初心者向けチュートリアルが開催さ れた(http://kokucheese.com/event/index/23667/)。今後も2月2日 (http://kokucheese.com/event/index/23744/)、2月9日、2月16日と引き続き開 催されていく予定である。本記事では、開催の様子をレポートする。

Javaの未来についてのNeal Gafter氏とのディスカッション

Choosing Options

Neal Gafter氏はOracleによるJava買収の影響に関する議論、Javaにセグメンテッドスタックやメタオブジェクトプロトコルを追加することについての主張、そしてJavaとC#との比較について話をしてくれた。

Google Dartのエッセンス:アプリケーションの構築、スナップショット、Isolate

GoogleはVMをともなう新しい言語であり、JSコンパイラでもあるDartをプレビューした。 InfoQはDartのアプリの構築に貢献する文法の裏側を探った:スナップショット、Isolate、モジュール方式

CSPベースのモデル検査ツール「Process Analysis Toolkit」

本記事ではCSPベースの「マルチドメイン・モデル検査ツール」である、PAT(Process Analysis Toolkit)について紹介する。モデル検査は、形式手法(Formal Method)という方法論を基礎とする技術であり、複雑さが増大しながらも安全性を求められる、現在のソフトウェア開発の状況に対する処方箋の1つとして注目されている手法である。

Jenkinsによる継続的インテグレーションのススメ(4) ~CloudBeesでJenkinsをサービスとして使う~

前回まで、Jenkinsの幾つかの側面に注目して解説をしてきました。シリーズ最後の今回は、Jenkinsをサービスとして使う方法を紹介します。

書籍『抽象によるソフトウェア設計-Alloyではじめる形式手法-』の紹介

Alloyは、MITにて開発された仕様記述言語であり、ツールによる自動解析を使い、インクリメンタルに形式仕様が書けることが特長である。筆者らはAlloy開発者による、Alloyを使った形式手法入門書を翻訳、今夏にオーム社より刊行した。本記事では、Alloyの簡単な概要と、翻訳書『抽象によるソフトウェア設計』(「Alloy本」)を紹介する。

Windows デバイスで開発するタッチユーザーインターフェイス

スマートフォンを中心としたマルチデバイスにおけるタッチユーザーインターフェイスへの対応は、既に必須の項目となりつつある。本記事では、Windows デバイスにおける UX のベースとなっている「メトロ」というデザイン言語を掘り下げながら、既存環境を意識しつつもどのようにタッチユーザーインターフェイス開発に取り組んでいくべきであるかについて解説していく。