2009/02/24[モデリング]連載第3回のテーマは「ステートマシン図(I)」です。ステートマシン図を描く際に誤って使われることが多いモデル要素や、{あまり嬉しくない|誤った}ステートマシン図の描き方/使い方などをいくつか紹介していきます。示すような構造を持つ(ひとつの「ヒーター」とひとつの「温度センサー」を部品として持つ)単純な「保温器」を想像してみてください。図1:単純な「保温器」のクラス図この「保温器」は「温度センサー」から取得した「現在温度」が設定された「上限温度」と「下限温度」の範囲に入るように「ヒーター」のON/OFFを制御するものとします。この「保温器」のステートマシン図(状態遷移図)を考えてみましょう。ガード条件の使い方を誤ってしまっている典型的な例です。この状態遷移図に従って実装された「保温器」は、おそらく「現在温度」が「上限温度」を超えても「ヒーター」がOFFにならず、どこまでも加熱し続けてしまいます(場合によってはペットの熱帯魚が全滅してしまったり、火災が発生してしまったりします)。このステートマシン図でまずいのは「加熱中」状態と「非加熱中」状態の間の遷移にかけられているガード条件の部分です。このようなステートマシン図を描く人は『これらのガード条件はいつでも常にチェックされていて、その条件が満たされたら遷移が起こる』という間違った解釈をしてしまっているのでしょう。この例では、トリガー(イベント)の指定がされていない自動遷移(ラムダ遷移)に対してガード条件がかけられている形式になっているので、このステートマシン図で示されている動作を「保温器」インスタンスが生成された時から順に正確に追ってみると以下のようになります。最初に現在の状態が「加熱中」になる。「加熱中」状態のentryアクション「ヒーターON」が実行される。entryアクションの実行が終わったら「非加熱中」状態に自動遷移しようとする。」が一回だけ評価される。この時点ではヒーターがONになったばかりなので、おそらく「現在温度」は「上限温度」を超えていない。ガード条件を満たさないのでこの自動遷移はブロック(阻止)される(現在の状態は「加熱中」のまま)。以後、「電源OFF」イベントが起こらない限り現在の状態は「加熱中」状態のままで、前のステップでブロックされた自動遷移も二度と起こらない(ここにかかっているガード条件の評価も行われない)ので、電源入りっぱなしの「ヒーター」はいつまでも加熱を続ける...。ガード条件というのは「遷移が起ころうとするのを{ブロック|ガード}するために付加する条件」で、そのガード条件がかけられている遷移が起ころうとする時にだけ評価されます。「ガード条件はいつでもチェックされている」と勘違いしないでください。ようにポーリング的な自己遷移を追加するやり方が考えられます。ステートマシン図と同じです。ここで使われている「after(時間)」という記述はUMLで用意されているTimeEventの一種で、この例では現在の状態が「加熱中」状態(または「非加熱中」状態)になった時から1秒後にafterイベントが自動的に発火し、一度現在の状態を抜けてから再度同じ状態に遷移します。現在の状態が「加熱中」(または「非加熱中」)状態にある間、常に1秒間隔で現在の状態に入りなおしているということです。このようにしておくと、1秒間隔で現在の状態が変わる(入りなおされる)ので、その都度自動遷移(ラムダ遷移)を起こそうとしてガード条件が評価されるようになります。ような記述をすることもできそうです。)」という記述に置き換わっています。この記述はUMLで用意されているChangeEventの利用例になっています。whenイベントは「条件式の値がいつでもチェックされていて、この式の値が偽(false)から真(true)に変わった時に発火する」というように解釈されます。このような便利なモデル要素は知らないと損ですよね。ステートマシン図は一見良さそう(正しく動作しそう)に思えるかもしれませんが、実はまだあまいところがあります。whenイベントは「条件式の値が偽(false)から真(true)に変わった時に発火する」ので、既にその条件式を満たしている状況の時にはこのイベントは起こりません。ステートマシン図では「保温器」のインスタンスが生成された時に既に「現在温度」が「上限温度」を超えていた場合、「ヒーター」がONになりっぱなしになって温度がどんどん上がっていってしまいます。ように初期状態からの遷移を条件によって振り分けるようにするなどの対処をしておくと良いでしょう。いう三つの運転モードを持つエアコンを想像してみてください。...というように現在の運転モードが循環的に切り替わるものとします。ようなステートマシン図を描いてしまうことが意外と多いようです。のどの状態が現在の状態になるか決定することができません。ステートマシン図では、「運転中」状態の入れ子の状態遷移に初期状態が明示されています。このステートマシン図では、「停止中」状態から「運転開始」イベントが起こると、現在の状態は「運転中」状態の入れ子の「冷房」状態になることが明示されています。ように入れ子の外部の状態から直接入れ子の内部の状態に遷移線を引いて表現することもできます。State)という便利なモデル要素が用意されています。履歴状態は、ある入れ子の状態遷移から抜けて別の状態に移る際に直前にどの状態にいたか記憶しておき、後でその直前にいた状態に現在の状態を戻してくれる機構を表現しています。このモデル要素を利用すると、いくつかのモードを持つようなシステムの中断(suspend)/再開(resume)処理などをシンプルに描き表したりすることができます。このような便利な履歴状態ですが、なぜか正しく表記されることが少ないようです。例として前出のエアコンの状態遷移を考えてみてください。運転を再開した時に、直前の運転モードで運転再開するようにしたいものとします。良く見られる「間違った」履歴状態の使い方の例です。このステートマシン図中で小さい丸で囲まれた「H」のアイコンが履歴状態です。同じように非決定な状態遷移図になってしまいます)。UMLの仕様書をしっかり読んでみると、「履歴状態に入ってくる遷移線によって状態遷移が起こる場合、履歴状態は現在の状態を記憶されている直前に居た状態にする」という旨の記述が見つかります。履歴状態のアイコンに入っていく遷移線が無いと履歴状態はその機能を発揮しないんですね。履歴状態のアイコンに遷移していく遷移線を明示してみました。いずれか)に現在の状態を遷移してくれます。このステートマシン図は一見良さそう(正しそう)に思えるのですが、まだ甘いところがあります。それは『まだ「運転中」状態に一度も遷移していない時に履歴状態へ遷移が起こったらどうすれば良いのか?』が明示されていないという点です。この「エアコン」のインスタンス生成直後、現在の状態は「停止中」にあります。その状況で「運転開始」イベントが起こると「運転中」状態中の履歴状態に遷移してきますが、この時点ではこの履歴状態は「直前にどの状態に居た」という情報を記憶していません(初めて「運転中」状態に遷移してきたのですから当然ですね...)。いうことで、このステートマシン図はまだ「非決定な(現在の状態が特定できない場合がある)状態遷移図」になっています。このように非決定な状態遷移になってしまうことを避けるために、(初期状態と同じように)最大1本だけ履歴状態から出て行く遷移線を引くことができます(状態遷移が決定的にできる場合はこのような遷移線を引かなくてもOKです)。この遷移線は、履歴状態が直前に居た状態に関する記憶を持たない場合のデフォルトの遷移先として用いられます。(その2)このステートマシン図だと、このエアコンはたとえば以下のような動作をします(適当なシナリオを想定しています)。インスタンス生成直後。現在の状態は「停止中」状態になる。「運転開始」イベントが発生すると、「運転中」状態の中の履歴状態への遷移が起こる。この時点で履歴状態は直前に居た状態に対する記憶を持たないので、現在の状態をデフォルト遷移で指定されている「冷房」にする。「運転切替」イベントが発生し、現在の状態が「暖房」になる。一回「運転切替」イベントが発生し、現在の状態が「除湿」になる。この時点で「運転停止」イベントが発生し、現在の状態は「停止中」に遷移する。ここで、履歴状態は直前に「除湿」状態に居たことを記憶する。イベントが発生すると、「運転中」状態の中の履歴状態への遷移が起こる。この時点で履歴状態は直前にいた状態に対する記憶を持っているので、現在の状態を「除湿」に戻す(デフォルト遷移は無視される)。これで、このエアコンは運転再開する毎にいちいち運転モードを指定しなおす必要がなくなりました。おわりにUMLのステートマシン図は、デビッド・ハレル氏が提唱したハレルチャート(ハレルの状態遷移図)をベースに、さらに様々な仕組みを拡張したとても高機能な状態遷移図になっています。その高機能さ故にすべての機能を使い切るのは難しいですが、「こういう状態変化を表現したいんだけど、うまく描けないなぁ...」などという時にUMLの仕様書を読み直して調べてみると「あまりよく知られていないけれど(実は)とても便利なモデル要素」が見つかったりすることがあります。いう時、「あぁ、多くの人達が長い時間をかけていろいろ試行錯誤してきた結果のノウハウがUML仕様の一部としてまとめられているんだなぁ」ということを実感します。いった「先人達の知恵」は有難く有効活用していきたいものですね。