iot dev technology 5:系统整台 & 测试调试微调 & 结项

2023-11-23

11. 系统整台

11.1 第一次整合

第一次整合的最佳时间点一般会有以下几种情形:

■ 硬件设计不会再有大变化:基本组件与PIN脚配置都已确定,剩下的只是微调的工作而已,即以后硬件的调整‘应该’都不会影响到驱动程序。

■ 驱动程序已经稳定:‘稳定’的定义应该是通过长时间且大量的单元测试、整合测试与压力测试,其中压力测试对驱动程序的稳定度更显重要,单单验证驱动程序的正确性是不够的.
例如keyboard driver测试不能一个一个key慢慢按,至少必须测试快速且连续的按法,或者多个key同时被压下、某些key长时间被按下等。
除了使用‘较死板’测试手法之外,固件工程师还必须写作一些压力测试程序,例如长时间连续对存储器读写、利用信号发生器制作特殊信号输入到系统中,测试驱动程序是否仍可正常运行。
固件工程师必须自觉地做完这些测试,我们已经说过很多次了,底层的不稳定会随着被整合的程序增加而放大,稳定度是驱动程序最重要的基本需求之一。

■ 重要的系统功能都已完成:至少要整合的应用程序会用到的系统功能都必须完成。

■ 至少要有两个应用程序已完成所有功能,且已经请客户或规格制定者在模拟器上确认过其完成度。

当然因为现实原因,往往开始整合时,条件都有缺陷:
所以整合经常出现失败的情况,失败原因有如下几种:

■ 设计更改:设计阶段结束后还是有可能进行设计修改,可能因为来自客户的要求,或是在实作阶段才发现设计的瑕疵等原因,偏偏这个时间点正是兵荒马乱的时候,包含系统工程师,所有人都忙着写自己的程序,所以往往设计变更的结果无法顺利传达给每位工程师。
而工程师忙起来时通常会‘不小心’忽略其他人的mail,当然也没那种闲功夫与精神主动到server下载新的设计文件。最后结果就是:有人始终活在旧设计的世界中、模拟器与实际机器上API的接口或行为不一致、应用程序使用了不存在的API、固件工程师居然不知道硬件的新修正,导致系统不稳定或某个装置在新的板子上‘突然’不work了,这些状况在整合时才一一浮现,而且通常一时三刻还看不出到底是谁出了问题。

■ 这应该算是管理上的问题,要避免这种状况并不困难,只要设法贯彻设计更新的流程即可。首先要求所有工程师都必须注意设计修改的通知,并要求外部厂商(Third Party),如硬件或软件模块设计者,不得片面修改设计。
系统工程师对新设计最好能实时对应,如果是API改变的话,只要模拟器的相关函数一修改,其他应用程序应该马上就会发现编译或连接error,一来可以提醒工程师注意设计更新的通知,二来所有受影响的程序可以马上跟着对应。

■ 实作失误:有些自诩天纵英才的工程师根本不把设计文件当一回事,他们会用自己的方式实现其所负责的模块,一种是函数定义(参数及返回值)不同,另一种则是函数行为的差异,两者都会造成整合失败。
定期且确实的code review可以及早阻止这种行为的发生,如果工程师对其负责的模块有较好的设计或想法,应该要循管道进行设计变更流程,研发团队主管必须以整体的角度来讨论是否需要更改之前的设计成果,并评估这个修改的影响范围与程度。

■ 进度控制失误:这很容易理解,到了预计开始整合的时间点,但却有个重要的程序模块未完成,那么,整合工作当然无法如预期时间开始。就像在攻击发起日,所有的部队都已到达攻击发起线,但有个主力部队迟到,导致战线的中央有个明显的缺口,此时发起攻击必败无疑,但延后攻击时间却也未免耽误军机。
所以项目经理要时时注意各个工作阶段的“瓶颈”所在,宁愿集中人力与资源去完成可能造成瓶颈的工作项目,让较不重要的功能delay。

■ 进度管理失误:实际上,有些功能的进度(除了检查程序之外)是‘看不到,的,通常是没有画面显示的模块,例如文件系统或其他算法的实现。如果主管没空做code review的话,有时工程师会刻意隐瞒进度落后的事实或目前碰到问题的困难度,谎称一切都on schedule,往往要等到整合前才穿帮。
为了掌握所有工程师的进度,各个小主管必须定时从server下载最新版本的程序,抽空检查其组员的程序,如果有问题应该立即召开code review会议,并协助该组员找出解决问题或追上schedule的方法。

■ 驱动程序压力测试不足:驱动程序在独立运行时可能完全没有问题,系统负担加重后,可能问题就会逐一出笼,这样的状况是不适合开始整合工作的,所以驱动程序开发不能以一般状况没问题为满足,固件工程师必须根据装置的特性,主动设计各个驱动程序的压力测试方法,可能的压力测试手法如下,只有做完所有装置的压力测试,这个平台才合适开始做系统整合。

■ 特殊的操作方式,例如:用笔或手指在touch panel上频繁的乱画。
    □ 长时间地操作某装置,例如:持续不停的压下某个key。
    □ 利用信号发生器产生特殊的信号,并连上CPU的特定PIN脚,模拟与此PIN脚连接的装置持续送出信号。
    □ 写测试程序主动频繁的操作待测装置,例如:持续地对存储器进行读写操作。

11.2 全功能整合

全功能整合阶段:主要做的事情是日程表的检讨。
Schedule检讨

