有一个惊人的数据,设计期间程序员平均每小时会引入1 ~ 3个缺陷,编码期间平均每小时引入5 ~ 8个缺陷。
有许多同样惊人的数据显示,协同构建可以缩短开发周期,通过代码复查检查错误成本比测试更低,而且可以检查到一些更隐蔽的风格、注释等错误。另外,开发者考虑到需要经过代码复查,编写时便会更加审慎。
这本书对结对编程没有那么重视,只是将其看做一个有一定意义但不适合大规模项目的实践方法,因此讨论得没那么详细。尽管如此,我阅读时联想自己结对编程的经历,还是很有启发。这里的错误几乎每一个我都有犯过,并在结对编程小结与收获中描述过。因为不想再抱怨一遍自己犯过的错误,下面就不再提了。
结对编程的核心思想在于一个人进行编程,另一个人注意检查有没有错误,并考虑设计与策略。
结对编程的关键:
- 要约定代码规范。不要将很多无谓的时间花在纠结代码风格上,而应该尝试对其标准化。我们就犯了这个问题。
- 不要让结对变成旁观。不掌握键盘的人应该主动积极地检查,并且思考下一步应该做什么。不过我们连旁观都没有(摔。
- 不要强迫在简单的问题上使用结对编程。很多问题可以讨论之后分别编程,或者对部分工作结对编程,而不需要完全结对。
我没有经验并不知道什么需要结对什么不需要结对,但个人感觉这次作业是需要结对的。
另外部分结对编程这个想法给了我启发,也许我们的团队项目中也可以考虑部分工作采取结对编程的方法(当然前提是队友同意我的奇思妙想啦)。 - 有规律地进行人员和工作调换。
对于结对双方(驾驶员和领航员)的工作调换,目的在于让双方都能熟悉系统的不同部分,并且合理地分摊工作量。
然而,书中还说到,对于采用了结对编程方法的团体项目,不同的结对小组之间的工作安排也应该调换,这点我不太能认同。对于一个大的项目,试图让每个人熟悉系统的每个部分在我看来是一个非常低效的做法。 - 鼓励双方跟上彼此的步伐。
- 保证双方都能看到显示器,而且字体、亮度等应该合适。这是一个emmmm比较琐碎的建议,只要结对双方能够沟通应该很好解决吧。
- 不要强迫关系不好的人结对。也要避免两个新手结对。我们这次就是新手结对。半学期以来我觉得新手进行软件工程的工作真的比较刷运气,运气好就成功了获取了成功经验,大部分时候获取的都是失败经验。
- 如果在团体项目中使用结对方法,也还是需要指定一个组长,来协调工作并对结果负责。
结对编程的好处:
- 结对编程有利于编写高质量代码。
- 结对编程能增加代码的可读性和可维护性。
- 结对编程效率更高,而且错误更少,减少了后期维护需要的投入,从而直接间接地有利于整体进度。
正式检查(即详查)似乎是一个相当正规的存在,有着相当正规的流程。因为我们现在用不到,所以看得半懂不懂、也没太多感想,先抄写一下。
一些比较突出的点有:
- 详查关注检测错误,而非急于对其修正。
- 详查者和代码编写者并非一人。
详查的优点:
作者同样给出了数据表明详查能够查出很多错误。另外,通过详查,开发者可以更了解自己现有的方法有什么缺陷,如何改善工作。最后详查还可以用来检查进度。
详查中的人员角色:
- 主持人。不一定需要是专家,但应该在技术上能够理解详查的各种细节。负责管理协调各方面工作,安排进度,还有一堆杂活。(从这些杂活就能看出来正式检查真的很正式啊……)
- 作者。相对次要,主要在代码不够明晰时讲解自己的代码。
- 评论员。负责进行探讨、找出问题。
- 记录员。
- 经理。没什么卵用,就是有时候人家想参加而已。
一般步骤:
- 计划。一些杂活。
- 概述。作者陈述代码设计及其技术背景。有时候这个阶段可以忽略。
- 准备。各个评论员独立地对代码进行考察。要求每个评论员关注代码的不同方面、特性往往能增加效果。
- 详查会议。挑选某个人阅读代码或者顺序解释代码逻辑,各个评论员提出自己认为的错误并进行讨论。当确认一个错误确实是错误是就停止,而不要纠结于如何改正之类的问题。主持人应该把握节奏,不要太快也不要太慢。
- 详查报告。
- 返工
- 跟进
- 在详查会议之后,如果有人想讨论解决方案,可以召开一个额外的半正式的小会议。
对于详查,书中给出了一些小的建议。
首先,可以建立一个核对表,详查过程中提醒评论员尤其注意某些错误。
其次,详查不应该成为代码的公开处刑。应该避免一些过于苛刻的评论,作者也不应该因为自尊心而袒护自己的代码。
这里的技巧相对不那么正式,比较适合我们用。
走查
其实所有形式的复查都可以叫做走查……不过各种形式的复查都有一些共同点:
- 通常由代码的作者主持。
- 焦点在于技术问题上。
- 和详查一样,重点在于检测错误而非纠正错误。
走查的结果:
作者对走查的效果较为悲观。好的情况下,走查的效果和详查相近。然而很多情况下,非正式的走查效果很差。
对其原因,作者并没有作出解释。不过作者指出,走查尽管非正式,仍然需要很高的成本,所以假如认为复查的需求真的足够迫切,就应该召开正式检查而非走查。否则,应该采用阅读代码等交互性较低的方式。
代码阅读
准备阶段:将源代码交给阅读人员。一般为1000 ~ 10000行,典型值为4000行。应安排两个人以上阅读代码,以维持一定的竞争。一般要求每天阅读1000行代码。
会议阶段:阅读人员指出自己发现的问题并进行讨论。
与详查、走查区别在于,代码阅读注意力集中在发现的问题上,而不需要遍历代码。有人曾指出详查会议的作用被高估了,大部分错误是在准备过程中发现的,而非讨论过程中发现的。这个看法我比较认同,因为在会议上遍历代码时注意力可能处于一个较为分散的状况,不像单独阅读时能够注意思考逻辑。这样可能更容易发现一些微妙的、有争议的问题,但我不认为效率能和单独阅读代码一样高。
私以为这个方法对我们比较合适。
分类:
- 单元测试。通常只涉及单个程序员或开发团队。
- 组件测试。似乎和单元测试差不多,不过会涉及多个程序员或开发团队。
- 集成测试。对多个组件进行联合测试,通常应该在有两个可供联合测试的类之后就尽快开始并持续到开发结束。
- 系统测试。主要针对安全、性能、资源消耗、时序、兼容性等问题。
我个人编写测试时,有一些问题,比如这次结对编程作业中因为需要一定的随机性,所以很难自动进行测试,而只能人工测试。不知这个问题如何解决。
事实上,测试检查错误的能力没有协同构建高,而且修正成本更高。
要点:
- 对模块的每个需求进行测试,对常见的疏漏进行测试。如:安全级别、数据存储、安装过程、系统可靠性。(但是很多问题除非发生,否则根本想不到需要测试)
- 对每个设计的关注点进行测试。
- 基础测试、数据流测试。
- 用检查表记录此前犯过的错误类型。
作者推荐测试先行。理由如下:
- 测试先行可以将需求先暴露出来,迫使开发者思考一下需求和设计。
- 编码过程中更容易发现缺陷并改正。
- 开发者往往更倾向于引入会正确运行的测试样例,而非会出错的测试样例。可能是因为受到了编码思路的限制。
- 开发者容易对覆盖率估计过于乐观,忽略一些更复杂的测试类型。
不完整的测试
没有测试能够完整地测试每种情况,所以应该挑选最容易出错误的情况。
#### 结构化的基础测试对于一个程序,至少应该对每个语句测试一次。如果是if或while,应该根据逻辑结构的复杂度增加测试,保证这个语句完全经历了测试。
可以如此计算需要的最少测试:
- 顺序结构数1。
- 遇if for while repeat and or之类的东西时,加一。也即,不仅各个分支需要进行测试,对于同一个布尔表达式中,若干个布尔值的不同组合方式也要测试。
- 对于case的各个情况,包括可能被省略的缺省状况,分别加一。
数据有三种状态:已定义(包括初始化)、已使用、已销毁。
调用数据的子程序也有两种状态:已进入、已退出。
编写测试前,首先要检查保证没有出现异常的数据状态组合,如先使用后定义、多次定义、多次销毁、局部变量定义后不使用便退出、使用已销毁的变量等等。
然后,不同彻底程度的测试如:
- 覆盖所有的定义。
- 覆盖所有的定义与使用路径。
这里书上讲得比较模糊、我不太理解,所以我查找了一些别的资料。
我的理解中,数据流判准主要用于这样的情况:一个子程序中调用其他子程序,或者顺序调用若干个子程序时,它们由于共用数据会彼此依赖,而单纯的单元测试可能难以发现因为数据上的依赖而形成的错误。
因此应该测试覆盖某个数据从定义到使用到销毁的各种可能路径。
可以借助图标,将数据的定义、调用等可视化,
然而个人看来,这样的工作量非常大,看起来操作性没有语句覆盖、分支覆盖强。之后尝试一下,不知道实际意义如何。
猜测错误
参考下面几个小节的常见错误。
边界值分析
对于> max_num这类表达式,需要分析和测试刚好小于max_num、刚好等于max_num、刚好大于max_num三类情况。
也应该对类型允许的最大最小数字进行检查。
隐蔽的边界值分析:如果边界值涉及若干个值,需要考察它们的极端情况。如要边界值涉及若干个值的和,需要考察它们都很大、都很小的情况。
几类坏数据
需要保证程序不会因坏数据发生超出预期的行为。如:
数据太少、数据太多、数据无效、长度错误、数据未初始化(这个在java里面比较突出)。
几类好数据
也需要测试好数据。如:
中间的正常情况、最小的正常情况、最大的正常情况、与旧数据的兼容性。
建议采用容易手工检查的测试数据
这个对我来说很实用,因为我经常自己算错了还以为是测试的问题。
但是不应该因此就省略若干情况,依然应该尽量面面俱到。
TBC
因为这个章节讲了很多查错的技巧,整体上内容比较多,所以就先写到这里了。
软件质量这一部分大概也看了一大半,总结一下:作者整体上较为看好协同构建的方法,并认为测试的作用往往被我们高估。另外,在协同构建的方法中,作者推荐详查(对于我们来说毫不现实)和阅读代码。