技巧1:记住ASSERT的定义。
对于许多开发人员来说,断言是一个令人困惑的话题,因为使用断言的许多方式都违背了它们最初的设计意图。我所见过的断言的最清晰的定义是这样的:
"断言是程序中特定点的布尔表达式,除非程序存在缺陷(Bug),否则其值将为真。"
想要理解上述断言定义的开发者要注意以下三点:
断言评估一个表达式是真还是假。
断言是对代码中某一点的系统状态的假设。
断言将验证系统假设,如果不成立,则表明代码中存在缺陷。
技巧2:使用ASSERT验证函数的先决条件
断言非常适合契约式设计环境,在这种环境中,开发人员清楚地定义了功能的先决条件。断言可以用来检查这个函数的输入是否满足先决条件。以图1所示的代码片段为例:
图1:函数的先决条件
功能的状态输入应在定义的系统状态范围内。如果State不是有效的状态值,那么它就不是错误,而是缺陷!断言可用于验证状态有效的假设,如图2所示:
图2:将断言应用于函数先决条件
在状态不小于最大值的情况下,断言表达式将被评估为假,然后程序将停止执行。停止程序执行可以让开发人员很容易马上发现代码哪里错了,而不是以后才知道。
3:使用ASSERT验证函数的后置条件。
断言还可以用来验证契约式设计环境中关于功能输出的假设。例如,如果前面定义的System_StateSet函数返回SystemState变量,开发人员可以预期它在预期的范围内。断言可以用来监控缺陷,如图3所示。
图3:将断言应用于函数的后置条件
开发人员看了上面的代码后可能会觉得这些检查毫无意义。新设置的SystemState怎么会有大于SYSTEM_STATE_MAX的值?答案是,这确实不应该不会发生的。然而,有时它会莫名其妙地改变,也许是通过中断或并行线程。这时,断言可以立即标记这个缺陷。
提示4:不要不要使用断言进行错误处理。
记住断言的定义后,开发人员应该记住断言是用来检测缺陷的,而不是用来处理错误的。错误处理是设计用来响应错误的用户输入和意外事件序列的软件。系统中预计会发生错误,但仅仅因为有无效的输入并不能这并不意味着代码中有缺陷。错误处理应该与缺陷发现分开。使用错误断言的一个典型例子是在试图打开文件进行读取时检查文件的指针,如图4所示。
图4:4:ASSERT的不当使用
读者可以清楚地看到,试图打开文件的结果与文件系统和用户数据的状态有关,而与代码中的缺陷无关。开发人员应该编写错误处理程序,而不是断言,这样当文件不存在时,错误处理程序可以用一些默认的可用数据来创建它,这样后续的代码就可以继续运行。
技巧5: Assert只对开发有意义,对生产没有意义。
开发ASSERT宏的初衷是在开发过程中启用,在生产后期禁用。您可以使用NDEBUG宏激活和停用ASSERT。禁用后,正确的断言对嵌入式系统的影响应该很小。
问题是,如果测试是在启用断言的情况下进行的(为了捕捉任何缺陷,应该这样做),那么现在禁用断言将导致交付的产品处于与测试产品不同的状态。断言确实会占用一些代码空间,但更重要的是,它们需要少量的时钟周期来评估它们的布尔表达式。禁用ASSERT可能会对资源有限的裸机系统的执行顺序产生很大影响,导致生产系统出现新的缺陷。开发团队需要判断是否值得冒关闭断言的风险。
另一种方法是保持断言活动,并将它们的输出重定向到系统日志。这可以确保任何遗留的缺陷可以被容易地识别,并且可以避免停止系统的操作,这不是一个明智的举动。
技巧六:不允许断言有副作用。
ASSERT的默认实现允许开发人员将一段可执行代码作为布尔表达式的一部分。例如,状态变量可以作为表达式的一部分来实现,并传递给ASSERT。但是如果传递给ASSERT的表达式有副作用,也就是会改变嵌入式系统的状态,那么禁用断言就会改变系统的行为。开发人员应该确保他们的表达式没有副作用,否则他们需要冒着只为产品代码唤醒而在系统中添加睡眠时间缺陷的风险。
提示:断言应该占代码的1%到3%。
对于代码库中应该有多少断言,每个开发人员都有自己的看法。大家都同意代码库中的断言数量应该大于0。断言为开发人员提供了一种在代码库出现缺陷时发现缺陷的好方法。调试是开发嵌入式系统中最耗时、最令人沮丧的事情之一。不管开发商的比例赞同率是1%、3%或者5%,使用断言肯定会对你有好处,会让嵌入式软件的开发变得有些有趣。
技巧8:使用断言作为可执行代码注释。
断言可以生成优秀的评论!编写优秀的表达式可以准确地告诉开发人员在代码的给定点会发生什么。开发人员应该做好他们断言的架构,帮助人们更清楚地理解系统中发生的事情,从而帮助减少缺陷。
总结
断言是一个优秀的工具,但是太多的嵌入式软件开发者忽略了它。本文讨论的八种技术只是如何正确使用断言的冰山一角。接下来,读者可以在测试平台中建立并开始使用断言,并研究它们在实际嵌入式系统中是如何工作的。
标签:代码缺陷系统