美国猎户座gn&c系统开发—基于模型的开发实践(四)
五、后pdr分析及产品开发
在cdr之后,gn&c关注的是将开发的算法转移到修正模型以使其具备良好的工程软件应该具备的属性。本节内容讨论影响gn&c的csu测试和审查的相关过程和模型属性。
大部分的gn&c基于模型的项目开发由数据流框图自动生成代码的算法,并将该代码移植到一个更大的框架里,该框架可以是利用uml或者其他图形化工具手动或半自动开发的。自动代码层面的选择是一项重要的项目决定并且应该在开发早期就做选择。自动代码生成可以是小的模块单元也可以是整个gn&c系统。对于猎户座项目,gn&c系统应用自动代码生成,包括图5所示的csu模型引用模块,以及输入和参数接线盒模块。这意味着与uml开发的应用代码的接口是根据上游csu输出数据和变量的bus(结构体)类型和变量定义的。csu的参数数据包括csu特定参数、飞行器物理参数和csu指令参数等。这些参数可以在可变范围内由程序进行修改(根据gn&c模式变化)。
该csu接线接口是一种折中,既允许将高层应用以面向对象设计的函数使用,也使用高效的手写代码模式逻辑,同时减少gn&c算法的手写代码比重。包含接线盒也意味着大部分的csu到csu的接口由自动代码生成方式定义,并由ramses的simulink设计环境转移到gn&c应用上。这种方法的缺陷是域层面的模式逻辑、csu参数变化和任何新的bus类型数据的添加都无法由ramses自动生成代码。猎户座项目的经验是手写的模式与bus层接口的代码很少出错,但是其开发需要消耗一定的时间和资源。
尽管自动代码生成是在csu及接线盒层面,csu的单元测试是在csu内部进行的。这样可以使csu设计人员不需要知道其他csu的输出数据类型并使得csu的测试驱动脱离其他csu或域层面的开发。接线盒作为集成测试的一部分进行检查与测试。
由于使用了mbd流程,不需要详细的实现级的需求,并且simulink框图为csu设计提供了一定程度的信息。然而,mbd实现过程仍然需要某些方面的文件信息。首先,猎户座的软件特定需求(srs's)往往不够详细,因而无法实现每一个csu的单元测试,因此派生设计需求(ddr's)在一个“csu memo”中针对每一个csu进行了创建和说明(见图11示例)。该“csu memo”也包括一些其他部分,记录了重要的设计信息。csu主要包括以下几部分:
1.派生的设计要求——在猎户座srs部分利用“应该”语句表达如何进行上层需求的单元测试。
2.设计和理论——提供gn&c算法的数学和逻辑表达。
3.csu接口——包括以csu接口bus类型创建的输入、输出和参数表。
4.参数配置设定设计——提供关于如何设定可配置参数的信息。
5.前提假设与限制。
6.应用接口——以html形式链接到csu。
7.单元测试描述。

