UML/MDAのためのオブジェクト制約言語OCL 第2版ってば。


ちょっと前に読み終わったので、幾つか。

これを読む前。OCLに対するイメージは、ちょっと漠然とした感じで、
こんな風に考えてました。

  • 只の箱や矢印が、更にそれっぽくなるかもしれない
  • 厳密なモデル?なんじゃそら…。
  • ファンタジーな生産性が期待*1

読み終わって、OCLが如何様なモノか掴んだ時、ちょっとガッカリした反面、
意外と大した事無いなぁ…と。


まぁ、このクラス図を見つつ、中の説明を読んで下さい。


いきなりコードから入るのもアレですが、例えば、こんな感じ。

context Company inv:
self.numberOfEmployees > 50

この中に出てくる、contextinvselfってのが予約語な訳ですよ。
んで、これは何の事を表現してるのかと言うと、
「Company」ってクラスの不変条件について定義しています。


つまり、この場合、会社はメンバである「numberOfEmployees」が、
50以下にならない。って事。*2
より分り易い文章にすると、

  • 会社は、50人より多い従業員が居る。

そんだけ。


もうちょっと他のも見て下さい。次々と予約語が増えていくのは気持ち悪いけど…

context Person::income(d : Date) : Integer
post: result = 5000

この場合、定義対象は、「Personクラスのincomeメソッド。引数は日付、戻り値は数値」。
んで、postresult予約語
「post」は、手続きが終了した後(事後条件)に、こうあるべき、を定義します。
ここでは、処理の中身について言及していないのがミソです。


どうやらこのモデルによると、トンデモな事に、収入の計算方法はどうあれ、
結果(result)は5000になるらしいです。*3


ここまでcontextの対象は、クラスとメソッドでしたが、
他にも、クラス間の関連、パッケージ*4等を対象とする事が出来ます。


invpostは仲間です。
ずらずらと似たようなのを並べると、こんな感じになります。

  • inv
    • 不変条件。「context」の状態がどうあれ守られるべき条件の定義
  • pre
    • 事前条件。処理(操作)の前提として守られるべき条件の定義
  • post
    • 事後条件。処理(操作)の結果守られるべき条件の定義
  • derive
    • 派生条件。「context」の状態がどうあれ、派生条件を評価した結果と同じにならなければならない条件の定義。取り敢えずinvのオマケ…。
  • init
    • 「context」の初期状態。主にクラスのメンバで使用?
  • body
    • 処理(操作)中に、守られるべき条件の定義。OCLの性格上(後述)むしろ処理そのもの。

後半怪しさが爆発します。特に、body
見て下さい。bodyを使うとこんなです。

context Person::getCurrentSpouse() : Person
pre: self.isMarried = true
body: self.mariages->select( m | m.ended = false ).spouse

これは、ちょっと難しいけれど、
「現在の配偶者を特定する。」メソッドの定義です。
このメソッドの処理は、結婚の経験者を対象とし(self.isMarried = true)、
婚姻歴婚姻関連(self.mariages->select)の内、
まだ離婚していない(m.ended = false)、その相手(.spouse)。


まず、注目して欲しいのは、アロー演算子(->)。
JavaC#みたいな最近の言語では見なくなったものの、
想像通り、関数を呼出す演算子です。
「Personのメンバであるmariages」ってのは、どうやら配列(Collection)らしいです。
そのメソッドには、selectってのがある訳ですね。
それを呼び出す時には、条件をつける事が出来ます。m.ended = falseの部分。
んで、「m」の部分が、イテレータ変数って奴です。パイプ?(|)の左側に定義されてます。
スクリプトスキーな人は、良く知った構文ですね。*5そう、この構文、正にクロージャです。
まぁ、何となく分ってもらえると思って今まで無視してきたドット演算子(.)も
構文全体のアヤシサを更に冗長しています。


更に、設計ドキュメントとしてのアヤシサを爆発させるのは、こんな構文です。

context Person inv:
let income : Integer = self.job.salary->sum() in
if isUnemployed then
  income < 100
else
  income >= 100
endif

ここでは、Personに関する不変条件を定義しています。
所得は、就いている職業の給与総和(self.job.salary->sum())だが、
就職していない(if isUnemployed then)と、100未満である。
就職しているなら(else)、100以上である。*6
と、なります。
ココで言うincomeは、ローカル変数って奴です。
income(d : Date)と紛らわしいのですが、ローカル変数は、
特定のcontext、ここではPersonに関するこの定義内でのみ有効な為、
Personクラスのメンバと定義が重複する事にはなりません。


クラス図上に存在しないメンバ(属性、操作)を定義する事も出来ます。

context Person
def: income : Integer = self.job.salary->sum()
def: nickname : String = ’Little Red Rooster’
def: hasTitle(t : String) : Boolean = self.job->exists(title = t)

ここまで見ればOCLが只の新しいスクリプト言語だって
分って貰えるんじゃないかなぁと思います。
別に、怖くもなんとも無いです。
つまり、OCLの言う所の厳密さってのは、コンパイラが作れる。
って事でしか無いんじゃないかなぁ…と。


ここまでは、実装寄りな内容でしたが、ここからは、設計の話です。

もし普及するとすれば、
OCLを設計技術の一部として使う事に何のメリットがあるのか、ちょっと考えてみます。


設計を行う際に、考慮対象になりうる事象の範囲が、ある程度標準的に明示される*7事により、
曖昧になり易い自然言語による仕様の記述量が全体として減ります。
つまり、「設計漏れ」を、従来よりも高い確度でかつ、
一定の基準で発見する事が出来るようになります。
設計品質をある程度底上げ出来る可能性がある、と言う意味で、
かなりメリットがあるんじゃないかなぁと思います。


但し、標準として定義される以上、OCLの表現能力では表現しきれないドメインも存在する事になります。
又、ツールが複数のモデルに渡って制約を評価し矛盾を発見する事が出来なければ、余り役に立たない可能性もあります。
例えば、クラス図で定義した不変条件が、
状態遷移図を記述する時にも有効にならなければ、
クラス図での不変条件に意味が無くなってしまいます。
又、OCLの言語的な部分に気を取られて、設計では無く実装を行ってしまう可能性もあります。


眠くなったので、まとめると、こんな感じ。

  • PIM→PSMの自動変換が可能になる理由は、OCLが新しいUML用のスクリプト言語だから。
  • OCLは難しいと言う人も居るが、使うと設計し易くなる可能性もある。
  • 便利なツールが無ければ、頑張れない可能性は高い。


ネタに使ったPDF

*1:出来る訳が無い。

*2:ウチの会社は50人未満ですが、何か?

*3:単位は不明。英語のドキュメントなので、ドル?

*4:Javaのpackage宣言をイメージしてもらうのが良いかと。

*5:僕はコードの暗号化を促進するので余り好きではありません。

*6:例によって単位不明。更に、incomeメソッドと定義が矛盾している様な気もするが、気にしない。

*7:contextの選択,inv,pre,post,derive,init等の定義