■ 利于进度管理。工程师对schedule都有一定程度的依赖,一旦某项工作delay之后,排在后面的工作自然也得跟着delay,此时,管理者最好实时介入帮忙重排schedule,否则许多工程师会就此失去时间的方向,认为反正都delay了,先把手头的工作完成,其他的慢慢再说吧!
■ 使追踪往后日子的进度成为可能,如果整个项目已经开始在delay了,则项目经理很难只根据旧的schedule追踪进度。系统中软件模块众多且实作状况都不相同,不能只是简单地把各单项的进度加上整体delay的时间而当作新的进度,必须趁着要开始整合的时间点,逐一了解每个模块开发的最新状况,制定合理、可施行、可管理的新schedule。
■ 通过对软件开发现况的“普查”,可以试着分析造成delay的症结所在,管理者可据此设法调派人力与资源,或许可以找到好的方法把delay的时间赶回来。

另外,在陆续的整合其他系统功能的过程中,会渐渐的出现一些问题,这些问题可能是:

■ Memory空间不足:这是全功能整合最常见的问题,因为存储器的使用量在项目早期并不容易估得精确,而硬件规格却已早早固定下来,而且应用程序在PC上使用模拟器开发几乎没有存储器用量的限制,所以‘用爆’存储器的问题往往在整合阶段才会爆发出来。
    □ Stack:虽然我们已经三令五申地要求所有工程师谨慎使用Stack,包含不要使用size太大的局部变量与注意递归(Recursive)调用的阶层,但总是有人会出状况。还记得Stack被破坏时显现的‘病兆’是什么吗?包含Stack内或Stack区域前后的存储器内容莫名奇妙被破坏,以及function call无法正确return等
    除了程序的疏忽导致Stack被破坏之外,也有可能是我们把Stack的最大用量估计得太小了,只要先试着把Stack区域放大,如果问题都解决了,几乎可以确定就是Stack区域太小问题
    解决这个问题的方法虽然多少有点劳师动众,但Stack Overflow呈现出来的现象就是系统很不稳定,其影响的范围几乎是整个系统,绝对必须优先解决。
        · 检查所有程序,确认Stack使用量最大的程序,并检讨是否真的需要这么大的Stack。
        · 使用Profiling的方法,得出系统可能Stack的最大使用量

    □ Heap:程序中使用动态存储器可能发生的问题如下。
        · 程序超用其配置来的存储器,导致Memory Pool中的队列被破坏。
        · 碎片。程序明明没有用到这么多存储器,但有时候就是要不到,这会导致某些功能有时候运行正常,有时候无法执行。外行人统一用‘不稳定’这个词来形容这种系统状态。
        · 目前配置之Heap空间的大小真的不符应用程序的需求。
    Heap的问题越早解决越好,最好每整合一个模块,就观察一下Heap有没有异常状况,否则,等到所有程序都整合了,Heap的使用状况将变得更复杂,出了问题会更难找出到底是哪些程序造成的。

    □ 程序size超出预期、ROM空间不足。
    在系统设计时会先预估各个用途的存储器使用量,并据此设计Memory Layout,但这毕竟都是估计值,等到整合工作开始后才会渐渐明朗。
    如果当初存储器配置的设计就是连接的很紧密,若某段存储器的size超出预期,势必会压到另一个存储器区段。这个状况在制作ROM的Binary File(或Image File)时就可以被发现,解决方法是程序减肥(Downsizing),或重新配置ROM里各个区块的位置。

■ Task不协调:如果应用程序必须用到多任务,则系统中会有多个Task(或Thread),而这些Task可能由不同的工程师负责实现;如果彼此没有协调好,或者有某位工程师对多任务系统的理解不扎实,在整合时可能会发生各个Task单独执行都很正常,但‘同时’执行时就出问题了
这些问题不外乎Task间通信、同步、优先级设定、共享资源的保护等,只要理解对都不难解决,可以去参考一些Multi-Threading Programming(多线程操作)的数据。重点是在已经整合的Task还少时就要尽量测试,有问题便实时解决.

■ Critical Section未保护:这里说的Critical Section有两种,第一种是Task间可能会‘同时’存取的全局变量,或同时执行的程序段;第二种是一般程序与ISR(中断处理程序)可能会同时存取的全局变量,或同时执行的程序段。
中断与工作切换(Task Switch)会被引发的时间点可能每次执行都不一样,程序必须设法避免操作Critical Section尚未完成前就被切换到别的程序段,否则等恢复执行时,Critical Section的状态已经被改变。反映出来的状况是每次执行的结果可能都不一样.
保护Critical Section的方法很多,driver层会提供中断enable/disable的功能,在进入Critical Section前禁止中断产生,离开Critical Section后再恢复,即在Critical Section内什么中断也不能产生,保护功能为最佳。

■ 许多在模拟器上OK,但在机器上NG的bug会纷纷出笼,除了上述存储器或Critical Section的问题之外,这类bug多半与驱动程序、Cross-Compiler、甚至与CPU的特性有关。
这种问题难处理的地方在于它不能在Windows上Trace,而应用程序工程师通常都不熟悉在机器上调试的流程,所以往往需要固件工程师的协助.

还有一种和系统整合有关的工作项目,叫做‘Porting’,顾名思义,就是将在别的平台运行正常的程序或函数库移植到我们的机器上
这些程序模块可能来源:

■ 其他项目的程序:即曾经开发过的技术或程序模块,也可以说是研发团队或公司的技术资产

■ Open Source的函数库:近年来开放源码的思想大为流行,几乎什么样的应用都可以找到开放源码的程序,尤其是Linux,它简直就是技术人员的宝库

■ Third Party厂商开发的函数库:我们是负责产品开发的团队,有些特殊应用的技术网络上找不到,也不是我们做得来的,如手写辨识、语音压缩等,这些技术通常可以当成一家软件公司的主要业务了。

11.3 发行第一个版本

