このページの内容は最新ではありません。最新版の英語を参照するには、ここをクリックします。
プログラムによるチャートのリファクタリング
この例では、Stateflow® API を使用して Stateflow チャートを読みやすくする方法を説明します。一貫性がないステートの名前、深い入れ子にされたステート、不要なジャンクションなどの一般的な様式のパターンをプログラムで検出して修正できます。Stateflow API の使用の詳細については、Stateflow API の概要を参照してください。
モデルを開く
このモデルには、昆虫の動作をエミュレートする Stateflow チャートが含まれています。このモデルの内容は次のとおりです。
昆虫は箱の中に入れられています。
昆虫はその目の前にある物体のみを検知できます。
昆虫は、壁にぶつかるか捕食者または被食者を検知するまで直線上を動きます。
昆虫は壁にぶつかると跳ね返り、反対方向に動き続けます。
昆虫は捕食者を認識すると、速度を上げて捕食者から遠ざかります。
昆虫は被食者を認識すると、速度を上げて被食者の方に向かいます。
昆虫は 12 時間ごとに止まって休憩します。
このチャートでは、ステートの命名規則に一貫性がなく、大文字で始まるステート名とそうでないステート名が混在しています。さらに、チャートの階層構造は深く、入れ子にされたステートのレベルは 3 を超えています。最後に、チャートには、入力遷移と出力遷移がそれぞれ 1 つのみである余分なジャンクションが含まれています。
model = "sfInsectExample";
open_system(model)
チャートのStateflow.Chart
オブジェクトにアクセスするには、関数find
を呼び出します。
ch = find(sfroot,"-isa","Stateflow.Chart",Name="BehavioralLogic");
一貫性がないステートの名前の修正
チャートを読みやすくするには、チャート内のすべてのステートで同じ命名規則を使用します。たとえば、すべてのステート名が大文字で始まる命名規則を使用できます。この命名規則に従っていない名前をもつステートを検索するには、オプションの引数 -regexp
を指定して関数find
を呼び出し、正規表現を指定します。詳細については、正規表現を参照してください。
misnamedStates = find(ch,"-isa","Stateflow.State", ... "-regexp","Name","^[a-z]\w*");
検索結果を検査するには、各Stateflow.State
オブジェクトの Path
プロパティと Name
プロパティにアクセスして、モデルから各ステートへのパスを表示します。
numMisnamedStates = numel(misnamedStates); misnamedStateNames = ""; for i = 1:numMisnamedStates state = misnamedStates(i); misnamedStateNames = misnamedStateNames + ... newline + " * " + state.Path + "/" + state.Name; end disp("The names of these states do not start with a capital letter:" + ... newline + misnamedStateNames)
The names of these states do not start with a capital letter: * sfInsectExample/BehavioralLogic/Awake/Safe/searching * sfInsectExample/BehavioralLogic/Awake/Danger/xBump * sfInsectExample/BehavioralLogic/Awake/Danger/yBump * sfInsectExample/BehavioralLogic/Awake/Safe/searching/normal * sfInsectExample/BehavioralLogic/Awake/Safe/searching/xBump * sfInsectExample/BehavioralLogic/Awake/Safe/searching/yBump
特定された各ステート名の先頭の文字を対応する大文字に置き換えるには、関数renameReferences
を呼び出して各Stateflow.State
オブジェクトの Name
プロパティを変更します。
for i = 1:numMisnamedStates state = misnamedStates(i); newName = [upper(state.Name(1)) state.Name(2:end)]; renameReferences(state,newName) end
ステートの深い階層構造の簡略化
入れ子にされたステートのレベルが多すぎるチャートは、わかりにくくなる可能性があります。レベルが 3 を超えるステート階層がチャートに必要な場合は、サブチャートを作成してチャートの複雑度を低減できます。詳細については、サブチャートを使用したモーダル ロジックのカプセル化を参照してください。
チャートで深い入れ子にされたステートを検索するには、オプションの引数 -function
を指定してfind
を呼び出し、補助関数 getDepth
を評価する関数ハンドルを指定します。この関数は、特定のステートと最も近い先祖チャートまたはサブチャートの間にあるレベルの数を計算します。この関数のコードを見るには、ステートの深さの取得を参照してください。
maxDepth = 3; deepStates = find(ch,"-isa","Stateflow.State", ... "-function", @(s)(getDepth(s) > maxDepth)); numDeepStates = numel(deepStates); deepStateNames = ""; for i = 1:numDeepStates state = deepStates(i); deepStateNames = deepStateNames + ... newline + " * " + state.Path + "/" + state.Name; end disp("These states occur at a depth greater than " + maxDepth + ":" + ... newline + deepStateNames)
These states occur at a depth greater than 3: * sfInsectExample/BehavioralLogic/Awake/Safe/Searching/Normal * sfInsectExample/BehavioralLogic/Awake/Safe/Searching/XBump * sfInsectExample/BehavioralLogic/Awake/Safe/Searching/YBump
ステートの階層構造が深いチャートを簡略化するには、Stateflow.State
オブジェクトの IsSubchart
プロパティを true
に設定してステートをサブチャートに変換します。このプロセスを大規模なチャート用に自動化するには、補助関数 convertStatesToSubcharts
を使用して、階層の特定の深さにある非リーフ ステートを再帰的に検索し、それらのステートをサブチャートに変換します。また、この関数はコンテンツ プレビューを無効にし、新しいサブチャートのサイズを変更します。この関数のコードを見るには、ステートからサブチャートへの変換を参照してください。
convertStatesToSubcharts(ch,maxDepth)
余分なジャンクションの削除
遷移パスが長いと、チャートの複雑度が増すことがあります。たとえば、遷移元を遷移先に分岐なしで接続する場合は、複数の余分なジャンクションがある一連の遷移を使用するよりも、単一の遷移を使用した方が簡単です。
余分なジャンクションを特定するには、オプションの引数 -function
を指定してfind
を呼び出し、補助関数 isSuperfluous
へのハンドルを指定します。この関数は、特定のジャンクションが次の条件を満たすかどうかをチェックします。
ジャンクションの入力遷移と出力遷移がそれぞれ 1 つのみである。
出力遷移が入力遷移と同じ方向に続いている。
入力遷移のアクションが出力遷移のトリガーまたは条件より前にない。
両方の遷移がトリガーでガードされていない。
この関数のコードを見るには、余分なジャンクションの特定を参照してください。
superfluousJunctions = find(ch,"-isa","Stateflow.Junction", ... "-function", @(j)(isSuperfluous(j))); numSuperfluousJunctions = numel(superfluousJunctions); superfluousJunctionIDs = ""; for i = 1:numSuperfluousJunctions junction = superfluousJunctions(i); superfluousJunctionIDs = superfluousJunctionIDs + newline + ... " * Junction " + junction.SSIdNumber + " in " + junction.Path; end disp("These junctions are part of a transition path " + ... "that you can replace with a single transition:" + ... newline + superfluousJunctionIDs)
These junctions are part of a transition path that you can replace with a single transition: * Junction 161 in sfInsectExample/BehavioralLogic/Awake/Danger * Junction 163 in sfInsectExample/BehavioralLogic/Awake/Danger * Junction 153 in sfInsectExample/BehavioralLogic/Awake/Danger * Junction 155 in sfInsectExample/BehavioralLogic/Awake/Danger
補助関数 removeJunctions
は、余分な各ジャンクション前後の 2 つの遷移を単一の遷移に置き換えます。チャートの配置を維持するために、新しい遷移は元の入力遷移と同じ時点から開始され、元の出力遷移と同じ時点で終了します。この関数のコードを見るには、ジャンクションの削除を参照してください。
removeJunctions(superfluousJunctions)
モデルを閉じる
新しいモデルに変更を保存し、モデルを閉じます。
newModel = model+"Refactored";
sfsave(model,newModel)
close_system(newModel)
補助関数
ステートの深さの取得
この関数は、特定のステートと最も近い先祖チャートまたはサブチャートの間にあるレベルの数を返します。
function depth = getDepth(state) parent = getParent(state); if isa(parent,"Stateflow.Chart") depth = 1; elseif parent.isSubchart depth = 1; else depth = getDepth(parent)+1; end end
ステートからサブチャートへの変換
この関数は、親チャートまたはサブチャートからの特定の深さにある非リーフ ステートを識別します。この関数は、各Stateflow.State
オブジェクトの IsSubchart
プロパティと ContentPreviewEnabled
プロパティを変更して、それらのステートをサブチャートに変換し、新しいサブチャートのコンテンツ プレビューを無効にします。可能であれば、この関数はサブチャートの中心を固定したまま、サブチャートのサイズを既定の 90 行 60 列に縮小します。このプロセスは、新しいサブチャートごとに再帰的に繰り返されます。
function convertStatesToSubcharts(parent,maxDepth) statesToConvert = find(parent,"-isa","Stateflow.State", ... "-function", @(s) (getDepth(s) == maxDepth), ... "-function", @(s) (~isempty(getChildren(s))), ... IsSubchart=false); for i = 1:numel(statesToConvert) state = statesToConvert(i); state.IsSubchart = true; state.ContentPreviewEnabled = false; reduceWidth(state,90) reduceHeight(state,60) convertStatesToSubcharts(state,maxDepth); end end function reduceWidth(state,newWidth) pos = state.Position; if pos(3) > newWidth state.Position = [pos(1)+pos(3)/2-newWidth/2 pos(2) newWidth pos(4)]; end end function reduceHeight(state,newHeight) pos = state.Position; if pos(4) > 60 state.Position = [pos(1) pos(2)+pos(4)/2-newHeight/2 pos(3) newHeight]; end end
余分なジャンクションの特定
この関数は、特定のジャンクションが次の条件を満たすかどうかをチェックします。
ジャンクションの入力遷移と出力遷移がそれぞれ 1 つのみである。
出力遷移が入力遷移と同じ方向に続いている。
入力遷移のアクションが出力遷移のトリガーまたは条件より前にない。
両方の遷移がトリガーでガードされていない。
function tf = isSuperfluous(junction) transitionIn = sinkedTransitions(junction); transitionOut = sourcedTransitions(junction); tf = oneIncomingTransition && oneOutgoingTransition && ... transitionsContinueInSameDirection && ... noActionBeforeTriggerOrCondition && ... noDoubleTriggers; function tf = oneIncomingTransition tf = (numel(transitionIn)==1); end function tf = oneOutgoingTransition tf = (numel(transitionOut)==1); end function tf = transitionsContinueInSameDirection tolerance = 1.5; theta = abs(transitionIn.DestinationOClock-transitionOut.SourceOClock); tf = (abs(theta-6) < tolerance); end function tf = noActionBeforeTriggerOrCondition tf = isempty(transitionIn.ConditionAction) || ... (isempty(transitionOut.Trigger) && isempty(transitionOut.Condition)); end function tf = noDoubleTriggers tf = isempty(transitionIn.Trigger) || isempty(transitionOut.Trigger); end end
ジャンクションの削除
この関数は、余分な各ジャンクション前後の遷移を単一の遷移に置き換えます。チャートの配置を維持するために、新しい遷移はジャンクションに入る元の遷移と同じ時点から開始され、ジャンクションから出る元の遷移と同じ時点で終了します。この関数は、次の規則に従って元の遷移のラベル文字列をマージします。
1 つの遷移のみが空でないトリガーをもつことができる。
空でない条件は AND 演算子
&&
を使用して結合する。空でない条件アクションと遷移アクションは横に並べて結合する。最初のアクションの末尾がコンマまたはセミコロンでない場合は、アクション間にコンマを追加する。
詳細については、遷移のアクションの定義を参照してください。
function removeJunctions(junctionsToRemove) for i = 1:numel(junctionsToRemove) junction = junctionsToRemove(i); transitionIn = sinkedTransitions(junction); transitionOut = sourcedTransitions(junction); newSourceEndpoint = transitionIn.SourceEndpoint; newSourceOClock = transitionIn.SourceOClock; newDestinationEndpoint = transitionOut.DestinationEndpoint; newMidPoint = (newSourceEndpoint+newDestinationEndpoint)/2; newLabelString = mergeLabelStrings; transitionIn.Destination = transitionOut.Destination; transitionIn.SourceOClock = newSourceOClock; transitionIn.DestinationEndpoint = newDestinationEndpoint; transitionIn.MidPoint = newMidPoint; transitionIn.LabelString = newLabelString; transitionIn.LabelPosition = [newMidPoint 0 0]; transitionIn.LabelPosition(1) = transitionIn.LabelPosition(1)-transitionIn.LabelPosition(3)/2; transitionIn.LabelPosition(2) = transitionIn.LabelPosition(2)-transitionIn.LabelPosition(4)/2; delete(junction) delete(transitionOut) end function label = mergeLabelStrings trigger1 = transitionIn.Trigger; trigger2 = transitionOut.Trigger; if isempty(trigger1) label = trigger2; elseif isempty(trigger2) label = trigger1; else error("Unable to merge transitions with multiple triggers " + ... trigger1 + " and " + trigger2 + ".") end condition1 = transitionIn.Condition; condition2 = transitionOut.Condition; if ~isempty(condition1) && ~isempty(condition2) label = label+"["+condition1+" && "+condition2+"]"; elseif ~isempty(condition1) label = label+"["+condition1+"]"; elseif ~isempty(condition2) label = label+"["+condition2+"]"; end action1 = transitionIn.ConditionAction; action2 = transitionOut.ConditionAction; if ~isempty(action1) && ~isempty(action2) if endsWith(action1,";") || endsWith(action1,",") label = label+"{"+action1+action2+"}"; else label = label+"{"+action1+","+action2+"}"; end elseif ~isempty(action1) label = label+"{"+action1+"}"; elseif ~isempty(action2) label = label+"{"+action2+"}"; end transaction1 = transitionIn.TransitionAction; transaction2 = transitionOut.TransitionAction; if ~isempty(transaction1) && ~isempty(transaction2) if endsWith(transaction1,";") || endsWith(action1,",") label = label+"/{"+transaction1+transaction2+"}"; else label = label+"/{"+transaction1+","+transaction2+"}"; end elseif ~isempty(transaction1) label = label+"/{"+transaction1+"}"; elseif ~isempty(transaction2) label = label+"/{"+transaction2+"}"; end end end