图11ddr示例
对于猎户座项目来说,simulink的csu框图被作为csu算法的根源。因此,自动生成的代码并未被正式检查。然而,出于对效率的考虑,对csu的模型图进行了严格的检查,其中包括对自动生成代码的审核。所有在模型上开发和执行的测试都在自动生成的代码上进行了运行。
模型成熟(model maturation)
图12详细的定义了模型的成熟过程,该过程来自于honeywell。它清楚地给出了开发一个模型的几个步骤,以便使该模型可测试、标准兼容和便于检查。
图12模型成熟过程
为了辅助开发人员设计csu,创建了“开发人员检查备忘录”,包含了在每个阶段需要检查的主要项目,这些阶段包括csu检查和csu单元测试等。起初,许多开发人员不确定他们的模型是否足够成熟以便集成到eba,该备忘录使他们更清楚地了解了他们的模型是否成熟,并提高了被审查的模型的质量。
该项目开发的一些示例入口和出口标准模型试验检查包括:
1) 是否所有低层需求被满足
2) 代码/模型是否被合适的说明/注释
3) 模型是否分解成了独立可测试单元
4) 每个可测试单元的复杂度是否都低于20
5) 模型建议检查是否都能通过
6) 单元测试能否100%覆盖模型
7) 模型是否能够自动生成代码(自动代码生成过程是否成功的完成并编译通过)
可测试单元(testable units)
随着对自动代码生成熟悉程度的增加,定义“可测试单元”的需求变得更加重要,其目标是使得simulink模型、stateflow表或嵌入式matlab函数和最终的cpp函数之间有一一对应的关系。这种一一对应关系可以帮助模型和生成的函数之间的测试追踪,也可以帮助避免由于过度内联和功能重复导致的重复测试。
测试由模型自动生成的代码存在的问题是不同的模型自动代码生成的一致性。模型层面的独立的测试单元并非总是可以转化为自动生成的代码中单独的可测试单元。一些源模块在上一级模型代码中是内联的,而其他一些则是上级cpp文件或者共享的公共程序中独立的函数。
为了部分地解决上述问题,在eml函数和模块设定中使用自动代码生成“指令”以强制自动代码生成器从eml/stateflow原函数产生单独的方法/函数。这种方法部分解决了上述问题,但是并未产生理想的一对一情形,因为eml/stateflow函数是以父模型的方式自动生成代码的。这就导致直接测试该方式及取消模型之间的函数共享是存在问题的。图13展示了单一的外部eml函数被两个独立模型调用的情况以及代表eml函数的自动生成代码的位置。所有的这些重复的代码在实际的后端代码测试中都产生了很大负担。
图13进行可测试单元封装的模型引用模块
然而,我们的大部分函数都是由eml或stateflow生成的。eml函数无法调用simulink模型(模型引用),因此,如果一个算法需要用eml编写,那么simulink和stateflow模型都将无法被调用。图14显示了simulink、stateflow和eml工具之间的调用能力。由此可知eml函数只能调用其他eml函数。
图14合法的调用关系
由于上述原因,模型之间共用的eml函数在使用时增加了许多限制。这就对喜欢利用eml进行算法开发的开发者带来的困难。
为mbd开发有意义的复杂性需求(develop meaningful complexity requirements for mbd)
在早期的猎户座开发过程中,相对于模块化、复杂性及其他软件工程化需求,更多考虑的是软件功能和性能。这部分归结于早期需要使用gn&c算法进行飞行器系统集成的时域分析以及复杂性定义和软件的设计指导是不断改进的事实。猎户座项目的软件开发计划(sdp)要求功能单元的循环复杂度(cc)(文献2)不超过20(手写代码和自动生成代码)。然而后来很快发现自动生成的代码一般比手写代码的循环复杂度高,并且模型中的可测试单元不能转化为生成代码中的可测试单元(见上文叙述)。
循环复杂度是衡量代码可测试性的一个很有用的方法。然而,这给用simulink开发的程序带来了一些挑战,因为如果不对模型自动生成代码并分析代码是没有直接的方法来确定代码的循环复杂度的。simulink提供的确定模型复杂度的功能,但是对猎户座而言,sdp需求仅仅是关于自动生成代码的,并不关心模型。并且,生成的代码的循环复杂度也并不能反映源模型的可测试性能。这种无关性主要是由于在数据初始化、参数赋值以及向量/矩阵数学方面对for循环的自由使用。标准的循环复杂度计算是每一个for循环都增加一次复杂度而不管该循环是否是静态的。这就导致了模型复杂度与代码复杂度相去甚远,为开发者增添了困扰。图15给出了模型中的一个单一模块是如何转化为由于矩阵乘法的静态嵌套循环引起复杂度为3的的代码的。这些额外的静态循环不增加代码的复杂性。
图15自动生成代码对复杂度的影响
确保每个函数最终生成代码的循环复杂度不超过20是比较困难的。开发团队发现,如果忽略静态for循环,生成代码的循环复杂度就接近源模型的复杂度了。考虑到这些静态for循环并不增加代码的复杂度,计算循环复杂度时并不考虑这些。为了衡量修正的模型复杂度,开发了忽略静态for循环的复杂度计算工具。在模型检查和单元测试检查中都用到了循环复杂度审查。
在2010b版的simulink中,获得模型复杂度并不容易,并且在指标报告时引发了新的问题,该问题已经在更高版本的simulink中的到了改进。
其他指标(other metrics)
利用sloc是计算项目指标的一个很好地方式,这些指标包括项目大小以及为后续任务估计工作量。然而,对mbd而言,相对于代码行数,模型大小是一个更好的指标。由于多种原因,自动生成代码的sloc指标与模型大小并不一致。因此,为考虑三种工具((simulink/stateflow/eml)的所有影响因素,创建了“模型大小”指标,并通过下面的公式进行计算(通过脚本文件自动计算):
模型大小=simulink模块数+eml代码行数+stateflow转换数
由于函数复杂性由循环复杂度需求进行管理,猎户座项目并没有一个针对函数或文件的sloc限制需求。不过,项目团队发现在过程追踪和项目大小衡量方面,这个工具还是十分有用的。
模型覆盖度和代码覆盖度(model coverage vs code coverage)
理论上讲,为缩小模型和代码之间的测试差异,模型覆盖度应该与代码覆盖度相同。然而,事实不一定如此。除了上文叙述的代码重复使用问题外,自动代码生成器会优化确定的模块,根据具体的使用清除无效的路径(死码),并在某些特定操作(如整数溢出)插入保护。同时,有些可能具有内部路径的模块在源模型中并不表示成这样。在simulink中,一个模块要么执行要么没有执行,直到该模块进行自动代码生成时,内部链接才显示出来。模型和代码中的有的差异可以通过改变配置设置选项来消除,有的可以通过修正模块设置来消除。但是,一对一的模型和代码覆盖度达不到100%。
单元测试(unit testing)
simulink系统测试(图16)被选择作为对模型进行单元测试的猎户座gn&c官方工具。考虑到稳定性和功能有限性,从系统测试工具中开发测试用例是比较麻烦的。举例而言,仿真错误报告只有通过与不通过,而无法得知产生问题的原因。随着团队人员对该工具的熟悉,我们找到了解决这些问题的变通方法。针对单元测试,团队还创建了一个标准和指导性文档。该文档列出了开发模型单元测试的标准格式、过程和api。该文档有助于实现整个gn&c系统的标准化单元测试。
图16系统测试执行流程图
由于系统测试的一些常见问题的存在,我们的单元测试框架被设计为尽可能少的依赖系统测试本身。所有的输入数据、初始化例程和对比数据都是在工具外生成的。系统测试工具基本上用来执行测试、返回测试结果并生成测试报告和覆盖度文档。测试及覆盖度报告的质量较高是系统测试工具的比较出彩的地方。由于我们的单元测试是独立开发的,因而将来选用一个更好用的工具将会变得比较容易。
ldra和sil/pil模式集成(ldra and sil/pil mode integration)
猎户座项目的官方代码分析工具是ldra(liverpool data research associates)测试平台。ldra测试平台为我们的软件提供静态和动态分析的核心引擎。mathworks工具默认只与bullseye coverage工具针对c对象进行兼容。gn&c fsw团队通过努力得到了mathworks与ldra的兼容性支持,从而可以产生运行于ldra的单元测试脚本。
simulink工具的一个十分有用的功能是以“sil” 或“pil”模式运行自动生成的代码。该功能可以允许开发者通过源模型产生单元测试,之后将该测试程序在实际的飞行软件上运行(sil)或者在目标环境中运行(pil)。不幸的是,我们早期使用c++封装对象和ldra工具,而“sil” 和“pil”模式尚不是“开箱即用”的。根据猎户座的要求,mathworks为实现该功能提供了一个补丁包。后来的simulink版本实现了对ldra编译的兼容性并支持c++(封装的)对象的“sil” 和“pil”测试模式。
六、结论
猎户座gn&c团队成功地运用mbd流程为eft-1任务的gn&c算法开发了软件。团队为传统方法到mbd过程的过渡花费了相当大的预先成本,但是后续收益正在逐步得到实现。预先成本中的一部分是不可避免的,但其他的或许可以通过该项目的经验教训在未来的项目开发中得以避免。这些花费包括:
1) 针对不熟悉mbd工具的工程师的短期培训
2) 多种工具开发费时费力
3) 配置管理问题
这些问题可以在未来的猎户座任务的算法开发与测试中得到弱化,这是因为许多的工具和技术问题已经得到了解决。该团队现在已经拥有成熟的mbd代码生成标准以及通过模型检查工具(model advisor)进行自动操作的准则。mathworks工具的改进和对该工具的更好使用使得编译更快速,代码自动生成的时间更短。同时,该团队已经对如何配置前文所述的各项mbd指标有了更好的理解。
gn&c目前已知的优势包括:
1) 无需像手写gn&c代码那样安排预算时间(cdr自动生成超过60000行源代码)
2) 详细的需求审查被mbd模型审查取代,这被证明是有效的
3) 自动测试架构和报告生成简化了程序的测试和产品化
4) 自动标准检查工具(例如model advisor)和图形化模型提升了检查的高效性
猎户座gn&c团队已经准备好完成eft-1任务并为其他的飞行阶段生成gn&c算法。其他需要使用mbd过程的项目也不用从头开始了。猎户座项目gn&c团队成员获得的关于工具和技术的经验教训可以为同样使用mbd进行项目开发的人员提供帮助。