原则上应该所有功能都整合上来后,才发行第一个版本并送测会比较好,否则,若功能A整合完毕就送测,测试人员兴高采烈地测试、写Bug Sheet,负责功能A的工程师也兴高采烈地修改,结果当功能B整合上来后,影响了功能A,导致其某些功能不正常,那之前的测试工作岂不是如同白做工?
可惜现实与理想总是存在差距,一般情况系统整合还没完成就已经开始测试了,此时需要约定几个规则以便让工作能够顺利做下去:

■ 在整合工作完成之前,所发行的版本都是内部版本。
■ 系统整合人员会尽量配合测试人员的需求发布内部版本,并详细告知系统中各个功能的完成度,也可建议测试项目的优先级。
■ 内部版本没有正式编号,如A01-build 05(当然还是需要个识别编号,用日期是个很好的选择)。
■ 如果没有必要,为避免增加复杂度,在此阶段只会发行模拟器版本,不会发行实际机器的版本。
■ 因为正式版本尚未发行,所以此时测试人员找出的问题点,尽量不要以‘bug’称之,也不需填Bug Sheet(工程师通常都有自尊心强的特性)。
■ 测试人员将所有找到的问题点汇集给RD主管,不直接接触工程师,由RD主管分配处理之。
■ 在第一个正式版本发行之前,bug管理系统暂不启动。

在正式版本出来之后,就要正式启动BUG控制系统
Bug控制系统除了可以追踪bug外,也是项目团队所有成员(包含客户)对bug处理方式的沟通平台。以Bugzilla为例:

■ 测试人员在填写bug时,会根据其认知将其分类,而每种类别的bug都会有预设的处理者,通常是RD部门的小主管,当某bug的状态有变化时,如新增、已修改(FIXED)、REOPEN、CLOSE等,Bugzilla会主动寄发mail给预设处理者(Assigner)。相关人员必须主动检查Bugzilla寄出来的mail,并登入Bugzilla Server处理之。

■ 测试人员会将可重现bug的操作手法详细写在Bugzilla上,预设的处理者应该根据bug的内容判断该bug的分类对不对,如果分类不恰当,应该立即更改之。接着,根据研发团队各成员的工作职责,决定应该由哪位工程师负责,并将这个bug指定给他,被指定的工程师称为Assigner。

■ 某bug的Assigner会收到Bugzilla发出的通知信,应该即刻登入确定可否处理该bug,如果这个bug不该是自己负责,则应该立即协调指定另一位工程师负责,而不是任其放着。

■ 如果Assigner对该bug有疑问、无法重复发生、认为是规格的模糊地带、或不需要修改等,应该在Bugzilla上陈述理由,测试人员及其他可仲裁的人员可对此bug在在线进行讨论,所有讨论的内容都会被记录在Bugzilla Server中。如果最后确定这不是个bug,或者裁定不需要修改,则bug的状态会被改为INVALID或WONTFIX,再经多次确认后,这种bug就可以被CLOSE。

■ 当Assigner“接受”并解决了某个bug之后,应该到Bugzilla Server描述造成该bug的原因、解决方式、Side Effect评估、预计在哪个版本可以对应等信息,留下这些信息最大的受益人应该就是Assigner本人。程序代码那么多,谁能记得什么bug改了什么地方?如果以后有类似的bug,或该bug的修改方式确实造成了Side Effect,只要参考Bugzilla内之前记录的资料,就能够以最快的速度找到程序的问题点。所以Assigner在填写解决方案时要尽可能详细,如果可以的话,最好把改了哪个程序文件的第几行都不分轻重的记录在server上。

■ 当某bug被解决后,其状态由ASSIGN或NEW变为FIXED,要经过测试人员的重复验证后才可以被设为CLOSE(结项)。如果测试人员发现该bug未完全被修复,或发生其他的Side Effect,则此bug状态会被设为REOPEN,负责的工程师应该再行检查。

■ 研发主管应该时时利用Bugzilla自动产生报告的功能,检验查看还有多少bug未被解决?哪位工程师“囤积”了最多的bug?有哪些bug从被找到至今尚未被解决的时间最长。此举可以找出人力与困难度的瓶颈,研发主管应该即刻处理,如约谈相关工程师并了解其工作方式,如果只是单纯loading太重,可以先调派人力减轻负担,如果是碰到无法解决的关键问题,研发主管也应当在团队内寻求解决方案。

12. Testing、Debugging与Tuning

一般人都会以为是技术不行的人才会被派去做测试,但这是错误的理解!
们可以由两方面来探讨技术这件事情,第一,测试真的是没技术的人在做的吗?第二,测试工作做的好坏对项目的影响程度是什么?
第一个问题:测试工作绝对不是没有技术的人在做的。在软件工程中测试工作有详细的分类,其中白箱测试、压力测试与自动测试等项目都是要写程序的,而且测试工程师必须比开发工程师更努力的详读产品规格书,并根据其经验与直觉,找出任何使用者可能发现的Bug。而且根据软件工程实践证明,让对软件思想有深刻理解的工程师进行软件测试,可以大幅提高软件质量。
第二个问题:我觉得测试工作做得好,对项目的贡献度绝对不输开发工作。因为现代软件项目的复杂度太高,特别是嵌入式系统还牵涉到硬件的稳定度等,要开发出没有bug的软件是不可能的事。而且,根据研究机关的研究结果显示,就算是质量要求最严格的军事项目,平均每千行的C语言程序中还是会出现零点几个bug,所以需要测试人员利用他们的项目素养,把这些bug尽量找出来,有些问题不是程序员可以找得到的
软件测试其中一个定义是:为了评估而质疑产品的过程。身为项目管理或开发人员,绝对不可轻忽测试的重要性。测试的方法论很多,但一般研发团队都有人力的限制,很可能无法按照这些规范来做,实际上,人少有人少的做法,但无论如何,测试工作都必须是有系统、有效率地根据既定的测试计划,按部就班地执行。绝不是把测试人员找来,每人发一台机器,任其随便操作就叫做测试

