3. Stateflow建模要素回顾

需求追溯与覆盖率分析并不是独立于模型之外的两件事,它们最终都要落到 Stateflow 的语义元素上:一条需求对应哪些状态?一个覆盖率目标覆盖的是转移还是状态?历史节点是否被激活过?如果对建模要素的语义边界不清楚,追溯矩阵就会挂错对象,覆盖率统计也会出现“看似达标、实则漏测”的情况。本节把 Stateflow 的核心建模要素按语义重新梳理一遍,为后续建立“需求—模型元素—覆盖率”的映射打基础。

状态:模型的最小语义容器

状态(State)是 Stateflow 中最基本的语义单元,它表示系统在某一时刻所处的稳定情形。一个状态可以携带三类内容:进入动作(entry)、退出动作(exit)和 during 动作(during)。例如一个电机控制状态可以写成:

state MotorRunning
  entry: speedRef = 1500;
  during: speed = readSensor();
  exit: speedRef = 0;
end

从追溯角度看,状态本身往往对应一条“系统应处于某模式”的需求,而 entry/exit 动作则对应“进入/退出该模式时应执行的行为”需求。因此状态节点是需求追溯中最稳定的锚点。覆盖率上,状态覆盖率统计的是“该状态是否被激活过”,它回答的是“这条模式需求是否被触发”,但不回答“状态内部的每条动作是否都执行过”。

转移、条件与动作:行为发生的三要素

转移(Transition)描述状态之间的迁移关系,它由三部分组成:源状态、目标状态,以及转移标签。转移标签的完整语法是 事件[条件]{条件动作}/转移动作,其中方括号内是条件(Condition),花括号内是条件动作(Condition Action),斜杠后是转移动作(Transition Action)。三者的执行时机完全不同,这是追溯与覆盖率最容易混淆的地方:

  • 条件:在转移被评估时求值,只决定“是否走这条转移”,不产生副作用;
  • 条件动作:条件为真、且转移被选中后立即执行,先于源状态退出;
  • 转移动作:在源状态退出之后、目标状态进入之前执行。

举例来说,evSpeed[cmd > 1000]{logCmd(cmd)}/setMode(FAST) 这条转移中,cmd > 1000 是条件,logCmd(cmd) 是条件动作,setMode(FAST) 是转移动作。若需求写的是“当指令大于 1000 时记录指令并切换到快速模式”,那么这条需求实际上同时绑定了条件、条件动作和转移动作三个元素,追溯时不能只挂到转移上。覆盖率上,转移覆盖率统计的是“该转移是否被触发过”,而条件覆盖率关心的是“条件表达式是否取过真和假两个分支”,二者粒度不同。

层次结构与并行状态:语义的嵌套与并发

Stateflow 支持状态的层次嵌套:一个父状态内部可以包含子状态、子转移,甚至更深层的嵌套。层次结构带来的关键语义是“默认转移”和“互斥(OR)分解”——父状态被激活时,必须通过默认转移进入某一个子状态,同一时刻只有一个子状态处于活动状态。与之相对的是并行(AND)分解:并行状态的所有子区域在同一时刻同时活动,各自独立演化。

这两类分解对追溯的影响非常直接。对于互斥子状态,需求通常描述的是“在父模式下,根据条件选择 A 或 B”,追溯对象是默认转移和子状态;对于并行状态,需求往往描述的是“同时满足多个子行为”,追溯对象是各个并行区域,覆盖率也必须按区域分别统计,不能用一个区域的活动代表整个并行状态被覆盖。

历史节点:记住上次离开的位置

历史节点(History Junction)用于记录父状态上次退出时所处的子状态,使得再次进入父状态时可以恢复到上次的位置,而不是重新走默认转移。它分为浅历史(Shallow History)和深历史(Deep History):浅历史只记住父状态直接子层的活动状态,深历史则递归记住所有嵌套层的活动状态。

