2009/04/22[モデリング]連載第4回のテーマは「ステートマシン図(II)」です。前回の記事に引き続き、ステートマシン図を描く際に誤って使われることが多いモデル要素や、{あまり嬉しくない|誤った}ステートマシン図の描き方/使い方などをいくつか紹介していきます。ChoiceとJunctionの違いいくつかの遷移をまとめたり、逆にガード条件によって何本かの遷移に振り分けて表記したりできるように、UMLのステートマシン図にはChoice擬似状態とJunction擬似状態という二種類の擬似状態が用意されています。ところが、ChoiceとJunctionの振る舞い/意味付けの違いを明確に意識せずに適当に使ってしまっているステートマシン図を見かけることがあります。あるデバイスの状態遷移を考えてみます。このデバイスでは電源ON時に初期化処理が実行されますが、タイミングによって初期化に失敗することがあるものとします。初期化に失敗した時、最大3回初期化処理の再試行(リトライ)をします(つまり、最初の初期化処理とリトライ3回分で最大4回初期化処理が実行される場合があります)。リトライを3回実行しても初期化に失敗してしまう場合はエラー状態になり、初期化に成功した場合は正常稼動中状態になります。上記のようなデバイスの状態遷移をChoice擬似状態を使った例(図1)とJunction擬似状態を使った例(図2)として、それぞれステートマシン図で描いてみました。どちらかのステートマシン図は上記の仕様と合致しません(間違った動作をします)。(Junction擬似状態)まず、Choice擬似状態(図1)の方の動作の一部を順に追ってみましょう。「初期化失敗」した時の遷移は以下のような手順で実行されます。1」が実行され、現在の状態が一時的にChoice擬似状態になる。評価され、どちらかtrueになった方の遷移で「初期化中」か「エラー」のどちらかの状態が現在の状態となる。同様に、Junction擬似状態(図2)の方の動作の一部を順に追ってみましょう。「初期化失敗」した時の遷移は以下のような手順で実行されます。それぞれ(可能であれば同時に)評価される。上記の評価の結果遷移のパスが確定すると遷移が起こる。以上のように、ChoiceとJunctionではガード条件と遷移アクションの評価/実行順が微妙に入れ替わったりします。この例で言うと、Choice擬似状態の例の方は仕様どおり最大3回のリトライで「エラー」状態に遷移しますが、Junction擬似状態の例の方は最大4回リトライが起こる可能性があります。ステートマシン図とだいたい等価な意味合いになります。(Junctionを使わないで描いた場合)また、分岐先の遷移にかかるガード条件の値域の範囲(ガード条件の組み合わせ方)について、Choice擬似状態とJunction擬似状態では扱い(意味合い)が若干異なってくるので少し注意が必要です。Junction擬似状態から先に分岐するどの遷移も有効にならないため、遷移全体がガードされて(ブロックされて)現在の状態は「状態2」のまま変わりません。これは「正しい(確定的な)」状態遷移です。同じ形の状態遷移をChoice擬似状態を使って描いてみた図です。」の遷移が起こって現在の状態が一時的にChoice状態に移ります。Choice擬似状態から先に分岐するどの遷移も有効にならないため「状態4」「状態5」「状態6」のいずれにも遷移することができなくなってしまいます。このような場合、この状態遷移は「不確定」ということになってしまいます。Choice擬似状態から先に分岐する遷移にかけられるガード条件の組み合わせは、すべての可能性のある値域をカバーするように(必ずどれか1本の遷移だけが有効になるように)設定しなければならないということです。」という特別なガード条件(他のガード条件がすべて成り立たなかった時に真となるガード条件)を設定した例です。あった時でも遷移が有効になるので確定的な状態遷移になります。据え置きイベントある図書館の蔵書のライフサイクルを考えてみます。この図書館では以下のような手順で蔵書を管理しています:新しく入荷した図書はタグなどを付けて装丁が整えられた後、蔵書として記録/管理されるようになる(この時点から蔵書インスタンスのライフサイクルが開始される)。新たに記録/管理された蔵書は貸出可能な状態になる。貸出可能な蔵書は期限付きで図書館利用者に貸し出される(貸出中の状態になる)。貸出中の蔵書を他の図書館利用者に貸し出すことはできない。図書館利用者が貸出中の蔵書を返却することで蔵書は再度貸出可能な状態になる。各々の蔵書は年に一度損耗状況の確認が行われ、読めなくなったもの(ページ抜け、破れ、汚れ等で読めなくなった蔵書)は廃棄される。貸出中に損耗確認期日が到来した蔵書については、返却後ただちに損耗確認が行われる。ようなステートマシン図を描いてみました。(その1)このステートマシン図は特に間違っているところがない正しい図なのですが、「貸出中」状態の入れ子あたりが「ちょっとイヤな感じ」に思えます。このような入れ子状態の形になっているのは『貸出中に損耗確認期日が到来した蔵書については、返却後ただちに損耗確認が行われる』という振る舞いに対応するためなのですが、たとえば「延滞(貸出期限を過ぎても返却されていない)」状態や「督促中(貸出期限を一定期間以上過ぎて督促状が送られている)」状態など振る舞いの拡張を想像すると、「貸出中」の入れ子状態がかなり複雑になってきてしまいそうです。event)」という概念が用意されていて、今回の例のような場合に上手く使用すると不要な状態を増やさずに同等の振る舞いを表現することができます。据え置きイベントを使って描き直したステートマシン図の例です。「貸出中」状態に「defer」という特別なアクションが付けられた内部遷移が記述されています。このようにアクションとして「defer」が付けられたイベント(据え置きイベント)は、他の状態に遷移するまで捨てられずに処理が据え置かれます。例で据え置きイベントが作用するシナリオを順に追ってみると、たとえば以下のような感じになります:現在の状態が「貸出中」になっている。イベントは捨てられずに据え置かれる。「返却」イベントが発生して現在の状態が「貸出中」から「貸出可」に遷移する。イベントが(あたかも「貸出可」状態にいる時に発生したかのように)処理される。現在の状態が「貸出可」から「損耗確認中」に遷移し、損耗確認アクションが実行される。このように、据え置きイベントは「何かの状態にいる時に、その状態では処理されないイベントを一時的に据え置いて取っておく(記憶しておく)」ような動作を簡潔に表現することができます。据え置きイベントは知っていると意外と便利な表記のひとつなのですが、残念なことに現状ではあまり広く知られている表記とは言えません(ほとんど知られていません)。そのため、据え置きイベントを使用するステートマシン図を描く時は、それを読む人のために簡単な補足説明を付けておくなどすると良いでしょう。おわりに私はいつも(UMLで規定されている各種の図のうち)ステートマシン図がもっともプログラミング時の視点に近い図だと感じています(もちろん構造的な側面はクラス図などと組み合わせて補完が必要ですが...)。オブジェクト指向システムを構成するインスタンス群は、それぞれが独立した状態遷移マシンとみなすこともできるので、個々のインスタンスのライフ・サイクルをしっかり理解しておくことはとても重要です。的な開発環境を利用する場合、状態モデルの重要度はさらに増してきます。ところが、上手に整理された「綺麗な」ステートマシン図を描くためのセオリー的な方法はあまり確立していないようで、今のところこの辺りのモデリング・スキルは経験とセンスによるところが大きいと言わざるを得ない感があります。経験を深めセンスを磨くために、「正確」で「綺麗」な良いモデルをできるだけたくさん見ることが必要だと思います。