12.1 测试

12.1.1 测试vs.质量系统

■ 软件测试的经典定义是:在规定的条件下对程序进行操作,以发现错误,对软件质量进行评估的过程。

■ 软件测试描述一种用来促进鉴定软件的正确性、完整性、安全性,和质量的过程。据此,你可能会想,软件测试永远不可能完整确立任意计算机软件的正确性。然而,在可计算理论(计算机科学的一个支派)一个简单的数学证明推断出下列结果:不可能完全解决所谓“死机”(指任意计算机程序是否会进入无限循环、或者罢工,并产生输出)问题。换句话说,软件测试是一种实际输出与预期输出间的稽核或者比较过程。

■ 测试的其中一个定义:为了评估而质疑产品的过程。
这里的“质疑”是测试员试着对产品做的事,而产品应以testing case或产品规格上的定义作为回应。虽然大部分测试的过程不外乎回顾、检查,然而,“测试”这个词意味着产品动态分析——让产品流畅运行。程序的质量标准通常会随系统不同而有差异,不过,某些公认特性是共通的:
可靠性、稳定性、轻便性、易于维护,以及实用性。

■ 软件测试一度被认为是编程能力偏低的员工的工作,直到今天,仍然有许多公司把优秀的人才放在程序编写上,也有更多公司让优秀的人才进行设计,却很少有公司让优秀的人才进行测试工作。
实际的软件工程实践证明,让对软件思想有深刻理解的工程师进行软件测试,可大幅度的提高软件质量。

■ 软件测试和其他工程中的测试有很大的不同。人们可以用牙齿咬黄金来测试其硬度、“真金不怕火炼”测试它的熔点,用化学方法测试它的纯度,可是我们缺乏完善的指标来描述软件产品,比如通过率等。在一千个软件拷贝中,如果一个发现了错误,实际上就是这些拷贝都有错误。更重要的是,其他工业产品都有“通过检测”这个概念,可是软件产品测试的目的现在仍然是:尽可能多的找到软件中的错误,而并非证明软件的正确。
问:“所以,测试工作和质量系统并不是同一件事啰?我之前好像都把这两件事情混在一起。”
答:“在制造业挂帅的时代,质量的思想是‘质量是测试出来的’。简单地说,就是在生产线上尽可能地挑出品质不良的货品,并据此改善产能。在那个领域里,测试和质量的确可以等同视之,但随着产品功能越来越复杂,最新的理解是‘质量是设计出来的’,即在设计阶段就必须考虑到质量,有很多方法论是用来保证设计的质量,无非就是严谨的设计审查,所有的设计活动都必须留下记录等。

我并非质量的专家,只知道测试工作是质量系统中的一环。软件评估的国际基准ISO 9126提供了传统的质量模型,它对质量的以下几个方面进行了规定。
■ 功能性:软件应用的功能性需满足所设定的需求,软件的实现必须符合相关基准。
■ 可用性:使用者能轻松学会并使用该应用,应用程序允许使用者能以协调高效的方式完成自己的任务。优质可用的应用也是简易的,对使用者隐藏了其复杂科技和实现。
■ 效率和性能:应用能在目标环境中高效运行,使用合理数量的系统资源,并在适当的时间内完成这些任务。
■ 可移植性:应用可以从一个环境或目标终端,以有效的方式被移植到另一个环境或终端。
■ 可维护性:如有必要,应能对应用作修改,如软件功能更新和修正patch等。

12.1.2 嵌入式系统”测试:Cross-Test

嵌入式系统测试最大的特点就是,测试人员可能会在不是最终产品的平台上测试,这些平台包含:

■ 模拟器。
■ Target Board。
■ 机器半成品(可能是某次试产的机器,其硬件电路不是最终的设计,也可能会有不稳定的问题)。
■ 机器成品。

有人把这样的测试策略称为Cross-Test:在模拟器上执行多数的测试,只有最后的系统测试与整合测试是在实际机器上做的,这样可避免发生实际机器不足的瓶颈,也可以减少购买昂贵资源,如ICE的费用。

12.1.3 测试计划

什么时间、用多少人、在什么平台上、花多少时间、测试哪些功能
制定测试工作计划有一个重点:启动某项测试工作的条件,不应该是个绝对日期,应该是达成项目的某个里程碑

■ 有多少开发人员可以帮忙执行要写程序的测试工作(如单元测试、白箱测试与压力测试等)。
■ 产品的规格范围,需要多少测试人力参与?
■ 预估测试工作开始的时间点,以及在多长时间内必须尽可能做完所有的测试工作。
■ 硬件设计预计何时稳定?预计何时可取得足够数量的机器。
■ 模拟器功能的完成日期是何时?实际机器与模拟器共享程序的范围是多少?(用以评估哪些功能在模拟器上测试才是有意义的)。
■ 团队中诸如电源供应器、ICE等设备的数量,研发人员的需求量是多少?(这些设备总是会优先配置给研发人员)。
■ 如果要用ICE或ROM模拟器等工具将程序下载到机器才能测试的话,每次下载的时间是何时?相关软件的操作复杂度高不高?
■ 组织中可以帮忙设计testing case的人力有多少?

12.1.4 测试的种类

■ 白箱测试或单元测试:白箱测试通常也称为单元测试、结构测试、逻辑驱动测试或基于程序本身的测试。

