状態遷移図と状態遷移表

 ごく簡単なプログラムを書く場合を除いて、プログラムのコーディング前に、十分な設計を行っておく必要がある。状態遷移図と状態遷移表は、プログラムの状態変化を視覚的に記述することで、設計を簡単化したりバグの発生を抑えることに非常に効果的。


                         [図  状態遷移図の構成要素]

状態を定義するルールは、

待ってる「イベント」が異なる場合(=役割が異なる処理)、状態を分ける。

となる。

 以下に、「ストップウオッチ」を例に、状態遷移図を説明する。ストップウオッチの構成として、以下の仕様を考える。

 ① ストップウォッチには「スタート」「ストップ」「リセット」の3つのボタンがある
 ② 「スタート」ボタンを押下すると計測を開始する
 ③ 「ストップ」ボタンを押下すると計測を一時停止し,計測結果時間を表示する
 ④ 結果表示中に「スタート」ボタンを押下すると計測を再開する
 ⑤ 結果表示中に「リセット」ボタンを押下すると計測結果時間をリセットする

 ※状態をステートと呼ぶこともある。

 これを、状態遷移図に置き換えると、以下のようになる。

   
              [図 ストップウオッチを例にした状態遷移図]

 

   スタートボタンを押した  ストップボタンを押した  リセットボタンを押した
     待機中      計測中        ―        ―
  計測中       ―      一時停止        ―
 一時停止      計測中        ―       待機中

             [図 ストップウオッチを例にした状態遷移表]

※ "―"は、状態遷移図では表現できていない遷移

 状態遷移表は、上段にイベント、左の欄外に現在の状態、表中には、イベントが発生した時の遷移先を記入する。


 仕様書には、遷移する状態しか書かれていない(遷移しない状態は省略されている)ので、状態遷移表を使って遷移しない状態を明らかにすることで、バグの発生を防ぐ。
 この様に「状態遷移図」と「状態遷移表」は守備範囲が異なる。それぞれの特性を良く理解して、設計に活用する事。

  • 状態遷移図は、プログラムの動き(動いてほしい事)を明らかにする
  • 状態遷移表は、プログラムが動いてほしくない事を明らかにする
     

 上記の状態遷移図で表現されるように、一定の条件で状態を変化させながら動くシステム(機械)を状態遷移機械、またはステートマシンと呼ぶ。
 ※ ここで取り上げたのは、状態の変化が、現状態と入力に依存するミーリ型ステートマシン。このほかにも、状態の変化が現状態のみに依存する、ムーア型ステートマシンがある。



状態遷移図とコーディング

以下に、ストップウオッチの状態遷移図をベースにプログラムを作成する手順を示す。

(1) 状態をdefine文で、文字列に割り当てる。(プログラムを見易くするため)
  状態遷移図で表現される状態は3つあるので、

  • 待機中  ⇒    S_WAIT
  • 計測中  ⇒    S_MASURE
  • 一時停止 ⇒    S_PAUSE

 として、それぞれdefine文で定義する。(別にdefineでなくてもenumでも良いが)

(2) 状態変数を宣言する。型は何でもよいが、int型にするのが適当と思われる。ここでは、

int     state;

として、宣言。

(3) 状態変数の初期値を設定。

(4) while文で無限ループ。(別にforでも構わない。お好みで)

(5) switch ~ case文を使って、状態を列挙する。ここで、default以下の文は、必ずしも必要ない。気休めの様なもの。
 (ハードウエア設計の場合は、こういう記述があった方が良い。システムに高い信頼性を求める場合に必要となる)

(1)~(5)をCで記述すると、以下のようになる。

/* 状態を定義 */
#define S_WAIT        0  // 待機中    
#define S_MASURE  1  // 計測中
#define S_PAUSE     2  // 一時停止

void start()
{
    unsigned int state;    // 状態変数を宣言

    state = S_WAIT;       // 初期状態を定義

    while (1)    // 状態を無限に回るので。
  {
        switch (state)  // switch文により、全ての状態を列記する。
    {
            case S_WAIT :
               // ここに処理を記述
               break;
   

            case S_MASURE :
          // ここに処理を記述
                break;


            case S_PAUSE :
          // ここに処理を記述
                break;


            default :         // 必ずしも必要ない。ただ、万が
                status = S_WAIT; //一の場合のシステムの信頼性が増す。
                break;
        }
    }
}

 あとは、case文で示された状態が、入力条件によって、どの条件に変化するかを記述する。
また、その時の出力も同時に記述する。
 例えば、待機中(S_WAIT)は、スタートボタンが押されることで、計測中(S_MASURE)に遷移するので、

        case S_WAIT :
                if (スタートボタンが押された)
                {
                    state = S_MASURE;
                    init_time();   // タイマを0に初期化
                }
                break;

 のように記述する。

 ここで、各状態でのアクション(出力)を以下の様に定義する。

  init_time()      ストップウオッチの初期化 (時刻を0にする)
  disp_time()    計測した時刻を表示
  start_time()    計測を開始
  stop_time()    計測を一時停止

以上を踏まえたうえで、他の状態も埋めてみる。

int main()
{
    unsigned int state; // 状態変数を宣言
    state = S_WAIT;     // 初期状態を定義
 
    while (1) // 状態を無限に回るので。
    {
        switch (state)   // switch文により、全ての状態を列記する。
        {
             case S_WAIT :
                 if (スタートボタンが押された)
                 {
                     state = S_MASURE;
                     init_time();
                 }
                 break;

             case S_MASURE :
                 if (ストップボタンが押された)
                 {
                     state = S_PAUSE;
                     stop_time();  // 計測を一時停止
                     disp_time();
                 }
                 else
                 {
                    start_time(); // 時間を計測
                 }
                 break;

             case S_PAUSE :
                 if (スタートボタンが押された)
                 {
                     state = S_MASURE;
                 }
                 else
                 {
                     if (リセットボタンが押された)
                     {
                         state = S_WAIT;
                         stop_time();
                         init_time();
                     }
                 }
                break;

                default :         // 必ずしも必要ない。ただ、万が一、誤動作
                     state = S_WAIT; // が起こった場合にシステムの信頼性が増す。
                     break;
        }
        measure_time();    // start_time()およびstop_time()で動作を決定
    }

    return 0;
}

 


状態遷移の考え方を使うと、複雑な順番に従ったシステムを容易に表現できる。例えば、

条件1が成立する場合:
  処理Bは、処理Aと処理Cを順に実行した後に、実行する。
条件1が不成立の場合:
  処理Bは、処理Cと処理Aを順に実行した後に、実行する。

とか。

最終更新:2016年01月14日 10:19