代码大全_cc21-25


第二十一章 项目大小如何影响创建 351 第二十一章第二十一章第二十一章第二十一章 项目大小如何影响创建项目大小如何影响创建项目大小如何影响创建项目大小如何影响创建 目录目录目录目录 21.1 项目大小 21.2 项目大小对开发活动的影响 21.3 项目大小对错误的影响 21.4 项目大小对生产效率的影响 21.5 小结 相关章节相关章节相关章节相关章节 创建前提:见第 3 章 创建管理:见第 22 章 在软件开发中常出现小的项目随着开发的进展,项目规模不断变大的现象,假定你计划用 6 个月的工作量开发——5000 行的软件包,并在进行测试时发现 25 个错误。软件开发成功了, 软件也可运行了,但可能导致程序的长度大大增加,甚至可达 50,000 行之多。 虽然最终软件的大小是所预期的 10 倍,并不意味着也需花费 10 倍的工作量,而可能是 20 倍的工作量。而且,20 倍的工作量并不意味着 20 倍的创建,也可能是 12 倍的创建和 40 倍的 系统综合和测试。你也将可能获得不仅是 10 倍的错误,而是 15 倍的错误。 如果你习惯于开发小项目,你的第一个中等项目将是非常困难的,而且不是你预期中的令 人愉快的成功。本章将讨论此问题并给出了具体应付方法。如果你已习惯开发中等项目,你可 以利用你的经验进行一次小项目开发。本章也将告诉你如何对项目进行控制。 21.1 21.1 21.1 21.1 项目大小项目大小项目大小项目大小 你所在项目的大小是典型的吗?项目大小的范围很宽,意味着项目不是典型的。估计项目 大小的一个方法是看项目开发组人员的多少。以下是二者间的粗略关系: 项目组人数 项目所占百分比 1~3 50% 4~8 33% 8~12 6% 12~20 5% 20~50 3% 50+ 2% 并不那么直观的数据,却反映了不同项目的大小和程序员人数之间的差别,大的项目往往 使用更多的程序员,以下是对程序员和项目大小的粗略估计: 第二十一章 项目大小如何影响创建 352 程序长度 程序员所占百分比 (以代码长度计算) 2k 5~10% 2k~16k 5~10% 16k~64k 10~20% 64k~512k 30~40% 512k+ 30~40% 21.2 21.2 21.2 21.2 项目大小对开发活动的影响项目大小对开发活动的影响项目大小对开发活动的影响项目大小对开发活动的影响 如果只有你一人开发项目,对项目成功或失败影响最大的正是你自己。如果你所在项目开 发组有 25 人,你也可能仍发挥最大作用,但更可能是大家各有一份功劳,整个集体对项目的成 功或失败有更大的影响。 交流和大小交流和大小交流和大小交流和大小 如果你是某项目的唯一开发者,你唯一交流的途径是你和用户所进行的交流,其作用就好 似将你的左、右大脑联系起来。在项目组人数增多时,交流途径也相应增加了,但是二者的关 系并不是线性的。交流途径是和人的平方数成线性关系的。以下是图示: 1 3 6 两个程序员之间的交流路径 三个程序员之间的交流路径 四个程序员之间的交流路径 10 45 五个程序员之间的交流路径 十个程序员之间的交流路径 你可看到,二位程序员之间仅有一条交流途径,五位程序员间有 10 条交流途径,而 10 位 程序员间有 45 条交流途径——假定每一位程序员都可和其他程序员交谈。其中 2%的有 50 或 更多程序员的项目,其交流途径至少有 1200 条可挖掘途径。交 流 途径越多,你放在交流上的时 间越多,也就越容易出现错误。大的项目要求采用一定的方法以简化交流方法,以对其作一定 程度的限制。 第二十一章 项目大小如何影响创建 353 简化交流的典型方法是规范化交流方法。不是让 50 个人按各种可能方式相互间进行交流, 而是让 50 个人都阅读和编写文档,有些是文本的,而有些则是图形的,有些是打印在纸上的, 或是表格形式的。 活动比例和大小活动比例和大小活动比例和大小活动比例和大小 正如项目越大,所要求的信息交流也相应增加,项目所需各种活动的种类也急剧地变化。 以下是关于不同项目的大小和各种开发活动的比例的关系: 项目大小和代码行数 对于小项目,创建是最为突出的活动,占了整个开发活动时间的 80%。对于中项目,创建 仍然是主导性的活动,但是所占比例下降了 50%,对很大的项目,结构、综合、系统测试和创 建所占时间比例大致相等。总之,随着项目的增大,创建所占整个工作时间的比例将减小。从 上图可看出随着项目的增大,创建最终将消失。 创建之所以随着项目的增大而变弱,是因为创建的各种活动——详细设计、调试、编码单 元测试——是线性增长,而其它各种活动则是按乘幂的关系增长的。 以下是图示: 例如,某一项目是另一项目大小的二倍,则二者的创建工作量之比可能为 2 : l ,而综合 和系统测试工作量之比为 4 : l 。当最终开发所得的第一个项目的大小是另一个的 10 倍时,它 可能需要 12 倍的创建工作量,100 倍的计划工作量和 40 倍的综合测试量。以下是随项目的增 大而工作量增大的其它活动: 第二十一章 项目大小如何影响创建 354 · 计划 · 管理 · 交流 · 需求开发 · 系统功能设计 · 接口设计和描述 · 总体结构 · 综合 · 错误消除 · 系统测试 · 文档生成 不论项目的大小如何,以下方法总是有价值的:结构化编码。其它程序员对逻辑和代码的 检查、联机开发系统和高级语言的使用,对于本项目,这些方法可能并不充分,但是对小项目 却是适用的。 方法和规模方法和规模方法和规模方法和规模 方法被广泛用于各种大小的项目中。对小系统,方法往往是偶然的和本能的。对大项目, 方法往往是精确和仔细计划的。 其中一些方法是可自由使用的,以致于程序员甚至不知道自己在使用它们。而部分程序员 甚至说这些方法太教条化了,而不采用它们。程序员有时可能会不自觉地采用某一种方法,程 序编制的任何一种途径都包含着某种方法的使用,不管这种方法是多么不自觉或原始的。就连 早上起床后去上班,虽然不是一种创造性的方法,但也是一种基本的活动。坚持不使用方法的 程序员,实际上只是不明确选用了方法——没有人不使用某一种方法。 正规的方法并不令人高兴。如果误用了,往往会引起麻烦。大的项目较为复杂,需加强对 方法的自觉使用。正如建造摩天大楼需应用各种方法一样,相同大小的项目也需各种方法。不 管你是否认为“军事才能”是一个矛盾的词,你应认识到军方是计算机的最大用户,并且也是 最大编程研究的赞助者之一。它提倡的对问题的正规方法包括从 12 个方面对一个程序项目进行 评分。在表 21 一 1 中给出了项目正规性明细表。 表表表表 21212121----1 1 1 1 项目正规性明细表项目正规性明细表项目正规性明细表项目正规性明细表 评 分 因素 1 2 3 4 5 总分 1.初始要求 没有,对各种 最小,有较多 有限制;对环 相当多的熟练 非常广泛地提 不同的设备重 的苛刻要求 境应用新的界 应用现存状态 高现存状态 __ 新编程 面 2.通用性 高限制;单目 有限制;规定 限制灵活性; 多目的;格性 非常灵活;在 的 一定范围的能 允许格式修改 灵活对象范围 不同的设备上 力 广 不同,可适应 对象范围很广 __ 第二十一章 项目大小如何影响创建 355 评分 因素 1 2 3 4 5 总分 3. 操作跨度 局部的或工具 部分命令 单一命令 多命令 广泛地适用于 的 各军事部门 4. 范围和对象 无 不经常 偶然 经常 连续 的修改 __ 5. 设备复杂性 单机进程处理 单机子进程处 多计算机标准 多计算机高级 主控制系统, 理,扩展外围 外围系统 编程,复杂外 多计算机,自 系统 围系统 动输入--输出 和显示设备 6. 人事安排 1-2 3-5 5-10 10-18 18 以上 7. 开发代价 3-15k 15-70k 70-200k 200-500k 大于 500k 8. 关键 数据处理 子程序操作 人事安全 单元残存 国家防务 9. 对程序修改 >=2 周 1-2 周 3-7 天 1-3 天 1-24 小时 的平均反应时 间 10. 对数据输 >=2 周 1-2 周 1-7 天 1-24 小时 9-60 分钟(交 入的平均反应 互式) 时间 11. 编程语言 高级语言 高级语言和限 高级语言和扩 汇编语言 机器语言 量制汇编语言 充汇编语言 12. 软件开发 无 有限 中等 广泛的 深入的 中的合作性 总计 使用项目正规明细表,一程序的得分可在 12-60 之间。得 12 分意味着对项目的要 求是轻微的,且对其正规性要求很少。得 60 分意味着项目要求很高,且 对 结构的规定也 很多。在社交场合,越是正式,你的服饰就越让人不安(高跟鞋,领带等)。在软件开发 中,项目越正规,你就越需付出更多的工作量。根据项目正规明细表,表 21-2 给出了文 件建议。 表表表表 21212121----2 2 2 2 文件建议文件建议文件建议文件建议 正规评分 建议文件 12-15 用户指南和小程序文件 15-26 低级文件以及操作使用手册,维修手册,测试计划、管理计划、结构配置划 24-38 低级文件加功能描述、质量确保计划 36-50 低级文件以及系统和子系统描述.测试分析报表 48-60 低级文件以及程序描述 对于特定的项目、数据要求文件、数据库描述和执行程序也可能在建议之列。 你不必因某种原因而创建这种文件。你编写配置管理计划并不是为了运行有关程序, 而只是强迫自己能向其它人解释。当你计划或创建一软件系统时,文件对你的实际工作 的副作用是可见的。如果你自己感到要编写一般的文件,这就有点不对头了。 第二十一章 项目大小如何影响创建 356 程序、产品、系统、系统产品程序、产品、系统、系统产品程序、产品、系统、系统产品程序、产品、系统、系统产品 并不只是代码行数和参加人数对项目的大小有影响。一个更微妙的影响是软件的质量和复 杂性。最初的 Gigatron 和 Gigatron Jr. 可能只需一个月的时间就能完成编写和调试工作。它只 是单一的程序,有一个测试。如果 2500 行的 Gigatron Jr. 软件花一月时间,为什么 5000 行的 Gigatron 需 6 个月呢? 一类最简单的软件是由用户自行开发的单一程序。更为复杂的一种程序类型称为——软件 “产品”——它是为了让其它人而不仅是用户自己使用。软件使用环境和生成环境是不同的。 在其交付之前,软件接受过深入的调试,并被文档化,有可能被其它人所维护,软件产生的代 价是程序开发代价的 3 倍。 另一种复杂类型是要求一群程序员相互协作进行开发。这样的系统称之为软件“系统”。开 发一个系统要比开发一个程序更为复杂,因为系统需开发各不同部分之间的接口以便能将其有 机地综合起来。一般开发一个系统所费代价是单一程序的 3 倍。 在开发“系统产品”时,它将加装产品外壳(用户界面),并将系统各部分综合起来。系统 产品所费代价是单一程序的 9 倍。 程序、产品、系统和系统产品的复杂性,是导致估计错误最为常见的原因,程序员在利用 过去的编程经验对创建一系统产品进行评估时,可能会低估近 10 倍。如果你用自己编写 2K 行代码的经验对开发一个 2K 行的程序进行评估,你所估时间仅为实际所需时间的 80%。编写 一个 2K 行代码的程序并不相同。如果你没有考虑各种非创建活动所费时间,你实际所花费时 间将比你所估计的多花 25%。 如果你所在项目变大,创建所占工作量将会减少。如果你的估计仅基于创建经验,发生的 估计错误的机会增大了。如果你用自己 2K 的创建经验评估 32K 程序所花时间,你所估计的时 间仅为实际需要的 50%。 估计错误主要是由于你对项目大小和开发较大程序的关系理解不全。如果你不理解产品其 它工作的重要性,估计错误数可增加 3 倍或更多。 21.3 21.3 21.3 21.3 项目大小对错误的影响项目大小对错误的影响项目大小对错误的影响项目大小对错误的影响 质量和错误都要受到项目大小的影响。你可能认为这种类型的错误将不受影响,但是随着 项目的变大,相当大的一部分错误通常要归于分析和设计错误。以下是一个图示: 对一些小项目,创建错误可占整个错误的 75%,方法对代码质量影响不大。对程序质量影 响最大的是个人编写程序的技巧。 对大项目程序,创建错误可占整个错误的 50%。分析和结构错误所占比例是不同的。事实 上较大项目需要更多的分析和设计,所以此类活动中发生错误的机会相应也大一些。对一些很 大的项目,创建错误比是较高的。对一些 500,000 行代码的程序,75%以上的错误是创建错误。 错误的类型随项目大小而变化,所发生的错误数也会发生变化。你可能会自然想到,某项 目大小为另一项目的二倍时,其所发生的错误总数应为小项目的二倍。但是,每行代码出现的 错误概率和错误数也会增大。当某一产品大小是另一产品的二倍时,它所出现的错误是另一产 品的二倍多。表 21-3 给出了项目大小和错误数关系: 第二十一章 项目大小如何影响创建 357 项目代码行数 表表表表 21 21 21 21----3 3 3 3 项目大小和错误数项目大小和错误数项目大小和错误数项目大小和错误数 项目大小(代码行数) 每 1000 行代码所发生错误数 小于 2k 0-25 2k-16k 0-40 16k-64k 0.5-50 64k-512k 2-70 52k或更多 4-100 本表原载《Program Quality and Programmer Productivity》 (Jones l977)。 以上数据只是来自一些特定项目,你实际所出现的错误数可能和以上数据略有出入。然而 本数据仍是有一定启发意义的。它表明随着项目的变大,其所出现错误数会急剧增加。大项目 出现错误数是小项目的 4 倍多。此数据也表明对超过一定大小的项目,不能编写出没有错误的 代码,错误总可以潜入,而不管采用什么方法预防错误。 21.421.421.421.4 项目大小对生产效率的影响项目大小对生产效率的影响项目大小对生产效率的影响项目大小对生产效率的影响 当和项目大小联系在一起时,生产效率和软件质量有许多共同之处。对于小项目(200 行 或更小),对生产效率影响最大的是单个程序员的技巧。随着项目的增大,开发组人数和组织对 生产效率影响迅速增大。 项目应为多大时,开发组人数才对生产效率有影响:Boehm 和 Gray 曾说过,较少人数的 开发组的效率比人数较多的开发数高 39%。 表 21--4 给出了项目大小和生产效率的一般关系: 生产效率实际上是由以下因素所决定:人员的素质、编程语言、方法、产品复杂性、编程 环境、工具支持和其它许多因素所决定的,所以可能表 21-4 的数据并不能直接用于你的编程 环境,你可视具体情况而定。 第二十一章 项目大小如何影响创建 358 表表表表 21212121----4 4 4 4 项目大小与生产效率的关系项目大小与生产效率的关系项目大小与生产效率的关系项目大小与生产效率的关系 项目大小(代码行数) 每月代码行数 小于 2k 333-2000 2k –16k 200-1250 16k-64k 125-1000 64k-512k 67-500 512k 或更多 36-250 本表原载《Program Quality and Programmer Productivity》(Jones1977)。 但是,本数据也是有所启发的,最小项目的生产率是最大项目的 10 倍。即使从某一中等项 目转移到另一中等项目——如从千行代码的项目转到 9 千行代码的项目——生产效率也可降低 一半。 21.521.521.521.5 小小小小 结结结结 · 在一些小项目中的活动并不能想当然地用于大项目中,你应仔细计划它们。随着项目 增大,创建作用减弱。 · 随着项目的增大,交流方法应简化。各种方法的使用应因时而异。 · 在相同条件下,大项目的生产率比小项目低一些。 · 在相同条件下,大项目的每行错误数比小项目的每行错误数多。 第二十二章 创建管理 359 第二十二章第二十二章第二十二章第二十二章 创建管理创建管理创建管理创建管理 目录目录目录目录 22.1 使用好的代码 22.2 配置管理 22.3 评估创建计划 22.4 度量 22.5 将程序员视为普通人 22.6 如何对待上司 22.7 小结 相关章节相关章节相关章节相关章节 创建前提:见第 3 章 程序长度:见第 21章 软件质量:见第 23 章 系统集成:见第 27 章 设计较好软件的要旨:见第 32 章 软件开发管理已成为二十世纪晚期人们所不可轻视的一个挑战。对软件计划管理进行一般 的讨论,已超出本书的范围,但是本文将讨论和创建有关的一些管理主题。由于本文讨论了许 多主题内容,在另外几个部分你也可以找到更多有关的内容。 本章主要讨论和创建有关的软件管理主题。 22.122.122.122.1 使用好的代码使用好的代码使用好的代码使用好的代码 由于代码是创建的主要输出,所以创建管理的关键问题是养成使用好代码的习惯。一般说 来,从开始就用标准并不是一个好的方法。程序员倾向于将管理者视为技术层次中的水平较低 者,甚至认为其差距犹如单细胞生物和冰川世纪灭绝的猛犸像一样大。如果有编程标准的话, 程序员可以买一个。 标准设置的考虑标准设置的考虑标准设置的考虑标准设置的考虑 如果你避免使用标准,标准对你也就不那么重要了,可考虑使用标准的替代:灵活的原则, 或者采用一些建议而不用原则,或者一些包含最好实际情况的例子。 方法方法方法方法 以下是取得较好代码效果的几个方法。这些方法不应被视为教条的代码标准。 将二人分在计划的每一部分将二人分在计划的每一部分将二人分在计划的每一部分将二人分在计划的每一部分。如果二人不得不各自编码,你应确保至少这二人认为代码能 第二十二章 创建管理 360 正常运行同时可读。协调二人的开发方法将涉及到指导者和受训者的配合。 如果要了解注释的详细内容,参看第二十四章“注释”。 注释每一行代码注释每一行代码注释每一行代码注释每一行代码。一条代码注释常包括程序员和至少两个注释者,这意味着至少有三人将 阅读代码的每一行。同级注释的另一个名字是“同级压力”。除了防止程序员离开计划体系外, 让程序员知道另外一些人还将阅读本代码,将会提高代码质量。即使你所在部门没有明确的代 码标准,注释也将会提供一个较好的途径,使代码向一个代码标准的方向发展,这些标准是群 体在注释过程中所得到的一些结论,而且,随着时间的推移,群体将派生出自己的标准。 采用代码许可采用代码许可采用代码许可采用代码许可。在一些领域中,技术草图将由管理工程师批准和鉴署。鉴名就意味着对管 理工程师来说,本草图在技术上是可行的,而且是没有错误的,一些公司也采用这种方法。在 代码将完成之前,高级技术主管将签署代码表。 用好的注释作示例用好的注释作示例用好的注释作示例用好的注释作示例。管理的一个重要方面,就是向你的人员清楚地表达你的意图。其中的 一条途径就是让你的程序员见识一些好的代码,或将其在公共场合张贴出来。这样作将会提供 清楚的代码示例,而 这正是你所期望的。同样,一 本 代 码 标准手册将包括一些“ 最好的代码表”, 指定一些表作为最好的代码以便其它人能效仿。这样一本手册将比一本英语标准手册要容易改 进,而且,无须费多大劲就能展示代码风格的精细,如果靠平铺直叙一点一点描述是困难的。 强调代码表是公用财产强调代码表是公用财产强调代码表是公用财产强调代码表是公用财产。程序员有时会觉得代码是“他们的代码”,好像这是其私人财产一 样。虽然这是程序员自己心血的结晶,但是代码是整个计划的一部分,计划中需要它的人应有 权得到它。代码在注释和维护阶段应当被其它人所看到。 曾经报道过的一个最为成功的计划是,一个用 11 人年的工作量开发的 83,000 行的代码。 在其先期 13 个月的运行中仅发现一个系统错误。当你知道这计划是在 60 年代晚期没有联机编 译和交互式调试条件下完成的时候,你会感到更为惊讶的。要知道,在 60 年代晚期——7500 行代码所花费的人年工作量,二倍于今天 3750 行代码所耗费的人年工作量。本计划的一个主程 序员说,计划成功的关键之一是明确确定所有的计算机运算(错误或其它)是公共的而不是个 人的财产。 奖赏好的代码奖赏好的代码奖赏好的代码奖赏好的代码。用奖励办法促进好的代码开发工作。当你开发你的系统时,请牢记以下几 点: · 此奖励应是程序员所需要的(许多程序员发现“哄小孩”似的奖励是令人生厌的,尤 其是这些来自那些非技术管理人员的时候)。 · 所奖励的代码应是非常好的。如果你奖励一个大家都知道其工作干得不好的程序员, 你将收到相反的效果。至于程序员是否态度好或是否按时上班则不是重要的。如果你 的奖励和相应技术价值不一致,你将失去信誉。如果你的技术不够高,不能判断哪一 个是好的代码,那么,你就根本无须奖励,或者让你的合作者们选择。 一个简单的标准一个简单的标准一个简单的标准一个简单的标准。如果你管理一个编程计划,同时你也有编程经验的话,做好工作的一个 有效的方法是说:“我必须能够阅读和理解本计划所编的所有代码”。管理者不是技术尖子,但 防止“聪明”或恶作剧式编码也是有好处的。 本书的角色本书的角色本书的角色本书的角色 本书的大部分是讨论良好的编程习惯,并不打算纠正教条的标准,甚至也不打算被用作为 教条的标准。将本书作这样的用场将和本书的一些重要主题相矛盾.将本书视为讨论的基础, 第二十二章 创建管理 361 作为一本有良好编程风格的参考资料,同时也可检查你所在环境中的编程习惯是否有益。 22.2 22.2 22.2 22.2 配置管理配置管理配置管理配置管理 软件计划是变化:代码在变化、设计在变化、需求在变化,而且需求的变化引起设计上更 多的变化,设计上的变化又会引起代码和测试情况的更多变化。 何调配置管理何调配置管理何调配置管理何调配置管理 配置管理是全面地处理各种变化,以便系统随着时间的推移能保证其完整性。其另一个名 字是“修改控制”。它包括如下各种方法,评估各种建议处理各种修改、在不同的时间保留系统 的各种备份。 如果你不控制设计上的修改,你 最 终 将 会发现,设计中某些部分的代码和最初的大相径庭, 你所写的代码也和新设计的部分不兼容。直到系统集成时你才知道它带来了许多不兼容性的问 题,而这时恰恰是最关键的时刻,因为谁也不知道这之间是怎样一回事。 如果你不控制代码的修改,你在改变子程序而同时也有其它人正在改变它。成功地将你的 修改和他人的修改协调一致将会避免这些问题。无节制的代码改变将会付出更多的测试量。已 经测试过的版本也可能是老的、未曾变更的版本;变更的版本也可能没有接受过测试。没有一 个良好的修改控制,在改变一个程序的同时,如果发现了新的错误,也不能回到原来的可能正 常工作的程序状态。 问题也会长期存在。如果不能有条理地处理各种修改,你将好比在雾中胡乱前进而不是朝 着一个明确的目标径直向前。没有好的修改控制,与其说是开发代码,倒不如说是在浪费你的 时间翻来复去。配置管理可帮助你有效地使用时间。 尽管结构配置有其明显的必要性,许多程序员过去并不使用它。一项调查发现超过三分之 一的程序员对结构配置并不熟悉。 结构配置并不是由程序员所最先提出。但是由于程序设计是多变的,所以结构配置对程序 员异常有用。对于软件计划来说,结构配置通常称为软件配置管理,或 SCM(通常也叫“scum”)。 SCM 着重于程序的源代码文档和测试数据。 SCM 的问题在于过程。防止汽车事故最可信的方法是禁止行车,而防止软件开发中问题的 途径是停止所有的软件开发。虽然这是一条控制修改的方法,但这是软件开发中的一条可怕的 途径。你将不得不仔细地计划,这样 SCM 才是一笔财富而不是你的包袱。 有关计划尺度对创建的影响,参看第二十一章。 在一个人的小计划中,不采用 SCM 而仅需考虑一下一般的定期备份,你可能照样干得较 好。在一个 50 人的大计划体制中,你可能需要一个完整的 SCM 计划,它包括备份档案正规的 过程,要求对设计进行修改控制及文档、源代码和测试的控制。 如果你的计划不大也不小,你不得不在以上两种极端方法中采用一种折衷的方法。以下几 个部分描述了完成一个 SCM 计划的一些可选择项。 软件设计修改软件设计修改软件设计修改软件设计修改 在开发过程中,你一定会被怎样改进系统的性能所困扰。如果你只是在每个修改发生时才 第二十二章 创建管理 362 去考虑它,你会发现自己进入了软件开发的歧路——整个系统在变化,离目标的完成也是遥遥 无期。以下是控制设计修改的几条准则: 遵循正规修改控制流程遵循正规修改控制流程遵循正规修改控制流程遵循正规修改控制流程。正如第三章所指出的那样,正规修改控制流程是你处理许多修改 要求的有力手段。建立正规流程,会对如何在整个计划的范围内考虑修改有一个清晰的了解。 建立修改控制委员会建立修改控制委员会建立修改控制委员会建立修改控制委员会。修改控制委员会的工作是在有修改请求时,从“米糠”中将“小麦” 区分开来。任何想修改的人,将变化请求提交给变化控制委员会。“修改请求”这个词指的是任 何将改变软件的请示:一个有新特点的主题、一个有特点的修改,一个报告真实错误的“错误 报告”。委员会定期对所提修改请示进行检查。它可能同意或不同意或不予理睬这些修改请求。 集成考虑修改请求集成考虑修改请求集成考虑修改请求集成考虑修改请求。本规则倾向于完成容易的修改。这种方式处理修改所带来的问题是, 好的修改机会可能会失去。当你按照预定计划已经完成了计划的 25%时,你想作一下简单的修 改,当然可以这样做。而如果你滞后于原定计划进度且已完成了计划的 90%时,你不可能再这 样作简单的修改。当你在计划的收尾阶段已经超出了预定时间期限时,至于比第一次修改要好 10 倍是无关紧要的——你没有必要去作一些无谓的修改。你也可能失去一些最好的机会,因为 你想到要作修改已经太迟了。 一个非正式的解决这个问题的方法是写下所有的主意和建议,而不管完成它的难易程度如 何,同时也应及时保留这些建议和主意直到你有时间去处理它们。最终,将其作为一个整体看 待,选择其中最有实效的一个。 估计改变所花的代价估计改变所花的代价估计改变所花的代价估计改变所花的代价。不管何时你的客户、你的老板、或者你自己打算改变系统时,先估 计修改花费的时间,其中包括修改所引起的代码注释和再测试整个系统,同时也应包括处理由 于用户程序的变化所引起的设计、编码、测试需求等的修改所需时间。让有关各方知道软件是 异常复杂交错的,即使乍看起来修改很小,但是时间预估仍然是必要的。不管改变刚提出时你 是多么乐观,你应避免作出草率的估计,匆忙的估计错误率可能会达到 10 或 100 倍。 从另外一个角度看待处理各种修改,参看 3.3 节“创建过程中对各种改变请求的处理”这 一节。 谨慎对待主要修改谨慎对待主要修改谨慎对待主要修改谨慎对待主要修改。如果顾客建议进行重大修改,这是对你的工作没有满足要求的警告。 如果在高级设计时提出这些建议,你应停止设计工作并回到初始设计要求状态。在进行设计之 前你应耐心等待,直到修改要求有所缓和为止。 如果在代码开发过程中,你的顾客坚持要作重大修改,你应坐下来和你的顾客谈一谈,并 指出它对已经进行的工作所产生的影响。其中一个明显的影响就是早期的一些工作将废弃。 软件代码改变软件代码改变软件代码改变软件代码改变 另外一个配置管理主题是控制源代码。如果当你改变了代码并发现其中产生的一个新的错 误,表面上看来和你的修改无关,你可能会想到将其和老版本的代码相比较,以便找出各种错 误源。如果这仍然一无所收获,你可能会去查阅更老一级的版本。如果你有版本控制工具且保 留源代码的多个版本的话,这种类型的版本回溯过程是很容易的。 版本控制软件版本控制软件版本控制软件版本控制软件。一些版本控制软件工作起来是如此地得心应手以致于你很少注意到你正在 使用它。在使用开发计划中版本控制软件更是非常有帮助。典型地,当你需要对一特定文件的 源代码进行操作时,在版本控制软件之下你查询本文件,如果别人已经注册登记了它,你将被 告知你无法调出此文件。当你能够调出文件时,你可以在无版本控制下随意对其进行操作直到 第二十二章 创建管理 363 你将其登录。当你登录文件时,版本控制软件询问你为何修改它,你对此回答你的理由。 由于你对此付出了最大努力,你将会得到如下好处: · 别人正在操作某一文件时你不致于和他发生冲突。 · 你能轻易地将所有文件副本升级为当前的版本,通常你仅需使用一条命令即可。 · 你能够回溯任何由版本控制登录的文件版本。 · 你能得到任何文件的任何版本修改表。 · 你无需担心文件备份,因为版本控制拷贝备份是自动的。 版本控制对合作开发计划是不缺少的,它是如此的有效,以致干微软公司的应用部,发现 源代码版本控制是具有重要竞争力的方便措施。 制作制作制作制作。一种特定类型的版本控制工具是和 UNIX 以及 C 语言相联系的实用程序。制作的 目的是将生成目标文件所需要的时间减少为最少。对工作文件中每一个目标文件及目标文件所 依赖的文件都可以应用本控制工具及怎样去制作它。 假定你有一个名叫 user.obj 的目标文件,在制作文件中,你说明你想制作 userface.obj 文件, 你必须编译 userface.c。你同时指明 userface.c 将依靠 userface.h,stdilb.h 和 project.h。“依靠” 这个概念仅意味着如果 userface.h,stdib.h 或者 project.h 改变了 userface.c 也需要再编译。 当你创建你的程序时,制作检查所有的依赖,同时确定需再编译的文件。如果你的 25 个源 文件中的 5 个依赖于 userface.h 中所定义的数据的,并且 userface.h 已经改变了,制作自动再编 译 5个依赖于它的文件。制作不会再编译另外 20 个不依赖于 userface. h 的文件。使用制作可 避免再编译所有 25 个文件或单独编译每一个文件。漏掉其中某一个文件,将会得到一些奇怪 的错误。总的来讲,制作实际上比一般的编译、链接、运行循环将更节省时间和提高可靠性。 备份计划备份计划备份计划备份计划 备份计划并不是令人兴奋的概念。备份计划即定期备份你的工作。如果你正在编写一本书, 你不会将其放到门廊上。如果你这样作,他们可能将被雨淋湿或被风吹走。你最好将其放在某 个安全的地方。软件不像纸这样真实可触摸,所以你更易忘记你已在机器上留下了一些错误的 东西。 计算机里的数据也会产生许多问题,磁盘可能失效,你或别人也许在无意中删掉一些重要 文件。一位愤怒的雇员能破坏你的机器,你也可能由于盗窃、洪水、火灾等不幸事件而失去你 的机器。 你应采用一些措施以便确保你的工作成果。你的备份计划应当包括定期进行备份,定期将 备份转移至安全处,除了源代码以外,还应包括项目所有的重要资料、文档、图形、注释。 人们在设计备份计划时,常忽视的方面就是对备份程序的测试,不时测试一下你保存下来 的东西以确保备份含有你所需要的一切,恢复文件时也能正常工作。 当你成功地完成项目后,你应保存下再开发所需要的东西——源代码、编译程序、工具、 需求、设计、文档等,并且要将它们保存在安全的地方。 第二十二章 创建管理 364 检查表检查表检查表检查表 配置管理配置管理配置管理配置管理 一般一般一般一般 · 你的软件配置管理计划是否用于帮助程序员,并能将额外开销减至最少? · 你使用 SCM 手段能否避免对项目失控? · 你所在组是否有修改请求?请示控制可以是非正式方式或正式的方式。 · 你是否能比较正确地估计每次修改的影响? · 你是否将重要修改视为需求分析不充分的警告? 工具工具工具工具 · 你是否使用版本控制软件以便配置管理? · 你是否使用版本控制软件以减少开发时的协调问题? · 你是否使用制作或其它控制依赖软件使编程更为有效和可靠? 备份备份备份备份 · 你是否将所有项目材料定期备份? · 你是否定期将所有项目备份移到安全地点存放? · 包括源代码、文档、图形和重要注释在内的所有材料都备份了吗? · 你是否对备份程序进行了测试? 因为本书主要是关于创建的,本节主要是从创建的观点讨论修改控制。但是修改从各级水 平上影响项目,需要有一集成修改控制策略才行。 22.3 22.3 22.3 22.3 评估创建计划评估创建计划评估创建计划评估创建计划 软件工程管理是二十世纪晚期对人类来说不可轻视的挑战之一。评估工程的大小和完成时 间是软件工程管理最为棘手的问题之一。一般的大软件工程完成需一年多时间,同时所需费用 也可能会超出预算的 100%(Jones 1986)。这主要是由于对工程的大小和所需时间估计有误和 开发过程不努力有关。本节讨论如何评估软件工程,并阐明如何获得更多的信息。 评估方法评估方法评估方法评估方法 你能从以下几个方面评估软件的大小和所需时间: 如果想获得评估方法的更多知识,参看《软件工程质度和模块》或《软件工程经济》等书。 · 使用进度软件 · 使用诸如 COCOMO 的算法,Barry Boehm 的估计模型 · 使用外面其它的专家评估 · 举行预评估会议 · 评估项目的每一部分,然后再集成这些评估 · 让人们评估自己的那部分,再进行集成评估 · 评估完成整个项目所需时间,然后将时间细分到项目的每一部分 · 参考以前项目的评估 第二十二章 创建管理 365 · 保存以前的项目估计看看它们的准确程度。用它们调整你的评估 这种方法摘自《软件工程经济》(伯亨利,1981)。 评估对象评估对象评估对象评估对象。你在评估什么?你为何要评估?你的对象需多大的评估精度?评估需多大确定 性?乐观和悲观的评估到底能有何差别? 使评估有充足时间并计划评估使评估有充足时间并计划评估使评估有充足时间并计划评估使评估有充足时间并计划评估。匆忙评估是不精确的。如果你要评估一个大的软件工程, 你应将评估视为一个小的项目,并花费时间去评估时间。 要获得软件工程的要求的更多信息,参看 3.3 节“需求前提”,以便你能作好评估。 不要草率估计软件工程的各种开销不要草率估计软件工程的各种开销不要草率估计软件工程的各种开销不要草率估计软件工程的各种开销。在某事物还没有被定义前,别人让你去估计产生它需 要多少工作量是不明智的。在作出估计之前应定义要求,或对初始探索阶段进行计划。 对你所需判别的对象从层次细节上进行评估对你所需判别的对象从层次细节上进行评估对你所需判别的对象从层次细节上进行评估对你所需判别的对象从层次细节上进行评估。从对项目活动的各细节上进行评估,一般说 来,你的检查越详细,你的评估就越难确,大多数定理指出总和的错误比错误的总和要大,换 句话说,某较大部分 10%的错误,在 50 个小部分中,10%的错误往往会抵消掉。 在软件开发中随处可见重复利用技术,这是重复利用的一个场合,如需了解重复技术的摘 要,参见“重复、重复、重复”32.7 这一节。 使用不同的评估方法并比较其结果使用不同的评估方法并比较其结果使用不同的评估方法并比较其结果使用不同的评估方法并比较其结果。在本节的开头给出的评估方法表,说明有几种方法。 他们将不会产生同样的结果,所以将这几种方法都试验一下,比较一下结果的不同。 小孩较早就知道如果他们向每一位家长要 1/3 碗的冰琪淋,比仅向一位家长要有多一些 获准同意的机会。有时家长们聪明地给予相同的回答,而有时他们则不。你应从不同的评估方 法中,看一看究竟能得出何种不同的答案。并不是每一种方法都是万能的,而且它们之间的差 别应是明显的。 定期再评估定期再评估定期再评估定期再评估。软件工程的各种因素在评估后都会有所变化,所以你应对定期再评估有所计 划。在项目接近尾声时,你能评估得更好,同时你也可掂量一下你的初始评估。使用你的评估 结果精化以后的评估。在项目收尾之时,你的评估精度也会有所提高。 Milestones 软件开发进度表 评估创建工作量评估创建工作量评估创建工作量评估创建工作量 有关不同大小项目的编码工作是细节,参看“工作量分配和大小”这一节(21.2)。 创建对项目进度的影响取决于项目的各项分配——一般为详细设计、编码、调试和单元测 试。正如下图所示,各项分配随不同大小的项目而有所不同。 第二十二章 创建管理 366 项目代码长度 除非你的公司有自己的项目设计数据,上图所给出项目的各项时间分配对开始评估你的工 程是颇有益处的。 对完成一个项目需要多少创建的最佳回答随项目和组织的不同各项分配有所不同。你在开 发项目过程中,应当利用你过去的工作经验来估计项目所需花费的时间。 项目进度的影响因素项目进度的影响因素项目进度的影响因素项目进度的影响因素 程序长度对软件效率和质量的影响并不总是凭直觉就能轻易体会得到的,要知道程序大小 对创建的影响,参着第二十一章“程序长度怎样影响创建”。 对软件开发进度影响最大的是程序的长度,但是也有许多其它因素影响逻辑开发进度。在 表 22-1 中给出了对商品程序产生影响的一些因素。为了利用这张表,你首先要对你项目的大 小作一个基本估计,然后用所给表中的每一行的因子去乘基本估计,调整所作估计。 以下为影响逻辑开发进度的一些不能轻易作结论的因素。这们都出自于《Software Engineering Economics》(1981,Barr Boehm)和《A Method of Programming Measurement and Estimation》(C.E.Wdston 和 C.P.Felis)。 · 协作动机 · 管理质量 · 代码再利用量 · 人事变动 · 语言水平 · 需求变更 · 和客户的关系如何 · 用户对要求的参与程度 · 用户的应用经验 · 什么范围内的程序员参与需求分析 · 对计算机、程序、数据的安全确保体系 · 文档量大小 · 项目对象(进度表、质量、可用性和其它可能对象) Detailed Design Coding and debuging Unit testing Constructing 第二十二章 创建管理 367 虽然以上因素不能轻易下结论,但它们是重要的,所以应将其同表 22-l 各种因素一并考 虑。 表表表表 22----1 比 率 第二十二章 创建管理 368 评估和控制评估和控制评估和控制评估和控制 重要问题是:你是想要预测,还是想要控制? 评估是使软件工程按时完成计划的重要组成部分。一旦你确定了交货日期和产品性能,剩 下的主要问题是怎样控制人力和技术资源的开销,以便能按时交付产品。从此种意义上来讲, 成功地利用各种资源以满足计划要求,比初始估计的准确性更为重要。 如果你落后了怎么办如果你落后了怎么办如果你落后了怎么办如果你落后了怎么办 很多软件开发进度落后于预定要求,对一些实际软件开发的调查结果表明,评估所需时间 要比实际所用时间少 20~30%。 当你落后时,增加时间并不是一个明智选择,如果你别无办法,可那样做。否则你可试一 试一个或几个方法: 努力赶上努力赶上努力赶上努力赶上。当项目进度落后时,通常的反应是对此抱有乐观态度。较为理智的想法是这样: “所用时间比我们所预期的要长一点,但 如今这是不可变更的,我们以后能挽回这点时间”,但 第二十二章 创建管理 369 是实际上人们很少这样作,对超过 300 个软件工程的调查指出越接近收尾阶段,延误和超进度 情况越来越严重。所以人们在以后不仅不能挽回已失去的时间,而是越来越落后。 扩大队伍人数扩大队伍人数扩大队伍人数扩大队伍人数。对一个已经落后于预定时间的软件工程,增加人数只会使其更加落后。这 好比向火上添油。这种解释是颇有说服力的,在新手能富有成效地工作之前,他们需要有一段 时间熟悉工作。对他们进行培训浪费了已受训人员的时间。而且增加人数也增加了软件开发的 复杂性和相互间的通信量。布鲁克指出一个妇女能在 9 个月中生下一个孩子,并不意味着 9 个 妇女能在一个月中生下 9 个孩子。无疑对布鲁克的告诫是应该注意的。它企图使人们全身心地 投入开发并按时交付。管理者应该知道开发软件不像柳接薄金属板一样;更多的工人并不就意 味着完成更多的工作量。 增加程序员到一个落后于预定时间的项目,会使其更加落后之说,也 掩盖了如下一个事实: 在某些情况下增加人数有可能加快开发进度。正如布鲁克所指出的那样,对于一个任务不能再 分割和不能独自工作的软件开发来说,增加人手无济于事。但是如果项目任务还可细分,并可 将其分派给不同的人,甚至是刚进入本项目的人员。另外一些研究人员也证明了你可以在软件 开发落后时增加人员不会使其更落后。 减少项目范围,减少项目的有力方法常被人们所忽略。如果你去掉某一特点,也就去掉了 相应的设计、编码、调试、测试和文档工作,同时你也就无须再设计这点和其它点的接口。 当你最初计划你的开发时,将开发产品的能力划分为“必要有”、“有更好”、“可选项”几 部分。如果你落在这里,先考虑“有更好”和“可选项”并将某些剔除掉。 将某特征从总体上减少一些,能得到一个具有同样功能但是更便宜的一个版本。你也可能 准时提交版本,但是它没有经过实际性能调试。也可能提供一个最不重要的功能勉强得到实现 的版本。你可能对时间要求放松一些,因为提供一个时间不急的版本是很容易的,你还可能放 松空间需求,因为提供扩展存储版本是很容易的。 对重要的功能开发时间,应再评估。在 2 小时、2 天或者 2 星期之内你能提供什么功能? 制作一个二星期后的版本,比二天与二个小时的版本有什么更多功能? 22.422.422.422.4 度度度度 量量量量 “度量”这个词指的是和软件开发有关的衡量。代码行数、错误数、每千行代码错误、每 个模块的错误、全局变量的数目、完成项目所需的时间等都是度量。 对开发过程进行度量比根本不度量要好,虽然度量并不很准确;也可能难以制定一个度量 标准;它需要随着时间能推移而不断精化。但是它能使你能对软件开发过程进行控制,而没有 度量则不行。 如果数据在科学实验中被用到,则它应先量化。为了评估软件开发方式,你应首先度量它 们,仅仅作出“这种新方式看上去更有成效”的断定是不充分的。 有时度量不必知道项目究竟是怎么一回事。当度量项目的某一方面时,并不知道项目的究 竟是变大了,是变小了,还是没有变。本度量仅是项目中这一方面的窗口。在你逐步精化你的 度量之前,你的这窗口也可能是较小和模糊不清的,但是这比没有窗口要好。 你能够度量软件开发过程的各个方面,表 22-2 给出了一些被证明为行之有效的度量。 第二十二章 创建管理 370 表表表表 22----2 有用的度量有用的度量有用的度量有用的度量 长度 总质量 代码总行数 总的错误数量 注释总行数 每个子程序的错误数目 数据说明语句总数 每千行代码平均错误总数 空行总数 失效的平均时间 编译错误 生产率 总的项目时间 维护性 每个程序所花时间 每个子程序所用参数数目 每个程序修改次数 每个子程序所用局部变量数目 项目费用 每个子程序被其它子程序调用次数 每行源代码费用 每个子程序的断点数目 每个缺陷所耗费用 每个子程序的控制流向复杂性 每个子程序的代码行数 错误跟踪 每个子程序的注释行数 每个子程序的数据描述数 每个错误的严重程度 每个子程序的空白行数 错误的位置 每个子程序的 goto 语句数 纠正每个错误的方法 每个子程序的输入/输出语句总数 对每个错误所负责的人 纠正每个错误所影响的代码行数 纠正每一个错误所用时间 发现一个错误所用平均时间 修正一个错误所用时间 纠正每一个错误的尝试次数 由于纠正错误所引起的新的错误数目 使用当前可用的软件工具,你能得到大多数度量。本书的讨论指出每种度量都是有用的。 当前,大多数度量并不能明确区分程序、模块和子程序(Shepperd 和 Ince l989)。度量主要用来 鉴别“外来”子程序。一个子程序的反常度量是说明子程序质量低需再检查它。不要对所有的 可能度量都想收集其数据——你将会置身于数据的汪洋大海之中而不知所然。你应当从诸如错 误总数、工作耗费时间、总费用、总的代码行数等简单的度量开始。 在项目开始过程中,标准各种度量,逐步精化它们,以作为衡量进展情况的标准。你应确 保你是抱着某种目的而收集数据,确立目标,从而确定实现目标还有何问题,最终度量这些问 题。美国宇航局软件工程实验室的一份数据收集总结指出,在过去的 15 年中所得出的最重要的 教训就是,度量之前你应定义度量目标。 22.522.522.522.5 将程序员视为普将程序员视为普将程序员视为普将程序员视为普通人通人通人通人 编程活动的抽象性要求程序员有适应办公室环境的天性,并且和使用者有密切的交往。高 技术公司常向其雇员提供公园式的合作园区,有系统的组织结构、舒适的办公环境以及其它“高 第二十二章 创建管理 371 级”环境以平衡精深的有时也是异常智力型的工作。最成功的公司其成功的原因在于高技术和 高接触的有机统一(Naisbitt 1982)。本节指出了程序员并不仅是其自我的反映。 程序员们怎样花费自己的时间程序员们怎样花费自己的时间程序员们怎样花费自己的时间程序员们怎样花费自己的时间 程序员不仅要花费其时间编程,也要花费时间开会、接受培训、阅 读 邮寄材料和思考。1964 年对贝尔实验室的调查发现程序员从以下几个方面花费他们的时间: 活动 源代码 事务 私事 开会 培训 邮寄 技术手册 程序运行 程序测试 合计 听说 4% 17% 7% 3% 1% 32% 和管理人员 谈话 1% 1% 打电话 2% 1% 3% 阅读 14% 2% 2% 18% 写/记录 13% 1% 14% 外出 4% 1% 4% 6% 15% 散步 2% 2% 1% 1% 6% 其它 2% 3% 3% 1% 1% 1% 11% 合计 35% 29% 13% 7% 6% 5% 2% 2% 1% 100% 以上数据是建立在对 70 位程序员的工作时间和动机时间研究的基础之上,它是过时了的, 而且不同活动中的时间分配随不同的程序员而异,但是此结果还是有所启发的。一个程序员大 约有 30%的时间花在和项目没有直接联系的活动之中,散步、私事等等。在以上调查中,程序 员花费了 6%的行路时间。这意味着他们一年有 125 个小时,一周有 2.5 个小时在路上消耗掉了。 你也许认为这并没有什么,但是当你看到程序员行路时间和他们花费在培训上的时间相当,并 且是三倍于他们阅读技术手册的时间,六倍于他们和管理人员谈话的时间之后,你会有所触动 的。 能力差别能力差别能力差别能力差别 不同程序员的才能和努力的区别是巨大的,就像其他领域一样。一项对写作、足球、发明 创造、治安和飞行等不同职业的研究表明,其中 20%的精英人物却取得了 50%的成就。以上 研究结果是建立在对各项得分、专利、已解决案例等数据之上的。由于不少没有实际可见的贡 献(如足球运动员没有得分、发明者没有自己的专利,侦探没有破获案子等),本数据也可能低 估了实际的效率差异。 对编程来说,许多研究表明对不同的程序员,其在程序书写质量、程序长度和效率方面存 在较大差别。 个人差别个人差别个人差别个人差别 在 60 年代,Sackman,Erikson 和 Grant 的研究初步表明,程序员在编程效率方面存在重大 差别。他们研究了平均工作经验为 7 年的专业程序员的编程过程,发现最好和最差的程序员的 初始编码时间比为 20:1,调试时间比 25:1,程序长度比为 5:l,程序执行速度比是 10:l, 他们也发现,程序员的工作经验和编码质量或效率并不存在必然联系。 第二十二章 创建管理 372 虽然像 25:l 这样的比例并不特别有意义,但是一般的常识如“在程序员之间存在重大差 别”是有其意义的,并且已经被对许多专业程序员的研究所证实(Curtis1981,Mills 1983, DeMarco 和 Lister 1985,Curtisal 1986,Card 1987,Boehm 和 Papaccio 1988,ValettMcGarry 1989)。 组间差别组间差别组间差别组间差别 不同的编程组之间也有软件质量和生产效率的差别。好的程序员往往趋于聚集在一起,正 如差的程序员一样。这个结论已经被对 18 个组织的 166 个专业程序员的研究所证实(DeMacro 和 Lister 1985)。 对七个相同的项目研究表明,其所耗工作量的范围之比为 3:4:l,同时程序长度之比可达 3:1。尽管存在种种差别,所调查的程序员并不是完全不同的组别。他们都是有几年工作经验 的专业程序员,并且都是计算机专业的毕业生。由此看来如果各级之间稍不同将会产生更大的 差别。 一早期研究表明,不同的编程小组完成同一项的程序,长度比可达到 5:1,并且所需时间 可能为 2.6:l。在研究了有关编程数据之后,柏雷·玻利亨认为,一个组间程序员能力差别为 15%的编程小组所需开发时间 4 倍于组间差别为 90%的编程小组。玻利亨和其它研究者还发现 80%的贡献来自于 20%的研究人员(1987)。 补充雇用人员是经常的。如果你为了得到高水平的程序员而比得到低水平的程序员要多付 出 2 倍的报酬的话,请你抓住这个机会。你将因雇用的程序员而在质量和效率方面得到回报, 而且这也会对你所组织的其它程序员的质量和生产率产生影响,因为好的程序员倾向于聚在一 起。 个人风格问题个人风格问题个人风格问题个人风格问题 编程计划管理人员往往不太清楚个人心理等对编程有较大影响。如果你试图要求遵守一定 的编程习惯,你可能会激怒你的程序员。以下为一些个人心理因素: · goto 语句的使用 · 编程语言 · 缩进风格 · 大括弧和 begin-end 关键词的使用 · 文本编辑的选择 · 注释风格 · 效率和可靠性的互换 · 方法的选用——例如,面向对象设计或创建设计 · 实用程序 · 变量命名习惯 · 局部变量的使用 · 度量,尤其是生产效率量度(如每天代码行数) 以上讨论的共同特征是每个程序员的个人风格的反映。如果你想以上述各项来要求你的程 序员,请考虑以下各点。 清楚地知道你是在处理一个敏感的领域清楚地知道你是在处理一个敏感的领域清楚地知道你是在处理一个敏感的领域清楚地知道你是在处理一个敏感的领域。在你开始你的行动之前弄清醒每位程序员的思想。 第二十二章 创建管理 373 使用“建议”要同时避免使用教条的“规则”或“标准”。 规避使用明确的控制规避使用明确的控制规避使用明确的控制规避使用明确的控制。为了控制缩进风格或大括弧使用,在源代码宣告完成之前要先经过 整齐打印格式化程序运行。让整齐打印机作格式化工作。为了控制注释风格,要求所有的代码 要经过检查,不清楚的代码要作修改。 你的程序员是否开发出了自己的标准你的程序员是否开发出了自己的标准你的程序员是否开发出了自己的标准你的程序员是否开发出了自己的标准。正如在其它地方所指出的那样,特定标准经常不如 一些已存在的标准重要。你不必为你的程序员设置标准。但是你应坚持要求你的程序员标准化 你认为是重要的地方。 为什么个人心理因素最重要以至于能保证不入歧途?对任何次要问题的限制可能不足以弥 补士气所产生的影响。如果你发现无区分地使用 goto 语句、全局变量、不可读风格或其它影响 整个项目的行为,你得为了提高代码质量而容忍一些摩擦。如果你的程序员是一个认真负责的 人,这倒并不是一个问题。最大的困难莫过于代码风格的细微差别,如果对整个项目无甚损失 你可不必理睬这些。 物理环境物理环境物理环境物理环境 以下是一个实验,走到乡间去,找到一个农场,再见到农场主。问他对平均每个工人来讲 其设备价值多少?农场主会看看他的仓库,看一看拖拉机、运输车、玉米、豌豆然后告诉你对 每个雇员来讲价值 100,000 美元。 接着再到城里,寻找到一个软件开发部,见到经理,问你对每个雇员来说,其设备价值多 少?经理看看办公室,扫视一下桌子、一把椅子、一些书籍、一台计算机并告诉你每雇员为 25, 000 美元。 物理环境对生产率有重大影响。DeMacro 和 Lister 询问了 35 个组织的 166 位程序员关于他 们物理环境的质量。许多雇员对自己的物理环境并不满意。在后来的开发竞争中,前 25%的高 水平程序员有着自己的更大、更安静的办公室,并且被来访者和各种电话打断机会要少。以下 为最好和最差程序员办公室环境差异摘要: 环境因素 最好的 25% 最差的 25% 未用房间空间大小 78平方英尺 46平方英尺 可接受工作环境 57%是 29%是 可接受个人环境 62%是 19%是 不受电话打扰情况 52%是 10%是 可不受来客打扰情况 76%是 19%是 频繁的不必要打扰 38%是 76%是 对工作空间的满意程度 57%是 29%是 以上数据表明了生产率和工作空间质量的紧密关系。最好的 25%的程序员的效率是最差的 25%程序员的 2.6 倍。DeMacro 和 Lister 首先认为好的程序员由于得到提升,他们的办公条件 可能一开始就好些,但是进一步调查证明,事实并不是这样。来自相同组织的程序员虽有相同 的条件,但是他们各自的表现并不一样。大的软件开发组织有相同的经验。Xerox,TRW,IBM 和 Bell 实验室表明平均每人 10,000 到 30,000 美元的投资对大大提高生产率是必要的(Boehm 1987),而这笔数目还能从提高的生产率中获得回报。有了较好的办公条件,估计可得到 39~ 第二十二章 创建管理 374 47%的生产率增长(Boch 等,1984)。概括地讲,将你的工作环境从最差的 25%的水平提高到最 好的 25%的水平可能会导致最少为 100%的生产率增长。 22222222....6 6 6 6 如何对待上司如何对待上司如何对待上司如何对待上司 在软件开发过程中,非技术人员往往是管理者。最为例外的情况是管理者有工作经验,但 是已是十年未再干过了。会技术的管理者是少见的。如果你为某一人工作,应尽力保住你的饭 碗。这不是一件容易的事情。作为一个阶层,每一位雇员都想升到他并不能胜任的层次。 如果你的上司并不是一个特别的人,你将不得不学会如何面对你的上司。“控制你的上司” 意味着你应告诉你的上司怎样去做,而不是用其他方法。其要决在于用这样一种方法使你的上 司相信你仍是受他管理的一员。以下是一些对付你上司的方法: · 拒绝按照你上司的吩咐去做,坚持按正确方法继续你的工作。 · 假装按照你上司的吩咐去做,暗地里按照正确的方法去做。 · 先对自己如何做有一个全盘计划,等着上司对你的见解做评论并让人如何去做。 · 告诉你上司正确的方法,这是一个中途应变方法,因为你的上司经常提升、调动或被 解雇。 · 寻找另一份工作。 最好的解决方法是努力说服你的上司。这并不是一件容易的事情,但是你可阅读《How to Win Friends and Influence People》(《怎样赢得朋友和影响他人》)一书以学会如何准备这件事。 22222222....7 7 7 7 小小小小 结结结结 · 配置管理,适当应用时,可使程序的工作变得更容易进行。 · 你能找到某种方法度量项目的某一方面,这样比根本不度量好。准确的度量是完善的 计划、质量控制和提高开发速度的关键。 · 程序员和管理者都是普通的人,当他们受到礼待时往往干得更好。 · 合适的软件工程评估是软件开发管理最富挑战性的方面,你不妨尝试几种方法,看看 评估的差别,以加深你对项目的认识。 第二十三章 软件质量概述 375 第二十三章第二十三章第二十三章第二十三章 软件质量概述软件质量概述软件质量概述软件质量概述 目录目录目录目录 23.l 软件质量特点 23.2 提高软件质量的方法 23.3 各种方法的效果 23.4 何时应作质量保证 23.5 软件质量的一般原则 23.6 小结 相关章节相关章节相关章节相关章节 评审:见第 24 章 单元测试:见第 25 章 调试:见第 26 章 创建前提:见第 3 章 本章研究软件质量技术。虽然本书是讨论软件质量的提高问题,但是本章主要是对每种活 动的质量和质量保证进行讨论。它主要给出一个大概的轮廓而不是对具体细节的讨论。如果你 想了解对评审、调试、测试的有关细节,请看下三章。 23.l 23.l 23.l 23.l 软件质量特点软件质量特点软件质量特点软件质量特点 软件既有外部也有内部质量特征。软 件 的 外部特征是用户应了解的软件产品属性,它包括: · 正确性。整个系统受说明、设计和实现的错误影响程度。 · 可用性。用户学会和使用系统的难易程度。 · 效率。对系统资源的最小利用,包括存储和执行时间。 · 可靠性。系统在一定条件下执行特定功能的能力——在每次失效之间有一个较长的平 均时间。 · 完整性。防止非法或不适当地访问。完整性思想包括:限制非法用户访问,同时确保 证数据恰当访问;并行数据表进行并行修改;数据段仅含有有效数据等等。 · 适应性。系统在应用或其它环境下不作修改就能使用的能力,而不必经过特定的设计。 · 精确性。系统不受错误影响的程度,尤其是数据输出方面。精确性和正确性是不同的。 精确性是对系统完成其工作性能良好的衡量,而不是它设计得是否正确。 · 坚固性。系统对无效输入或压力环境中能继续执行其功能的能力。 以上有些方面是重复的,但是它们在某些场合有其特定的意义而在一些场合则没有。 第二十三章 软件质量概述 376 外部特征是用户所关心的特征。用户常关心软件是否易使用,而不是是否容易修改。他们 也关心软件是否能正确工作,而不是代码是否可读或结构较好。 但是,程序员既关心内部特征也关心外部特征。本书着重讨论代码,所以着重于内部特征, 其包括: · 可维护性。修改一个软件系统,提高其性能或修正其错误的能力。 · 灵活性。修改系统使其能适应于不同的用途或环境的能力,而不必对系统进行特定的 设计。 · 可移植性。能修改所设计的某一系统使其能在其它环境下运行的能力。 · 可重用性。能将系统的一部分用于其它系统的难易程度。 · 可读性。能读懂或理解系统源代码的能力,尤其是在细节说明这一级上。 · 可测试性。对整个系统进行单元或系统测试以证实其满足所有需求性能的测试难易程 度。 · 可理解性。能从整个系统水平或细节说明这一级上理解整个系统的难易程度。可理解 性要比可读性从更一般的水平上讨论系统的紧密性。 从所列的外部性能特点可知,它们和一些内部特征重合了,但是它们各自还是有着不同的 意义。 系统的内部性能是本书所讨论的主题,但是在本章中不对其进行深入讨论。 内部特征和外部特征不是非常分明的,因为从某些方面来说,内部特征影响外部特征。软 件的内部特征,如可理解性或可维护性的不佳将损害你纠正错误的能力,这会影响系统的外部 性能如正确性和可靠性。软件设计的非灵活性不能对用户的要求反应迅速,反过来这也会影响 外部性能,如可用性。关键是一些性能是适用用户的,而另外一些是适于程序员的,你应知道 如何选择。 为了获得某些最大性能,不可避免会跟其它性能发生矛盾。从互相矛盾的事物中挑选一个 最优解答,使软件开发成为一工程活动。图 23-1 给出了一些外部性能影响其它特性的途径。软 件质量的内部特征也同样有此关系。 被影响 特性 特性 正确性 可用性 效 率 可靠性 完整性 适应性 精确性 坚固性 正确性 ↑ ↑ ↑ ↑ ↓ 可用性 ↑ ↑ ↑ 效 率 ↓ ↑ ↓ ↓ ↓ ↓ ↓ 可靠性 ↑ ↑ ↑ ↑ ↑ ↑ ↓ 完整性 ↓ ↑ ↑ 适应性 ↓ ↑ ↑ 精确性 ↑ ↓ ↑ ↓ ↑ ↓ 坚固性 ↓ ↑ ↓ ↓ ↓ ↑ ↓ ↑ 最有趣的是本章着重于并不总是和其它特性有联系的一些特性。有时一种特性阻碍另一种 特性,有时则有助于另一种特性,或者既不妨碍也不促进。例如,正确性是关于满足规范要求 的特性。坚固性是关于在非预期环境继续进行工作的特性。着重于正确性将会影响坚固性,反 23-1 对软件质量其它特性产生积极消极或无影响的外部关系表(其中;↑帮助,↓影响) 第二十三章 软件质量概述 377 之亦然,而着重于适应性将会损害坚固性,反之亦然。 本图仅指出了质量特点的各典型联系。对任一给定的项目,某两特点间的联系可能和其典 型联系有所不同。对特定质量目标和各目标间的相互影响有一个通盘的考虑是有益的。 23.2 23.2 23.2 23.2 提高软件质量的方法提高软件质量的方法提高软件质量的方法提高软件质量的方法 软件质量保证,是保证系统满足性能要求的有计划、有组织的活动。开发高质量产品的最 为有效的方法是提高产品本身的质量。软件质量保证最好的方法是控制软件的开发过程。以下 是关于软件质量管理计划的组成部分: 质量管理目标质量管理目标质量管理目标质量管理目标。提高软件质量一个有效方法,是从上节所讨论过的外部和内部特征中挑选 出明确的目标。没有明确的目标,程序员就可能着重于并不是你所要求的特性。本书以下几部 分讨论了建立明确目标的益处。 确定质量保证活动确定质量保证活动确定质量保证活动确定质量保证活动。对质量保证的一般看法是将质量视为一个目标。的 确,在 一 些组织中, 急促和草率的编程往往是一件常见的事。程序代码充满错误但能很快完成编程的程序员往往能 得到更多的奖励。而高质量的程序员。虽然编出的程序优秀而且确保其是可用的,却往往得不 到这种礼遇。在这样的组织中,人们也就不会对程序员不再把编程质量放在首位而惊讶了。所 以应该让程序员明白质量是第一的。进行独立的质量保证活动使这种重要性得以体现出来,而 程序员也能作出相应的反应。 测试策略测试策略测试策略测试策略。如想了解测试的详情,请参看第二十五章“单元测试”。 执行时间可以作为对产品可靠性的估计。开发者往往将调试作为质量评估和质量提高的主 要手段。本章的其余部分更加详细地说明了调试的重要性。设计在高质量的软件创建过程中起 着重要的作用。而质量保证在对产品的质量、结构和设计有所约束的同时,也可得出相应的测 试策略。 软件工程准则软件工程准则软件工程准则软件工程准则。如想了解和创建有关的软件工程准则,请看第 3.6“编程约定”这一节。 在软件开发过程中,有一些准则用以控制某技术特性。这些准则可应用于开发过程中的各 个阶段,如问题定义、需求分析、结构、创建和系统测试。本书的准则,从某种程度上说,是 软件工程创建的准则(详细设计、编码、单元调试和集成)。 非正式技术检查非正式技术检查非正式技术检查非正式技术检查。许多软件开发者在将工作送交正式检查之前,往往先要对其进行非正式 检查。非正规检查包括手工检查设计、代码或对整个代码普查一下。 正规技术检查正规技术检查正规技术检查正规技术检查。软件开发管理是在最低级阶段发现问题,即在问题花费最少的阶段发现问 题。为了实现此目标,软件工程开发者常使用“质量门”——(quality gate)定期的检查以确 定某阶段的产品质量是否达到了继续下一步的要求。质量门常在需求分析和结构、结构和详细 设计、详细设计和编码、编码和测试之间使用。质量门可以是普查、客户检查等检查。 外部检查外部检查外部检查外部检查。外部检查是一种特定类型的技术检查,以决定项目的状态或正在开发之中的软 件质量。检查组来自外部并将其调查结果交给委托者,通常是你的上司。 开发过程开发过程开发过程开发过程。以上所提的各部分和软件质量有明确的关系,而与软件开发过程有着暗含的关 系。含质量保证活动的开发过程将比不含质量保证活动的过程会开发出更好的软件。其它不和 软件质量有明显关系的活动也将会影响软件的质量。 开发过程中一个并不明确、但能影响软件质量的情况是危险管理的使用。如果危险是可预 第二十三章 软件质量概述 378 测的、可靠的,则软件也是可预测的和可靠的。如果危险是特别的,则所编软件将是混乱的— —反映出产生它的组织的结构。 修改控制过程修改控制过程修改控制过程修改控制过程。妨碍软件质量的一个障碍是失控的修改。失控的修改会导致编码和设计的 毁坏。结构或设计上的失控修改会导致代码和设计不一致。而修改代码以适应新的设计将比按 预定计划需要更多的时间。对代码的失控修改,也会导致代码本身内部的不一致性,因不知道 哪些代码被调试过、检查过而产生的不一致性。而失控的文档修改——需求、结构、设计和代 码的集成表述,能产生以上各种影响。所以,有效地处理各种修改是进行有效开发的关键。 结果的定量结果的定量结果的定量结果的定量。质量确保计划应是可衡量的,否则你无法清楚计划是否产生了效果。度量告 诉你计划的成功或失败,也允许你对进度作出一定的修改,以看看进度是否有所加快。度量能 产生另外一些影响。人们常因度量的使用而注意到什么被度量了。仔细挑选你的度量对象,人 们习惯于着重那些被度量的工作,而忽略没有度量的工作。 原型原型原型原型。原型是对系统关键功能的可实现模块的开发。开发者应将用户界面部分地原型化以 确定可用性,同时进行计算以决定执行时间,以及数据安排所应满足的存储要求。对 16 个发表 的和 8 个未发表的用例调查,将原型和传统的规范开发方式相比较。这比较提示了原型可导致 更好的设计、更好地满足用户需求,以及提高可维护性。 设置目标设置目标设置目标设置目标 明确地设置质量目标是确保软件质量一个有效的步骤,但是它易被忽略掉。你可能怀疑如 果当你设置明确的质量目标之后,程序员们能否实现它们?回答是他们实现了的,只要他们知 道目标并且目标本身也是合理的。如果目标经常改变或根本不可能实现,程序员对目标是会无 动于衷的。 Gerald Weinberg 和 Edward Schulman 进行了一项有趣的试验,以研究设立质量目标对程序 员所产生的影响(1974)。其研究对象是分别工作于 5 个不同程序版本的 5 组程序员。这 5 个组 的质量目标是大致同的,每个组被告知可尽力去实现其不同的某方面目标。一个组被告知满足 最小存储要求,另一个组应产生最为清晰的输出,一个组被告知建立最好懂代码,另一个组被 要求使用最少的语句,最后一组则被告知用最少的时间完成程序的编制工作。以下是其结果: 每组目标范围 目标 最少存储 输入可读性 程序可读性 最少语句 最小编程时间 最少存储 1 4 4 2 5 输出可读性 5 1 1 5 3 程序可读性 3 2 2 3 4 最少语句 2 5 3 1 3 最少编程时间 4 3 5 4 1 本研究的结果是显而易见的。5 个组中的 4 个最先完成了被告知应优化的目标。而另一个 组是其次才完成了应优化的目标。以上各组对各自的目标都完成得相当好。 以上调查最令人吃惊的是人们往往按照所要求的去作。程序员有着较高的成功动机,他们 本表摘自《Goals and Performance in Computer Programming》(Weinberg and Schulman 1974)。 第二十三章 软件质量概述 379 会按所要求的目标去作,但是他们必须知道目标。第二个含义是,正如所预计的那样。由于目 标间常相互冲突,通常并不可能满足所有的目标。 23.3 23.3 23.3 23.3 各种方法的效果各种方法的效果各种方法的效果各种方法的效果 不同的质量确保活动将会导致不同的效果。人们已经研究了多种方法,其发现和纠正错误 的效果也是可知的。本节讨论质量确保活动的效果的几个方面。 有一些方法较其它方法能更好地发现错误,不同的方法将导致不同类型错误的发现。评估 错误检查方式的一个方法是,确定在产品寿命周期中所发现的错误对全部错误的百分比。表 23-1(摘自 Caper Jone 的《编程效率》)一书给出了 10 种常见方法发现错误的百分比。 表表表表 23----1 错误发现百分比错误发现百分比错误发现百分比错误发现百分比 步骤 最低比 中等比 最高比 对设计文件的人工检查 15% 35% 70% 非正式组内设计检查 30% 40% 60% 正式设计检查 35% 55% 75% 正式代码检查 30% 60% 70% 模型或原型 35% 65% 80% 人工代码检查 20% 40% 60% 单元测试(单个子程序) 10% 25% 50% 功能测试(相关于程序) 20% 35% 55% 系统测试(整个系统) 25% 45% 60% 段测试(动态数据) 35% 50% 65% 集成测试 93% 99% 99% 最有趣的是本数据提示出对任何单一方法其中等比不会超过 65%。而且,对于大多数一 般类型的错误如单元测试,其中等比仅为 25%。 本数据有力地说明,如果项目开发者争取得到一个较高的错误检查比,他们就需要集成应 用几种方法。Glenfotd Myers 的研究就证实了这点。Myers 研究了一组工作经验最少为 7 年而 平均工作经验有 11 年的程序员。在给出一个有 15 个已知错误的程序之后,他让每一位程序员 使用以下几种方法寻找错误: · 描述测试 · 对源代码测试 · 对描述和源代码进行普查 Myers 发现了很大的差别。每位程序员所发现的错误数从 1 到 9 个错误不等。而平均发现 错误数为 5.1,或者是程序错误总数的三分之一。 当单独使用时,不能说出以上各种方法谁优谁劣。人们所发现的错误数差别是如此之大, 但是,任何二种方法的组合使用(包括独立的两组使用相同的方法)就可能将所发现的错误总 数提高近二倍。对美国宇航局软件工程实验室的调查指出不同的人所发现的错误是不同的,只 有两位代码阅读者通过代码阅读发现了 29%的错误。 本表摘自《Programming Productivity》(Jones 1986) 第二十三章 软件质量概述 380 Glenford Myers 指出在发现某一类错误时,人工检查比计算机调试更为有效,反之亦然。 以上事实后来被下面的研究结果所证实:通过阅读代码能发现较多的控制错误。 由此得出的结论是,成对地使用错误检查方法要比单独使用好。Jones 也通过观察到集成错 误检查效率比任何单一方法要高许多。任何使用单一测试方法其效果并不明显。 Jones 指出多 种测试方法如单元测试、功能测试、系统测试的综合应用所产生的集成错误检查率将不会低于 60%,这对于商业软件来说是合适的。 发现错误的代价发现错误的代价发现错误的代价发现错误的代价 有些错误检查方法将比其它方法代价高。其中最为经济的方法是发现每个错误的代码都是 最低。这要求每件事物都是平等的。所有事情都等同这个条件是不可能的,因为每个错误所花 代价是受总的错误数影响的。另外每个错误都应被发现。而且这种评估只考虑错误检查方法并 不估计其它因素。 在1978 年 Myers 的研究之前,二种测试运行方法平均发现每个错误的代价差别不大,但是 人工检查方法所付出的代价是测试方法的二倍。不幸的是,以上结果并不是不容置疑的,因为 此次的研究对象缺乏检查经验。 当人们有了经验后,他们的检查工作也会更为有效。因此,最近的研究结果已有力地说明 检查比调试更为合算。有人对一个系统的三次交付进行了研究,第一次交付时,使用各种方法 也仅发现了 15%的错误。第二次交付时发现了 41%的错误,第三次则发现了 61%的错误。如 将以上事实用于 Myers 的研究,就有可能得出,对每个错误来说检查的代价仅为调试的一半而 不是调试的二倍的结论。对软件工程实验室的研究发现,阅读代码要比调试平均每小时多发现 80%多的错误。 修改错误的代价修改错误的代价修改错误的代价修改错误的代价 参见第 3.1 节“求助于数据”和第 25.4 节“典型的错误”。 发现错误的代价是总代价的一部分。另一个问题就是修改错误的代价。乍看起来你可能以 为修改一个错误应花费相同的代价。 其实并不是这样,错误留在系统中的时间越长,将其除去所花的代价也越大,能较早发现 错误的方法将导致低的错误改正代价。而且,有些方法如人工检查,一步就可完成发现和改正 错误的全过程。另外一些方法如测试,在发现错误之后还需另外的工作以分析和改正错误。从 总体上看,一步方法要比二步方法更为合算。微软公司的应用部已经发现,用代码检查的一步 方法可在 3 小时之内发现和改正一个错误,而用调试二步方法 12 小时之内才能发现和改正一个 错误。Collofello 和 Woodfied 曾经报道过一个由超过 400 人开发的 700,000 行程序,他们发现 代码检查和调试所获得的收效之比为 1.38:0.17。 以下是关于有效的软件质量程序必须包括的几种技术问题: · 对系统关键部分的正式设计检查 · 使用快速原型化技术进行模块化或原型化 · 代码阅读或检查 · 运行测试 第二十三章 软件质量概述 381 23.4 23.4 23.4 23.4 何时应作质量保证何时应作质量保证何时应作质量保证何时应作质量保证 正如第三章所指出的那样,错误进入软件的时间越早,它就越深藏于软件的其它部分中, 也就越不易将其移去。一个分析上的错误能在设计上产生一个或多个相应的错误,而这也会导 致代码产生许多错误。分析的错误能导致不必要的结构或坏的结构选择。多余的结构将导致多 余的代码、测试和文档。正如在浇铸地基之前,在设计蓝图上找出各种缺陷一样,在各活动之 前找出分析和结构错误不失为一种好方法。 另外,由于需求分析和结构比创建更为包罗万象,所以一个单一的结构错误能影响好几个 模块和不少子程序,而单一的创建错误不太可能影响多于一个的子程序或模块。由于这种原因, 尽力发现各种错误也是合算的。 在软件开发的各个阶段都有可能潜入错误。因此,你在开发过程中,应自始至终强调质量 保证工作。在工作一开始就应将其作为整个项目计划的一个部分。而且,质量保证工作也应坚 持到项目结束为止,这样才能最后确保产品的质量。 23.5 23.5 23.5 23.5 软件质量的一般原则软件质量的一般原则软件质量的一般原则软件质量的一般原则 如同没有免费午饭,或者即使有也不能保证其质量较好一样。软件开发和烹饪是全然不同 的事情,而且从某种意义上看,软件质量是不一般的。软件质量的一般原则是提高其质量并少 各种花费。 要理解此原则应基于对以下事实的理解:提高效率和质量的最好方法是减少代码再加工的 时间,不论再加工是由于要求的变更、设计的修改或调试。软件产品的工业平均生产率是每人 每天约 8 到 20 行代码。要编制 8 到 20 行代码是只需要几分钟的事情,那么,其它的时间干什 么去了呢? 开发一软件要比仅开发一个程序所花的时间多得多,而且软件开发所做的事要比仅编码多 得多。但这不是剩下的时间所作之事。 剩余的时间通常用于调试。调试通常要占一个传统的初始软件开发周期的 50%。消除掉防 止错误的软件调试可提高生产率。因此,缩短软件开发时间最为明显的方法是提高产品质量, 减少调试和再开发软件所需时间。 对实际数据的分析征实了这点,在对含超过 400 人年工作量和近三百万行代码的 50 个软件 项目进行调查后,美国字航局软件工程实验室发现,改进质量保证和降低错误相联系,但是并 不意味着总的开发费用降低。 对 IBM 公司的研究也得出了以下调查结果:如果不顾质量而只是想用最短的时间将软件开 发出来,往往很可能需要较长的时间和花费超出。从一开始就着眼于取得最高可能质量和可靠 性的软件开发,易于取得最好的开发进度、最高的生产率甚至是最好的市场成功率。 在一个较低的水平上也同样存在这种情况。在 1985 年的某一研究中,让 166 位专业程序员 对同一描述编写程序。结果是程序平均行数为 220 行,并且只有少数人仅用 5 小时就完成了编 程工作。最为有趣的是花费中等时间的程序员所编的程序错误要少得多。图 23-2 给出了这种结 果: 第二十三章 软件质量概述 382 最快的组需花 5 倍的时间才能取得和最慢的组相同的错误率。往往并不一定是开发无 错误的软件需更多的时间,而是开发没有错误所需时间少些。正如上图所示,没有错误的开发 所需时间更少。 虽然,对某一类项目,质量保证需有所花费。如果你在为航天飞机或生命保障系统编写代 码,可靠性需求会使本项目花费更多。 跟传统的编码——测试——调试相比,一个明确的软件质量程序有助于各种费用的节 省。它避开重新分配资源以进行前期质量确保活动。前期活动较后期对产品质量有更大的影响, 你在前期活动中所投入的时间将会节省更多的后期时间。其结果是较少的错误、较短的开发时 间和较低的代价。在下二章中你将会看到软件质量的一般原则的更多例子。 检查表检查表检查表检查表 质量保证步骤质量保证步骤质量保证步骤质量保证步骤 · 你是否有项目较为重要的特定质量描述? · 你是否让其它人明白项目的质量目标? · 你是否要求不同的外部和内部恃征? · 你是否考虑过某些特性可能和其它相互矛盾或采用共同促进的方式? · 你的项目是否需要几种不同的错误检查方法,以便能发现几种类型的错误? · 你的项目是否包括这样一个计划,以便在软件开发过程中采取措施确保软件质量? · 你的软件质量是否能按某种方式度量,以确定软件质量是提高或下降了? · 你在管理中是否知道质量确保在刚开始可能增加费用,但今后的花费能节省? 23.6 23.6 23.6 23.6 小小小小 结结结结 · 并不是所有质量保证目标都能实现。确定你想实现的目标,并将其让你所在组的每一 个人知道。 图 23-2 产生最多错误的软件开发并不是最快或最慢的软件开发 第二十三章 软件质量概述 383 · 各种错误检查方法就其自身来说并不十分有效。仅用一种方法消除错误并非有效。成 功的质量确保计划使用几种不同的方法,以确定不同类型的错误。 · 你可在创建之前或创建过程中使用几种有效的方法以发现错误。你发现错误越早,其 所引起的损失也越小。 · 软件开发领域中的质量保证是面向过程的。软件开发并不包括影响最终产品的重复开 发阶段,所以产品开发过程控制着产品的质量。 · 质量最终是主动的,它要求对系统的资源进行再分配,以便能预防错误而不是花费较 大地去提高质量。 第二十四章 评 审 384 第二十四章第二十四章第二十四章第二十四章 评评评评 审审审审 目录目录目录目录 24.1 评审在软件质量保证中的地位 24.2 检查 24.3 其它评审方法 24.4 小结 相关章节相关章节相关章节相关章节 软件质量概要:见第 23 章 单元测试:见第 25 章 调试:见第 26 章 创建前提:见第 3 章 你可能同其它程序员一样有了一定的经验。你为你的用户作好了演示的准备并已从程序中 排除了所有错误。在演示过程中,用户提议采用奇怪的输入,按随意秩序操作键盘,或者将程 序运行 10 次。突然毛病不知从什么地方冒出来了。所有的评审都试图用一种或多种方式使你的 工作在用户见到前显得更完美。如果你已读过检查的有关章节,你可能从本章得不到多少新知 识。在第 24.l 节中所给出的检查效果的数据可能会使你吃惊,你也可能没有意识到第 24.3 节所 述的代码阅读检错法的重要性。但是如果你目前了解的都来自自己的经验,请读下去!其它人 既然有着不同的经验,你也可从中获得不少新见解。 24.124.124.124.1 评审在软件质量保证中的地位评审在软件质量保证中的地位评审在软件质量保证中的地位评审在软件质量保证中的地位 所有的技术评审,虽有着不同之处,都是建立在本思想之上:开发者对其工作中的一些故 障点是一无所知的,而另外一些人则不存在这个盲区。所以让别人来检查你的工作是有益的。 解释一下问题,足以使你找到问题症结之所在。常见到一些程序员走进另一个程序员的办 公室并说道:“请你看看我的程序,好吗?我遇到麻烦了。”于是程序员开始解释问题,约三分 钟后,在“帮助者”未发一言之前,被帮助者自己就已明白了错误之所在。 有些人从某种特定的技术角度使用“评审”这个词。在本章中,“评审”是检查、初查、代 码阅读以及别人查看你的工作的其它方法的总称。 评审和其它质量保证方法互补评审和其它质量保证方法互补评审和其它质量保证方法互补评审和其它质量保证方法互补 评审的主要技术目的是提高软件质量。正如第二十三章所指出的那样,单一的软件测试方 法只能取得有限的效果——单元测试的检错比仅为 25%,功能测试为 35%,而集成测试为 45%, 相比之下,设计和代码检查的检错比平均为 55%、60%。而 评审的辅助作用在于减少了开发时间, 从而降低了开发费用。对评审结果事例的研究情况是令人难忘的: 第二十四章 评 审 385 · 在一次软件维护中,在引入代码检查前,55%的联机维护修改是错误的,而在使用代码 检查后,只有 2%的修改是错误的。当所有的修改一并考虑时,应用代码检查法一次就 可改正 95%的错误,而未用代码检查法,一次只能纠正 20%以下的错误。 · 在同一开发组开发 11 个程序的过程中,开发的 5 个没有用评审的检查方法,其余的 6 个则用到评审的检查方法。在所有的程序都提交生产后,头 5 个程序平均每百行代码 有 4.5 个错误,而经过评审的其余 6 个,则每百行代码只有 0.82 个错误。评审竟减少 了 80%以上的错误。 · Aetna 保险公司发现,程序中 82%的错误可通过检查发现并且检查可将开发资源减少 25%。 · IBM 公司的 500,000 行的 Orbit 项目使用了 11 种检查方法。它不仅交付时间短,而且 所产生的错误仅为正常情况时的 1%。 · 对 AT&T 公司的一个超过 20D 人的机构研究表明采用评审检查方法后生产率可提高 14%,而错误可减少 90%。 · 喷气推进实验室估计,在早期发现和定位错误,每次检查可节省开支 25,000 美元。 以上结果有力地说明了软件质量的一般原则,即减少软件中的错误数也能缩短开发时间。 不同的研究表明,在除了发现错误方面较调试更为有效之外,评审较调试能发现各种不同 类型的错误。另外一个影响是,当人们意识到其工作将接受评审时,他们往往会仔细检查自己 的工作。所以,即使调试相当有效,评审对一个程序的集成性质量保证是必需的。 评审传活使用技巧和编程经验评审传活使用技巧和编程经验评审传活使用技巧和编程经验评审传活使用技巧和编程经验 软件标准可被记录和传播,但是如果没有人谈论它或鼓励别人去使用它的话,它们将不会 被遵循。评审是对程序员的代码有所反馈的一种重要方法。代码、标准、代码和标准相一致的 原因等都是评审所涉及的话题。 除了能得到遵循标准程序的反馈外,程序员还需要以编程方面获得更多的反馈信息:格式 化、注释、变量名、局部和全局变量的使用、设计方法、处理本地事情的方法等等。没有经验 的程序员需要从熟练的程序员处获得指导。有经验的程序员往往显得很忙,应鼓励他们抽出时 间进行经验交流。评审给有经验或缺乏经验的程序员们提供了交流技术的途径。因此,无论在 现在或将来,评审给提高质量提供了机会。 评审质量和进度评审质量和进度评审质量和进度评审质量和进度 评审一般包括两种类型:管理的和技术的,管理评审用于评估进度和建议纠正之中。它们 着眼于花费和计划。管理评审是重要的,但是不是本书讨论的主题。 技术评审也可用于评估进度,但是它要求技术进度是可评估的。这通常意味着以下二个问 题:(1)技术工作是否正在进行之中?(2)技术工作是否做得较好?以上问答是技术评审的副 产品。 在创建中应自始至终多用评审方法在创建中应自始至终多用评审方法在创建中应自始至终多用评审方法在创建中应自始至终多用评审方法 本书是讨论创建的,所以对细节的设计和代码的评审是本章的主题。但是,本章对评审的 大部分讨论同样适于管理、计划、需求分析、结构和维护的评审,读过本章后,你就可将评审 第二十四章 评 审 386 应用于软件开发的任何阶段。 24.2 24.2 24.2 24.2 检检检检 查查查查 检查是一种特殊类型的评审,它在错误检查中被证明是行之有效的,并且和测试相比,它 是一种相对较为经济的办法。检查方法由 Michael Fagan 所发明,并在 IBM 应用了几年,最终 由 Fagan 将其出版发行。虽然任何评审方法都包括阅读设计资料或代码,检查和走马观花式的 参观是不同的: · 检查表将检查者的注意力集中在过去所遇到的问题的领域中 · 检查侧重于错误检查而不是纠错 · 评审者在检查之前应先举行预备会议,而他们最后得出的问题应是经过共同讨论的 · 所有的参加者被分配以不同的任务 · 检查协调者并不是被检查产品一方的人 · 协调者在协调检查方面受过一定的训练 · 每次检查所得数据都被收集起来,以便反馈给今后的检查以提高检查质量 · 一般管理人员并不参加检查会议,而技术主管则有可能参加会议 你能从检查中获何收益你能从检查中获何收益你能从检查中获何收益你能从检查中获何收益 一般来说,设计和代码检查的联合使用可去除产品中 60%到 90%的错误。检查可较早地发现 有错误苗头的子程序,Fagan 指出检查比预查每 1000 行代码要少 30%的错误。设计者和编码者 通过参与检查可以提高工作能力,同时检查也可提高生产率达 20%左右。在使用设计和编码检 查的项目中,检查可花费项目总时间的 15%。 检查中的各项分工检查中的各项分工检查中的各项分工检查中的各项分工 检查的一个重要特点是每个人都各司其职。以下是各种分工: 协调者协调者协调者协调者。协调者负责控制检查进度,使其既有一定的检查速度又能最大限度地发现错误。 协调者必须有一定的技术胜任能力,当然并不苛求他是受检查的设计或代码方面的专家。 但是他应能了解相关细节。这类人员负责检查的一切方面如分配受检的设计、代码或检查表、 确立会议室地点、报告检查结果、落实检查会议所确定的有关内容。 项目主持者项目主持者项目主持者项目主持者。设计者或代码编写人员在检查中扮演着相对次要的角色。检查的部分目标是 保证设计或代码能站得住脚,如在评审、检查过程中发现设计或代码并不清晰,主持者将被告 之修改,以使它更为清楚。否则,主持者应解释不清晰部分的设计或代码,甚至,让他解释为 什么看起来有错误的部分例实际上可以接受。如果检查者对项目本身并不熟悉,主持者应对整 个项目作简单介绍以便为检查会议作准备。 检查者检查者检查者检查者。检查者是任何对设计或代码有直接兴趣者,但他不是本项目的主持者。设计的检 查者可能是对设计有帮助的程序员。检查者中也可能有测试者或高水平的结构人员。检查者的 任务是发现缺陷。他们常在准备过程中发现错误,当检查会议讨论设计或代码时,整个组将会 发现更大的错误。 记录员记录员记录员记录员。记录员记录所发现的错误和检查会议上的情况。有时记录员也可由协调者或由另 第二十四章 评 审 387 外一些人兼任。但主持者或检查者不作为记录员的兼任。 管理者管理者管理者管理者。软件检查并不纯粹是技术检查。管理者的出现改变了技术间的相互作用。人们常 感到他们不是检查材料,而是需要评估的。这也会将问题的重心从技术上转移到政治上去。管 理者有权知道检查的结果,检查报告也应让管理者知道。 同时,检查结果不应该用来作为对性能的评估。不要杀死正在下金蛋的天鹅。检查中的代 码仍需不断完善。性能评估应建立在最终产品的基本之上,而不是基本未完成的工作之下。 总的说来,一次检查至少应有三个参加者。协调者、主持者、检查者,并且以上三角色不 能各自兼任。一般将检查组规模控制在 6 人左右,因为再增加人数的话,就难以管理了。对喷 气推进实验室的研究表明,三人以上的检查组并不能提高所发现的错误总数。 检查的一般过程检查的一般过程检查的一般过程检查的一般过程 检查包括以下几个明显的阶段: 计划计划计划计划。主管者将设计或代码提交给协调者,协调者决定谁将参与检查,并确定何时何地召 开检查会议,接着将设计、代码或引人注目的检查表分发给每一位检查员。 总览总览总览总览。当检查员对要受检查的项目不太熟悉时,主管项目者应花费一小时左右的时间介绍 一下生成设计或代码的环境。但是这并不是件容易的事情,因为它往往给需检查的设计或代码 造成不清晰的假象。设计或代码应能自己站得往脚。总览不应提及它。 准备准备准备准备。每一位检查者在正式工作之前,先花 90 分钟的时间熟悉设计或代码。检查者使用检 查表以激励或指导对检查材料的检查工作。 为了对用高级语言编写的应用程序代码进行评估,检查员可先每小时阅读 700 行代码。而 为了对用高级语言编写的系统程序代码进行评估,检查员每小时只能阅读 125 行代码。最有效 的检查速度差别很大,所以你应记录下你所在组中的各种速度以便在你的环境中最有效地应用 它们。 检查会议检查会议检查会议检查会议。协调者选择一些人——通常是项目主持者解释设计或代码的阅读。所有的逻辑 都要解释,包括每个逻辑结构的分枝。在检查时,记录所发现的错误,如果检查确认一个错误 时,往往会停止对错误的讨论。记录员记下这种类型的错误并标明其重要性,检查工作继续进 行。 对设计或代码的检查不能太快也不能太慢。如果太慢了,易使人的注意力迟缓同时,效率 也太低了。如果检查过快,就可以漏掉很多应发现的错误。最优检查速度随环境而异,正如准 备速度一样。你应作好记录工作,以便随着时间的推移你能找出你所在环境的最佳速度。有些 组织已经发现对系统码来说,最佳检查速度是每小时 90 行代码。对应用程序代码来说,最佳检 查速度可提高到每小时 500 行代码。 在会议过程中不必讨论问题的解答,应着重于确定错误。有些检查组甚至不允许讨论一个 错误是否为一个真正的错误。他们认为如果被错误的是非弄混淆了,相应的设计、代码和文档 也同样需得到澄清。 会议时间通常不应超过两小时。这并不意味着你制造一个假的火灾警报以便让人们在二小 时之内都出来。但是 IBM 和其它公司的经验表明,检查者并不能一次在超过 2 小时的时间内全 神贯注地工作。同样,在同一天中计划多次检查也是不明智的。 检查报告检查报告检查报告检查报告。在一天中的检查会议后,协调者编制一份列出每个错误的类型和严重性的检查 第二十四章 评 审 388 报告。检查报告有助于纠正所有错误并且可得出对程序组有特别意义的错误表。如果你能坚持 收集每次所花时间和所发现错误数的数据,你就可用这些硬数据确定检查员的工作效率。否则, 你无法清楚地知道检查员是否工作得更好。你也能明白检查员是否适于在你的环境中工作从而 决定是变动还是不聘用他们。数据收集工作是重要的,因为任何一种新方法都需要用某种标准 衡量其优劣。 再工作再工作再工作再工作。协调者将错误提交给某些人,通常是主管者,以供其修改。他们将清单上所列错 误一一改正。 执行执行执行执行。协调者负责检查再工作的执行。如果超过 5%的设计或代码需再加工,整个检查过程 应重新进行。 如果低于 5%,协调者仍然可以要求进行再检查或亲自证实再加工。 虽然在检查过程中参与者不能讨论所产生问题的解答,有些人仍想探讨有关问题。但是, 不能不管大家喜欢不喜欢就随便作修改。将评审过程“规范化”以便你能知道你的修改是否有 益。 检查过程的所有部分都是必需的。已经发现,去掉或合并其中任意几个部分都将耗去更多 的代码。如果你想改变检查过程而没有一种可以度量改变的方法,那么你就别这样做。如果你 可对整个过程进行度量,而且知道改变后检查过程工作得更好,那么你就应坚持下去。 在检查过程中,你将发现某些类型的错误较其它错误更经常发生。建立一个引起人们对这 类错误注意的检查表,以便检查者能将重点放在这类错误上。随着时间的流逝,你将会发现一 些检查表上所没有的错误,所以你可将其加进检查表中。你也可能发现初始检查表上出现的一 些错误停止出现了,这时你可将其从检查表中去掉。在进行一些检查以后,你所在组就能得到 一个符合需要的错误检查表,同时对故障区域有一个清晰的了解,这样才可能让程序员有针对 性地进行训练和得到帮助。将你的检查表限制在一页之内,较长的检查表在所要求的细节水平 上是很难使用的。 检查本身检查本身检查本身检查本身 检查本身是为了发现设计或代码中的错误。它不是为了寻找选择对象或者争辨问题的是非。 当然也不应批评设计或代码的主持者。检查对项目主持者来说应是有积极意义的,它应明显表 明全体参与者都可提高程序质量,并且对所有参与者来说他都能从中获得不少体验。另外,检 查组不能向项目主持者说有一些人是笨蛋应让其卷起铺盖滚蛋。像“任何知道 Pascal 语言的人 都知道从 Num 循环到 0 是更为有效”的此类评论是根本不合适的。如果真是这样,协调者应将 这种情况明白无误地表达出来。 由于检查要对设计或代码作出评论,项目主持者可能感到自己和它们有某种牵连,主持者 自然觉得自己对代码有一种亲密之情。主持者应能预测到检查组所发现的一些错误其实并非错 误,而且有一些可能是有争议的问题。尽管那样,主持者应接受每个已指出的错误并继续下去。 接受某一评论并不意味着主持者就认为它是正确的。项目主持者尤不应庇护受检查的工作。在 检查之后,主持者可独立地考虑每个问题并决定其是否有效。 检查者应懂得项目主持者是最终负责怎样处理一个错误的人。喜欢发现问题是好的(有时 除了检查之外,还提出解答方法),但 是 每一位检查者都应尊重主持者最后决定怎样处理这些错 误的权力。 第二十四章 评 审 389 检查表检查表检查表检查表 有效检查法有效检查法有效检查法有效检查法 · 你的检查表是否着重将检查员的注意力引向过去常发生错误的地方? · 是否侧重于缺陷检查而不是纠错? · 在检查会议之前检查员是否有足够的准备时间?每一位检查员都作好了准备吗? · 每一位参与者是否都扮演不同的角色? · 会议是否开得富有成果? · 会议是否限制在 2 小时之内? · 协调者在指导检查方面接受过特殊的训练吗? · 在每次检查中,错误类型数据是否都作了收集,以便于你今后制作检查表? · 是否收集了准备和检查率,以便可以优化将来的准备和检查? · 每次检查所指定的条款是否都落实了?是由协调员本人还是重新作了检查? · 管理员是否明白为什么他不参加检查会议? 检查概述检查概述检查概述检查概述 检查表可使检查员注意力集中在某一类错误上。由于检查过程有标准的检查表和标准的各 司其职的人员,它是一个有组织的过程。它也是一个自我优化过程,因为它使用有正规的反馈 循环以提高检查表质量、监控准备和检查速度。有了以上对过程的控制和优化,不管它怎样开 始的,检查很快就成为一种强有力的方法。软件工程学会(SEI)已经定义了用于度量软件开发 过程效率的技术标准。检查过程表明了何为最高水平。同时,检查过程应是有组织的、可重复 的,并能使用可度量反馈以提高检查质量。你同样可将本方法实际应用于本书所讨论过的任何 技术中。在开发过程中将这些思想归纳起来,那么简单地说,它们可使你所在机构向着最高质 量和生产率的层次进军。 24.3 24.3 24.3 24.3 其它评审方法其它评审方法其它评审方法其它评审方法 其它评审方法不像检查方法那样得到实际经验的有力支持,所以目前它们所涉及的范围不 广。本节所讨论的评审方法有:普查、代码阅读、软件演示。 普查普查普查普查 普查是一种流行的评审方法。普查这个词定义并不严密,因为人们实际上可称任何类型的 评审方法为“普查”。 由于普查定义不严密,所以也就很难对其给出确切的定义。当然,普查包括二人或多人讨 论设计或代码这种情况。普查可能就像即席的随意探讨会一样不正式。有时它也可能像一个预 定的会议或送给管理者的总结报告一样正规。在某种意义上讲,“什么地方有两或三个人聚在一 起”,什么地方就存在普查。普查方法的支持者赞成使用这样一个宽松的定义。所以 本文只打算 找出普查的一些共同之处,剩余的细节留给你自己去处理。 普查通常由接受评审的设计或代码的主持者所采用。 第二十四章 评 审 390 · 普查的目的是为了提高程序的技术质量,而不是评估程序。 · 所有参与者通过阅读设计或代码为普查做准备并寻找错误。 · 普查可为老资格程序员向新手提供传播经验和合作精神的机会,它也为年轻的程序员 提出新方法,并向一些过时的结论挑战提供机会。 · 普查通常需花费 30 到 60 分钟的时间。 · 普查侧重于发现错误,而不是纠正错误。 · 管理人员并不参加普查。 · 普查这个概念是灵活的,它也适应于特定的场合。 你能通过普查获何收益你能通过普查获何收益你能通过普查获何收益你能通过普查获何收益 如果用得恰当,普查可得到和其它评审方法类似的结果,就是说,它一般能发现程序中 30% 到 70%的错误(Myers 1979,Boehm 1987,Yourdon 1989)。普查的检错效率比评审稍低一些, 但是在一些场合,普查还是相当有效的。 如使用不得当,普查可能会造成不少麻烦。普查的最低效率为 30%,这并无多大价值。至 少一个组织已经发现对代码的扫视检查将是“异常不合算”的(Boeing Computer Services)。 Boeing 发现,说服项目人员自始至终应用普查方法是困难的,而且当压力增大时,应用普查方 法几乎是不可能的(Glass 1982)。 检查和普查的对比检查和普查的对比检查和普查的对比检查和普查的对比 检查和普查有何差别?以下为二者差别摘要。 性质 检查 普查 正规协调者培训 是 否 参与者分工明确 是 否 谁“主持”检查或普查 协调者 通常为作者 “怎样发现错误”——检查表 是 否 集中评审量——寻找最常见类型的错误 是 否 正规执行以减少不正确的定位 是 否 由于对程序员的详细错误反馈所导致的较 少后期错误 是 影响不大 对结果的分析导致检查效率的提高 是 否 对过程中导致发现错误的数据的分析反过 来也导致检查效率的提高 是 否 在排除错误方面,检查比普查显得更为有效。但是为什么有人爱用普查呢? 如果你拥有一个较大的评审组,普查不失为一种较好的评审方法,因为它给接受评审的程 序带来多种不同的见解。如果所有参加普查的人都相信解答是正确的,就不会有大的缺陷。 如果有来自其它组织中的评审员,普查也可能是可行的。在检查中,每个人的职责分工是 明确的,在人们有效地运用它们之前需要一个实践过程,让以前未曾参加过检查的人当评审员 是不利的。如果你想让他们出一份力,普查可能是最好的选择。 检查比普查更有所侧重也能获得更好的收获。如果你想为所在组织选择一个评审标准,在作 第二十四章 评 审 391 出选择之前好好考虑一下。 代码阅读代码阅读代码阅读代码阅读 代码阅读是对检查和普查的另一种选择。在代码阅读时,你能读源代码并寻找错误。你也 同时对代码的质量如其设计、风格、可读性、可维护性和有效性提出你的见解。 对美国宇航局软件工程实验室的一项研究表明,代码阅读平均每小时可发现 3.3 个错误。 调试平均每小时可发现 1.8 个错误(Card 1987)。代码阅读在软件生存期中要比其它调试方式 多发现 20%到 60%的错误。 同普查一样,代码阅读的定义并不严格。代码阅读通常是二人或多人各自阅读代码然后和代 码的开发者一起讨论。以下是阅读的方法: · 在开会之前,代码开发者将源代码分发给代码阅读者。代码长度通常从 1000 到 10000 行不等。典型的是 4000 行。 · 一个或多个人共同阅读代码。最少应有 2 个人,以便激励在评审者间的竞争。如果你 使用的人数多于 2 个,你应度量每个人的贡献,以便了解多余的几个人究竟有多大的 贡献。 · 评审者独立地阅读代码。其速度大约是每天 1000 行。 · 当评审者完成代码阅读后,代码开发者主持召开代码阅读讨论会。会议持续时间为 1 个或 2 个小时,其侧重点是代码阅读者所发现的问题。通常人们并不是一行一行通读 代码。本会议不是必需的。 · 代码开发者确定由评审者所发现的错误。 代码阅读和检查、普查的区别在于代码阅读侧重于个人对代码的评审,而不是着重于会议上 的讨论。其结果是,评审者的时间大都花费在寻找代码的问题了。这样,花费在开会上的时间将 减少因为每人只需花费少部分时间在这上面。因为会议所花时间对一个中等组来说是可观的。 除非各组员能在二小时内碰头,开会耽搁的时间也少。代码阅读在当评审员不在一起时是相当有 价值的。 软件演示软件演示软件演示软件演示 软件演示是一种将软件产品向用户展示的方法。在与政府所签合同的软件开发过程中,用户 评审是常见的,因为这在需求、设计和代码方面对评审有所要求。软 件 演示的目的是向用户表明 软件质量是好的,所以软件演示是管理评审而不是技术评审。 你不要指望依靠软件演示来提高产品的技术质量。准备软件演示可能对技术质量有间接影 响,因为大部分时间都花费在使软件用户界面较好,而不是用在提高软件技术质量上面。 24.4 24.4 24.4 24.4 小小小小 结结结结 · 总的来说,评审在发现错误方面较测试要好。 · 评审侧重于错误发现而不是纠错。 · 评审往往比测试要能发现更多种不同的错误,意味着你应使用评审或调试方法以确保 软件的质量。 第二十四章 评 审 392 · 使用检查表、准备良好的分工、连续的处理以便最大限制地提高检错能力,检查往往 较普查能发现更多的错误。 · 普查和代码阅读是检查的候补方法。代码阅读可有效地利用每个人的时间。 第二十五章 单元测试 393 第二十五章第二十五章第二十五章第二十五章 单元测试单元测试单元测试单元测试 目录目录目录目录 25.l 单元测试在软件质量中的作用 25.2 单元测试的一般方法 25.3 测试技巧 25.4 典型错误 25.5 测试支持工具 25.6 提高测试质量 25.7 测试记录 25.8 小结 相关章节相关章节相关章节相关章节 软件质量概述:见第 23 章 评审:见第 24 章 集成;见第 27 章 调试:见第 26 章 创建前提:见第 3 章 测试是最为流行的提高质量的方法——它受到工业研究和大学研究的有力支持,而且也得 到商业研究的支持。在本章中,测试是指单元测试——对单个子程序和模块而不是对整个系统 的测试。“测试”在本章中也可指“玻璃盒”测试,你可用它来测试你自己的代码——而功能测 试是一独立测试者用基本的功能描述来检查一个程序。 如果一个系统足够小,你可应用本章的技术来测试整个系统。如果系统稍大一点,它可能 由一个专门的测试组织来测试,而你所需作的是,在其合并成为一个总系统之前对每个子程序 进行单元测试。 有些程序员将“测试”和“调试”混用,但是细心的程序员是区分这两种活动的。测试是 发现错误的方法,而调试是对错误进行诊断并改正它们。本章仅讨论错误检查。错误纠正将在 第 26 章中详细讨论。 25252525....l l l l 单元测试在软件质量中的单元测试在软件质量中的单元测试在软件质量中的单元测试在软件质量中的作用作用作用作用 测试对任何一个软件质量来说是重要的,在许多场合,它只是其中一部分。这是不幸的,因 为评审的各种方式证明,它们比测试要能发现更多的错误,而且评审发现每个错误所花费仅为 测试的一半左右(Card 1987)。单个测试方法(单元测试、功能测试、部分测试、系统测试),通常 只能发现少于 50%的错误数。几种测试方法的集成使用,只会发现少于 60%的错误(Jones 1986)。然而,由于测试已被广泛使用,而且它比评审要能发现更多种类型的错误,因此它应该 第二十五章 单元测试 394 是软件开发周期的一部分。 如果你列出一些软件开发活动,并问“哪件事情和其它事情不同?”回答将是“测试”。由于 以下原因,测试对许多开发者来说都是一项较难掌握的活动: · 测试的目的是同其它开发活动的目的相矛盾的。测试是为了发现错误。一次成功的测 试将中断软件的开发。而其它开发活动是为了防止错误和防止软件的终止。 · 测试结果不能证实错误的多少,只能证实其存在。如果你进行了广泛的测试并发现了 成千上万的错误,并不意味着你已经发现了所有的错误。未发现错误,也并不意味着 软件是完美无缺的。 · 仅用测试并不能提高软件质量。测试结果可以说明软件质量的好坏,但是并不能提高 它。想靠增大测试量来提高软件质量,就如你想靠不断给自己称重而达到减肥的目的 一样。在你称重前你所吃的东西将决定你到底有多重,你所用的软件开发技术决定了 你调试所发现的错误数。你如想减肥,不必再买一台新磅秤。你只需改变你的饮食习 惯。如果你想提高软件质量,不必进行大量测试;你应认真在开发过程中下功夫。 · 测试需要你假定你能在代码中找到错误,如果你认为不会找到错误,你倒可能真的找 不到,你应对此有一个粗略计划。如果你执行一个程序,并希望找不到错误,你就可 能忽视许多你应发现的错误。Glend Myers 让一群有经验的程序员对一个有 15 个错误 的程序进行测试。程序员所发现的平均错误个数仅为 5 个。最好的是仅发现了 9 个错 误。其它错误未被发现的原因是由于没有仔细检查错误的输出结果。其实这些错误是 可见的,但是程序员并没有注意到它们。 你应期望在代码中能发现错误。存在这样的想法可能被视为是反常的,但是指望发现错误 的正是你而不是他人。 一个重要问题是,对一个典型项目进行单元测试究竟要花费多少时间?普遍的数据是测试 占项目时间的 50%,但是由于以下原因它并不是正确的。首先,以上数据包括了调试和测试, 测试本身所花时间会少些;其次,以上数据是典型所花时间量而不是应花费的时间量。最后, 以上数据既包括了系统测试也包括了单元测试。 正如图 25—1 所示那样,依项目的大小和复杂性不同,单元测试所占总的项目时间从 8% 到 35%不等。这和已经报道的许多数据是相一致的。 其次,你怎样处理单元测试的结果呢?通常,你可借此评估所开发产品的可靠性。即使你 无法改正测试所发现的错误,你也可以知道软件的可靠程度,测试结果的另一个用途就是可以 利用它们引导对软件的修改。最后,经过一段时间的积累,你就可通过测试中所记录错误发现 最常见的错误类型。你可以利用这些数据选择合适的训练课程,指导今后的技术评审活动以及 今后的测试。 创建中的测试创建中的测试创建中的测试创建中的测试 正如本章前言所指出的那样,整个测试的范围要比创建过程的测试范围广泛得多。本书并 不讨论广义的测试:系统测试、功能测试、黑盒子测试等等。 以上测试方法有时忽略了本章的“玻璃盒”或“白盒子”测试方法。你通常愿意将子程序 设计成黑盒子形状——子程序的用户只需了解接口要求,而不必知道子程序的内部情况。在测 试子程序时,应将其视为玻璃盒子,既看子程序的内部源代码,也检查其输入和输出,这样作是 第二十五章 单元测试 395 较好的。当然如果了解盒子的内部情况,你就可更仔细地测试你的子程序,至少你有更多的机 会发现错误。和不熟悉代码者相比较,你处在发现错误的更有利位置上。 在创建过程中,你编写子程序,对其进行手工检查,然后评审或测试它。不论你的集成或 系统测试策略如何,在你将其合并之前,你应仔细地测试每个单元。如果你在编写几个子程序, 你应不时测试一下它们。单个测试子程序并不容易,但是要调试它们是容易的。如果你马上将 未测试的子程序组合起来并且发现了错误,那么其中任何一个子程序都有可能有错误。如果你 每次将一个子程序加进已经测试过的子程序中,你就能知道任何一个新错误可能是由新的子程 序或者是由新、旧子程序的相互作用所引起的。这时,使用调试方法较为简单。 25252525....2 2 2 2 单元测试的一般方法单元测试的一般方法单元测试的一般方法单元测试的一般方法 一个有条理的单元测试方法,可使你用最小的努力最大限度地发现各种类型的错误。请记 住以下各点: · 对每个需求进行测试,以便确保需求得到实现。在需求阶段上计划测试或尽量使测试 早一些——在你编写单元测试前。你应考虑测试对需求的遗漏。安全性、存储、安装 程序、系统可靠性都是测试的对象,并且在需求分析时它们都易被疏忽。 · 对和设计有关的程序进行测试以确保设计得到了实现。在设计阶段尽早计划测试—— 在你开始进行子程序的详细编码工作之前测试。 · 在详细测试的基础上对需求和设计测试增加基本测试。使用数据流测试和其它测试方 法仔细检查你的代码。从最低限度来说,你应对每行代码进行测试。下节将讨论基本 测试和数据流测试。 在产品生成过程中应编制测试用例。这样就能避免需求和设计中的错误,而改正这类错误 所费代码要比代码错误大。尽早计划测试和发现错误以便能合理地改正错误。 图 25-l 随着项目大小(代码行数)的增加,单元测试占整个开发时间的百分比减小 第二十五章 单元测试 396 25.3 25.3 25.3 25.3 测试技巧测试技巧测试技巧测试技巧 为什么有可能利用测试确定程序中的错误呢?为了测试程序的性能,你不得不对你的程序 测试每一种输入数据或输入数据的组合。即使对一个简单的程序,这样的工作也令人难以容忍。 例如,你的程序接收人名、地址、电话号码并将其存在一个文件中。这是一个简单的程序,并 且比任何让你厌烦的程序都要简单。进 一 步假定每个可能的名字和地址是 20 个字符的长度,并 且它们要用到 26 个可能的字符。以下是可能的输入数据量: 名字 2620(20 个字符,每个字符有 26 种可能选择) 地址 2620(20 个字符,每个字符有 26 种可能选择) 电话号码 1010(10 个数字,每个数字有 10 种可能选择) 总的可能 =2620*2620*1010=1066 即使是这样较小输入量,你也将有 1066 种测试用例。如果诺亚走出其方舟并以每秒 1 兆次 的速度对程序进行测试,他即使到现在也才完成了全部工作量的 l%。显然,如果你增大实际数 据量,对全部可能性进行测试几乎是不可能的。 不完全测试不完全测试不完全测试不完全测试 对各种可能全部进行测试是不可能的,实际说来,测试的艺术在于从所有测试用例中找出 最能发现错误的示例来。在 10 种可能测试示例中,只有少部分可能发现错误。你应侧重于从所 有测试示例中找出能提示不同点的用例,而不是那些不断地重复着的用例。 当你计划测试时,你应排除那些没有告诉你任何新东西的用例,这就是说,此时对新数据 的测试将不会产生错误。 人们已提出了不少有效的非实例测试法,下文将讨论其中的一些方法。 善于结构的测试善于结构的测试善于结构的测试善于结构的测试 尽管其名字是粗糙的,基于结构的测试,其实是一个简单概念。其意思是你应对你程序中 的每一条语句至少测试一次,如果本语句是一条逻辑语句,通常是用 if 或 while,你就应根据 if 或 while 表达式中的复杂性仔细测试,这样才能确保每条语句都经过了测试。确保所有语句 都经过测试的最简单的方法是由程序计算路径数,然后设计最少数量的测试用例,以确保所有 路径都得到了测试。你可能听说过“代码覆盖”测试或“逻辑覆盖”测试;它们都是使你的测 试程序中所有路径的方法。由于这两种方法覆盖了所有路径,它们和基于结构的测试是相似的, 但是这二种方法覆盖所有路径时并不使用最小的测试用例集。如果你使用代码覆盖或逻辑覆盖 方法,你所需的测试用例可能比你用基于结构的测试所需的测试用例要多。 你可用表 25-1 所给出的方法计算所需的最少测试用例数。 表表表表 25252525----l l l l 确定基于结构的测试方法所需的测试用例确定基于结构的测试方法所需的测试用例确定基于结构的测试方法所需的测试用例确定基于结构的测试方法所需的测试用例 1.对于程序的第一条直接路径开始,设定计数值为 1。 2.每遇到下一个关键词或其等价词: if,while,repeat,for,and 和 or 计数值加1。 3.在 case 语句中每遇到一个 case, if 数值加 1。如果 case 语句中不含缺省语句,计数值再加1。 第二十五章 单元测试 397 以下是一个例子: 计算路径数目的一个简单的 Pascal 程序例子: Statement1; ――开始计数 1 Statement2; if X< 10 then ――遇到 if 计数 2 begin Statement3; End;1 Statement4; 本例中,你一开始将计数置为 1,在遇到 if 语句后计数值变为 2,这意味着你至少需要 2 个用例以便覆盖程序中的所有路径。在以上例子中,你应有以下示例: · 受 if 控制的语句得到执行(X<10) · 不受 if 控制的语句得到执行:(X>=10) 代码例子还应更实际一点,以便对测试工作有一个清晰的了解。例子中实际还包括有缺陷 的代码。 下面的程序稍复杂一点。在本章以后都使用这个例子并且它可能有一些错误。 确定基于结构的测试所需测试用例的一个 Pascal 例子; 1 { Compute Net Pay} 程序开始,计数 1 2 3 TtlWithholdings: =0; 4 5 for ID: =1 to NumEInPloyees ——for,计数 2 6 begin 7 8 { compute social security withholding, if below the maximum 9 if(Employee[ID].SSWithheld<MAX_SOCIAL_SECURITY =then ——if,计数 3 10 begin 11 SocialSecurity: =ComputeSocialSecurity(Employee[ID]) 12 end; 13 14 {set default to no retirement contribution} 15 Retirement: =0; 16 17 {determine discretionary employee retirement contribution} 18 if(Employee[ID].WantsRetirement)and ——if,计数 4 and,计数 5 19 (EligibleFotRetirement( Employee[ID])) then 20 begin 21 Retirement: =GetRetirement(Employee[ID]); 22 end; 23 24 GrossPay: =ComputeGrossPay(Employee[ID]); 第二十五章 单元测试 398 25 26 { determine IRA contribution } 27 IRA :=0; 28 if( EligibleForIRA( EmPloyee[ID])) then ——if,计数 6 29 begin 30 IRA:=IRAContribution(Employee[ID],Retirement,GrossPay) 31 end; 32 33 { make weekly paycheck} 34 Withholding:=ComputeWithholding(Employee[ID]); 35 NetPay :=GrossPay-Withholding-Retirement- 36 SocialSecurity-IRA; 37 PayEmployee(EmPloyee[ID],NetPay); 38 39 { add this employee’s paycheck to total for accounting} 40 TtlWithholdings := TtlWithholdingst 十 Withholding; 41 TtlSocialbourity := TtlSocialSecurity +SocialSecurity; 42 TtlRetirement :=TtlRetirement + Retirement; 43 end; {for} 44 45 SavePayRecords( Ttlwithholdings, Ttlsocialsecurity, TtlRetirement); 本例中,你需要有一个初始计数值,每遇到 5 个关键词的一个,计数值加 1。但是并不意 味着任意 6 个测试用例就覆盖所有示例。它只是表明最少需要 6 个用例。除非这些用例构造得 相当好,否则将不会覆盖所有情况。关键是当你计算所需用例的数目时,你应注意你所用的相 同的关键词。代码中每个关键词代表了或真或假的一类事物。你能确信对每个真或假应至少有 一个测试用例与其—一对应。 以下是覆盖了上例中所有基数的测试用例: 用例 测试描述 测试数据 1. 无用例 所有布尔值为真 2. 初始 for 条件为假 NumEmployees<1 3. 第一个 if 为假 Employee[ID]SSWithheld>= MAX_SOCIAL_SECURITY 4. 第二个 if 为假 (因为 and 的第一部分为假) not Employee[ID].WantsRetirement 5. 第二个 if 为假(因为 not EligilbleForRetirement(Employee[ID]) and 的第二部分为假) 6. 第三个 if 为假 not ElgibleForIRA(Employee[ID]) 如果你的子程序比以上所讨论程序还要复杂,你所用测试用例以便覆盖所有路径的数目将 会大大增加。短的子程序有较小的测试路径。没有较多 and 和 or 的布尔表达式需测试的变量数 也较少。测试的容易也正说明了子程序较短并且你的布尔表达式较为简单。 既然已为你的子程序创建了 6 个测试用例,并且满足了基于结构的测试需求,你是否以为 第二十五章 单元测试 399 你的子程序已经测试完了?不是,这种类型的测试只能保证代码是可执行的。它并不能解释数 据间的差别。 数据流测试数据流测试数据流测试数据流测试 本章最后一节和本节可使你明白在计算程序中控制流和数据流是何等重要的。 数据流测试是基于数据使用至少同控制流一样易受错误影响这个事实。Boris Beizer 断言现 代程序中至少含有一半的数据说明和初始化(1990)。 数据的存在有以下三种状态: 已定义数据已定义数据已定义数据已定义数据。数据已经初始化,但是还没有被使用。 已使用数据已使用数据已使用数据已使用数据。数据已经作为子程序或其它程序的变量用于计算了。 已无效的数据已无效的数据已无效的数据已无效的数据。数据曾一度定义过,但是在某种程序上已失去定义。例如,如果数据是一 个指针,可能它已经被释放了。如果数据是一个 for 循环指针,可能程序已经跳出了循环,而 程序对超出 for 循环的指针没有定义。如果数据是一个文件指针而当文件关闭后,记录指针就 无效了。 除了“定义”、“使用”、“失效”这些词外,使用一些描述立即或退出子程序的词是方便的: 进入进入进入进入。控制流在对变元处理前立即进入子程序。 退出退出退出退出。控制流在对变元处理后立即退出子程序。 数据状态的组合数据状态的组合数据状态的组合数据状态的组合。数据组合的一般状态是某变量已被定义使用了一次或多次,可能失效, 请看以下方式: 定义定义定义定义————————定义定义定义定义。如果某变量在使用前已被定义过 2 次。那么你需要的不是一个好程序,而 是一台好计算机。这样浪费较大且易出错,即使实际上没有错误。 定义定义定义定义————————退出退出退出退出。如果变量是一个局部变量,没有必要知道是否定义它,也可能没有使用本 变量就退出子程序。如果是子程序常量或局部变量的话,以上行为没有任何影响。 定义定义定义定义————————失效失效失效失效。如果你定义了一个变量但是没有使用它的话,这是一种浪费。你所用变量 多了,可能你需要使程序更小。 进入进入进入进入————————失效失效失效失效。这是对局部变量而言的,如果你没有定义或使用一个变量,你用不着使其 失效。另一方面,如果是一子程序常量或局部变量,只要在使其失效之前对其定义,本模式也 同样有效。 进人进人进人进人————————使用使用使用使用。本模式也同样是对局部变量而言的。变量在使用之前需定义。另一方面, 如果是一子程序常量或局部变量,只要在某使用之前进行定义,本数据模式也同样有效。 失效失效失效失效————————失效失效失效失效。变量不需失效二次,变量也无需恢复,除非你需要一台新的计算机。否则, 恢复变量的使用意味着编程的马虎,双重失效对指针来说是重要的——挂起你机器的最好方法 是释放一指针两次。 失效失效失效失效————————使用使用使用使用。使用一个已失效的变量有时也有作用,但是这总是一个逻辑错误。如果代 码看起来也能工作,但这只是偶然,在某个时候,它会产生故障引起更大的损害。 使用使用使用使用————————定义定义定义定义。使用和随之定义一个变量是否带来问题取决于变量在使用前是否已定义。 当你见到一个使用一定义型数据时,你应检查以前的定义。 在开始测试之前检查反常的数据状态序列。在检查所有反常的数据序列后,编写数据流测 试的用例关键是检查所有可能的定义——使用路径。你检查的广度可随情况而异,它包括: 第二十五章 单元测试 400 • 所有定义。对每个变量的所有定义进行测试(这就是说,任何一个变量每接收一个数 据时都应对其测试)。这是不好的策略,因为如果你想测试代码的每一行的话,你可 能 会失败。 • 所有定义——使用混合数据状态。测试每一个某处定义然后在另一处使用的变量。这 较测试所有定义是一种较好的策略,因为它仅执行每一行代码,而不确保每一定义 ——使用类型的数据将接受测试。以下是一个例子: 将接受数据流测试的一个程序例子: if (Condition 1) x = a; else x = b; if (Condition 2) y = x + 1; else y = x – 1; 为了覆盖程序中的所有路径,你需要使条件 1 是真或假的测试用例。你也需要使条件 2 为 真或假的测试用例。以上可以用二个测试用例来处理:用例 1(条件 1 为真,条件 2 为真)和用例 2(条件 1 为假而条件 2 为假)。以上二个用例是你用基于结构的测试方法所必需的。它们也是你 执行定义变量的每一行代码所必需的;它们可自动地进行简单的数据流测试。 为了覆盖每一种定义一使用类型,你需增加一些测试用例。你应还有使条件 1 和条件 2 同 为真或假的测试用例: x = a; …… y = x + 1; and x = b; y = x – 1; 但是你还是需要更多的用例以测试定义——使用混合类型的数据。你需要: (1)x=a 然后 y=x-1; (2)x=b 然后 y=x+1。在本例中,你可增加 2 个用例而得到以上组合:用例 3(条件 1 为真, 条件 2 为假)和用例 4(条件 1 为假条件 2 为真)。 开发测试用例的较好方法是从基于结构化的测试开始,它可向你提供一些定义使用数据流。 然后增加一些测试用例以便得到完整的定义使用数据流测试用例。 正如上一节所讨论的那样,基于结构的测试给第 25.3 节中的一个 45 行的 Pascal 语言程序 提供了 6 个测试用例。对每个定义一使用类型的数据流进行测试需要有更多的用例。可能其中 一些被一些已存在的测试用例所覆盖。以下是在基于结构的测试所产生的测试用例上所增加的 第二十五章 单元测试 401 数据流混合类型用例。 用例用例用例用例 测试描述测试描述测试描述测试描述 7 在第 15 行中定义在第 30 行第一次使用,且没有被以前其它测试用例覆盖。 8 在第 15 行中定义在第 35 行第一次使用,且没有被以前其它测试用例所覆盖。 9 在第 21 行中定义在第 35 行第一次使用,且没有被以前其它测试用例所覆盖。 当你做过几次数据流测试用例后,你就能明白哪些测试用例是颇有成效的,哪些是已被覆 盖了的。当你测试受阻时,你可列出所有的定义一使用混合类型数据。虽然看起来可能费事, 但是,它可使你明白一些用基于结构测试的方法所不能得到的用例。 等效类划分等效类划分等效类划分等效类划分 一个好的测试用例应覆盖相当大一部分可能的数据输入。如果二个测试用例提示出相同的 错误,你可挑选其中的任一个。“等效类划分”的概念是以上思想的体系化并可减少所需的测试 用例数。 本书第 25.3 节的 45 行 Pascal 程序中,第 9 行是使用等效类划分的好地方。测试条件是 Employee[ID].SSWithheld < MAX_SOCIAL_SECURITY。此时测试用例有二类:第一类是 Employee[ID].SSWithheld 比 MAX_SOCIAL_SECURITY 小,第二类是 Employee[ID].SSWithheld 大于或等于 MAX_SOCIAL_SECURITY。程序的其它部分可能有其它的等效类,这意味着你可 能将测试比Employee[ID].SSWithheld二个值更多的用例,但是对于我们所讨论的这个程序来说, 只需讨论 2 个即可。 当你已经对程序进行了基本和数据流测试时,再进行等效类划分将不会使你对程序有深入 的认识。当你从子程序的外部看它(如从描述而不是从源代码时),或数 据较为复杂而这种复杂 并没有在程序的逻辑结构中有所反应时,等效类划分是异常有用的。 错误猜测错误猜测错误猜测错误猜测 除了使用正规的测试技术外,好的程序员常使用一些非正规的、直接推断方法以提示代码 中的错误。对应用编程来说,测试方法的不同往往导致测试结果的不同。例外情况是实时处理, 但它不在本文的讨论范围内。 其中的一个直接推断方法是错误猜测。“错误猜测”这 个词是对一个合理的概念所取的平庸 的名字。错误猜测意味着测试用例建筑在对程序可能发生错误处的猜测上,错误猜测是需要一 定经验的。 你对错误的猜测是建筑在直觉或过去的经验上。第 24 章指出检查的一个优点是它们能指出 和列出常见错误表。错误表可用来检查新的代码。当你将过去所遇到的错误记录下来,你就有 可能增大你的错误猜测所发现错误的可能性。 以下几部分讨论了几种可进行错误猜测的错误类型。 边界分析边界分析边界分析边界分析 测试的一个最有收效的领域是边界条件仅发生微妙的错误,如将 Num – 1 值认为是 Num 的值。或将 >= 误为 > 。 第二十五章 单元测试 402 边界分析就是写出测试边界条件的用例来。如果你正在调试小于 MAX 范围内的数,你有 以下三种可能条件,显示如下: 正如上图所示,有三种可能的边界用例:小于最大值,最大值自身,以及大于最大值。一 般需要以上三个测试用例,以便确保没有发生常见的错误。 在 25.3 节中所示 45 行的 Pascal 程序包含着这样一个测试:Employee[ID].SSWithheld > MAX_SOCIAL_SECURITY,根据边界分析的原则,需检查三个用例: 用例用例用例用例 测试描述测试描述测试描述测试描述 1 定义用例 1是使 Employee[ ID].SSWithheld<MAX_SOCIAL_SECURITY 这个条件为真时边界条件 为真。于是,用例 1 使 Employee[ID].SSWithheld 为 MAX_SOCIAL_SECURITY – 1 值。本测试用例 已经产生。 3 定义用例 3 是使布尔条件 Employee[ID].SSWithheld<MAX_SOCIAL_SECURITY 为假时,边界条件为 假。于是.用例了使 Employee[ID].SSWithheld 之值为 MAX_SOCIAL_SECURITY+l 本测试用例也已 经产生。 10 另外一个测试事 件是测试 Employee[ID].SSWithheld=MAX_SOCLAL_SECURITY 这 是个死 循 环事件。 复合边界复合边界复合边界复合边界 边界条件分析也同样存在最少和最大容许值。在本例中,它可能是最小或最大总开支、总 收入或总贡献,但是由于对这些值的计算已超出子程序的范围,对它们的测试用例在此不作深 入探讨。 当边界含有几个变量是,一种更微妙的边界条件将从中产生。例如,二个变量相乘,当 2 个数都是大正数时将会出现什么情况呢?大的负数呢?0 呢?如果传递给一个子程序的字符串 都是非同一般的呢?在第 25.3 节的 45 行 Pascal 程序,当每个雇员的工资数相当多时——如一 帮年薪为 250,000 美元的程序员(我们总是这样希望的),此时你会想到变量 TtlWitholdings, Ttlsocialsecurity 和 Ttlretirement 到底有多大吗?以上是需要另外一个测试用例的: 用例用例用例用例 测试描述测试描述测试描述测试描述 11 现有一大帮雇员,每个人的薪水相当高——由于某种原因,1000 位雇员每人年薪为 250,000 美元,他 们不需交任何社会保险机井区所有人都扣除退休保险金。 以下是一个测试用例,但是和以上用例相反雇员人数少而且每个人的薪水为 0.00 美元 用例用例用例用例 测试描述测试描述测试描述测试描述 12 10 位雇员,每人年薪为 0.00 美元 坏数据的排序坏数据的排序坏数据的排序坏数据的排序 除了猜测边界条件时所发生的错误外,你也可猜测和测试几种其它类型的坏数据。典型的 坏数据测试用例包括: Boundary Boundary below Max Max above Max 第二十五章 单元测试 403 • 太小的数据(或无数据) • 太大的数据 • 错误类型的数据(无效数据) • 错误长度的数据 • 未赋初值的数据 如果你听从已经提出的建议,你就能想到一些测试用例。例如,用例 2 或用例 12 包括了“太 小的数据”,你提出一些含有“错误长度”的数据的用例是困难的。下面提出了一些用例: 用例用例用例用例 测试描述测试描述测试描述测试描述 13 一共有 32,768 位雇员.要测试的数据是很大的.当然,数据到底有多大要因系统而异.但是,从本 例中你可看到这个数据是相当大的. 14 薪水为负值,是错误类型的数据. 15 雇员人数为负数。是错误类型的数据。 好数据排序好数据排序好数据排序好数据排序 当你试图找出程序中的错误时,往往忽视微小的用例中也往往包含错误。通常,基本测试 段中的微小用例常是一种较好类型的数据。以下是值得你去检查的较好类型的数据。 • 微小事件 • 最小正常配置 • 最大正常配置 • 和其它旧数据的兼容性 对以上各种类型的数据进行检查能发现各种错误,这取决于测试对象。 最小正常配置不仅可用于一种测试,还可用于多种测试。它和边界条件的许多最小数值在 本质上是相似的,但是最小正常配置是建立最小数值集合而不是正常期望值集。一个例子是在 测试数据表时,需存储一张空数据表。为了测试字处理程序,你需要存储一个空文件。在对例 子的运用过程中,测试最小正常配置需增加以下测试用例: 用例用例用例用例 测试描述测试描述测试描述测试描述 16 一位雇员.测试最小正常配置 而最大正常配置和最小配置相反。它和边界测试本质上相似,但是,它是建立最大数值集 而不是正常期望数值。其中的例子是存储数据表。或者打印最大尺寸数据表格。对一个字处理 器来说,它应能保存一份最大建议长度的文件。在对程序的测试过程,对最大正常配置的测试 取决于最大正常雇员人数。如假定最大人数为 500,你应增加以下测试用例: 用例用例用例用例 测试描述测试描述测试描述测试描述 17 最大人数为 500,测试最大正常配置 最后一类正常数据测试,是测试和旧数据的兼容性。其使用场合是当用一个程序或子程序 代换一个过时的程序或子程序时,新的子程序应能和旧程序一样利用这些旧数据产生相同的结 果,当旧的子程序存在错误时,此种版本的连续性是回归测试的基础,其目的是为了确保修正 后能保持原来的质量而不至于后退。在运行程序时,兼容性标准不应增加测试用例。 第二十五章 单元测试 404 检查表检查表检查表检查表 测试用例测试用例测试用例测试用例 • 每个子程序的要求是否有自己的测试用例? • 子程序结构的每个部分是否都有自己的测试用例? • 程序中每一行代码都是否至少被一个测试用例所测试过?这是否是由通过计算测试 每一行代码所需的最少用例来确定的? • 所有定义——使用数据流路径是否被至少一个测试用例所测试过? • 代码是否被看起来不大正确的数据流模式所检查过?比如定义一定义,定义一退出, 和定义一失效? • 是否使用常见错误表以便编写测试用例来发现过去常出现的错误? • 是否所有的简单边界都得到了测试:最大、最小或易混淆边界? • 是否所有复合边界都得到了测试? • 是否对各种错误类型的数据都进行了测试? • 是否所有典型的中间数都得到了测试? • 是否对最小正常配置进行了测试? • 是否对最大正常配置进行了测试? • 是否测试了和旧数据的兼容性?是否所有保留下来的硬件、操作系统旧的版本,以及 其它软件旧版本的接口都得到了测试? • 测试用例是否便于手工检查? 使用便于手工检查的用例使用便于手工检查的用例使用便于手工检查的用例使用便于手工检查的用例 假定你为某一工资系统编写测试用例,你需输入某人的薪水,其方法是你可随意敲进几个 数,试敲入: 1239078382346 这是一个相当高的薪水,比 1 万亿美元还要多,你可截短一部分得到一个更为实际的数字: 39,078.38 美元。 现在,进一步假定本测试用例成功了,就是说,发现了错误。你怎样知道这是一个错误? 现在,再假定你知道答案,因为你用手工计算出了正确的答案。当你用一个奇怪的数字如 34,078.38 美元进行计算时,你在得到程序结果的同时,自己却将结果计算错了。另一方面,一 个较好的数字如 20,000 美元你一眼就能记住它。0 非常容易送入计算机,对 2 相乘是绝大多数 程序员轻而易举就能完成的。 你可能认为一些令人厌烦的数据如 39,078.73 美元可能更易提示出错误,但是它确实是不如 其它数有效。 25.4 25.4 25.4 25.4 典型错误典型错误典型错误典型错误 本书可使你明白当对错误有深刻了解时,你能测试得很好。 第二十五章 单元测试 405 哪一个程序含最多的错误哪一个程序含最多的错误哪一个程序含最多的错误哪一个程序含最多的错误 你可能很自然地想到错误均分在你的整个源代码中。如果你平均每 1000 行代码发现 10 错 误,你可假定平均第 100 行子程序你可发现一个错误。这确实是一个很自然的假设,但是它是 错误的。实际上,绝大多数错误往往倾向于集中在少数有缺陷的子程序中。以下是错误和代码 的一般联系: • 80%的错误往往出现在 20%的子程序中(Endres 1975,Gremillion 1984,Boehm 1987)。 • 50%的错误往往出现在 10%的子程序中(Endres 1975,Gremillion 1984)。 在你认清必然之前,你也可能认为这种联系是并不重要的。 首先,20%的项目子程序占用了 80%的开发代价。但是,这并不意味着这花费最多的 20% 的子程序就是含最多错误的 20%的子程序。 其次,尽管高缺陷率子程序所耗代价比是一定的,它所耗代码是异常昂贵的。在 60 年代, IBM 公司对 OS/360 操作系统进行研究时发现,所有错误并不是均分于整个子程序中而是集中 在少数子程序中。那些常带有错误的子程序是“程序开发中昂贵的实体”(Jones 1986)。在 1000 行代码中。这些子程序所含错误数可高达 50 个,修改这些错误常常是十倍于开发整个系统所需 的平均时间。 第三,开发 代价高昂的子程序的含义也是显而易见的。正如一古老的名言所说一样,“时间 就是金钱”,其推论是“金钱就是时间”。如果你避开那些令人讨厌的子程序,就可将各种开支 削减 80%左右,同时将整个项目的工作量削减相当多。这是软件质量一般原则的鲜明表述,即 提高开发质量就能提高开发进度。 第四,避开令人生厌的子程序,对维护的含意也是清晰的。维护侧重于判断、重设计、重 写那些被认为是有错的子程序。 Gerald Weinberg 曾报道过一个允许维护程序员花费少量时间选 择和重写那些产生最多问题子程序的例子。在相当短的时间内,总错误比和维护量都减少了很 多。 错误排序错误排序错误排序错误排序 已有研究者试图按其类型将错误排序,并确定每种错误类型的范围。每位程序员都曾遇到 过令人生厌的错误:边界错误、忘记重新初始化循环变量等等。本书中的所有检查表提供了更 多的细节。 Baris Beizer 集成了几种研究数据,得出了一种不容置疑的详细错误排序方法。以下是其研 究结果的摘要: 25.18% 结构错误 22.44% 数据错误 16.19% 功能实现错误 9.88% 实现错误 8 98% 系统错误 8.12% 功能需求错误 2.76% 测试定义或执行 1.74% 系统、软件结构错误 第二十五章 单元测试 406 4.71% 其它 Beizer 的研究结果为两位有效数字,但是对错误类型的研究通常并不是不可置疑的,目前 人们已经报道了许多类型的错误,研究错误的所得结果相差也很大。其结果差别可能为 50%而 不是一个百分点。不同的报道其结果相差大,像 Beizer 这样将各种研究结果集成起来所得出的 结论可能是没有什么意义的。但是即使所得结论不是无可置疑的,它还是有一定建设性意义的。 以下是由 Beizer 的研究所得出的一些推论: 大多数的错误的范围是相大多数的错误的范围是相大多数的错误的范围是相大多数的错误的范围是相当有限的。当有限的。当有限的。当有限的。某一项研究表明:不用修改多于一个的子程序,你就 可发现 85%的错误(Endres1975)。 许多错误并不是结构性错误许多错误并不是结构性错误许多错误并不是结构性错误许多错误并不是结构性错误。。。。研究人员进行了 97 次采访发现三个最常见的错误源为:贫乏 的应用控制知识、需求的冲突和缺乏信息交流和协调( Curits , Krasner 和 Iscoe1988)。 大多数实现错误来自程序员大多数实现错误来自程序员大多数实现错误来自程序员大多数实现错误来自程序员。。。。在所有错误中,有 95%的错误是由程序员本人引起的,2%的 错误由系统软件所引起,l%是由硬件引起(Brown 和 sampaon19733,Ostrand 和 Weyuker1984)。 书写错误是一个相书写错误是一个相书写错误是一个相书写错误是一个相当普遍的错误源当普遍的错误源当普遍的错误源当普遍的错误源。。。。一项研究表明 36%的错误是书写错误(Weiss1975)。 在 1987 年对接近了百万行的动态飞行软件研究表明 18%的错误是书写错误(Card1989).另一 研究表明:4%的错误是拼写错误(Endre1975)。在作者本人的一个程序中。一位同事发现几个 拼写错误只是由于通过一个拼写检查程序运行一个可执行文件所引起的。你应对细节计算有所 注意,如你对此有所怀疑,你应考虑三种最为昂贵的编程错误。Gerald Weinberg 报道这三种错 误的花费分别为 16 亿美元、9 亿美元、2.45 亿美元。以上每一花费都包括了对前一个修正程序 的任何修改。对设计的误解是许多程序常犯的错误。 BeizerBeizerBeizerBeizer 的研究发现的研究发现的研究发现的研究发现 16.19%16.19%16.19%16.19%的错误是由于对设计的误解的错误是由于对设计的误解的错误是由于对设计的误解的错误是由于对设计的误解。。。。而另一项研究则表明 19%的错误 是由于对设计的误解(1990)。花费时间对设计有一个深刻的理解是值得的。虽然这并不能产生 立竿见影的效果,但是在整个项目的生存周期中你将会得到回报的。 避免赋值语句的错误是质量的关键避免赋值语句的错误是质量的关键避免赋值语句的错误是质量的关键避免赋值语句的错误是质量的关键。。。。一项研究表明 41%的错误来自赋值语句,它和绝大部 分错误是边界错误或循环错误是相矛盾的(Youngs1974),这项研究还同时表明:发 现赋值语句 错误所花时间是发现其它错误所需时间的 3 倍(Gould1975),它指出了一个主要的盲区。在此 以前,我们似乎没有给予赋值错误以和边界错误相同的地位。但是花费时间考虑如何避免它们 是值得的。 大多数错误是容易改正的大多数错误是容易改正的大多数错误是容易改正的大多数错误是容易改正的。。。。大约 85%的错误在几个小时之内就可改好。而约 15%的错误修 改时间为几小时或几天不等,其余 1%的错误所需时间稍长一点。本结果也得到 Beizer 的约 20% 的错误需花费 80%的资源来改错的观点支持。通过回溯设计进行分析和设计评审可避免出现较 多的错误。 用错误数度量你所在组的经验用错误数度量你所在组的经验用错误数度量你所在组的经验用错误数度量你所在组的经验。。。。本节中所讨论的结果不同,说明了不同的人其实际经验是 相差悬殊的、这使你很难利用其它组的经验。一些结果跟通常的直觉是相矛盾的、你可能需要 用其它工具弥补你的直觉的不足。一个较好的方法是度量你的进度,这样你就能够知道问题之 所在。 错误创建所导致的出错比较错误创建所导致的出错比较错误创建所导致的出错比较错误创建所导致的出错比较 如果错误划分不明确,许多数据错误就可归于由不同的开发活动所引起的。其中,创建总 第二十五章 单元测试 407 会引起不少错误。有时,人们认为改正创建错误要比改正分析或结构错误省事些。改正单个创 建错误可能是省事一些,但这并不意味着定位全部创建错误的代价小。 • 在小规模项目中,实现错误占了整个错误相当大的一部分比例。在一次对一个小项目 (1000 行代码)的代码研究中,75%的错误来自编码,10%错误来自分析,15%的错误来 自设计(Jones1986a)。这种错误划分可视为许多小项目的错误分配。 • 实现错误至少要占整个错误的 40%。虽然对大一点的项目这个数字稍低些。有些研究 人员甚至发现,对一些非常大的项目,实现错误甚至可占整个错误的 75%(Grady1987)。 一般说来,对应用领域懂得越多,整个结构也就越好。这样,错误就倾向于集中在细 节设计和编码上面(Basili 和 Perricone1984)。 • 创建错误的修改虽然比对分析和设计错误的修改要省事一些,但是代价仍是昂贵的。 对 Hewlett-Packard 的两个项目研究发现:改正创建错误所花代码是设计错误的 25%到 50%。但是当从整体上考虑较大数量的创建错误时,改正创建错误的总代码是改正设 计错误代价的 1 到 2 倍。 图 25-2 给出了项目大小和错误数的大致关系 你能发现多少错误你能发现多少错误你能发现多少错误你能发现多少错误 你所能发现错误的数量随开发过程的质量而异。以下是可能值: • 对于所交付软件,一般是每千行代码约 15 到 20 个错误。软件开发通常采用联合开发 技术,可能 包括结构化程序开发。出现 1/10 错误的软件开发是少见的,而 10 倍以上 错误的软件往往不会报道(它们可能永远也不会完工)。 • 微软公司开发部的经验是,在开发过程中每 1000 行代码为 10 到 20 个错误,而产品投 放市场后平均每千行发现 0.5 个错误。之所以能取得这样的水平是因为采用了第 243 节所述的代码阅读技术和独立测试技术。 • Harlan Mils 开创了”无尘开发”的方法,这种方法可以得到每千行代码为 3 个错误,而 在产品投放后每千行代码为 0.1 个错误的错误率。有一些项目,如航天飞机软件,通 过采用各种开发方法,如代码阅读、评审、统计调试等,甚至可以做到 500,000 行代码 第二十五章 单元测试 408 中不出现一个错误。 无尘开发的结果有力地证实了软件质量的基本原则:开发一高质量软件要比开发一低质量 软件并改正其错误要省事。对一个 80,000 行使用无尘开发的项目,其生产率是 740 行代码每人 月,而一般方法其生产率是约 150 行代码每人年。代价小而生产率高的原因是由于无尘开发实 际上不需调试。Mills 宣称当一个开发组在完成 3 或 4 个无尘开发项目后,它有可能将所出现错 误数减少 100 倍,而同时却能提高生产率近 10 倍。这真是值得啊! 测试自身的错误测试自身的错误测试自身的错误测试自身的错误 你可能有如下体验:你发现软件有错误,你凭直觉觉得某部分代码可能出错了,但是所有 的代码似乎又都没有错。你试着用几种新的测试用例发现错误,但是所得结果仍是正确的。你 花费几小时反复阅读代码并且用手工计算结果,但是仍不能发现错误。过了几小时之后,你重 新检查测试数据,你终于找到了错误!原来是测试数据本身出了错。你会感到你浪费数小时的 时间来寻找测试数据而不是代码中的错误是一件多么愚蠢的事啊! 这也是一个常遇到的体验。测试用例可能比接收测试的代码含有更多的错误。原因很简单 ——主要是在开发者编写测试用例时疏忽所致。这些测试用例往往是即席制作,而不是在仔细 设计和实现过程中得到的,它们通常被视为一次性测试,并且是可以抛弃的某种东西。 你可按以下几种方法减少你测试用例中的错误数: 检查你的工作检查你的工作检查你的工作检查你的工作。。。。你应如同开发代码一样仔细地开发你的测试用例。你当然也要对自己的测 试用例进行二次检查,可在调试器程序一行一行地调试你的测试代码,就如你调试你的程序代 码一样。普查和检查你的测试数据是较为合理的方法。 在软件开发过程中计划测试用例在软件开发过程中计划测试用例在软件开发过程中计划测试用例在软件开发过程中计划测试用例。。。。有效测试计划应从需求分析阶段或你接受指定的程序任 务时开始,这有助于减少测试用例中的错误。 保留你的测试用例保留你的测试用例保留你的测试用例保留你的测试用例。。。。花费一点时间提高你的测试用例质量。将它们保留下来以便进行循环 测试和对第二版本的利用。如果你习惯于保留测试用例,你就可以很容易地发现问题之所在。 25.5 25.5 25.5 25.5 测试支持工具测试支持工具测试支持工具测试支持工具 本节讨论你可购买到或自己开发的测试工具。在此也无法说出某一种具体产品,因为在你 读到此处时它们可能都过时了。你可查阅最近一些你最喜爱的程序员杂志。 建立“脚手架”以便测试你的子程序建立“脚手架”以便测试你的子程序建立“脚手架”以便测试你的子程序建立“脚手架”以便测试你的子程序 “脚手架”这词来自建筑术语。“脚手架”用于方便工人能到达建筑物的一些地方。软件“脚 手架”的唯一目的是为了能方便地测试代码。一种类型的软件“脚手架”是可被接受测试的高 级子程序调用的低级子程序。这样的子程序可称为“残桩”。残桩的可实现程序由你确定,这取 决于所需的正确程度。它应能: • 不需作任何动作就能马上交还控制。 • 能输出诊断信息,可能是输人参数的反应。 • 能测试反馈给它的数据。 第二十五章 单元测试 409 • 能从交互式输入中的返回值。 • 不论输入如何都能返回一个标准答案。 • 可增加分配给实时子程序的时钟周期。 • 其功能相当于一缓慢、厚实、简单或欠精确的实时子程序。 另一种类型的“脚手架”是调用正在测试中的伪子程序。这称为“驱动”或“测试工具”。 它应能: • 能调固定输入集的子程序。 • 对交互式输入进行提示,并调用有关子程序。 • 从命令行中接收变量(操作系统应能支持这种使用)并调用有关子程序。 • 从文件中读变元,并调用子程序。 • 由预定义输入数据集对有关子程序多次调用。 另一种“脚手架”是虚拟文件,它和实际文件有着相同的构成。一个较小的虚拟文件可提 供许多方便。由于它较小,你可了解它的确切内容并且确信文件本身无错,由于你是特意将其 用作测试的,你可设计其内容以使其间的任何错误是显而易见的。 显然,创建“脚手架”需要付出劳动,如果子程序中的错误已被发现,你还可重新利用“脚 手架”。如果你使“脚手架”,你就可测试子程序而不必担心其受别的子程序的影响。当你使用 一种精妙的算法时,“脚手架”是非常有用的。受测试的代码和其它代码嵌套,使执行每个测试 用例往往易使人墨守成规。“脚手架”允许你直接执行代码。你花在创建“脚手架”的几分钟时 间可节省数小时的调试时间。 你可在一个文件中写入不少子程序,其中含一个 main()“脚手架”的子程序。main()程序 从命令行接受变量输入,并且将传递给正在受测试的子程序,以便于将其和其它子程序集成之 前能运行你自己的子程序。在你集成代码时,可先保留子程序和脚手架代码,然后使用预处理 命令或注释以使脚手架代码失效。由于通过预处理使其失效,脚手架代码并不影响可执行代码, 并且其保留在文件底部,它并不是可见的。留下它并无妨碍。你也可再次使用它,将其移去和 存档是不需花费多少时间的。 结果比较结果比较结果比较结果比较 如果你有自动测试工具检查实际输出和预期输出的不同,回归测试和重复测试就将容易得 多。检查输出的一个简单易行的方法是将实际输出送入一文件中,然后用文件比较工具将实际 输出和已预先存入一文件中的期望数据相比较。如果输出结果不同,你可能发现了一个错误。 测试数据生成程序测试数据生成程序测试数据生成程序测试数据生成程序 你也可编写代码,有选择地运行程序的某一部分。几年以前,作者发明了一个加密算法, 并编写了使用此算法的程序。程序的目的是对文件加密以使只有用正确的口令才能将其解密。 这种算法并不是简单地改变文件的内容,而是将其彻底改变。将一个程序正确解密是重要的, 否则你可能会破坏整个程序。 作者为此设计了一数据生成程序以便能充分地检查程序中的加密和解密部分。它产生一个 随机字符的长度从 0K 到 500K 不等的随机文件。也产生长度从 1 到 255 不等的随机字符串,以 第二十五章 单元测试 410 将其作为口令。对每一随机用例,数据生成程序产生两个相同随机文件;将其中一个加密。然 后重新初始化自身,再解密加密文件。然后将解密文件和未加密的文件作比较。如果发现有不 同之处,数据生成程序将其打印出来。 作者将测试用例加权以便使文件平均长度为 30K,这比最大长度 500K 要小得多。否则, 文件长度为 0K 到 500K 不等,平均测试文件长度为 250K。较短的平均长度意味着可以测试更 多的文件、口令、文件结束条件和其它案件。所得结果是令人满意的。在运行了 100 个测试用 例后。作者发现了 2 个错误,它们都是由 实际中可能不会出现的特殊用例所引起,但 是 它们终究是错误,发现它们也是令人兴奋的。 然后,作者试运行程序数周,对超过 100,000 个文件进行了测试而没有发现一个错误。经过对 文件内容、长度和口令的测试,作者确信程序是正确的。 以下是作者从中得出的教训: · 设计良好的随机数据生成程序可产生你没有想到的异常数据。 · 随机数据生成程序要比人工更能深入地检查你的程序。 · 随着时间的流逝,你就能精细随机生成测试用例,以便能侧重于实际范围的数据输入。 这意味着着重测试最可能为用户所用的领域,而使这些领域的可靠性最高。 · 模块化设计是有利于测试的。作者能将加密和解密代码抽取出来,并将其各自用于用 户界面代码中,这样,编写测试驱动程序的工作是相当容易的。 · 如果所测试的代码有所变更,你可重新利用测试驱动程序。一旦在作者改正了两个早 期错误后,马上就开始了重新测试的工作。 覆盖监控覆盖监控覆盖监控覆盖监控 Robert Grady 曾报道过没有检查代码覆盖的测试通常只运行了 55%的代码。覆盖监控是一 种跟踪已运行和未运行代码的工具。覆盖监控对系统测试非常有用,因为它可告诉你测试用例 是否运行了所有代码。如果你已运行了所有测试用例而覆盖监控指示还有一些代码未执行, 这时你便可知道你还需增加测试用例。 符号调试程序符号调试程序符号调试程序符号调试程序 符号调试程序是对代码普查和检查的技术补充。调试程序可一行一行调试代码,对变 量值进行追踪,同计算机一样解释代码。在调试程序中对代码单步调试,并观察其工作情况是 非常有用的。对你的代码用调试程序进行普查和让别的程序员对你的代码遂行评审,在许多方 面是相似的。你的人工检查和调试程序有着不同的盲点。你可向前和向后看一看你的高级语言 代码以及汇编代码,以了解高级语言代码是如何翻译成汇编代码的。你也可以查看寄存器和堆 栈以了解变量是如何传递的。你也可查看被你的编译程序所优化的代码以了解各种优化的性能。 以上各种优点和调试的使用目的并无太大的联系,是确诊已发现的错误,但是对调试的创造性 使用会产生许多和调试初始特性大不相同的好处。 系统的测试系统的测试系统的测试系统的测试 另外一些测试支持工具是用来测试系统的。许多人都听到过一个程序的 99%能正常工作但 第二十五章 单元测试 411 最少 1%老是错误的故事。问题一般在于未初始化某一个变量,而且使用它是困难的,因为 99%的 未初始化变量其值为 0。 这类测试工具应有如下能力: · 存储器填充。你应确保不存在任何未初始化变量。有些测试工具在你运行程序之前 将特定存储单元设置为随意的某个值,以使未初始化变量不致被设置为 0。在有些场 合,存储器单元可能被设为某个特定值。例如,对 8086 微处理器,OXCC 是关于中断 点的机器语言代码。如果你将有关存储器单元填以 0XCC,当某个错误执行了你没有料 到的地方时,会触发调试程序中的断点,这样你就能发现错误。 · 存储器检查。在多任务系统中,当程序运行时有些工具可重新安排存储器,以便确保 你没有将有关代码数据安排在绝对存储器单元,而是安排在相对存储器单元中。 · 可选择存储器失效。存储器驱动器可模拟使某一个程序运行时存储器容易不够的小 容量存储环境:存储器请求无效,在失效之前给出准许任一个存储器请求,或在准许 某一存储器请求前使另外任意一个请求失效。这对测试用到动态存储分配的复杂程序 是非常有用的。 · 存储器访问检查(边界检查)。如果你工作在保护模式的操作系统之下,如 OS/2 操作 系统,你没有必要知道是否会发生指针错误,因为操作系统并不允许你使用超出程序 之外的存储单元指针。如果你工作于一个实时操作系统如 MS-DOS 或 Apple Macintosh 之下,你应知道你所用指针是否正确,因为操作系统并不检查存储器越界。值得庆幸 的是,你可买到存储器访问检查程序来检查指针运算以确保你的指针不出错。这样的 测试工具可用于检查未初始化或悬挂指针。 错误数据库错误数据库错误数据库错误数据库 一个强力测试工具是为所报道过错误而建立的数据库。这样的数据库既是管理也是技术工 具。它使你能检查重复发生的错误、追踪新错误被发现和修正的速度、以及跟踪打开和关闭错 误的状态及其严重性。如果你想了解有关错误数据库的更多的信息,请看 25.7 节。 25.6 25.6 25.6 25.6 提高测试质量提高测试质量提高测试质量提高测试质量 提高测试质量的方法和其它提高任何过程的质量的方法是相似的。你应对整个过程有相当 的了解,这样可稍微变更过程并观察不同情况之间的差异。当观察到一种变更带来了积极的影 响时,便可修改此过程,以便使其更好。以下讨论了如何提高测试质量的方法。 计划测试计划测试计划测试计划测试 进行有效测试的关键是从项目开始就计划测试。将测试看成和设过、编码同样重要意味着 时间将分配给测试,测试被认为是重要的。并且是一个高质量过程。测试计划应能使测试过程 可重复。如果你不能重复某一测试过程,你也就不能提高测试的质量。 再测试(回归测试)再测试(回归测试)再测试(回归测试)再测试(回归测试) 假定你已经对你的产品进行了仔细的调试且没有发现错误,然后在某处对产品作了改动并 第二十五章 单元测试 412 想确证它仍能通过所有测试——就是说修改并不引入新的错误。用于防止使软件质量倒退或“回 归”的测试叫“回归测试”。 一项对数据处理人员的研究发现 52%被调查者对“回归测试”并不熟悉。这是一个不妙的 信号,因为除非你在修改后再对系统地重新测试,否则几乎不可能得到一个高质量的软件产品。 如果每做一次修改都进行不同的测试,你将无法确证你是否引进了新的错误。因此,每次都应 用到回归测试。有时,当某一产品趋于成熟时增加新的测试,但是旧测试仍保留下来。 管理回归测试的唯一可行方法是使回归测试自动化。当人们将相同的测试进行许多次并看 到许多次的结果都相同时;他们会对此厌烦,这会导致人们非常容易忽视错误,使回归测试目 的破产。 25.725.725.725.7 测试记录测试记录测试记录测试记录 除了使测试过程可重复之外,你还需度量项目以便于你能明白修改是提高还是恶化了软件 质量。以下是一些可供你度量所在项目的数据: · 对错误的管理性描述(所提交的数据、编写报告的人所修改的数据) · 对问题的充分描述 · 重复问题的方法 · 对问题的建议处理方法 · 相关错误 · 问题的严重性——例如,致命错误,令人讨厌的错误或无关紧要的错误 · 错误排序——分析、设计、代码或测试错误 · 代码错误的排序——边界错误、赋值错误、数组指针错误、子程序调用错误等等 · 所确定的错误位置。 · 由修改所引起的模块和子程序修改 · 个人对错误态度(这是有争议的,且可能对士气有一定影响) · 每个错误所影响行数 · 发现错误所需时间 · 改正错误所需时间 一旦你得到以上数据,你可对其进行分析以确定项目质量是好还是不好。 · 每个子程序的错误数。从最坏到最好的顺序排序 · 发现每个错误所需平均测试时间 · 发现每个错误所需平均测试用例数 · 定位每个错误所需平均编程时间 · 测试用例所覆盖代码的百分比 · 每种严重错误中突出错误数 25.825.825.825.8 小小小小 结结结结 · 开发者使用测试是完成测试策略的一个重要组成部分。模块和系统测试也是重要的, 第二十五章 单元测试 413 但它们不在本书的讨论之内。 · 各种测试方法的混合使用也是搞好软件质量程序的一个方面。高质量开发方法,包括有条 理地对需求和设计进行测试。检查和普查在发现错误方面也至少同调试方法一样有效, 并且可发现各种类型的错误。 · 通过采用基本测试、数据流分析和错误猜测你可生成许多测试用例。 · 错误倾向于集中在少数几个子程序中。找到这几个子程序,重新设计和重写它们。 · 测试数据往往比受测试的代码含有更多的错误。这种找错方法只会浪费时间而不会 提高代码质量,测试数据错误往往比编程错误更令人讨厌,这可通过仔细地进行测试 来避免。 · 最终提高测试质量的最好方法是,有条理的度量它,利用你的知识提高测试质量。
还剩62页未读

继续阅读

下载pdf到电脑,查找使用更方便

pdf的实际排版效果,会与网站的显示效果略有不同!!

需要 20 金币 [ 分享pdf获得金币 ] 0 人已下载

下载pdf

pdf贡献者

ecjtuxuan

贡献于2010-11-23

下载需要 20 金币 [金币充值 ]
亲,您也可以通过 分享原创pdf 来获得金币奖励!
下载pdf