■ 黑箱测试:黑箱测试是一种概念,测试者不需要了解程序的内部情况,通过比较系统的预期输出与实际输出来测试系统功能是否正常,以下的各种测试方法(基本功能测试、整合测试、系统测试等)都算是黑箱测试。这种测试不需要了解软件的内部构造,是从使用者的角度对程序进行的测试,只要知道程序的输入、输出和系统的功能即可。

■ 基本功能测试:按照测试软件的各个功能划分,进行有条理的测试,必须保证测试范围能覆盖所有功能和各种功能条件组合。

■ 整合测试:所谓的整合测试乃相对于基本功能测试而言,主要在确认当多个基本功能整合在一起并交互操作后有没有问题。此外,整合测试也必须检查出一些环境上的问题,比如存储器配置上的一些错误。值得注意的是,小范围的整合测试可以在模拟器环境上完成,此举可以加速测试的进度,然而,最后还是必须在实际机器上重复测试过。

■ 系统测试和确认测试:这两种测试必须要在实际机器上做,毕竟嵌入式系统项目的产出应该是一台应用于特定领域的机器,而不是PC程序,当然要在实际机器上测试所有预计的功能是否都可正确运行。系统测试旨在检查整个系统的功能是否完全合乎产品规格的需求,此处系统应该采用更广义的定义,即包含外观、结构、硬件、软件等,而这些测试当然必须在实际机器上执行。

■ 压力测试或极限值测试:压力测试是确认系统在负载极大时,是否还可以正常运行,执行压力测试有两个层次,一个是以使用者的角度,例如对键盘频繁且不规律的操作、同时执行许多应用程序,或使用仪器设备制造无用的网络封包或拥塞的网络环境等。另一个层次则是以系统架构或程序模块的角度来做压力测试,即由工程师写程序来测试系统模块,例如,反复不断的读写存储器,以测试其稳定度;模拟驱动程序持续不断的送出硬件事件,以测试上层模块是否可以应付此种状况、反复的allocate/free动态存储器、反复的读写文件等。

■ 自动测试:在每次推出新版本或程序经过大幅修改时,测试人员一定要再三确认基本功能正常无误,否则会发生为了修改某个使用者不那么容易碰到的问题,结果造成基本功能不work的现象。如果系统功能复杂,或者要检查的项目繁多,在版本发行频繁的时期,由人工执行这些测试会用掉过多时间及宝贵的测试人力,而且基本功能检查是个颇为烦闷的工作,难免发生人为疏失。较有效率的方法是开发一个自动测试的程序,可以在人工不介入的状况下,把所有的基本测试执行过一次,并编制简单的测试报告。

■ 硬件相关测试:嵌入式系统和一般软件项目最大的不同就是要搭配硬件,所以测试工作当然要包含硬件测试。硬件测试自然有其一套流程,软件工程师无须了解太多细节。

■ 强度测试:强度测试通常和结构设计有关,例如,机器从一公尺高摔下不造成任何破坏、75公分摔下来电池盖不能弹开、遭受多少重量的打击后LCD不会破裂、按钮可以承受多少次的操作等。

12.1.5 TDD(Test Driven Development)

TTD的中心思想是通过倒转开发流程(先有模块的测试程序,然后才有该模块的Implementation) , ‘逼迫’开发团队必须认真的做需求分析、明确定义模块的规格与接口,否则会连测试程序都无法开工。

12.1.6 静态测试概论

静态分析,就是我们不需要真正去执行编译、连接好的可执行文件,而只是通过扫瞄程序对程序的数据流和控制流等信息进行分析,找出系统的缺陷,得出测试报告。
对软件进行科学、细致的静态分析,使系统的设计符合模块化、结构化,面向对象的要求,使开发人员编写的程序代码符合规定的Coding Style,就能够避免软件系统中大部分的错误(如不匹配的参数、不适当的循环嵌套和分支嵌套、不允许的递归、未使用过的变量、空指针的引用和可疑的计算等),同时为日后的维护工作节约大量的人力与时间。

12.2 Bug Sheet管理

ugzilla则是记录bug和程序修改方式之间对应关系的好工具。此外,Bugzilla还应该是项目成员以bug为中心的在线讨论平台,某个bug当初为什么决定这样处理的来龙去脉应该都清清楚楚地被记录下来,这样可以减少许多事后的争议.

12.3 debug基本技法

■ 如果驱动程序已经通过严格测试,当发生PC OK但机器上NG的bug时,通常是存储器使用不当的问题。

■ 函数返回时死机,应该就是Stack Overflow的问题。

■ 当某块memory被破坏时,可以检查一下map文件,看看这块存储器的前后各有什么用途,应该就是邻近的存储器使用不当。

■ 当CPU发生Address Error时,在某些有Address Alignment限制的CPU上,可能是在奇数地址操作了偶数个Byte,此时,应该要检查程序中指针的值。

■ 如果heap被破坏了,可以在任何可疑的地方调用‘检查Memory Pool完整性’的函数,应该就可以找出程序中破坏heap的地方。

■ 在嵌入式系统中,局部变量是没有初始值的,必须由程序员自己设定。如果没设定局部变量的初始值就直接使用的话,将可能发生每次执行结果都不同的现象。

■ 在程序中要用sizeof()取得变量或数据结构真正占据存储器的size,不要自己想当然的直接填写数字,否则,很容易造成存储器操作的错误

12.4 Tunning

Tunning指的就是性能优化啦
性能优化前有一系列须知:

■ 在进行程序优化之前,先要有一个稳定、可靠的版本。而且在确定优化未完成前,不要将程序汇入正式的版本之内。