历史节点是追溯与覆盖率中极易被忽略的元素。一条“系统重新上电后应恢复到断电前的运行模式”的需求,其模型实现往往就是历史节点,而不是某个普通状态。如果覆盖率只统计状态和转移,历史节点是否被激活、是否真正恢复了正确子状态,就可能完全没有被度量。因此在建立追溯矩阵时,应把历史节点作为独立对象登记,并为其设计“首次进入走默认转移、再次进入走历史恢复”两类测试场景。

Stateflow 建模要素语义关系图 状态 · 转移 · 条件 · 动作 · 层次 · 并行 · 历史节点 父状态:SystemMode(互斥 OR 分解) Idle entry: led = OFF; during: checkCmd(); Run entry: startMotor(); exit: stopMotor(); 默认转移 evStart[cmd>0] /setMode(RUN) evStop[cmd==0]{logStop()} H 历史节点(浅历史) 恢复上次子状态 并行状态:Monitor(AND 分解) 区域1:温度监控 TempHigh / TempNormal 独立转移与条件 区域2:电流监控 CurrHigh / CurrNormal 与区域1同时活动 两区域并发执行,覆盖率需分别统计 追溯映射:需求 → 状态 / 转移 / 条件 / 动作 / 历史节点;覆盖率:状态覆盖、转移覆盖、条件覆盖、历史恢复场景覆盖

把建模要素映射到追溯与覆盖率

理解上述要素之后,可以建立一张清晰的映射关系。需求追溯的对象不是“整个模型”,而是具体的语义元素:模式类需求挂到状态,行为触发类需求挂到转移及其条件,动作类需求挂到 entry/exit/during 或条件动作、转移动作,恢复类需求挂到历史节点,并发类需求挂到并行区域。覆盖率目标同样按元素类型区分:状态覆盖率回答“模式是否被激活”,转移覆盖率回答“迁移是否发生”,条件覆盖率回答“条件真假分支是否都取过”,而历史节点则需要专门的“首次进入 + 再次进入”场景来覆盖。

在建立追溯矩阵时,建议为每个语义元素分配唯一标识,例如 ST_MotorRunning、TR_Idle2Run、CND_cmd_gt_1000、HJ_SystemMode。这样需求、模型元素与覆盖率报告之间才能形成可核查的对应关系,而不是停留在文字描述层面。

一个可执行的追溯登记示例

下面给出一个简化的追溯登记表结构,用于把本节要素落到可操作的对象上。字段只保留与 Stateflow 语义直接相关的部分:

需求编号需求描述模型元素类型元素标识覆盖率目标
REQ-001系统应处于空闲模式状态ST_Idle状态覆盖
REQ-002指令大于 0 时进入运行模式转移 + 条件TR_Idle2Run / CND_cmd_gt_0转移覆盖 + 条件覆盖
REQ-003进入运行模式时启动电机entry 动作ACT_Run_entry动作执行覆盖
REQ-004重新上电后恢复上次模式历史节点HJ_SystemMode历史恢复场景覆盖
REQ-005温度与电流应同时监控并行区域PAR_Monitor各区域分别覆盖

这张表的价值在于:它把“需求—模型元素—覆盖率”三者绑定在同一行上。后续无论是做需求变更影响分析,还是查看覆盖率缺口,都可以直接定位到具体元素,而不是在模型里盲目搜索。需要强调的是,表中的元素标识和覆盖率目标属于示例性约定,实际项目中应结合建模规范统一命名,并确保与需求管理工具中的条目一一对应。

常见语义误区

在工程实践中,以下几类误区会直接影响追溯与覆盖率的准确性。第一,把条件动作和转移动作混为一谈,导致需求挂错执行时机;第二,忽略默认转移,认为父状态被激活就等于某个子状态被覆盖,实际上默认转移本身也是一条需要被触发的转移;第三,把并行状态当作互斥状态处理,只统计其中一个区域就宣称整个并行状态已覆盖;第四,遗漏历史节点,使得“恢复类”需求在覆盖率报告中完全不可见。识别并规避这些误区,是后续建立可靠追溯与覆盖率体系的前提。

历史节点的覆盖率不能简单等同于“父状态被激活”。必须区分首次进入(走默认转移)和再次进入(走历史恢复)两种路径,否则历史恢复逻辑可能从未被真正执行过,却显示为已覆盖。