第二章 个人技术和流程
《构建之法》第二章主要讲了个人技术和流程,因为团队需要一定的流程来管理开发活动,每个工程师在软件生命周期所做的工作也应该有一个流程,这一章中着重介绍PSP,即个人软件开发流程。主要讲了单元测试、回归测试、效能分析、个人软件开发流程。
其中,单元测试能让自己负责的模块功能定义尽量明确,模块内部的改变不会影响其他模块,而且模块的质量能得到稳定的、量化的保证。单元测试应该准确、快速地保证程序基本模块的正确性。下面是验证单元测试好坏的一系列标准:
1. 单元测试应该在最基本的功能/参数上验证程序的正确性:单元测试应该测试程序中最基本的单元—如在C++/C#/Java中的类,在此基础上,可以测试一些系统中最基本的功能点(这些功能点由几个基本类组成)。从面向对象的设计原理出发,系统中最基本的功能点也应该由一个类及其方法来表现。单元测试要测试API中的每一个方法及每一个参数。
2. 单元测试必须由最熟悉代码的人(程序的作者)来写:代码的作者最了解代码的目的、特点和实现的局限性。所以,写单元测试没有比作者更适合的人选了。最好是在设计的时候就写好单元测试,这样单元测试就能体现API的语义,如果没有单元测试,语义的准确性就不能得到保障,以后会产生歧义。
3. 单元测试过后,机器状态保持不变:这样就可以不断地运行单元测试,如果单元测试创建了临时的文件或目录,应该在Teardown阶段删掉。如果单元测试在数据库中创建或修改了记录,那么也许要删除或恢复这些记录,或者每一个单元测试使用一个新的数据库,这样可以保证单元测试不受以前单元测试实例的干扰。
4. 单元测试要快(一个测试的运行时间是几秒钟,而不是几分钟):快,才能保证效率。因为一个软件中有几十个基本模块(类),每个模块又有几个方法,基本上我们要求一个类的测试要在几秒钟内完成。如果软件有相互独立的几个层次,那么在测试组中可以分类,如数据库层次、网络通信层次、客户逻辑层次和用户界面层次,可以分类运行测试,比如只修改了“用户界面”的代码,则只需运行“用户界面”的单元测试。
5. 单元测试应该产生可重复、一致的结果:如果单元测试的结果是错的,那一定是程序出了问题,而且这个错误一定是可以重复的,单元测试不能解决所有问题,不必期望它会发现所有的缺陷。
6. 独立性—单元测试的运行/通过/失败不依赖于别的测试,可以人为构造数据,以保持单元测试的独立性:程序中的各个模块都是互相依赖的,否则它们就不会出现在一个程序中。一般情况下,单元测试中的模块可以直接引用其他的模块,并期待其他的模块能返回正确的结果。如果其他的模块很不稳定,或者其他模块运行比较费时(如进行网络操作),而且对于本模块的正确性并不起关键的作用,这时可以人为地构造数据,以保证单元测试的独立性。
7. 单元测试应该覆盖所有代码路径:单元测试应覆盖所测单元的所有代码路径,包括错误处理路径。为了保证代码覆盖率,单元测试必须测试公开的和私有的函数/方法。
-
代码覆盖率对于“应该写但是没有写的代码”无能为力。例如代码申请了内存或其他资源,但并没有释放。又如,代码中并没有处理错误情况。就像没有处理和文件、网络相关的一些异常情况,例如文件不存在、权限有问题,等等
-
代码中有效能问题,虽然代码执行了,并且也正确地返回了,但是代码效率非常低。有些情况下,可以针对代码效率写一个单元测试
-
多线程环境中的同步问题,这个问题和代码执行的时序、共享资源的锁定有关
-
其他与外部条件相关的问题(例如与设备、网络相关的问题)
8. 单元测试应该集成到自动测试的框架中:要把单元测试自动化,这样每个人都能随时、随地运行单元测试。团队一般是在每日构建之后运行单元测试的,这样单元测试的错误就能及时被发现并得到修改。
9. 单元测试必须和产品代码一起保存和维护:单元测试必须和代码一起进行版本维护。如果不是这样,过了一阵,代码和单元测试就会出现不一致,程序员要花时间来确认哪些是程序出现的错误,哪些是由于单元测试滞后造成的错误。
回归测试是工程师在新版本上运行所有已通过的测试用例,以验证有没有“退化”情况发生,如果这样的“倒退”是由于模块的功能发生了正常变化引起的,那么测试用例的基准就要修改,以便和新的功能保持一致。
在单元测试的基础上,我们就能够建立关于这一模块的回归测试。在软件项目中,如果一个模块或功能以前是正常工作的,但是在一个新的构建中出了问题,那么这个模块就出现了一个退步,“从正常工作的稳定状态退化到不正常工作的不稳定状态”。在一个模块的功能逐步完成的同时,与此功能有关的测试用例也同样在完善中。
效能分析工具:两种分析方法为抽样和代码注入;一般做法为先用抽样的方法找到效能瓶颈所在,然后对特定的模块用代码注入的方法进行详细分析;如果我们不经过分析就盲目优化,也许会事倍功半。
1. 抽样:简单来说,抽样就是当程序运行时,编译器时不时看一看这个程序运行在哪一个函数内,并记录下来。程序结束后,编译器就会得出一个关于程序运行时间分布的大致印象。这种方法的优点是不需要改动程序,运行较快,可以很快找到瓶颈,但是不能得出精确的数据,也不能准确表示代码中的调用关系树。
2. 代码注入:代码注入就是将检测的代码加入到每一个函数中,这样程序的一举一动都被记录在案,程序的各个效能数据都可以被精准地测量。这一方法的缺点是程序的运行时间会大大加长,还会产生很大的数据文件,也相应增加了数据分析的时间,同时,注入的代码也影响了程序真实的运行情况。
个人软件开发流程:根据数据显示,从学生到职业程序员,并不是更加没完没了地写程序,花在写代码的时间反而少了许多,而在“需求分析”和“测试”这两方面明显要花更多时间,在具体编码上花费时间少。
个人项目耗时记录表
PSP2.1 | PSP Stage | Time(%) SDE |
---|---|---|
Planning | 计划 | 6% |
Estimate | 估计这个任务需要多少时间 | 6 |
Development | 开发 | 88% |
Analysis | 需求分析 | 10 |
Design Spec | 生成设计文档 | 6 |
Design Review | 设计复审(和同事审核设计文档) | 6 |
Coding Standard | 代码规范(为目前的开发制定合适的规范) | 3 |
Design | 具体设计 | 12 |
Coding | 具体编码 | 21 |
Code Review | 代码复审 | 9 |
Test | 测试(自测,修改代码,提交修改) | 21 |
Reporting | 报告 | 6% |
Test Report | 测试报告 | 2 |
Size Measurement | 计算工作量 | 1 |
Postmortem & Process Improvement Plan | 事后总结,并提出过程改进计划 | 3 |
PSP有如下的特点:
1. 不局限于某一种软件技术(如编程语言),而是着眼于软件开发的流程,这样,开发不同应用的软件工程师可以互相比较;
2. 不依赖于考试,而主要靠工程师自己收集数据,然后分析,提高;
3. 在小型、初创的团队中,很难找到高质量的项目需求,这意味着给程序员的输入质量不高。在这种情况下,程序员的输出(程序/软件)往往质量也不高,然而这并不能全部由程序员负责;
4. PSP依赖于数据:
需要工程师输入数据,记录工程师的各项活动,这本身就需要不小的时间代价
如果数据不准确或有遗失,怎么办?让工程师编造一些?
如果一些数据不利于工程师本人(例如:花很多时间修改缺陷),我们怎么能保证工程师愿意如实地记录这些数据呢?
5. PSP的目的是记录工程师如何实现需求的效率,而不是记录顾客对产品的满意度。
虽然,在实际软件开发中,需要团队的合作,但是在团队中,每个人都应该到达一定的水平,也就是这章讲述的个人技术和流程,可以说这是基础,只有每个人的技术和开发流程打到一定的标准,有一定的规范,才有利于团队之间多人的合作,才能保证配合开发出来的软件的各个模块都是符合标准的,具有一定健壮性的,利于维护和更新。