■ 调整编译器优化的等级。但务必记得一件事,编译器无法帮程序员完成所有优化的任务,尤其是风格不佳,或写得零零落落的程序。

■ 优先使用改良算法的手段来做性能调整。举例来说,无论再怎么精简程序,也无法弥补气泡排序法(Bubble Sort)与快速排序法(Quick Sort)间的性能差距。

■ 以空间换取时间。如果存储器还有剩的话,可以试着思考利用多余的存储器空间来改良算法,进而改善性能。

■ 不要把程序优化与‘用汇编语言修改’混为一谈。首先。应该找到程序的热区(Hot Spot) ,也就是程序中性能的‘瓶颈’所在,再利用所有你在学校学到与优化有关的技巧来试着修改它
    □ 尽量改写程序中用到乘法、除法与余数计算的部分
    □ 最大限度地减少分支(if……else if……else或switch指令)的数目与复杂度。
    □ 尽量循序处理数据。
    □ 检查并尽量减少for循环或while循环内的迭代次数
    □ 在迭代次数较多的for循环或while循环,尽量精简循环内的程序。

■ 不要试图从头到尾改写某个模块为汇编语言,先检查编译器产生的汇编语言代码是否有明显的性能问题,再根据这份汇编语言代码来修改即可。在修改时,应仔细研究CPU各个指令的性能,尽量使用性能较高的指令。

■ 去除不必要程序代码。许多用以调试的程序代码会忘了去除,导致在正式版本中,系统执行了许多没有必要的程序段。

12.4.1 执行时间测量

通常是订个计时器。如果测量精度比较低的话,可以考虑测试同一个功能一百万次,取其总和,再除以一百万,这样误差就很小

12.4.2 找到Hot Spot

一般情况下,Hot Spot都会出现在下列几种特性:

■ 迭代次数很多的循环。
■ 乘除法运算频繁的程序区段。
■ 频繁被调用的函数。
■ 递归的程序段。
■ 频繁task switch的程序段。
■ 容易造成heap碎片的程序。

12.4.3 在较快的存储器中执行程序或操作数据

除了上述优化的技巧之外,在嵌入式系统中有个很有效的方法,就是将造成系统性能瓶颈的程序区段,传输到速度较快的存储器中

广义来说,各种数据存储媒介的速度由慢到快依序是:
■ 必须经文件系统存取的NAND Flash(最慢)。
■ 直接存取NAND Flash。
■ 必须经文件系统存取的NOR Flash或Mask-ROM。
■ 直接存取NOR Flash或Mask-ROM。
■ CPU外部的RAM。
■ CPU内部的RAM。
■ CPU的一般寄存器(最快)。

12.4.4 Foot-Print调整

所谓Foot-Print就是足迹的意思,在系统开发中包含code size、run-time RAM usage、data size、SDRAM Bandwidth等。在项目初期要估计正确并不容易,其实应该说是很难才对!所以有经验的项目经理都倾向一方面在硬件规格上多要一点,另一方面压缩工程人员的空间。
你应该很容易可以想象,系统开发到后期才发现内存不够用了,这会是多麻烦的事情!
开发到后期才有这个问题,约等于寄了。~

13. 结项

使用者按不出来的bug可以不算bug
在项目开发的质量理论中,最重要的不是零缺陷,而是产品与项目的质量是受控制且可以保证的。

13.1 版本发行:兵荒马乱的日子

13.1.1 版本发行流程

发行版本基本的流程是:

■ 通知所有可能修改程序的工程师即将制作新版本,令其在某期限内将修改的程序check in到程序版本控制server上。

■ 令工程师填写新旧版本间的差异点,包含修改过什么程序、要解决什么问题、Side Effect评估等,并由负责发行版本的人列出新版本的重大修正项目。

■ 暂时关闭版本控制server,并从server取得最新版本的程序。

■ 重新build整个项目,如果其间发生任何错误,可要求任何相关人员立即提供协助,让版本可顺利发行是这个时间点最重要的事,所有项目人员都要全力配合。

■ 将build完成的Binary File烧录或下载到实体机器,并于短时间内测试完所有基本功能。
    □ 硬件相关特性测试,如系统耗电流、LCD Frame Rate(画面更新频率)、使用键盘时的反应速度与反馈力道等。
    □ 应用程序的基本功能应该快速地全部操作一遍。
    □ 基本的系统性能测试,这也是使用者层次的测试,事前先定义一些较具代表性的操作方式,每次发行版本之前都应该测试一下,观察其性能是否有变化。
    □ 执行工厂专用的自动测试程序。

