状態遷移表の代用 - Display JSON Data on State diagram

# はじめに

PlantUML では、ステート図を描くことができますが、状態遷移表を描くことができません。
その代用として、JSONデータの表示機能を使い、トリガーの一覧表を描くようにしました。

その一覧表は、Microsoft MakeCode for micro:bit 向けの[pxt-mstate拡張機能](https://jp-rad.github.io/pxt-mstate/)で出力することができます。

**出力例**
```plantuml
@startuml
'
' PlantUML Web server:
' https://www.plantuml.com/plantuml/
'
' State Diagram - https://plantuml.com/state-diagram
state __M0__ {
[*] -> 容量水量 : <<start>>
state アイドル
アイドル --> 相手待ち : [ 動いた ]
state 傾き待ち
傾き待ち --> 分配候補 : tilt
傾き待ち --> 受取待ち : sender
state 傾き待ち : --> 置き待ち : [ after 5000ms ]
state 分配候補 : sender送信
分配候補 --> 置き待ち : free [ 相手 ] / share送信と減算
state 分配候補 : --> 置き待ち : [ after 1000ms ]
state 受取待ち : free送信
受取待ち --> 置き待ち : share [ 相手 ] / 加算
state 受取待ち : --> 置き待ち : [ after 1000ms ]
state 容量水量
容量水量 --> 置き待ち
state 相手待ち : moved送信\nmoved受信（moved再送とpair送信）
相手待ち --> 傾き待ち : pair [ 相手 ]
state 相手待ち : --> 置き待ち : [ after 2000ms ]
state 置き待ち
置き待ち --> アイドル : [ 静止1s ]
}
'
' generator: https://github.com/jp-rad/pxt-mstate
'
' PlantUML Web server:
' https://www.plantuml.com/plantuml/
'
json M0 {
"**trigger**" : ["**transitions**"] ,
"(__completion__)" : [{"**source**":[{"":["**[guard] / effect**","**target**"]}],"アイドル":[{"":["[ 動いた ]","相手待ち"]}],"傾き待ち":[{"":["[ after 5000ms ]","置き待ち"]}],"分配候補":[{"":["[ after 1000ms ]","置き待ち"]}],"受取待ち":[{"":["[ after 1000ms ]","置き待ち"]}],"容量水量":[{"":[""
,"置き待ち"]}],"相手待ち":[{"":["[ after 2000ms ]","置き待ち"]}],"置き待ち":[{"":["[ 静止1s ]","アイドル"]}]}] ,
"free" : [{"**source**":[{"":["**[guard] / effect**","**target**"]}],"アイドル":[],"傾き待ち":[],"分配候補":[{"":["[ 相手 ] / share送信と減算","置き待ち"]}],"受取待ち":[],"容量水量":[],"相手待ち":[],"置き待ち":[]}] ,
"moved" : [{"**source**":[{"":["**[guard] / effect**","**target**"]}],"アイドル":[],"傾き待ち":[],"分配候補":[],"受取待ち":[],"容量水量":[],"相手待ち":[],"置き待ち":[]}] ,
"pair" : [{"**source**":[{"":["**[guard] / effect**","**target**"]}],"アイドル":[],"傾き待ち":[],"分配候補":[],"受取待ち":[],"容量水量":[],"相手待ち":[{"":["[ 相手 ]","傾き待ち"]}],"置き待ち":[]}] ,
"sender" : [{"**source**":[{"":["**[guard] / effect**","**target**"]}],"アイドル":[],"傾き待ち":[{"":["","受取待ち"]}],"分配候補":[],"受取待ち":[],"容量水量":[],"相手待ち":[],"置き待ち":[]}] ,
"share" : [{"**source**":[{"":["**[guard] / effect**","**target**"]}],"アイドル":[],"傾き待ち":[],"分配候補":[],"受取待ち":[{"":["[ 相手 ] / 加算","置き待ち"]}],"容量水量":[],"相手待ち":[],"置き待ち":[]}] ,
"tilt" : [{"**source**":[{"":["**[guard] / effect**","**target**"]}],"アイドル":[],"傾き待ち":[{"":["","分配候補"]}],"分配候補":[],"受取待ち":[],"容量水量":[],"相手待ち":[],"置き待ち":[]}] ,
"**__default__**" : ["容量水量"]
}
'
' generator: https://github.com/jp-rad/pxt-mstate
'
' PlantUML Web server:
' https://www.plantuml.com/plantuml/
'
@enduml

```

# Display JSON Data on State diagram

PlantUMLには、JSONデータを表形式で表示する機能があります。

**描画例**

```ts
@startuml

json TableName {
  "key1": "value1",
  "key1'": "**value1**\\n__value2__(\\\\n)",
  "**key1''**": ["element1", "element2"],
  "Key2": { "SubKey1": "line1\\nline2"
          , "SubKey2": ["row1", "row2"]
          , "SubKey3\\n(\\\\n)": ["row1\\nrow1-line2", "row2\\nrow2-line2"]
          },
  "Key3 stringlength": {"Sub1": "string length", "Sub2": "value"}
}

@enduml
```

```plantuml
@startuml

json TableName {
  "key1": "value1",
  "key1'": "**value1**\\n__value2__(\\\\n)",
  "**key1''**": ["element1", "element2"],
  "Key2": {"SubKey1": "line1\\nline2", "SubKey2": ["row1", "row2"], "SubKey3\\n(\\\\n)": ["row1\\nrow1-line2", "row2\\nrow2-line2"]},
  "Key3 stringlength": {"Sub1": "string length", "Sub2": "value"}
}

@enduml
```

# 最初の検討

まず、最初に状態の一覧表を検討しました。

## パターンS1

状態、説明（description）、トリガー（trigger）と順に表示するようにしました。
トリガーは複数ありますので、`<<CASEn>>`と表示して区別できるようにしています。

```plantuml
@startuml
json パターンS1 {
  "**状態A**": [
     { "description": [ "(説明a1)\\n(説明a2)" ]
     , "trigger": { "(completate)": ["[ガードa1] / エフェクトa1","状態X"]
                  , "スタートボタン\\n**<<CASE1>>**": ["[ガードa2] / エフェクトa2","状態B"]
                  }
     }
  ]
  ,
  "**状態B**": [
     { "description": [ "(説明b1)\\n(説明b2)" ]
     , "trigger": { "ストップボタン\\n**<<CASE1>>**": ["[ガードb1] / エフェクトb1","状態A"]
                  , "ストップボタン\\n**<<CASE2>>**": ["[ガードb2] / エフェクトb2","状態Y"]
                  }
     }
  ]
}

@enduml
```

## パターンS2

同一トリガーを1行にグループ化しました。

```plantuml
@startuml
json パターンS2 {
  "**状態A**": [
     { "description": [ "(説明a1)\\n(説明a2)" ]
     , "trigger": { "(completate)": ["[ガードa1] / エフェクトa1","状態X"]
                   , "スタートボタン": ["**<<CASE1>>**", "[ガードa2] / エフェクトa2","状態B"]
                   }
     }
  ]
  ,
  "**状態B**": [
     { "description": [ "(説明b1)\\n(説明b2)" ]
     , "trigger": { "ストップボタン": ["**<<CASE1>>**", "[ガードb1] / エフェクトb1","状態A"
                                     ,"**<<CASE2>>**", "[ガードb2] / エフェクトb2","状態Y"]
                  }
     }
  ]
  }
@enduml
```

## パターンS3

`description`や`trigger`を見出しにしました。

```plantuml
@startuml
json パターンS3 {
  "**状態A**": [ "**description**"
     , "(説明a1)\\n(説明a2)"
     , { "**trigger**":   ["**[guard] / effect**","**state to**"]
       , "(completate)":  ["[ガードa1] / エフェクトa1","状態X"]
       , "スタートボタン": ["**<<CASE1>>**", "[ガードa2] / エフェクトa2","状態B"]
       }
  ]
  ,
  "**状態B**": [ "**description**"
     , "(説明b1)\\n(説明b2)"
     , { "**trigger**":   ["**[guard] / effect**","**state to**"]
       , "ストップボタン": ["**<<CASE1>>**", "[ガードb1] / エフェクトb1","状態A"
                          ,"**<<CASE2>>**", "[ガードb2] / エフェクトb2","状態Y"]
       }
  ]
}

@enduml
```

## パターンS4

トリガー内の状態遷移をグループ化し、`<<CASEn>>`表記を廃止しました。

```plantuml
@startuml
json パターンS4 {
  "**状態A**": [ "**description**"
     , "(説明a1)\\n(説明a2)"
     , { "**trigger**":   [ {"": ["**[guard] / effect**", "**state to**"]} ]
       , "(completate)":  [ {"":["[ガードa1] / エフェクトa1","状態X"]} ]
       , "スタートボタン": [ { "": ["[ガードa2] / エフェクトa2","状態B"]} ]
       }
  ]
  ,
  "**状態B**": [ "**description**"
     , "(説明b1)\\n(説明b2)"
     , { "**trigger**":   [ {"": ["**[guard] / effect**", "**state to**"]} ]
       , "ストップボタン": [ { "": ["[ガードb1] / エフェクトb1","状態A"]}
                          , { "": ["[ガードb2] / エフェクトb2","状態Y"]} ]
       }
  ]
}
@enduml
```

## JSON構造 - パターンS4

```plantuml
@startjson
{
  "**状態A**": [ "**description**"
     , "(説明a1)\\n(説明a2)"
     , { "**trigger**":   [ {"": ["**[guard] / effect**", "**state to**"]} ]
       , "(completate)":  [ {"":["[ガードa1] / エフェクトa1","状態X"]} ]
       , "スタートボタン": [ { "": ["[ガードa2] / エフェクトa2","状態B"]} ]
       }
  ]
  ,
  "**状態B**": [ "**description**"
     , "(説明b1)\\n(説明b2)"
     , { "**trigger**":   [ {"": ["**[guard] / effect**", "**state to**"]} ]
       , "ストップボタン": [ { "": ["[ガードb1] / エフェクトb1","状態A"]}
                          , { "": ["[ガードb2] / エフェクトb2","状態Y"]} ]
       }
  ]
}
@endjson
```

# どのような表が必要か

ここまで、状態を基準（ルート）に、その一覧と説明、トリガーを表にしましたが、状態の一覧は、ブロックのソースコードやUMLのステート図でも容易に確認できます。また、説明の一覧についてもUMLのステート図の方が確認しやすく感じます。

そこで、トリガーを基準（ルート）とした一覧表を検討します。

## パターンT1

トリガー、状態の順に表示しました。

```plantuml
@startuml
json パターンT1 {
  "**__start command__**": ["**default state**","状態S"],
  "**(__completion__)**": [
       { "**state**": {"": ["**[guard] / effect**", "**state to**"]}
       , "状態1": {"":["[ガード1] / エフェクト1","状態X"]}
       , "状態2": { "": ["[ガード2] / エフェクト2","**[__X__]**"]
                          , "": ["[ガード3] / エフェクト3","状態Z"]}
       }
  ],
  "**トリガー1**": [
       { "**state**": {"": ["**[guard] / effect**", "**state to**"]}
       , "状態1": {"":[" / エフェクト1","状態X"]}
       , "状態99999": { "": ["[ガード222222222222222222222]","状態Y"]
                          , "": ["[ガード3]","状態Z"]}
       }
  ],
  "**トリガー999999999**": [
       { "**state**": {"": ["**[guard] / effect**", "**state to**"]}
       , "状態99999": { "": ["","**[__X__]**"]}
       }
  ]
}
@enduml
```

## パターンT2

トリガー内にすべての状態を列挙しました。
これにより、トリガーの影響を受けない状態が明確になりました。また、状態列の表示幅が揃い、見た目も良くなったと思います。

状態遷移表の代用として、この形式の表を採用しました。

```plantuml
@startuml
json パターンT2 {
  "**__start command__**": ["**default state**","状態S"],
  "**(__completion__)**":
       { "**state**": {"": ["**[guard] / effect**", "**state to**"]}
       , "状態1": {"":["[ガード1] / エフェクト1","状態X"]}
       , "状態2": { "": ["[ガード2] / エフェクト2","**[__X__]**"]
                          , "": ["[ガード3] / エフェクト3","状態Z"]}
       , "状態99999": {"":["",""]}
       }
  ,
  "**トリガー1**": 
       { "**state**": {"": ["**[guard] / effect**", "**state to**"]}
       , "状態1": {"":[" / エフェクト1","状態X"]}
       , "状態2": {"":["",""]}
       , "状態99999": { "": ["[ガード222222222222222222222]","状態Y"]
                          , "": ["[ガード3]","状態Z"]}
       }
  ,
  "**トリガー999999999**": 
       { "**state**": {"": ["**[guard] / effect**", "**state to**"]}
       , "状態1": {"":["",""]}
       , "状態2": {"":["",""]}
       , "状態99999": { "": ["","**[__X__]**"]}
       }
  
}
@enduml
```

## JSON構造 - パターンT2

```json
{
  "**__start command__**": ["**default state**","状態S"],
  "**(__completion__)**": [
       { "**state**": {"": ["**[guard] / effect**", "**state to**"]}
       , "状態1": {"":["[ガード1] / エフェクト1","状態X"]}
       , "状態2": { "": ["[ガード2] / エフェクト2","**[__X__]**"]
                          , "": ["[ガード3] / エフェクト3","状態Z"]}
       , "状態99999": {"":["",""]}
       }
  ],
  "**トリガー1**": [
       { "**state**": {"": ["**[guard] / effect**", "**state to**"]}
       , "状態1": {"":[" / エフェクト1","状態X"]}
       , "状態2": {"":["",""]}
       , "状態99999": { "": ["[ガード222222222222222222222]","状態Y"]
                          , "": ["[ガード3]","状態Z"]}
       }
  ],
  "**トリガー999999999**": [
       { "**state**": {"": ["**[guard] / effect**", "**state to**"]}
       , "状態1": {"":["",""]}
       , "状態2": {"":["",""]}
       , "状態99999": { "": ["","**[__X__]**"]}
       }
  ]
}
```

```plantuml
@startjson
{
  "**__start command__**": ["**default state**","状態S"],
  "**(__completion__)**": [
       { "**state**": {"": ["**[guard] / effect**", "**state to**"]}
       , "状態1": {"":["[ガード1] / エフェクト1","状態X"]}
       , "状態2": { "": ["[ガード2] / エフェクト2","**[__X__]**"]
                          , "": ["[ガード3] / エフェクト3","状態Z"]}
       , "状態99999": {"":["",""]}
       }
  ],
  "**トリガー1**": [
       { "**state**": {"": ["**[guard] / effect**", "**state to**"]}
       , "状態1": {"":[" / エフェクト1","状態X"]}
       , "状態2": {"":["",""]}
       , "状態99999": { "": ["[ガード222222222222222222222]","状態Y"]
                          , "": ["[ガード3]","状態Z"]}
       }
  ],
  "**トリガー999999999**": [
       { "**state**": {"": ["**[guard] / effect**", "**state to**"]}
       , "状態1": {"":["",""]}
       , "状態2": {"":["",""]}
       , "状態99999": { "": ["","**[__X__]**"]}
       }
  ]
}
@endjson
```

# まとめ

状態遷移表の代用として、PlantUMLのJSONデータ表示機能を使い、トリガーの一覧表を検討しました。
検討の結果、[パターンT2](#パターンt2)の形式を採用し、[pxt-mstate拡張機能](https://jp-rad.github.io/pxt-mstate/)で、出力できるようにしました。

ステートマシンを設計する際、ステート図とトリガーの一覧表とを使って、状態とトリガーの双方の視点から、状態遷移に抜け漏れがないことを確認することができます。