为什么您应该使用基于标准的开发实践
Why you should use standards-based development practices (even if you don’t have to)
整个行业围绕着功能安全、安全和编码标准(如IEC 61508、ISO 26262、IEC 62304、MISRA C和CWE)所倡导的验证和验证实践而发展起来。当然,并不是每个人都有义务遵循这些标准所提倡的正式过程和方法,尤其是当他们的软件不需要满足这些标准的严格性时。但标准支持最佳实践,因为经验表明,它们代表了实现高质量、可靠和健壮软件的最有效方法。
遵循这些标准的最佳实践开发技术有助于确保首先不会在代码中引入错误,从而减少对大量调试活动的需求,这些活动可能会减慢上市时间并增加成本。当然,并不是所有的开发者都有足够的时间和预算用于航空航天、汽车或医疗器械行业。然而,他们所部署的技术代表了一个工具箱,对任何开发团队都有巨大的潜在好处,无论关键性是否强制使用它们。
错误类型和解决错误的工具
在软件中可以找到两种关键类型的错误,并使用工具解决这些错误,以防止错误的引入:
编码错误。一个例子是试图访问数组边界之外的代码。这类问题可以通过执行静态分析来检测。
应用程序错误。只有通过准确地知道应用程序应该做什么,这意味着根据需求进行测试,才能检测到这些问题。
编码错误和代码复查
静态分析是检测代码错误的一种有效技术,尤其是当它从项目开始部署时。一旦代码被分析,就可以查看不同类型的结果。代码评审是根据编码标准(如misrac:2012)检查代码的地方,这是本文将重点讨论的内容。
理想情况下,所有的嵌入式项目都应该使用像Ada这样的安全语言。Ada包含许多特性来强制执行一个自然减少错误的思考过程(例如严格的输入)。不幸的是,很难找到程序员艾达知识和经验,所以大多数公司,而不是使用C和/或C++。然而,这些语言甚至给经验丰富的开发人员带来了隐患。幸运的是,通过执行代码复查,可以避免大多数潜在的陷阱。
避免代码中的缺陷的最好方法是避免将它们放在那里。这听起来很明显,但这正是编码标准所做的。在C和C++世界中,大约80%的软件缺陷是由错误使用大约20%的语言造成的。如果语言的使用受到限制,以避免语言中已知有问题的部分,那么缺陷就可以避免,软件质量也会大大提高。
与C/C++编程语言有关的基本语言失败的原因是未定义行为、实现定义行为和未指定行为。这些行为会导致软件错误和安全问题。
作为实现定义行为的一个示例,考虑当有符号整数右移时高阶位的传播。结果是0x40000000还是0xc000000?
Figure 1: The behavior of some C and C++ constructs depends on the compiler used.
答案取决于您使用的编译器(图1)。可能是两者之一。函数参数的求值顺序在C语言中未指定。在图2所示的代码中,rollDice()函数只是从保存值“1、2、3和4”的循环缓冲区中读取下一个值,预期返回值将是1234。但是,不能保证这一点,至少有一个编译器将生成返回值3412的代码。
Figure 2: The behavior of some C and C++ constructs is unspecified by the languages.
C/C++语言存在很多这样的缺陷,但通过使用编码标准,这些未定义的、未指定的和实现的行为可以被避免。类似地,使用goto或malloc之类的构造可能会导致缺陷,因此可以使用编码标准来防止这些构造被使用。在混合有符号和无符号值时会出现许多问题,这在大多数情况下不会产生问题,但有时可能会出现有符号值溢出并变为负的情况。
编码标准还可以检查代码是否以特定的样式编写;例如,验证是否未使用制表符、缩进是否为特定大小或圆括号是否位于特定位置。这一点很重要,因为需要进行一些手动代码检查,并且当在不同的编辑器中查看代码时,制表符的大小不同,那么奇怪的布局会分散审阅者的注意力,使其无法集中精力审阅代码。
一些开发人员对编写“聪明”的代码感到内疚,这些代码可能是高效和紧凑的,但也可能是神秘和复杂的,使其他人难以理解。最好保持它的简单性,让编译器负责生成一个高效的二进制文件。再一次,使用编码标准可以帮助防止开发人员创建未记录的、过于复杂的代码。
最著名的编程标准可能是MISRA标准,该标准于1998年首次为汽车行业发布。这些标准的流行反映在提供某种程度的MISRA检查的嵌入式编译器的数量上。MISRA的最新版本是MisraC:2012,它的页数几乎是前一版本的两倍。这些附加文档中的大多数都包含关于为什么每个规则存在的有用解释,以及该规则的各种异常的详细信息。MISRA有几个指导原则,在适用的情况下,它们包含对标准或未定义、未指定和实现定义行为的引用。在图3中可以看到一个例子。
Figure 3: MISRA C references to undefined, unspecified, and implementation-defined behavior.
大多数MISRA指南是“可判定的”,这意味着工具应该能够识别是否存在违规行为。然而,有一些准则是“不可判定的”,这意味着工具并不总是能够推断出是否存在违规行为。例如,未初始化的变量作为输出参数传递给应该初始化它的系统函数。但是,除非静态分析可以访问系统函数的源代码,否则无法知道该函数在初始化变量之前是否使用了该变量。如果使用了一个简单的MISRA检查器,那么它可能不会报告这种违规行为,可能会导致假阴性。或者,如果MISRA检查员不确定,那么它可以报告违规行为,可能导致误报。什么是最好的?不知道可能有问题吗?或者确切地知道在哪里花时间来确保绝对没有问题?当然,最好是有假阳性而不是假阴性。
2016年4月,MISRA委员会发布了对MISRA C:2012的修正案,增加了14条指南,以帮助确保MISRA不仅适用于安全关键型软件,而且适用于安全关键型软件。其中一个指导方针是指令4.14,如图4所示,它有助于防止由于未定义行为而导致的陷阱。
Figure 4: MISRA and security considerations.
应用程序错误和需求测试
只有通过测试产品做了它应该做的事情,这意味着有需求,才能发现应用程序错误。避免应用程序错误需要设计正确的产品和正确的设计产品。
设计正确的产品意味着预先建立需求,并确保需求和源代码之间的双向可追溯性,这样每个需求都得到了实现,每个软件功能都可以追溯到一个需求。任何缺失或不必要的功能(不满足要求)也是应用程序错误。产品权利设计是确认开发的系统代码满足项目需求的过程,这可以通过执行基于需求的测试来实现。
图5显示了一个双向跟踪的示例。在这个简单的例子中,选择了一个单一的功能,并强调了从功能到低级需求,再到高级需求,最后到系统级需求的上游可追溯性。
Figure 5: Bidirectional traceability, with function selected.
在图6中,选择了一个高级需求,突出显示了对系统级需求的上游可追溯性,以及对低级需求和源代码功能的下游可追溯性。
Figure 6: Bidirectional traceability, with requirement selected.
这种可视化可追溯性的能力可以在生命周期的早期发现可追溯性问题(应用程序错误)。
测试代码功能需要知道它应该做什么,这意味着有低级需求来说明每个函数的功能。图7显示了一个低级需求的示例,在本例中,它完整地描述了单个功能。
Figure 7: Example low-level requirement.
测试用例来自于低层次的需求,如表1所示。
表1:来自低级需求的测试用例。
使用单元测试工具,可以在主机或目标上执行这些测试用例,以确保代码的行为符合需求。图8显示所有测试用例都已回归并通过。
Figure 8: Performing unit tests.
当所有的测试都被测试时,确保所有的测试都已经被测试过了。如果覆盖率不是100%,那么可能需要更多的测试用例,或者有多余的代码需要删除。
结论
随着软件复杂性的增加,潜在的软件错误也会增加。最佳实践开发技术有助于防止这些错误的发生。最佳实践开发包括使用最先进的编码标准,如MISRA C:2012,测量代码的度量,跟踪需求,并实现基于需求的测试。在没有义务满足标准的情况下,这些技术的应用程度显然由开发团队决定。然而,标准支持这些实践,因为经验表明,它们是实现高质量、可靠和健壮软件的最有效方法。不管一个产品是否安全关键,这肯定是一个只对其开发团队有利的结果。