■ 编写Release Note,其中包含:
    □ 项目名称。
    □ 版本名称。
    □ 日期。
    □ 发行文件明细:包含文件名称、用途、文件size与checksum等(如果版本是通过网络上传/下载,难免会出现错误。
    □ 新旧版本间的重大修改列表。
    □ ROM的checksum值。
    □ 如果客户有要求的话,可能还要附上测试报告或bug分析报表等数据。

■ 正式发行——应尽可能通知所有的关系人。

■ 版本备份——现在大容量烧录设备相当便宜,应该尽可能的把目前状态备份起来,包含:所有程序与数据、版本控制server数据库、技术文件等。

■ Bug管理server上增加新版本名。

■ 重新开启版本控制server。

13.1.2 正式版与内部测试版

应付客户无理要求时导致正式版本无法出(比如测试不全,客户要求增加功能等等),此时就出个测试版。
我们倾向不把这样的版本当作正式版本,否则测试人员的步调会被严重地打乱,如果客户可以接受的话,我们会称这个专为某人需求所制作的版本为‘评估板’、‘内部测试版’等。
其实这种状况很多,不仅仅只是来自客户的要求,在做性能调整时,当有了一定的进展后,我们会先做一个内部版本,要求测试人员在这个版本上,全面性地测试这样的调整对整个系统有没有Side Effect。这样的版本当然不会是正式的版本,甚至程序还可能会show出一些调试信息,如果把这种版本的所有bug都登上bug管理系统,那么,接下来肯定会天下大乱

13.1.3 版本号名称规范

在设计版本号命名规则时,必须考虑到3种人。

■ 客户或end user:版本号必须能告诉客户,和上一个版本相比,这个版本究竟有了多大的变化,是修复了几个无伤大雅的bug、新增了一个小功能,还是新增了一个大功能,或是对系统架构做了比较大的修改?客户可据此自行判断使用哪个版本风险较小。
以下是一个版本号定义的范例,用3个字母a、b、c代表一个版本,各字母的意义分别为:
a:产品世代
b:有新增功能或完成一个里程碑
c:小修改(bug修改)
我们还可在数字上做文章。举例来说,使用Linux的人都知道,kernel版本号若是偶数,表示这是稳定版;反之,则表示这是测试版本,可能有较大改变的功能正在开发中。所以想体验最新功能的工程师,自然会使用奇数的版本,而一般使用者则会使用比较稳定的偶数版本。
此外,我们可以使用诸如Alpha-1.0.2、Beta-0.5或RC-0.5(RC是Release Candidate的意思),这样的命名可用来表示开发进度、开发状态或质量属性。

■ 测试人员:同一个版本可能会不只一次送测,所以测试人员必须能从版本号来区别这些版本。最简单的做法就是在版本号上再增加一位数字编号,表示这个版本的第几次送测,或者直接在版本号后直接增加送测日期。
版本控制软件会为程序每个时间点的snapshot指定一个编号,任何时间只要指定这个编号,就能取出该时间的所有程序。有时工程端送测时会把这个编号加在版本号后面,如0.9.8-build1234,其中1234就是表示版本控制软件中的编号。测试人员可根据编号自行取出所有程序代码,自己build并下载,然后才开始测试。这样能避免系统工程师用来出版本的程序与版本控制软件里的版本不一致,使得测试结果不具意义,而且很可能最终会导致某程序的版本遗失。

■ 开发团队内部成员:开发团队常需要制定许多不对外发行的内部版本,代表某个里程碑达成,或表示从这个时间点将开始对系统进行大修改,必要时仍可‘退回’这个版本。这些版本通常都有技术上的意义,所以很难用数字表示,一般的做法是利用版本控制软件的功能,为某个时间打上诸如‘系统主功能完成’、‘内部测试版本for final release testing’、‘动态存储器配置算法大改开始’的标签(Label)。

13.2 自动测试程序

按下神秘按键,进入某个测试模式
用于工厂的自动测试程序可不是随便写写就好,也是有需求规格的,通常这个程序的规格会由主管生产事务的工程师,与软、硬件开发工程师讨论后制定而成,这个程序的使用者是工厂生产线上的操作员,而非一般的消费者,所以操作模式与使用者界面势必与产品的应用程序本身大相径庭。
自动测试设计的内容如下:

■ 各个硬件功能测试,硬件设计中的重要组件IC与周边设备都应该要测试,确定其可正常运行。
■ 确认RAM读写功能OK。
■ 计算ROM的checksum。
■ 基本的CPU与存储器性能测试。

许多硬件的测试还是必须让操作员介入,如键盘测试,总要每一个key都按过一次才能确定OK;LCD总要有人看着,才知道显示的内容是否合乎预期;音乐或语音播放的功能,必须要有人听着才知道播放结果是否正确……”
所以完全自动只是理想,不需要操作员介入几乎是不可能的

13.3 决定量产版本

通常情况下,由客户决定,如果客户没脑子,就需要自己说服客服选择自己认为合理的版本(要是客户选的版本有问题,卖的时候出了事故,客户肯定也会找开发商背锅,即使版本是他自己选的)
质量是一个相对的概念,并不是绝对的,我们的标准觉得OK,但对旁人来说可能还觉得不够,反之亦然。

13.4 出货≠结项

好不容易客户签了量产同意书,虽然现在第一批产品已经上市了,但客户还在追着许多未解的bug,执意要我们尽快全部修改完,并发行一个正式的‘Running Change’版本,看来客户还没有想要结项的意思

13.4.1 Running Change

所谓Running Change,就是已经把产品出货在市场上售卖了,客户还是希望我们更改bug的情况。
然而这时,开发商的项目人员基本都跑去做别的项目了
如果使用者对现行产品规格有意见或建议的话,我们应当严肃地告诉客户,这些新功能的规格已经超出原本项目的范围了,如果确定要修改的话,应该要重新发起一个项目,即便这个项目的范围相对较小,所需时间与资源较少。如此一来,项目经理才可(才有理由)在组织内正式召集项目成员进行修改。这才是正确的方法,我们不该漠视产品出货后组织成员解散的事实。
如果碰到老是喜欢搞‘Running Change’这套的客户,在项目开始之前,我会建议最好可以顺便签下维护合约,将预估的人力与工时一并计费,如此一来,项目经理才有理由在产品出货后,还能把重要的项目成员留下,这样对产品的质量也比较有保障。

13.4.2 CostDown版

所谓的Cost Down版,是在产品功能和外型完全不变的前提下精简硬件设计,如flash换成ROM,选用较便宜的替代组件,以达成降低造价与成本的目的。
就是客户觉得这个产品成本太高了,要你做一个功能,外形一模一样,但是内部硬件成本降低的版本。
无论如何,既然大幅度更改了硬件设计,所有量产前的程序应该都要再来一次,例如试产、硬件测试与环境测试、软件测试、发行版本等。所以我说应该重新启动一个项目来做这个Cost Down版本并不为过吧

13.4.3 出货之后:你以为就结束了吗?

■ 客诉:
所谓的客诉是使用者购买了某个产品后,使用上发现了重大的瑕疵或致命的问题,使用者可能投书、上公司的官方网站,或在网络的讨论区陈述自己的发现与不满。通常使用者不会为了小问题而大费周章,且客诉影响公司品牌形象甚巨,如果不好好处理,可能会严重影响产品在市场上的接受度与销售量。当客户的产品接到客诉时,势必会要求研发团队进行分析与解释,这件事情如果不好好处理的话,就会变成客户对我们研发团队的‘客诉’。所以客诉事件必须优先处理的工作,此时,项目经理一定要把相关工程师借调出来,找出问题所在,并视问题的严重性与客户的需求,决定是否要发行新版本。

■ 组件涨价、停产或买不到货:有的存储器IC或电子组件就如同期货一样,价格时时在变化,当价格涨到不敷成本时,客户就会要求硬件工程师寻找替代方案(Second Source),如此一来,无可避免的也会冲击到软件。我也曾碰过某个IC因为单价比较贵,工厂备料不足,结果量产后不久市场即缺货,导致我们产品生产线可能面临停摆的窘境,此时,客户也会要求尽快寻找该IC的替代品,同时软件也必须对应并发行版本。严格来说,这些都是预期之外的状况,通常研发部门都会尽量帮忙。

■ 量产时不良率偏高:我已经遇过好几次这种状况,有大批成品已从工厂出货了,生产线的不良率都维持在可接受的范围内,突然在某一时段内的不良率突然飙升,生产线因而停摆,客户当然要求调查。如果一切条件都没变的话,最简单的推论当然就是软件不稳定,这句话几乎是工厂拖延时间或推卸责任的万灵丹。生产线停下来是极为严重的事情,于是相关工程师被召集到工厂分析问题,虽然我们都知道,同样的软件不太可能在生产几千台之后才爆发出问题来。

13.5 项目结项

因为基于客户的态度不同,结项分成了两种:

■ 行政结项:对研发团队来说,项目的经验累积相当重要,所以项目经理一定要趁key man都还在时进行行政结项,所谓的行政结项最重要的工作就是收集并整理所有的文件、资料与检讨报告。
我的做法是当产品出货后的一个礼拜,所有大、小主管什么事都不用做,就和我关在会议室中收集资料和检讨此次项目的得失,至于其他基层人员则‘在营休假’,只要配合他的主管收集资料即可。
当所有资料收集完成、制作清单并备份之后,才可以分批解散项目成员,归还项目资源,如计算机、测量仪器等,直至所有文件归档之后,才算完成行政结项作业
    □ 技术文件之外的所有的项目基本文件,包含合约复本、项目计划书、预算书、成本分析、Schedule、WBS(Work Breakdown Structure)、人员组织图等。
    □ 产品规格书及其修改记录。
    □ 硬件相关图面,如电路图、Layout图等,最好从Target Board到最终量产的版本都有。
    □ 硬件设计规格书以及所有组件的data sheet。
    □ 自组件厂商处取得之所有技术文件与sample code。
    □ 所有与ID以及结构设计有关的图面与文件。
    □ 每一个版本的备份光盘。
    □ 量产版本的完整程序代码。
    □ 版本管理系统之数据库备份。
    □ bug管理系统之数据库备份与各时期的bug状态报表。
    □ 所有与项目有关的mail与会议记录。
    □ 项目检讨报告。

■ 合约结项:如果项目发起人(通常是客户)愿意进行合约结项的话,我们会安排正式的close meeting,并把行政结项阶段收集的所有资料准备好,然后‘恭候’客户代表前来验收。
在这个会议中,双方可以讨论项目进行期间的缺失,以及客户对最终交付成果的看法,有些客户会要求对尚未解决的open issue或bug逐一解释,再次确认对产品没有严重影响后,就会将其close掉。
根据客户类型的不同,close meeting有时很快就可结束,有时要耗费n天;有时只要项目经理或老板在就可以;有时则需要工程师列席备询。此时我们通常会逆来顺受,只求客户赶快在结项同意书上签名,并确认取得尾款的日期,最后还要请客户填写满意度调查表。合约结项完成后,这个项目到此正式结束。

项目管理的教科书告诉我们,应该要先完成合约结项,才可以进行行政结项。但对嵌入式系统开发项目而言,实际上真的很难等到合约结项,才开始做项目资料的收集与人员解散

13.6 期许下一个项目

以软件工程师的角度来说,写嵌入式系统的程序并没有特别复杂,甚至我们现在几乎不写汇编语言了,我觉得最重要的是理解要正确,只要以下理解正确,一般软件工程师要跨入嵌入式系统开发领域并不困难。

■ 彻底搞清楚计算机系统的运行原理。千万不要拘泥于某颗CPU、某个IC或某种类电子产品的操作细节或开发工具,要认清楚什么只是厂商的‘规定’,什么才是放诸四海皆准的思想、技巧和知识。
■ 清楚嵌入式系统的特性。
■ 系统架构的重要性。
■ 模拟器的重要性与用途。
■ 驱动程序其实并不如想象的困难麻烦、硬件的理解也不如想象中可怕。
■ 即使是写应用程序,都该考虑资源的局限性。
■ 不要排斥与不同领域技术人员的沟通与合作。

这是一个知识爆炸的时代,无论嵌入式系统开发、项目管理、质量管控或其他事情,重要的都是理解是否正确,这会直接影响你处理事情的方法与态度,至于实作的细节,用到的时候再去查就好了。