北京理工大学
软件工程实践
汤铭端
中国航天科工集团公司 706所
第五讲
软件实现
内容和目的
? 编程语言的选择
? 编程风格
? 程序效率
? 编码要求
? 静态分析
? 代码审查
? 软件调试
编程(编码)
? 编程是设计的自然结果
? 编程语言的特性和编程风格会深刻地影响软件
的重量和可维护性
? 软件实现是一个不断变换的过程:设计 —— 源
程序 —— 目标代码 —— 机器码
? 为了保证程序编码的质量,程序员必须深刻理
解、熟练掌握并正确地运用程序设计语言的特
性。此外,还要求源程序具有良好的结构性和
良好的程序设计风格。
编程的目的
? 编码的目的
? 是使用选定的程序设计语言,把模块
的过程性描述翻译为用该语言书写的
源程序(源代码)
模块的过程性描述
(不可执行的 )
源程序
(可执行的 )
编码
结构化程序设计
? 结构化程序设计是一种设计程序的技术
? 它采用 自顶向下逐步细化 的设计方法和
单入口 (Single entry)单出口 ( Single exit)
的控制结构
? 这种控制结构包括有,
? 顺序
? 选择
? 循环
结构化程序设计的原则
1、使用语言中的顺序、选择、重复等有限的基
本控制结构表示程序
2、选用的控制结构只准许有一个入口和一个出
口
3、程序语句组成容易识别的块( Block),每块
只有一个入口和一个
出口
4、复杂结构应该用基本控制结构进行组合嵌套
来实现
5、严格控制 GOTO语句
编程语言的选择
? 应用领域
? 算法及运算的复杂性
? 软件运行的环境
? 性能
? 数据结构的复杂性
? 软件开发组成员对该语言的熟悉程度
编程风格
? 程序必须是可以理解的
? 程序的风格应该强调简单和清晰
? 影响程序风格的因素有,
? 源程序文档化
? 数据说明的方法
? 语句的结构
? I/O的方法
源程序文档化
? 选择好标识符(变量和标号)的名字
? 挑选有意义的标识符名字
? 安排注解
? 序言式注解(头文件)
? 功能注解
? 使程序的结构一目了然
? 缩进
源程序文档化
? 标识符的命名
? 安排注释
? 程序的视觉组织
符号名的命名
? 符号名即标识符,包括 模块名, 变量名,
常量名, 标号名, 子程序名,, 数据区
名 以及 缓冲区名 等。
? 这些名字应能反映它所代表的实际东西,
应有一定实际意义 。
? 例如,表示次数的量用 Times,表示总量
的用 Total,表示平均值的用 Average,表
示和的量用 Sum等。
符号名的命名
? 名字不是越长越好,应当选择精炼的意
义明确的名字。 必要时可使用缩写名字,
但这时要注意缩写规则要一致,并且要
给每一个名字加注释 。同时,在一个程
序中,一个变量只应用于一种用途。
? NEW.BALANCE.ACCOUNTS.PAYABLE
? NBALAP
? N
程序的注释
? 夹在程序中的注释是程序员与日后
的程序读者之间通信的重要手段。
? 注释决不是可有可无的。
? 一些正规的程序文本中,注释行的
数量占到整个源程序的 1/ 3到 1/ 2,
甚至更多。
? 注释分为序言性注释和功能性注释。
序言性注释(头文件)
? 通常臵于每个程序模块的开头部
分,它应当给出程序的整体说明,
对于理解程序本身具有引导作用。
有些软件开发部门对序言性注释
做了明确而严格的规定,要求程
序编制者逐项列出。
头文件的内容
? 程序标题 ;
? 有关本模块 功能和目的 的 说明 ;
? 主要算法 ;
? 接口说明,包括调用形式,参数描述,子程序清单;
? 有关数据描述,重要的变量及其用途,约束或限制条
件,以及其它有关信息;
? 模块位臵,在哪一个源文件中,或隶属于哪一个软件
包;
? 开发简历,模块设计者,复审者,复审日期,修改日
期及有关说明等。
功能性注释
? 功能性注释嵌在源程序体中,用以描述
其后的语句或程序段是在做什么工作,
或是执行了下面的语句会怎么样。而不
要解释下面怎么做。
? 例如,
/* ADD AMOUNT TO TOTAL */
TOTAL = AMOUNT+ TOTAL
不好。
功能性注释
? 如果注明把月销售额计入年度总额,便使读者
理解了下面语句的意图,
/* ADD MONTHLY-SALES TO ANNUAL-
TOTAL */
TOTAL = AMOUNT+ TOTAL
? 要点
? 描述一段程序,而不是每一个语句;
? 用缩进和空行,使程序与注释容易区别;
? 注释要正确。
视觉组织 — 空格、空行和移行
? 恰当地利用 空格,可以 突出运算的优先
性,避免发生运算的错误。
? 例如,将表达式
(A<- 17)ANDNOT(B<= 49)ORC
写成
(A<- 17) AND NOT (B<= 49) OR C
? 自然的程序段之间可用 空行 隔开;
视觉组织
? 移行 也叫做 向右缩格 。它是指程序中的各行不
必都在左端对齐,都从第一格起排列。这样做
使程序完全分不清层次关系。
? 对于 选择语句 和 循环语句,把其中的程序段语
句向右做 阶梯式移行 。使程序的逻辑结构更加
清晰。
? 例如,两重选择结构嵌套,写成下面的移行形
式,层次就清楚得多。
视觉组织
IF( … ) THEN
IF( … ) THEN
……
ELSE
……
ENDIF
……
ELSE
……
ENDIF
数据说明
? 数据说明的次序应该规范化
? 多个变量说明时最好按字典数顺序排列
? 对复杂结构用注解说明
数据说明
? 在设计阶段已经确定了数据结构的组织
及其复杂性。在编写程序时,则需要注
意数据说明的风格。
? 为了使程序中数据说明更易于理解和维
护,必须注意以下几点,
1.数据说明的次序应当规范化
2.说明语句中变量安排有序化
3.使用注释说明复杂数据结构
数据说明的次序应当规范化
? 数据说明次序规范化,使数据属性
容易查找,也有利于测试,排错和
维护。
? 原则上,数据说明的次序与语法无
关,其次序是任意的。但出于阅读、
理解和维护的需要,最好使其规范
化,使说明的先后次序固定。
数据说明的次序应当规范化
? 在 FORTRAN程序
中数据说明次序
① 常量说明
② 简单变量类型说
明
③ 数组说明
④ 公用数据块说明
⑤ 所有的文件说明
? 在类型说明中还可
进一步要求按如下
顺序排列
① 整型量说明
② 实型量说明
③ 字符量说明
④ 逻辑量说明
说明语句中变量安排有序化
? 当 多个变量名在一个说明语句中说明 时,
应当对这些变量 按字母的顺序排列 。带
标号的全程数据 (如 FORTRAN的公用块 )
也应当按字母的顺序排列。
? 例如,把
integer size,length,width,cost,price
写成
integer cost,length,price,size,width
使用注释说明复杂数据结构
? 如果设计了一个复杂的数据结构,应当
使用注释来说明在程序实现时这个数据
结构的固有特点。
? 例如,对 PL/1的链表结构和 Pascal中用户
自定义的数据类型,都应当在注释中做
必要的补充说明。
语句结构
? 每个语句应该简单直接,不应该为提高效率而
把语句复杂化
? 使程序简单易懂
? 避免采用复杂的条件语句
? 不要用, 否定, 条件的条件语句
? 避免多重的循环嵌套或条件嵌套
? 用括号使逻辑表达式或算术表达式更为清晰
? 用空格及有意义的符号使语句内容清晰明确
? 反问自己, 如果这程序不是我编的,我能看懂吗?,
语句结构
? 在设计阶段确定了软件的逻辑流结构,
但构造单个语句则是编码阶段的任务。
语句构造力求简单,直接,不能为了片
面追求效率而使语句复杂化。
语句结构
? 1,在一行内只写一条语句
? 在一行内只写一条语句,并且采取适当的移
行格式,使程序的逻辑和功能变得更加明确。
? 许多程序设计语言允许 在一行内写多个语句 。
但这种方式 会使程序可读性变差 。因而不可
取。
语句结构
? 例如,有一段排序程序
FOR I:=1 TO N- 1 DO BEGIN T:=I; FOR
J:=I+ 1 TO N DO IF A[J]< A[T] THEN T:=J;
IF T≠I THEN BEGIN WORK:=A[T] ;
A[T]:=A[I]; A[I]:=WORK; END END;
? 由于一行中包括了多个语句,掩盖了程序的循
环结构和条件结构,使其可读性变得很差。
语句结构
FOR I:=1 TO N-1 DO //改进布局
BEGIN
T:=I;
FOR J:=I+ 1 TO N DO
IF A[J]< A[T] THEN T:=J;
IF T≠I THEN
BEGIN
WORK:=A[T];
A[T]:=A[I];
A[I]:=WORK;
END
END;
语句结构
? 2.程序编写首先应当考虑清晰性
? 程序编写首先应当考虑清晰性,不要刻意追求技巧性,使程
序编写得过于紧凑。
? 例如,有一个用 C 语句写出的程序段,
A[I] = A[I]+ A[T];
A[T] = A[I]- A[T];
A[I] = A[I]- A[T];
? 此段程序可能不易看懂,有时还需用实际数据试验一下。
? 实际上,这段程序的功能就是交换 A[I]和 A[T]中的内容。目的是为
了节省一个工作单元。如果改一下,
WORK = A[T];
A[T] = A[I];
A[I] = WORK;
就能让读者一目了然了。
语句结构
? 3.程序要能直截了当地说明程序员的用意。
? 程序编写得要简单,写清楚,直截了当地说明程序
员的用意。
? 例如,
for ( i = 1; i <= n; i++ )
for ( j = 1; j <= n; j++ )
V[i][j] = ( i/ j ) * ( j/ i )
除法运算(/)在除数和被除数都是整型量时,其
结果只取整数部分,而得到整型量。
语句结构
? 当 i< j 时,i / j = 0
当 j< i 时,j / i = 0
得到的数组 V 是一个单位矩阵
当 i≠j时 V[i][j] = ( i/ j ) * ( j/ i ) = 0
当 i= j时 V[i][j] = ( i/ j ) * ( j/ i ) = 1
? 写成以下的形式,就能让读者直接了解程序编写者的
意图。
for ( i= 1; i <= n; i++ )
for ( j= 1; j <= n; j++ )
if ( i == j )
V[i][j] = 1.0;
ELSE
V[i][j] = 0.0;
语句结构
4,除非对效率有特殊的要求,程序编写要
做到 清晰第一, 效率第二 。 不要为了追
求效率而丧失了清晰性。事实上,程序
效率的提高主要应通过选择高效的算法
来实现。
5.首先要保证 程序正确,然后才要求 提高
速度 。 反过来说,在使程序高速运行时,
首先要保证它是正确的。
语句结构
? 6.避免 使用临时变量 而使可读性下降。
? 例如,有的程序员为了追求效率,往
往喜欢把表达式
A[I]+ 1/ A[I];
写成 AI= A[I];
X= AI+ 1/ AI;
这样将一句分成两句写,会产生意想不
到的问题。
语句结构
7,让编译程序做简单的优化。
8,尽可能 使用库函数
9,避免 不必要的转移 。
? 同时如果能保持程序可读性,则
不必用 GO TO语句。
求三个数中最小值的程序
IF ( X< Y ) GOTO 30
IF (Y< Z) GOTO 50
SMALL= Z
GOTO 70
30 IF ( X < Z) GOTO 60
SMALL= Z
GOTO 70
50 SMALL= Y
GOTO 70
60 SMALL= X
70 CONTINUE
求三个数中最小值的程序
程序只需编写成,
small= x;
if ( y < small ) small= y;
if ( z < small ) small= z;
所以程序应当简单,不必过于深奥,
避免使用 GOTO语句绕来绕去。
语句结构
10.尽量只采用 三种基本的控制结构 来编写程序。
除 顺序结构 外,使用 if-then-else来实现 选择结构 ;使用 do-
until或 do-while来实现 循环结构 。
11,避免使用 空的 ELSE语句和 IF… THEN IF… 的语句 。
这种结构容 易使读者产生误解 。 例如,
if ( char >= 'a’ )
if ( char <= ’z’ )
cout <<,This is a letter。” ;
else
cout <<,This is not a letter。” ;
可能产生二义性问题。
语句结构
12.避免采用过于复杂的条件测试。
13.尽量减少使用“否定”条件的条件语句。 例如,
如果在程序中出现
if ( !( char<‘ 0’ || char >‘ 9’ ) )
……
改成
if ( char >= '0’ && char <= '9’ )
……
不要让读者绕弯子想。
语句结构
14,尽可能用 通俗易懂的伪码 来描述程序的流程,
然后再翻译成必须使用的语言。
15,数据结构要有利于程序的简化。
16,要 模块化,使模块功能尽可能单一化,模块
间的耦合能够清晰可见。
17,利用 信息隐蔽,确保每一个模块的独立性。
18,从 数据 出发去构造程序。
语句结构
19,不要修补不好的程序,要重新编写。
也不要一味地追求代码的复用,要
重新组织。
20,对太大的程序,要分块编写、测试,
然后再集成。
21,对递归定义的数据结构尽量使用递
归过程。
输入 /输出
? 对批处理 I/O
? 符合逻辑地组织输入
? I/O出错检查
? 好的 I/O出错恢复功能
? 清晰的输出报告格式
? 对交互式 I/O
? 简单而有提示的输入方式
? 完备的出错处理及出错恢
复
? 人机对话输出
? I/O格式的一致性
? 原则,
? 检查所有输入数据的合法性
? 检查输入项的各种重要组合
是否合理
? 输入格式要简单
? 最好采用数据结尾指示符,
而不应要求用户规定, 输入
项目的数量,
? 交互式 I/O要求用户输入时,
标明交互输入可选择的种类
和范围
? 输出时保持格式的一致性
? 设计和标明所有的输出
输入和输出
? 输入和输出信息是与用户的使用直接相
关的。输入和输出的方式和格式应当尽
可能方便用户的使用。一定要避免因设
计不当给用户带来的麻烦。
? 因此,在软件需求分析阶段和设计阶段,
就应基本确定输入和输出的风格。系统
能否被用户接受,有时就取决于输入和
输出的风格。
输入和输出设计和编码原则
1,对所有的输入数据都要进行检验,识别错误的输入,
以保证每个数据的有效性;
2,检查输入项的各种重要组合的合理性,必要时报告输
入状态信息;
3,使得输入的步骤和操作尽可能简单,并保持简单的输
入格式;
4,输入数据时,应允许使用自由格式输入;
5,应允许缺省值;
6,输入一批数据时,最好使用输入结束标志,而不要由
用户指定输入数据数目;
输入和输出设计和编码原则
7,在交互式输入输入时,要在屏幕上使用提示符明确提
示交互输入的请求,指明可使用选择项的种类和取值
范围。同时,在数据输入的过程中和输入结束时,也
要在屏幕上给出状态信息;
8,当程序设计语言对输入/输出格式有严格要求时,应
保持输入格式与输入语句的要求的一致性;
9,给所有的输出加注解,并设计输出报表格式。
输入/输出风格还受到许多其它因素的
影响。如输入/输出设备(例如终端的
类型,图形设备,数字化转换设备等)、
用户的熟练程度、以及通信环境等。
Wasserman交互系统设计原则原则
? 把计算机的内部特性掩盖起来不让用户看到
? 使程序, 穿上防弹衣,,保证程序不被用户破坏
? 如果用户的请求会产生重大的后果,就要提醒用
户
? 在使用此程序时提供联机的帮助条件
? 按照用户的水平设计输入要求
? 按照输出设备的速度设计输出信息
? 区别对待不同类型的用户
? 保持一致的响应时间
? 应尽量减少用户处理出错的工作量
效率
? 要求有效地利用临界资源是自然的
? CPU周期和内存单元常被看作临界资源
? 有关效率的三个格言,
? 效率是一种性能需求。软件的效率应根据需要,而
不是尽可能地高!
? 好的设计必然提高效率。
? 程序的简单性与程序的效率往往是一致的。
? 总之不要去牺牲程序的清晰性、可读性或正确
性去追求效率的非本质的提高
源程序的效率
? 源程序的效率与算法
效率直接相关
? 编程风格会影响运行
速度及所需内存的大
小
? 编译器的, 优化, 特
性是提高效率的一种
手段
? 原则,
? 在具体编程前应简化算术
表达式及逻辑表达式
? 细心地分析多层嵌套循环
以确定能否把一些语句或
表达式移到循环之外
? 尽量避免采用多维数组
? 尽量避免采用指针及复杂
的表
? 采用, 快, 的算术运算
? 不要把不同的数据类型混
在一起
? 只要可能就采用整型数的
算术运算和布尔表达式
内存效率
? 大机器领域内存几乎无限制,虚存使
,内存效率, 不等同于, 占用最小的内
存,
? 微机领域内存的限制仍是很现实的问题
? 采用汇编语言可以节省内存
? 提高运行效率的技术往往可以同时节省
内存
? 使程序简单是提高内存效率的关键
输入输出效率
? 提高 I/O效率的指导原则,
? 全部 I/O应有缓冲以避免过于频繁的信息交
换
? 对外存应该选用最简单的可接收的存取方式
? 与外存联系的 I/O操作应成块地传送数据
? 与终端及行式打印机联系的 I/O操作应考虑
设备的特性,以改进质量与速度
? 如果, 超高效率, 的 I/O无法被人们理解,
则是毫无意义的
程序效率,讨论效率的准则
? 程序的效率是指 程序的执行速度 及 程序所需占用的内
存的存储空间 。程序编码是最后提高运行速度和节省
存储的机会,因此在此阶段不能不考虑程序的效率。
让我们首先明确讨论程序效率的几条准则
? 效率是一个性能要求,应当在需求分析阶段给出。 软
件效率以需求为准,不应以人力所及为准。
? 好的设计可以提高效率。
? 程序的 效率与程序的简单性 相关。
? 一般说来,任何对效率无重要改善,且对程序的简单
性、可读性和正确性不利的程序设计方法都是不可取
的。
程序效率,算法对效率的影响
? 源程序的 效率与详细设计阶段确
定的算法的效率直接有关 。在详
细设计翻译转换成源程序代码后,
算法效率反映为程序的执行速度
和存储容量的要求。
设计向程序转换的指导原则
① 在编程序前,尽可能化简有关的算术表达式和逻辑表
达式;
② 仔细检查算法中的嵌套的循环,尽可能将某些语句或
表达式移到循环外面;
③ 尽量避免使用多维数组;
④ 尽量避免使用指针和复杂的表;
⑤ 采用“快速”的算术运算;
⑥ 不要混淆数据类型,避免在表达式中出现类型混杂;
⑦ 尽量采用整数算术表达式和布尔表达式;
⑧ 选用等效的高效率算法;
? 编译程序的“优化”功能可自动生成高效率的目标代
码
程序效率,影响存储器效率的因素
? 在大中型计算机系统中,存储限制不再是主要问题。在这
种环境下,对 内存采取基于操作系统的分页功能的虚拟存
储管理 。 存储效率与操作系统的分页功能直接有关 。
? 采用结构化程序设计,将程序功能合理分块, 使每个模块
或一组密切相关模块的程序体积大小与每页的容量相匹配,
可减少页面调度,减少内外存交换,提高存储效率。
? 微型计算机系统中,存储器的容量对软件设计和编码的制
约很大。因此 要选择可生成较短目标代码且存储压缩性能
优良的编译程序,有时需采用汇编程序。
? 提高存储器效率的关键是程序的简单性。
程序效率,影响输入/输出的因素
? 输入/输出可分为两种类型,
? 面向人 (操作员 )的输入/输出
? 面向设备的输入/输出
? 如果操作员能够十分方便、简单地
录入输入数据,或者能够十分直观、
一目了然地了解输出信息,则可以
说面向人的输入/输出是高效的。
提高输入 /输出效率的指导原则
? 输入 /输出的请求应当最小化;
? 对于所有的输入 /输出操作,安排适当的缓冲区,以减
少频繁的信息交换。
? 对辅助存储 (例如磁盘 ),选择尽可能简单的,可接受
的存取方法 ;
? 对辅助存储的输入 /输出,应当 成块传送 ;
? 对终端或打印机的输入 /输出,应考虑设备特性,尽可
能改善输入 /输出的质量和速度;
? 任何不易理解的,对改善输入 /输出效果关系不大的措
施都是不可取的;
? 任何不易理解的所谓“超高效”的输入 /输出是毫无价
值的;
Kernighan和 Plauger给出的好
的程序设计风格的规则
? 写得清晰 —— 而不是太灵巧
? 简单而直接地说明你的用意
? 使用库功能
? 避免使用临时变量
? 写清晰 —— 不要为了, 效率, 而牺牲清晰
? 让机器干苦活
? 用调用一个公共的例程去代替重复的表示
? 选用不易混淆的变量名
? 避免使用不必要的转移
Kernighan和 Plauger给出的好
的程序设计风格的规则
? 不使用条件转移代替一个逻辑表达式
? 假如一个逻辑表达式难以理解,则试着 对其 进
行 变换
? 使用数组,避免重复的控制序列
? 选用使程序简单的数据表示法
? 先用一个容易理解的伪语言写 程序,然后再翻
译成你所用的语言
使用 IF… ELSEIF… ELSEIF… ELSE… ENDIF实施
多路选择
? 模块化,使用子例程
Kernighan和 Plauger给出的好
的程序设计风格的规则
? 确保注释与代码一致
? 不要用注释去精确地重复代码 —— 使每一个注
释有价值
? 不要注释或修补坏代码 —— 重写
? 使用有意义的变量名
? 使用有意义的语句标号
? 程序格式应有助于读者理解 程序
? 为你的数据设计提供文档
? 仅 使用 GOTO去实施一个基本的结构
Kernighan和 Plauger给出的好
的程序设计风格的规则
? 假如你能保持程序的可读性,则完全不必使用
GOTO
? 以小片段的方式写和测试一个大程序
? 对已定义的递归数据结构使用递归过程
? 检测输入的合理性和合法性
? 确保输入没有违反程序的限制
? 使用文件的结尾或标记终止输入,而不是用计
数来终止输入
? 识别错误的输入(若有可能要恢复输入)
Kernighan和 Plauger给出的好
的程序设计风格的规则
? 输入简易,输出自明
? 使用一致的输入格式
? 使输入容易校对
? 若可能,使用自由格式输入
? 使用自标识的输入,允许缺省,输出时反映这
两者
? 确保在使用之前,所有变量已被置初值
? 在出现故障时不要停机
? 使用排错编译程序
Kernighan和 Plauger给出的好
的程序设计风格的规则
? 用 DATA语句或 INITIAL属性置常数
? 用可执行的代码置变量初值
? 尽量注意由错误引起的分岔
? 注意在相等时转移的正确性
? 当一个循环从边上或底上退出到同一个地方时,
必须十分小心
? 确保你的代码, 没有做什么优美方面的事情,
? 在边值处测试程序
? 人工核查某些答案
Kernighan和 Plauger给出的好
的程序设计风格的规则
? 10.0乘以 0.1很少是 1.0
? 不要单独地进行浮点数相等比较
? 先保证正确,再提高速度
? 在提高速度之前,做好自动防止故障的措施
? 先保证清晰,再提高速度
? 不要为谋求, 效率, 上的少量增益而牺牲清晰
? 让你的编译程序做简单的优化
? 不要滥用再使用代码,取而代之的是改组
? 确保特殊情况是确实的特殊
Kernighan和 Plauger给出的好
的程序设计风格的规则
? 保持简单,以提高速度
? 不要浪费代码而提高速度 —— 找一个较
好的算法
? 使用工具装备你的程序,在, 效率, 改
变以前测量( END)
程序编码结构要求
? 采用结构化编码方法
? 按以下 22个特性构造
( 1)完整性
? 任何程序或控制段的开始和结束,必须
包含在单一的结构化模块中。
( 2)功能
? 每个模块必须完成单一的定义良好的功
能,模块的全部元素都是为了实现唯一
的功能。
? 此外,模块实现的细节对其它模块来说
应该是隐蔽的。如果实现的细节改变,
直接受影响的只是实现功能的那个模块,
并且只有该模块需要修改。
( 3)入口/出口
? 每个模块一般要求单一入口和单一出口。
处理完成后,模块必须返回到调用它的
程序而不是其它模块。
? 接口(入口与出口条件和参数)必须明
确表示和定义。
( 4)说明
? 模块的说明语句和数据语句应放在程序
首部之后和第一个可执行语句之前,虽
然允许每行可有多个数据说明,但应保
证清晰地定义复杂的数据结构。
( 5)常量
? 常量必须在程序的说明中定义,常量通
常有数组规模,π值,DO和 FOR 循环中
的循环数、数组下标、记录规模和行长
度等。
( 6)变元
? 在调用语句中的变元一般不得包含算术
或逻辑表达式。
? 每个变元必须由单一变量来表示。
? 在调用一个模块的过程中,变元的类型
和个数必须与模块的形参相一致。
? 此外,必须标明调用过程中使用的形参。
( 7)幂
? 幂的指数必须用整数表示,即A**3
而不是A** 3.0。
( 8)混合方式运算
? 应避免使用混合运算的算术表达式,不
能避免时应仔细计算表达式的值,以保
证类型转换得到预期的结果,应对转换
进行适当的注释。
( 9)转移
? 尽量少用无条件转移语句。如需使用,
必须在同一程序单元内转移,并应向转
移语句所在点的前方转移。
? 允许使用为实现结构化程序设计所必需
的转移指令。
( 10)错误处理
a.必须记录所有错误的发生及处理情况,保存系
统发生错误的全部记录,以利于后续错误情况
的处理和分析;
b.必须预先了解错误的潜在影响,并且在它们出
现之前确定如何处理;
c.应尽可能使错误不扩散,不引起连锁反应;
d.当程序不能执行时,必须在中止前释放已取得
的全部资源和结果。
( 11)范围检查
? 为保证变量值落在预期的范围内,必须进行范
围检查。
? 如果变量值不在规定范围内,则必须进行适当
的错误处理。
? 变量的定义域必须在设计时规定,在实现时声
明,并在运行时检查。
? 必须进行范围检查的变量应包括:参数;数值
下标; CASE结构中的变量参数;循环结构中用
作初值、增量和终值的变量。
( 12)序标和下标
? 循环序标参数和数值下标必须表示为整
型的常量或变量。
( 13)循环终止
? 必须保证循环终止,终止条件不能靠对
循环参数所作假设的正确性,而是靠对
循环入口事前直接的验证。
( 14)标号和名字的使用
? 标号和名字必须有意义、一致并明显、
唯一,不易混淆。
( 15)全局变量和共享变量
? 应避免使用全局或共享变量。当一个变量由两个或多
个模块共享时,每个模块都必须正确地使用该变量。
? 由多个模块使用的变量应作为变元进行通信。限制变
量的范围有助于掌握变量的准确含义。
? 当有明确的技术根据需要使用公共数据时,应有下列
限制,
a.仅使用可由名字访问的公用域;
b.公用数据域的长度和说明,对于使用它的每个模块
都是相同的;
c.可能时,应仅保留公用块的一个副本,而且必须用
,INCLUDE,语句包容在每一个模块中而不是在模
块中各自编码描述。
( 16)连接约定
? 如果模块是用汇编语言实现的,则必须定义和
使用模块间通信的标准约定。目的是构造独立
的、自包容的模块,它可以用良好定义的方法
与其它模块通信。
? 应考虑的约定有,
a.在将控制转给其它模块之前必须保护寄存器的内容,
并在控制返回时恢复;
b.通信必须通过变元素;
c.必须有一个与调用模块返回地址进行通信的标准方
法。
( 17)输入与输出
? 输入例程和输出例程必须集中在有限几
个模块内。
? 将这些例程汇集到少数几个模块中有利
于输入和输出数据的监控。
? 此外,将这些例程限制在有限范围内可
使修改更容易。
( 18)命名
? 代码和数据单元的名字必须体现它们的
功能和内容。
( 19)程序首部
? 每个代码模块必须包含一个标准化的程
序首部注释语句块,作为每个程序模块
的内部源文档。它放在模块的首部,描
述模块的功能、用途和操作要求。
? 程序首部应包括下面的信息,
a.名称:包含用于标识模块的名称、版本标识
符和入口。
b.目的:包含模块目的/功能的简要说明。
程序首部
? 程序首部应包括下面的信息,
c.输入/输出,包含该模块所使用的每个I/O文件
名,并指明是向模块输入、还是输出、还是两者兼
有。使用交互式屏幕I/O的模块也应提供屏幕功
能的说明。
d.参数:提供模块所需的全部变元的定义以及模块的
返回值(输出参数)。参数定义必须包括:名字、
数据类型、大小和功能。
e.调用:提供该模块调用的全部例程的清单和调用该
模块的全部例程的清单。
f.全局数据:提供模块中使用的全部公用或全局数据
的清单。
程序首部
? 程序首部应包括下面的信息,
g.限制:包含约束或限制模块性能特点的任何特殊或
非常规因素的清单。
h.异常结束:包含异常返回条件和动作的清单。
i.方法:包含为实现模块的功能而使用的方法的详细
说明。
j.编程者:代码编制人名及所属机构。
k.版本号及完成日期:当前的模块版本号和它的完成
日期。
l.修改记录:包含对该模块的修改人、所属机构、日
期及对应的版本号。
( 20)行间注释
? 源程序体内的注释必须解释程序执行的
处理,必须指明每一个控制语句(有条
件地更改一个数据值或语句的执行顺序
的语句)的目的。
a.IF语句:说明条件满足时执行动作的理由。
b.I/O语句:标明处理的记录或文件的性质。
c.DO语句:说明执行动作的理由。
d.CALL语句:说明调用过程的理由。
( 21)注释比例
? 注释行数不得少于源程序总行数的1/
5。
( 22)缩进和分段
? 源代码必须清楚地缩进表达,以表明结构化程
序段外形的控制范围,使编码的逻辑关系对应
于程序列表的实际位置。
? 可执行语句限于每行一条。
? 超过一行的语句的后续部分应缩进该语句的第
一行下面。
? 一行允许有多个数据说明。数据说明应使用某
种逻辑顺序(例如字母顺序)排列。
( 23)模块规模
? 每个软件单元的源代码行数(不含注释
行),平均不应超过60行,最大不应
超过200行。
程序复杂性度量
? 程序复杂性主要指 模块内程序的复杂性 。
它直接关联到软件开发费用的多少,开
发周期的长短和软件内部潜伏错误的多
少。
? 减少程序复杂性,可提高软件的简单性
和可理解性,并使软件开发费用减少,
开发周期缩短,软件内部潜藏错误减少。
复杂性度量需要满足的假设
? 为了度量程序复杂性,要求,
? 它可以用来计算任何一个程序的复杂性;
? 对于不合理的程序,例如对于长度动态
增长的程序,或者对于原则上无法排错
的程序,不应当使用它进行复杂性计算;
? 如果程序中指令条数、附加存储量、计
算时间增多,不会减少程序的复杂性。
代码行度量法
? 源代码行数度量法基于两个前提,
? 程序复杂性随着程序规模的增加不均衡地增长;
? 控制程序规模的方法最好是采用分而治之的办法。
将一个大程序分解成若干个简单的可理解的程序段。
? 方法的基本考虑是 统计一个程序模块的
源代码行数目,并以源代码行数做为程
序复杂性的度量。
代码行与出错率的关系
? 设 每行代码的出错率 为 每 100行源程序中可能有的错误数目 。
? Thayer曾指出,程序出错率的估算范围是从 0.04%~ 7%之
间,即每 100行源程序中可能存在 0.04~ 7个错误。他还指出,
每行代码的出错率与源程序行数之间不存在简单的线性关
系。
? Lipow指出,对于 小程序,每行代码出错率为 1.3%~ 1.8% ;
对于 大程序,每行代码的出错率增加到 2.7%~ 3.2% 之间,
这只是考虑了程序的可执行部分,没有包括程序中的说明
部分。
? Lipow及其他研究者得出一个结论:对于少于 100个语句的
小程序,源代码行数与出错率是线性相关的。随着程序的
增大,出错率以非线性方式增长。
McCabe度量法
? McCabe度量法,又称环路复杂性度量,是一
种 基于程序控制流 的复杂性度量方法。
? 它 基于一个程序模块的程序图中环路的个数,
因此计算它先要画出程序图。
? 程序图是退化的程序流程图。流程图中每个处
理都退化成一个结点,流线变成连接不同结点
的有向弧。
? 程序图仅描述程序内部的控制流程,完全不表
现对数据的具体操作,以及分支和循环的具体
条件。
McCabe计算
? 计算环路复杂性的方法,根据图论,在一个强
连通的有向图 G中,环的个数由以下公式给出,
V(G)= m- n+ p
其中,V(G)是有向图 G中环路个数,m是图 G中
弧数,n是图 G中结点数,p是图 G中的强连通
分量个数。
? Myers建议,对于复合判定,例如,(A= 0)∩(C
= D)∪ (X=‘ A’) 算做三个判定。
? 为使图成为强连通图,从图的入口点到出口点
加一条用虚线表示的有向边,使图成为强连通
图。这样就可以使用上式计算环路复杂性。
圈复杂度 — 程序复杂性度量
? McCabe圈复杂度 V(G) ≤7
? 将程序流程图退化为程序
图,将程序流程图中每个
处理符号都退化为一个点,
箭头变成有向弧,调整使
成为强连通图
? V(G)=m-n+p
? m=弧数
? n=节点数
? p=分离部分的数目(连通
图 p=1)
计算示例
? 在例示中,结点数 n= 11,弧数 m= 13,
p= 1,则有
V(G)= m- n+ p= 13- 11+ 1= 3,
? 等于程序图中弧所封闭的区域数。
McCabe几点说明
? 环路复杂度取决于程序控制结构的复杂度。当程序的
分支数目或循环数目增加时其复杂度也增加。 环路复
杂度与程序中覆盖的路径条数有关 。
? 环路复杂度是可加的。例如,模块 A的复杂度为 3,模
块 B的复杂度为 4,则 模块 A与 模块 B的复杂度是 7。
? McCabe建议,对于复杂度超过 10的程序,应分成几个
小程序,以减少程序中的错误。 Walsh用实例证实了这
个建议的正确性。在 McCabe复杂度为 10的附近,存在
出错率的间断跃变。
? McCabe环路复杂度隐含的前提是,错误与程序的判定
加上例行子程序的调用数目成正比。 加工复杂性、数
据结构、录入与打乱输入卡片的错误可以忽略不计。
McCabe度量的缺点
? 对于不同种类的控制流的复杂性不能区
分
? 简单 IF语句 与 循环语句 的复杂性同等看
待
? 嵌套 IF语句 与 简单 CASE语句 的复杂性
是一样的
? 模块间接口 当成 一个简单分支 一样处理
? 一个 具有 1000行的顺序程序 与 一行语句
的复杂性相同
Halstead的软件科学
? Halstead软件科学研究确定计算机软件开发中的一
些定量规律,它采用以下一组基本的度量值。
? 这些度量值通常在程序产生之后得出,或者在设
计完成之后估算出。
? 程序长度 (预测的 Halstead长度 )
令 n1表示程序中不同运算符 (包括保留字 )的个数,
令 n2表示程序中不同运算对象的个数,令 H表示
“程序长度”,则有
H=n1?log2 n1+n2 ? log2n2
? 这里,H是程序长度的预测值,它不等于程序中语
句个数。
Halstead的软件科学
? 在定义中,运算符包括:算术运算符;赋值符(=或,=);逻辑运算符;分界符 (,或;或,);关
系运算符;括号运算符;子程序调用符;数组操作符;循环操作符等。
? 特别地,成对的运算符,例如, begin…end”,,for…to”,,repeat …until”,,while…do”,
,if…then…else”,“( … )” 等都当做单一运算符。
? 运算对象包括变量名和常数。
Halstead的软件科学
? 实际的 Halstead长度,设 N1为程序中实际出现的运算符总个数,N2为程序中实际出现的运算对象总个数,
N为实际的 Halstead长度,则有,N = N1 + N2
? 程序的词汇表,Halstead定义程序的词汇表为不同的运
算符种类数 n1和不同的运算对象种类数 n2的总和。若令 n为程序的词汇表,则有,n = n1+n2
? 程序量,V = N ? log2n
? 它表明了程序在 词汇上的复杂性 。其最小值为
V* = (2+n2*) ? log2(2+n2*)
这里,2表明程序中至少有两个运算符:赋值符 = 和函数调用符
f ( ), n2*表示输入/输出变量个数。
示例程序
SUBROUTINE SORT ( X,N )
DIMENSION X( N )
IF ( N,LT,2 ) RETURN
DO 20 I=2,N
DO 10 J=1,I
IF ( X(I),GE,X(J) ) GO TO 10
SAVE = X(I)
X(I) = X(J)
X(J) = SAVE
10 CONTINUE
20 CONTINUE
RETURN
END
运算符 计数 运算对象 计数
可执行语句结束 7 X 6
数组下标 6 I 5
= 5 J 4
IF ( ) 2 N 2
DO 2 2 2
, 2 SA VE 2
程序结束 1 1 1
,L T, 1 N 1 = 7 N 2 = 22
,G E,1
GO T O 10 1
n 1= 10 n 2 = 28
示例计算
? H = 10 ? log210+7 ? log27 = 52.87
? N = 28+22 = 50
? V = (28+22) ? log2(10+7) = 204
? 等效的汇编语言程序的 V= 328。
这说明汇编语言比 FORTRAN语
言需要更多的信息量 (以 bit表示 )
Halstead的软件科学
? 程序量比率 (语言的抽象级别 )
L = V* / V 或 L = (2 / n1)?(n2 / N2)
? 它表明了一个程序的最紧凑形式的程序量与实际程序量之比,反
映了程序的效率。其倒数 D = 1 / L 表明了实现算法的困难程度。
? 程序员工作量 E = V / L
? 程序的潜在错误
? Halstead度量可以用来预测程序中的错误。预测公式为
B = (N1+N2)?log2(n1+n2) / 3000
B为该程序的错误数。它表明程序中可能存在的差错 B 应与程序
量 V成正比。
示例
? 例如,一个程序对 75个数据库项共访问
1300次,对 150个运算符共使用了 1200次,
那么预测该程序的错误数,
B = (1200+1300)?log2(75+150)/3000
? 6.5
即预测该程序中可能包含 6~ 7个错误
Halstead的重要结论
? 程序的实际 Halstead长度 N可以由词汇表
n算出。即使程序还未编制完成,也能预
先算出程序的实际 Halstead长度 N,虽然
它没有明确指出程序中到底有多少个语
句。
? 这个结论非常有用。经过多次验证,预
测的 Halstead长度与实际的 Halstead长度
是非常接近的。
Halstead度量的缺点
? 没有区别自己编的程序与别人编的程序。 这是与实际
经验相违背的。这时应将外部调用乘上一个大于 1的的
常数 Kf (应在 1~ 5之间,它与文档资料的清晰度有关 )。
? 没有考虑非执行语句。 补救办法:在统计 n1,n2,N1、
N2时,可以把非执行语句中出现的运算对象,运算符
统计在内。
? 在允许混合运算的语言中,每种运算符与它的运算对
象相关。
? 如果一种语言有整型、实型、双精度型三种不同类型
的运算对象,则任何一种基本算术运算符 (+、-,×,
/ )实际上代表了 6 种运算符。 在计算时应考虑这种因
数据类型而引起差异的情况。
Halstead度量的缺点
? 没有注意调用的深度。 Halstead 公式应当对调用子程
序的不同深度区别对待。在计算嵌套调用的运算符和
运算对象时,应乘上一个调用深度因子。这样可以增
大嵌套调用时的错误预测率。
? 没有把不同类型的运算对象,运算符与不同的错误发
生率联系起来,而是把它们同等看待。 例如,对 简单 if
语句 与 while语句 就没有区别。
? 忽视了嵌套结构 (嵌套的循环语句、嵌套 IF语句、括号
结构等 )。 一般地,运算符的嵌套序列,总比具有相同
数量的运算符和运算对象的非嵌套序列要复杂得多。
解决的办法是对嵌套结果乘上一个嵌套因子。
静态分析
? 静态分析是通过分析、但不执行软件的程序,找出其
中的错误和疑点
? 静态分析手段之一,就是使用工具对软件单元甚至软
件所有成分的源代码进行分析,获得以下主要信息,
? 语法错误信息;每个语句中标识符的引用分析,如
变量、参数等;每个模块调用的子模块;未给初值
的变量;已定义的但未使用的变量;未经说明或无
用的标号;对任何一组输入数据均不可能执行到的
代码段
? 可以根据所获信息对程序进行分析,发现其中可能存
在的缺陷
? 还可以通过静态分析确定程序的分支、路径、转移等
结构关系,为进行动态测试产生测试数据提供方便
代码审查
? 代码审查是由代码审查员根据软件详细设计说
明,按照规定的代码审查单,对被审查的程序
代码逐条进行审查,以确保编码与设计的一致
并确保编码的正确性
? 代码审查应着重审查错误代码而不是多余代码
或代码的遗漏
? 各软件开发机构应根据自身情况和所开发的软
件的特点,形成并不断补充完善自己使用的代
码审查单
通用的代码审查单
格式,
— 嵌套的 IF是否已正确地缩进?
—— 注释准确并有意义吗?
—— 是否使用了有意义的标号?
—— 代码是否基本上与开始时的模块模式
一致?
—— 是否遵循全套的编程标准?
通用的代码审查单
入口和出口的连接,
—— 初始入口的最终出口正确吗?
—— 对另一个模块的每一次调用,
全部所需的参数是否已传送给每
一个被调用的模块?
被传送的参数值是否正确地设置?
通用的代码审查单
程序语言的使用,
—— 是否使用一个或一组最佳的动词?
—— 模块中是否使用完整定义的语言的有限子集?
存储器使用,
—— 每一个域在第一次使用前正确地初始化了吗?
—— 规定的域正确吗?
—— 每个域是否已由正确的变量类型声明?
通用的代码审查单
测试和转移,
—— 测试条件正确吗?
—— 用于测试的是正确变量吗?
—— 每个转移目标正确并至少执行一次吗?
性能,
—— 逻辑是否被最佳地编码?
—— 提供的是一般的错误还是异常的例程?
通用的代码审查单
维护性,
—— 所提供的列表控制是否有利于提高可读性?
—— 标号和子程序名符合代码的意义吗?
逻辑,
—— 全部设计是否均已实现?
—— 编码是否做了设计所规定的内容?
—— 每个循环是否执行正确的次数?( END)
调试
? 调试的任务:诊断和改正程序中的错误
? 调试的活动,
? 根据迹象确定错误的正确位置
? 仔细分析研究以确定问题的原因并进行改正
? 确定错误位置占调试 95%的工作量
? 调试工作是一个具有很强技巧性的工作
? 软件运行失效或出现问题,往往只是潜在错误的外部
表现
? 调试是通过现象,找出原因的一个思维分析的过程
错误改正后要进行回归测试
? 修改程序可能带来新的错误
错误的特征和调试的困难
? 症状和原因可能是相隔很远的
? 症状可能在另一个错误被纠正后消失或暂时性消失
? 症状可能实际上并不是由, 错误, 引起的(如舍入
误差)
? 症状可能是由不太容易跟踪的人工错误引起的
? 症状可能是和时间有关的,而不是处理问题
? 很难重新产生完全一样的输入条件(如实时应用)
? 症状可能时时有时无的
? 症状可能是由分布在许多不同任务中的原因引起的
调试的步骤
(1) 从错误的外部表现形式入手,确定程序
中出错位置 ;
(2) 研究有关部分的程序,找出错误的内在
原因 ;
(3) 修改设计和代码,以排除这个错误 ;
(4) 重复进行暴露了这个错误的原始测试或
某些有关测试 。
调试技术
? 输出存储器内容
? 打印中间结果
? 自动工具(自动插装)
调试策略
? 试探法
? 回溯法
? 对分查找法
? 归纳法
? 演绎法
强行排错
? 使用较多,效率较低,
? 通过内存全部打印来调试
? 在程序特定部位设置打印语句
? 自动调试工具
? 应用以方法之前,都应当对错误的征兆进行全
面彻底的分析,得出对出错位置及错误性质的
推测,再使用一种适当的调试方法来检验推测
的正确性。
试探法
? 分析错误征兆
? 猜想故障的大致位置
? 采用某些调试技术获得程序中被怀疑地
方附近的信息
? 试探法通常是缓慢而低效的
回溯法
? 检查错误征兆,确定最先发现, 症状, 的地
方
? 用人工沿程序的控制流往回追踪源程序代码,
直到找出错误根源或确定故障范围为止
? 另一种形式是正向追踪,使用输出语句检查
一系列中间结果,以确定最先出现错误的地
方
? 回溯法比较适用于小程序,大规模程序使彻
底回溯不可能
回溯法调试示例
? 例如,程序中发现错误处是某个打印语
句。通过输出值可推断程序在这一点上
变量的值。再从这一点出发,回溯程序
的执行过程,反复考虑:, 如果程序在
这一点上的状态(变量的值)是这样,
那么程序在上一点的状态一定是这
样,..”,直到找到错误的位置。
对分查找法
? 如果已经知道每个变量在程序内若干关
键点的正确值,则可以用赋值语句或输
入语句在程序中点附近, 注入, 这些变
量的正确值,然后检查程序的输出
? 如果输出结果正确,故障在程序的前半
部分;反之在程序的后半部分
? 重复使用直到定位或范围小到容易诊断
归纳法
? 归纳法是一种从特殊推断一般的系统化
思考方法
? 归纳法调试的基本思想是:从一些线索
(错误征兆 )着手,通过分析它们之间的
关系来找出错误。
归纳法四个步骤,
? 收集有关的数据:列出已经知道的关于程序哪
些事做得对哪些事做得不对的一切数据
? 组织数据:整理数据,发现规律,发现矛盾,
即在什么条件下出现错误,什么条件下不出现
错误
? 导出假设:分析线索之间的关系,提出故障的
假设
? 证明假设:解释所有原始的测试结果
归纳法数据组织
? 归纳法常以 3W1H形式组织可用的数据,
?, What” 列出一般现象;
?, Where”说明发现现象的地点;
?, When” 列出现象发生时所有已知情
况;
?, How” 说明现象的范围和量级;
演绎法
? 演绎法是一种从一般原理或前提出发,
经过排除和精化的过程来推导出结论的
思考方法。
? 演绎法排错是测试人员首先根据已有的
测试用例,设想及枚举出所有可能出错
的原因做为假设;然后再用原始测试数
据或新的测试,从中逐个排除不可能正
确的假设;最后,再用测试数据验证余
下的假设确是出错的原因
演绎法四个步骤
? 设想可能的原因
? 用已有的数据排除不正确的假设
? 精化余下的假设:具体化和定位
? 证明余下的假设
演绎法流程图
调试原则,确定错误性质和位置的原则
? 用头脑去分析思考与错误征兆有关的信
息
? 避开死胡同
? 只把调试工具当做辅助手段来使用。利
用调试工具,可以帮助思考,但不能代
替思考
? 避免用试探法,最多只能把它当做最后
手段
调试原则,修改错误的原则
? 在出现错误的地方很可能还有别的错误
? 修改错误的一个常见失误是只修改了这个错
误的征兆或这个错误的表现,而没有修改错
误的本身
? 当心修正一个错误的同时有可能会引入新的
错误
? 修改错误的过程将迫使人们暂时回到程序设
计阶段
? 修改源代码程序,不要改变目标代码
修改的问题
? 在改错时回答以下问题,
? 这个错误在程序的其它地方也会产生
吗? 举一反三
? 将要进行的修改可能会引发的, 下一
个错误, 是什么? 消除影响
? 为了防止这个错误,首先应当做什么?
缺陷预防
谢谢!
68389085( O)
68389504( H)
mdtang@btamail.net.cn
软件工程实践
汤铭端
中国航天科工集团公司 706所
第五讲
软件实现
内容和目的
? 编程语言的选择
? 编程风格
? 程序效率
? 编码要求
? 静态分析
? 代码审查
? 软件调试
编程(编码)
? 编程是设计的自然结果
? 编程语言的特性和编程风格会深刻地影响软件
的重量和可维护性
? 软件实现是一个不断变换的过程:设计 —— 源
程序 —— 目标代码 —— 机器码
? 为了保证程序编码的质量,程序员必须深刻理
解、熟练掌握并正确地运用程序设计语言的特
性。此外,还要求源程序具有良好的结构性和
良好的程序设计风格。
编程的目的
? 编码的目的
? 是使用选定的程序设计语言,把模块
的过程性描述翻译为用该语言书写的
源程序(源代码)
模块的过程性描述
(不可执行的 )
源程序
(可执行的 )
编码
结构化程序设计
? 结构化程序设计是一种设计程序的技术
? 它采用 自顶向下逐步细化 的设计方法和
单入口 (Single entry)单出口 ( Single exit)
的控制结构
? 这种控制结构包括有,
? 顺序
? 选择
? 循环
结构化程序设计的原则
1、使用语言中的顺序、选择、重复等有限的基
本控制结构表示程序
2、选用的控制结构只准许有一个入口和一个出
口
3、程序语句组成容易识别的块( Block),每块
只有一个入口和一个
出口
4、复杂结构应该用基本控制结构进行组合嵌套
来实现
5、严格控制 GOTO语句
编程语言的选择
? 应用领域
? 算法及运算的复杂性
? 软件运行的环境
? 性能
? 数据结构的复杂性
? 软件开发组成员对该语言的熟悉程度
编程风格
? 程序必须是可以理解的
? 程序的风格应该强调简单和清晰
? 影响程序风格的因素有,
? 源程序文档化
? 数据说明的方法
? 语句的结构
? I/O的方法
源程序文档化
? 选择好标识符(变量和标号)的名字
? 挑选有意义的标识符名字
? 安排注解
? 序言式注解(头文件)
? 功能注解
? 使程序的结构一目了然
? 缩进
源程序文档化
? 标识符的命名
? 安排注释
? 程序的视觉组织
符号名的命名
? 符号名即标识符,包括 模块名, 变量名,
常量名, 标号名, 子程序名,, 数据区
名 以及 缓冲区名 等。
? 这些名字应能反映它所代表的实际东西,
应有一定实际意义 。
? 例如,表示次数的量用 Times,表示总量
的用 Total,表示平均值的用 Average,表
示和的量用 Sum等。
符号名的命名
? 名字不是越长越好,应当选择精炼的意
义明确的名字。 必要时可使用缩写名字,
但这时要注意缩写规则要一致,并且要
给每一个名字加注释 。同时,在一个程
序中,一个变量只应用于一种用途。
? NEW.BALANCE.ACCOUNTS.PAYABLE
? NBALAP
? N
程序的注释
? 夹在程序中的注释是程序员与日后
的程序读者之间通信的重要手段。
? 注释决不是可有可无的。
? 一些正规的程序文本中,注释行的
数量占到整个源程序的 1/ 3到 1/ 2,
甚至更多。
? 注释分为序言性注释和功能性注释。
序言性注释(头文件)
? 通常臵于每个程序模块的开头部
分,它应当给出程序的整体说明,
对于理解程序本身具有引导作用。
有些软件开发部门对序言性注释
做了明确而严格的规定,要求程
序编制者逐项列出。
头文件的内容
? 程序标题 ;
? 有关本模块 功能和目的 的 说明 ;
? 主要算法 ;
? 接口说明,包括调用形式,参数描述,子程序清单;
? 有关数据描述,重要的变量及其用途,约束或限制条
件,以及其它有关信息;
? 模块位臵,在哪一个源文件中,或隶属于哪一个软件
包;
? 开发简历,模块设计者,复审者,复审日期,修改日
期及有关说明等。
功能性注释
? 功能性注释嵌在源程序体中,用以描述
其后的语句或程序段是在做什么工作,
或是执行了下面的语句会怎么样。而不
要解释下面怎么做。
? 例如,
/* ADD AMOUNT TO TOTAL */
TOTAL = AMOUNT+ TOTAL
不好。
功能性注释
? 如果注明把月销售额计入年度总额,便使读者
理解了下面语句的意图,
/* ADD MONTHLY-SALES TO ANNUAL-
TOTAL */
TOTAL = AMOUNT+ TOTAL
? 要点
? 描述一段程序,而不是每一个语句;
? 用缩进和空行,使程序与注释容易区别;
? 注释要正确。
视觉组织 — 空格、空行和移行
? 恰当地利用 空格,可以 突出运算的优先
性,避免发生运算的错误。
? 例如,将表达式
(A<- 17)ANDNOT(B<= 49)ORC
写成
(A<- 17) AND NOT (B<= 49) OR C
? 自然的程序段之间可用 空行 隔开;
视觉组织
? 移行 也叫做 向右缩格 。它是指程序中的各行不
必都在左端对齐,都从第一格起排列。这样做
使程序完全分不清层次关系。
? 对于 选择语句 和 循环语句,把其中的程序段语
句向右做 阶梯式移行 。使程序的逻辑结构更加
清晰。
? 例如,两重选择结构嵌套,写成下面的移行形
式,层次就清楚得多。
视觉组织
IF( … ) THEN
IF( … ) THEN
……
ELSE
……
ENDIF
……
ELSE
……
ENDIF
数据说明
? 数据说明的次序应该规范化
? 多个变量说明时最好按字典数顺序排列
? 对复杂结构用注解说明
数据说明
? 在设计阶段已经确定了数据结构的组织
及其复杂性。在编写程序时,则需要注
意数据说明的风格。
? 为了使程序中数据说明更易于理解和维
护,必须注意以下几点,
1.数据说明的次序应当规范化
2.说明语句中变量安排有序化
3.使用注释说明复杂数据结构
数据说明的次序应当规范化
? 数据说明次序规范化,使数据属性
容易查找,也有利于测试,排错和
维护。
? 原则上,数据说明的次序与语法无
关,其次序是任意的。但出于阅读、
理解和维护的需要,最好使其规范
化,使说明的先后次序固定。
数据说明的次序应当规范化
? 在 FORTRAN程序
中数据说明次序
① 常量说明
② 简单变量类型说
明
③ 数组说明
④ 公用数据块说明
⑤ 所有的文件说明
? 在类型说明中还可
进一步要求按如下
顺序排列
① 整型量说明
② 实型量说明
③ 字符量说明
④ 逻辑量说明
说明语句中变量安排有序化
? 当 多个变量名在一个说明语句中说明 时,
应当对这些变量 按字母的顺序排列 。带
标号的全程数据 (如 FORTRAN的公用块 )
也应当按字母的顺序排列。
? 例如,把
integer size,length,width,cost,price
写成
integer cost,length,price,size,width
使用注释说明复杂数据结构
? 如果设计了一个复杂的数据结构,应当
使用注释来说明在程序实现时这个数据
结构的固有特点。
? 例如,对 PL/1的链表结构和 Pascal中用户
自定义的数据类型,都应当在注释中做
必要的补充说明。
语句结构
? 每个语句应该简单直接,不应该为提高效率而
把语句复杂化
? 使程序简单易懂
? 避免采用复杂的条件语句
? 不要用, 否定, 条件的条件语句
? 避免多重的循环嵌套或条件嵌套
? 用括号使逻辑表达式或算术表达式更为清晰
? 用空格及有意义的符号使语句内容清晰明确
? 反问自己, 如果这程序不是我编的,我能看懂吗?,
语句结构
? 在设计阶段确定了软件的逻辑流结构,
但构造单个语句则是编码阶段的任务。
语句构造力求简单,直接,不能为了片
面追求效率而使语句复杂化。
语句结构
? 1,在一行内只写一条语句
? 在一行内只写一条语句,并且采取适当的移
行格式,使程序的逻辑和功能变得更加明确。
? 许多程序设计语言允许 在一行内写多个语句 。
但这种方式 会使程序可读性变差 。因而不可
取。
语句结构
? 例如,有一段排序程序
FOR I:=1 TO N- 1 DO BEGIN T:=I; FOR
J:=I+ 1 TO N DO IF A[J]< A[T] THEN T:=J;
IF T≠I THEN BEGIN WORK:=A[T] ;
A[T]:=A[I]; A[I]:=WORK; END END;
? 由于一行中包括了多个语句,掩盖了程序的循
环结构和条件结构,使其可读性变得很差。
语句结构
FOR I:=1 TO N-1 DO //改进布局
BEGIN
T:=I;
FOR J:=I+ 1 TO N DO
IF A[J]< A[T] THEN T:=J;
IF T≠I THEN
BEGIN
WORK:=A[T];
A[T]:=A[I];
A[I]:=WORK;
END
END;
语句结构
? 2.程序编写首先应当考虑清晰性
? 程序编写首先应当考虑清晰性,不要刻意追求技巧性,使程
序编写得过于紧凑。
? 例如,有一个用 C 语句写出的程序段,
A[I] = A[I]+ A[T];
A[T] = A[I]- A[T];
A[I] = A[I]- A[T];
? 此段程序可能不易看懂,有时还需用实际数据试验一下。
? 实际上,这段程序的功能就是交换 A[I]和 A[T]中的内容。目的是为
了节省一个工作单元。如果改一下,
WORK = A[T];
A[T] = A[I];
A[I] = WORK;
就能让读者一目了然了。
语句结构
? 3.程序要能直截了当地说明程序员的用意。
? 程序编写得要简单,写清楚,直截了当地说明程序
员的用意。
? 例如,
for ( i = 1; i <= n; i++ )
for ( j = 1; j <= n; j++ )
V[i][j] = ( i/ j ) * ( j/ i )
除法运算(/)在除数和被除数都是整型量时,其
结果只取整数部分,而得到整型量。
语句结构
? 当 i< j 时,i / j = 0
当 j< i 时,j / i = 0
得到的数组 V 是一个单位矩阵
当 i≠j时 V[i][j] = ( i/ j ) * ( j/ i ) = 0
当 i= j时 V[i][j] = ( i/ j ) * ( j/ i ) = 1
? 写成以下的形式,就能让读者直接了解程序编写者的
意图。
for ( i= 1; i <= n; i++ )
for ( j= 1; j <= n; j++ )
if ( i == j )
V[i][j] = 1.0;
ELSE
V[i][j] = 0.0;
语句结构
4,除非对效率有特殊的要求,程序编写要
做到 清晰第一, 效率第二 。 不要为了追
求效率而丧失了清晰性。事实上,程序
效率的提高主要应通过选择高效的算法
来实现。
5.首先要保证 程序正确,然后才要求 提高
速度 。 反过来说,在使程序高速运行时,
首先要保证它是正确的。
语句结构
? 6.避免 使用临时变量 而使可读性下降。
? 例如,有的程序员为了追求效率,往
往喜欢把表达式
A[I]+ 1/ A[I];
写成 AI= A[I];
X= AI+ 1/ AI;
这样将一句分成两句写,会产生意想不
到的问题。
语句结构
7,让编译程序做简单的优化。
8,尽可能 使用库函数
9,避免 不必要的转移 。
? 同时如果能保持程序可读性,则
不必用 GO TO语句。
求三个数中最小值的程序
IF ( X< Y ) GOTO 30
IF (Y< Z) GOTO 50
SMALL= Z
GOTO 70
30 IF ( X < Z) GOTO 60
SMALL= Z
GOTO 70
50 SMALL= Y
GOTO 70
60 SMALL= X
70 CONTINUE
求三个数中最小值的程序
程序只需编写成,
small= x;
if ( y < small ) small= y;
if ( z < small ) small= z;
所以程序应当简单,不必过于深奥,
避免使用 GOTO语句绕来绕去。
语句结构
10.尽量只采用 三种基本的控制结构 来编写程序。
除 顺序结构 外,使用 if-then-else来实现 选择结构 ;使用 do-
until或 do-while来实现 循环结构 。
11,避免使用 空的 ELSE语句和 IF… THEN IF… 的语句 。
这种结构容 易使读者产生误解 。 例如,
if ( char >= 'a’ )
if ( char <= ’z’ )
cout <<,This is a letter。” ;
else
cout <<,This is not a letter。” ;
可能产生二义性问题。
语句结构
12.避免采用过于复杂的条件测试。
13.尽量减少使用“否定”条件的条件语句。 例如,
如果在程序中出现
if ( !( char<‘ 0’ || char >‘ 9’ ) )
……
改成
if ( char >= '0’ && char <= '9’ )
……
不要让读者绕弯子想。
语句结构
14,尽可能用 通俗易懂的伪码 来描述程序的流程,
然后再翻译成必须使用的语言。
15,数据结构要有利于程序的简化。
16,要 模块化,使模块功能尽可能单一化,模块
间的耦合能够清晰可见。
17,利用 信息隐蔽,确保每一个模块的独立性。
18,从 数据 出发去构造程序。
语句结构
19,不要修补不好的程序,要重新编写。
也不要一味地追求代码的复用,要
重新组织。
20,对太大的程序,要分块编写、测试,
然后再集成。
21,对递归定义的数据结构尽量使用递
归过程。
输入 /输出
? 对批处理 I/O
? 符合逻辑地组织输入
? I/O出错检查
? 好的 I/O出错恢复功能
? 清晰的输出报告格式
? 对交互式 I/O
? 简单而有提示的输入方式
? 完备的出错处理及出错恢
复
? 人机对话输出
? I/O格式的一致性
? 原则,
? 检查所有输入数据的合法性
? 检查输入项的各种重要组合
是否合理
? 输入格式要简单
? 最好采用数据结尾指示符,
而不应要求用户规定, 输入
项目的数量,
? 交互式 I/O要求用户输入时,
标明交互输入可选择的种类
和范围
? 输出时保持格式的一致性
? 设计和标明所有的输出
输入和输出
? 输入和输出信息是与用户的使用直接相
关的。输入和输出的方式和格式应当尽
可能方便用户的使用。一定要避免因设
计不当给用户带来的麻烦。
? 因此,在软件需求分析阶段和设计阶段,
就应基本确定输入和输出的风格。系统
能否被用户接受,有时就取决于输入和
输出的风格。
输入和输出设计和编码原则
1,对所有的输入数据都要进行检验,识别错误的输入,
以保证每个数据的有效性;
2,检查输入项的各种重要组合的合理性,必要时报告输
入状态信息;
3,使得输入的步骤和操作尽可能简单,并保持简单的输
入格式;
4,输入数据时,应允许使用自由格式输入;
5,应允许缺省值;
6,输入一批数据时,最好使用输入结束标志,而不要由
用户指定输入数据数目;
输入和输出设计和编码原则
7,在交互式输入输入时,要在屏幕上使用提示符明确提
示交互输入的请求,指明可使用选择项的种类和取值
范围。同时,在数据输入的过程中和输入结束时,也
要在屏幕上给出状态信息;
8,当程序设计语言对输入/输出格式有严格要求时,应
保持输入格式与输入语句的要求的一致性;
9,给所有的输出加注解,并设计输出报表格式。
输入/输出风格还受到许多其它因素的
影响。如输入/输出设备(例如终端的
类型,图形设备,数字化转换设备等)、
用户的熟练程度、以及通信环境等。
Wasserman交互系统设计原则原则
? 把计算机的内部特性掩盖起来不让用户看到
? 使程序, 穿上防弹衣,,保证程序不被用户破坏
? 如果用户的请求会产生重大的后果,就要提醒用
户
? 在使用此程序时提供联机的帮助条件
? 按照用户的水平设计输入要求
? 按照输出设备的速度设计输出信息
? 区别对待不同类型的用户
? 保持一致的响应时间
? 应尽量减少用户处理出错的工作量
效率
? 要求有效地利用临界资源是自然的
? CPU周期和内存单元常被看作临界资源
? 有关效率的三个格言,
? 效率是一种性能需求。软件的效率应根据需要,而
不是尽可能地高!
? 好的设计必然提高效率。
? 程序的简单性与程序的效率往往是一致的。
? 总之不要去牺牲程序的清晰性、可读性或正确
性去追求效率的非本质的提高
源程序的效率
? 源程序的效率与算法
效率直接相关
? 编程风格会影响运行
速度及所需内存的大
小
? 编译器的, 优化, 特
性是提高效率的一种
手段
? 原则,
? 在具体编程前应简化算术
表达式及逻辑表达式
? 细心地分析多层嵌套循环
以确定能否把一些语句或
表达式移到循环之外
? 尽量避免采用多维数组
? 尽量避免采用指针及复杂
的表
? 采用, 快, 的算术运算
? 不要把不同的数据类型混
在一起
? 只要可能就采用整型数的
算术运算和布尔表达式
内存效率
? 大机器领域内存几乎无限制,虚存使
,内存效率, 不等同于, 占用最小的内
存,
? 微机领域内存的限制仍是很现实的问题
? 采用汇编语言可以节省内存
? 提高运行效率的技术往往可以同时节省
内存
? 使程序简单是提高内存效率的关键
输入输出效率
? 提高 I/O效率的指导原则,
? 全部 I/O应有缓冲以避免过于频繁的信息交
换
? 对外存应该选用最简单的可接收的存取方式
? 与外存联系的 I/O操作应成块地传送数据
? 与终端及行式打印机联系的 I/O操作应考虑
设备的特性,以改进质量与速度
? 如果, 超高效率, 的 I/O无法被人们理解,
则是毫无意义的
程序效率,讨论效率的准则
? 程序的效率是指 程序的执行速度 及 程序所需占用的内
存的存储空间 。程序编码是最后提高运行速度和节省
存储的机会,因此在此阶段不能不考虑程序的效率。
让我们首先明确讨论程序效率的几条准则
? 效率是一个性能要求,应当在需求分析阶段给出。 软
件效率以需求为准,不应以人力所及为准。
? 好的设计可以提高效率。
? 程序的 效率与程序的简单性 相关。
? 一般说来,任何对效率无重要改善,且对程序的简单
性、可读性和正确性不利的程序设计方法都是不可取
的。
程序效率,算法对效率的影响
? 源程序的 效率与详细设计阶段确
定的算法的效率直接有关 。在详
细设计翻译转换成源程序代码后,
算法效率反映为程序的执行速度
和存储容量的要求。
设计向程序转换的指导原则
① 在编程序前,尽可能化简有关的算术表达式和逻辑表
达式;
② 仔细检查算法中的嵌套的循环,尽可能将某些语句或
表达式移到循环外面;
③ 尽量避免使用多维数组;
④ 尽量避免使用指针和复杂的表;
⑤ 采用“快速”的算术运算;
⑥ 不要混淆数据类型,避免在表达式中出现类型混杂;
⑦ 尽量采用整数算术表达式和布尔表达式;
⑧ 选用等效的高效率算法;
? 编译程序的“优化”功能可自动生成高效率的目标代
码
程序效率,影响存储器效率的因素
? 在大中型计算机系统中,存储限制不再是主要问题。在这
种环境下,对 内存采取基于操作系统的分页功能的虚拟存
储管理 。 存储效率与操作系统的分页功能直接有关 。
? 采用结构化程序设计,将程序功能合理分块, 使每个模块
或一组密切相关模块的程序体积大小与每页的容量相匹配,
可减少页面调度,减少内外存交换,提高存储效率。
? 微型计算机系统中,存储器的容量对软件设计和编码的制
约很大。因此 要选择可生成较短目标代码且存储压缩性能
优良的编译程序,有时需采用汇编程序。
? 提高存储器效率的关键是程序的简单性。
程序效率,影响输入/输出的因素
? 输入/输出可分为两种类型,
? 面向人 (操作员 )的输入/输出
? 面向设备的输入/输出
? 如果操作员能够十分方便、简单地
录入输入数据,或者能够十分直观、
一目了然地了解输出信息,则可以
说面向人的输入/输出是高效的。
提高输入 /输出效率的指导原则
? 输入 /输出的请求应当最小化;
? 对于所有的输入 /输出操作,安排适当的缓冲区,以减
少频繁的信息交换。
? 对辅助存储 (例如磁盘 ),选择尽可能简单的,可接受
的存取方法 ;
? 对辅助存储的输入 /输出,应当 成块传送 ;
? 对终端或打印机的输入 /输出,应考虑设备特性,尽可
能改善输入 /输出的质量和速度;
? 任何不易理解的,对改善输入 /输出效果关系不大的措
施都是不可取的;
? 任何不易理解的所谓“超高效”的输入 /输出是毫无价
值的;
Kernighan和 Plauger给出的好
的程序设计风格的规则
? 写得清晰 —— 而不是太灵巧
? 简单而直接地说明你的用意
? 使用库功能
? 避免使用临时变量
? 写清晰 —— 不要为了, 效率, 而牺牲清晰
? 让机器干苦活
? 用调用一个公共的例程去代替重复的表示
? 选用不易混淆的变量名
? 避免使用不必要的转移
Kernighan和 Plauger给出的好
的程序设计风格的规则
? 不使用条件转移代替一个逻辑表达式
? 假如一个逻辑表达式难以理解,则试着 对其 进
行 变换
? 使用数组,避免重复的控制序列
? 选用使程序简单的数据表示法
? 先用一个容易理解的伪语言写 程序,然后再翻
译成你所用的语言
使用 IF… ELSEIF… ELSEIF… ELSE… ENDIF实施
多路选择
? 模块化,使用子例程
Kernighan和 Plauger给出的好
的程序设计风格的规则
? 确保注释与代码一致
? 不要用注释去精确地重复代码 —— 使每一个注
释有价值
? 不要注释或修补坏代码 —— 重写
? 使用有意义的变量名
? 使用有意义的语句标号
? 程序格式应有助于读者理解 程序
? 为你的数据设计提供文档
? 仅 使用 GOTO去实施一个基本的结构
Kernighan和 Plauger给出的好
的程序设计风格的规则
? 假如你能保持程序的可读性,则完全不必使用
GOTO
? 以小片段的方式写和测试一个大程序
? 对已定义的递归数据结构使用递归过程
? 检测输入的合理性和合法性
? 确保输入没有违反程序的限制
? 使用文件的结尾或标记终止输入,而不是用计
数来终止输入
? 识别错误的输入(若有可能要恢复输入)
Kernighan和 Plauger给出的好
的程序设计风格的规则
? 输入简易,输出自明
? 使用一致的输入格式
? 使输入容易校对
? 若可能,使用自由格式输入
? 使用自标识的输入,允许缺省,输出时反映这
两者
? 确保在使用之前,所有变量已被置初值
? 在出现故障时不要停机
? 使用排错编译程序
Kernighan和 Plauger给出的好
的程序设计风格的规则
? 用 DATA语句或 INITIAL属性置常数
? 用可执行的代码置变量初值
? 尽量注意由错误引起的分岔
? 注意在相等时转移的正确性
? 当一个循环从边上或底上退出到同一个地方时,
必须十分小心
? 确保你的代码, 没有做什么优美方面的事情,
? 在边值处测试程序
? 人工核查某些答案
Kernighan和 Plauger给出的好
的程序设计风格的规则
? 10.0乘以 0.1很少是 1.0
? 不要单独地进行浮点数相等比较
? 先保证正确,再提高速度
? 在提高速度之前,做好自动防止故障的措施
? 先保证清晰,再提高速度
? 不要为谋求, 效率, 上的少量增益而牺牲清晰
? 让你的编译程序做简单的优化
? 不要滥用再使用代码,取而代之的是改组
? 确保特殊情况是确实的特殊
Kernighan和 Plauger给出的好
的程序设计风格的规则
? 保持简单,以提高速度
? 不要浪费代码而提高速度 —— 找一个较
好的算法
? 使用工具装备你的程序,在, 效率, 改
变以前测量( END)
程序编码结构要求
? 采用结构化编码方法
? 按以下 22个特性构造
( 1)完整性
? 任何程序或控制段的开始和结束,必须
包含在单一的结构化模块中。
( 2)功能
? 每个模块必须完成单一的定义良好的功
能,模块的全部元素都是为了实现唯一
的功能。
? 此外,模块实现的细节对其它模块来说
应该是隐蔽的。如果实现的细节改变,
直接受影响的只是实现功能的那个模块,
并且只有该模块需要修改。
( 3)入口/出口
? 每个模块一般要求单一入口和单一出口。
处理完成后,模块必须返回到调用它的
程序而不是其它模块。
? 接口(入口与出口条件和参数)必须明
确表示和定义。
( 4)说明
? 模块的说明语句和数据语句应放在程序
首部之后和第一个可执行语句之前,虽
然允许每行可有多个数据说明,但应保
证清晰地定义复杂的数据结构。
( 5)常量
? 常量必须在程序的说明中定义,常量通
常有数组规模,π值,DO和 FOR 循环中
的循环数、数组下标、记录规模和行长
度等。
( 6)变元
? 在调用语句中的变元一般不得包含算术
或逻辑表达式。
? 每个变元必须由单一变量来表示。
? 在调用一个模块的过程中,变元的类型
和个数必须与模块的形参相一致。
? 此外,必须标明调用过程中使用的形参。
( 7)幂
? 幂的指数必须用整数表示,即A**3
而不是A** 3.0。
( 8)混合方式运算
? 应避免使用混合运算的算术表达式,不
能避免时应仔细计算表达式的值,以保
证类型转换得到预期的结果,应对转换
进行适当的注释。
( 9)转移
? 尽量少用无条件转移语句。如需使用,
必须在同一程序单元内转移,并应向转
移语句所在点的前方转移。
? 允许使用为实现结构化程序设计所必需
的转移指令。
( 10)错误处理
a.必须记录所有错误的发生及处理情况,保存系
统发生错误的全部记录,以利于后续错误情况
的处理和分析;
b.必须预先了解错误的潜在影响,并且在它们出
现之前确定如何处理;
c.应尽可能使错误不扩散,不引起连锁反应;
d.当程序不能执行时,必须在中止前释放已取得
的全部资源和结果。
( 11)范围检查
? 为保证变量值落在预期的范围内,必须进行范
围检查。
? 如果变量值不在规定范围内,则必须进行适当
的错误处理。
? 变量的定义域必须在设计时规定,在实现时声
明,并在运行时检查。
? 必须进行范围检查的变量应包括:参数;数值
下标; CASE结构中的变量参数;循环结构中用
作初值、增量和终值的变量。
( 12)序标和下标
? 循环序标参数和数值下标必须表示为整
型的常量或变量。
( 13)循环终止
? 必须保证循环终止,终止条件不能靠对
循环参数所作假设的正确性,而是靠对
循环入口事前直接的验证。
( 14)标号和名字的使用
? 标号和名字必须有意义、一致并明显、
唯一,不易混淆。
( 15)全局变量和共享变量
? 应避免使用全局或共享变量。当一个变量由两个或多
个模块共享时,每个模块都必须正确地使用该变量。
? 由多个模块使用的变量应作为变元进行通信。限制变
量的范围有助于掌握变量的准确含义。
? 当有明确的技术根据需要使用公共数据时,应有下列
限制,
a.仅使用可由名字访问的公用域;
b.公用数据域的长度和说明,对于使用它的每个模块
都是相同的;
c.可能时,应仅保留公用块的一个副本,而且必须用
,INCLUDE,语句包容在每一个模块中而不是在模
块中各自编码描述。
( 16)连接约定
? 如果模块是用汇编语言实现的,则必须定义和
使用模块间通信的标准约定。目的是构造独立
的、自包容的模块,它可以用良好定义的方法
与其它模块通信。
? 应考虑的约定有,
a.在将控制转给其它模块之前必须保护寄存器的内容,
并在控制返回时恢复;
b.通信必须通过变元素;
c.必须有一个与调用模块返回地址进行通信的标准方
法。
( 17)输入与输出
? 输入例程和输出例程必须集中在有限几
个模块内。
? 将这些例程汇集到少数几个模块中有利
于输入和输出数据的监控。
? 此外,将这些例程限制在有限范围内可
使修改更容易。
( 18)命名
? 代码和数据单元的名字必须体现它们的
功能和内容。
( 19)程序首部
? 每个代码模块必须包含一个标准化的程
序首部注释语句块,作为每个程序模块
的内部源文档。它放在模块的首部,描
述模块的功能、用途和操作要求。
? 程序首部应包括下面的信息,
a.名称:包含用于标识模块的名称、版本标识
符和入口。
b.目的:包含模块目的/功能的简要说明。
程序首部
? 程序首部应包括下面的信息,
c.输入/输出,包含该模块所使用的每个I/O文件
名,并指明是向模块输入、还是输出、还是两者兼
有。使用交互式屏幕I/O的模块也应提供屏幕功
能的说明。
d.参数:提供模块所需的全部变元的定义以及模块的
返回值(输出参数)。参数定义必须包括:名字、
数据类型、大小和功能。
e.调用:提供该模块调用的全部例程的清单和调用该
模块的全部例程的清单。
f.全局数据:提供模块中使用的全部公用或全局数据
的清单。
程序首部
? 程序首部应包括下面的信息,
g.限制:包含约束或限制模块性能特点的任何特殊或
非常规因素的清单。
h.异常结束:包含异常返回条件和动作的清单。
i.方法:包含为实现模块的功能而使用的方法的详细
说明。
j.编程者:代码编制人名及所属机构。
k.版本号及完成日期:当前的模块版本号和它的完成
日期。
l.修改记录:包含对该模块的修改人、所属机构、日
期及对应的版本号。
( 20)行间注释
? 源程序体内的注释必须解释程序执行的
处理,必须指明每一个控制语句(有条
件地更改一个数据值或语句的执行顺序
的语句)的目的。
a.IF语句:说明条件满足时执行动作的理由。
b.I/O语句:标明处理的记录或文件的性质。
c.DO语句:说明执行动作的理由。
d.CALL语句:说明调用过程的理由。
( 21)注释比例
? 注释行数不得少于源程序总行数的1/
5。
( 22)缩进和分段
? 源代码必须清楚地缩进表达,以表明结构化程
序段外形的控制范围,使编码的逻辑关系对应
于程序列表的实际位置。
? 可执行语句限于每行一条。
? 超过一行的语句的后续部分应缩进该语句的第
一行下面。
? 一行允许有多个数据说明。数据说明应使用某
种逻辑顺序(例如字母顺序)排列。
( 23)模块规模
? 每个软件单元的源代码行数(不含注释
行),平均不应超过60行,最大不应
超过200行。
程序复杂性度量
? 程序复杂性主要指 模块内程序的复杂性 。
它直接关联到软件开发费用的多少,开
发周期的长短和软件内部潜伏错误的多
少。
? 减少程序复杂性,可提高软件的简单性
和可理解性,并使软件开发费用减少,
开发周期缩短,软件内部潜藏错误减少。
复杂性度量需要满足的假设
? 为了度量程序复杂性,要求,
? 它可以用来计算任何一个程序的复杂性;
? 对于不合理的程序,例如对于长度动态
增长的程序,或者对于原则上无法排错
的程序,不应当使用它进行复杂性计算;
? 如果程序中指令条数、附加存储量、计
算时间增多,不会减少程序的复杂性。
代码行度量法
? 源代码行数度量法基于两个前提,
? 程序复杂性随着程序规模的增加不均衡地增长;
? 控制程序规模的方法最好是采用分而治之的办法。
将一个大程序分解成若干个简单的可理解的程序段。
? 方法的基本考虑是 统计一个程序模块的
源代码行数目,并以源代码行数做为程
序复杂性的度量。
代码行与出错率的关系
? 设 每行代码的出错率 为 每 100行源程序中可能有的错误数目 。
? Thayer曾指出,程序出错率的估算范围是从 0.04%~ 7%之
间,即每 100行源程序中可能存在 0.04~ 7个错误。他还指出,
每行代码的出错率与源程序行数之间不存在简单的线性关
系。
? Lipow指出,对于 小程序,每行代码出错率为 1.3%~ 1.8% ;
对于 大程序,每行代码的出错率增加到 2.7%~ 3.2% 之间,
这只是考虑了程序的可执行部分,没有包括程序中的说明
部分。
? Lipow及其他研究者得出一个结论:对于少于 100个语句的
小程序,源代码行数与出错率是线性相关的。随着程序的
增大,出错率以非线性方式增长。
McCabe度量法
? McCabe度量法,又称环路复杂性度量,是一
种 基于程序控制流 的复杂性度量方法。
? 它 基于一个程序模块的程序图中环路的个数,
因此计算它先要画出程序图。
? 程序图是退化的程序流程图。流程图中每个处
理都退化成一个结点,流线变成连接不同结点
的有向弧。
? 程序图仅描述程序内部的控制流程,完全不表
现对数据的具体操作,以及分支和循环的具体
条件。
McCabe计算
? 计算环路复杂性的方法,根据图论,在一个强
连通的有向图 G中,环的个数由以下公式给出,
V(G)= m- n+ p
其中,V(G)是有向图 G中环路个数,m是图 G中
弧数,n是图 G中结点数,p是图 G中的强连通
分量个数。
? Myers建议,对于复合判定,例如,(A= 0)∩(C
= D)∪ (X=‘ A’) 算做三个判定。
? 为使图成为强连通图,从图的入口点到出口点
加一条用虚线表示的有向边,使图成为强连通
图。这样就可以使用上式计算环路复杂性。
圈复杂度 — 程序复杂性度量
? McCabe圈复杂度 V(G) ≤7
? 将程序流程图退化为程序
图,将程序流程图中每个
处理符号都退化为一个点,
箭头变成有向弧,调整使
成为强连通图
? V(G)=m-n+p
? m=弧数
? n=节点数
? p=分离部分的数目(连通
图 p=1)
计算示例
? 在例示中,结点数 n= 11,弧数 m= 13,
p= 1,则有
V(G)= m- n+ p= 13- 11+ 1= 3,
? 等于程序图中弧所封闭的区域数。
McCabe几点说明
? 环路复杂度取决于程序控制结构的复杂度。当程序的
分支数目或循环数目增加时其复杂度也增加。 环路复
杂度与程序中覆盖的路径条数有关 。
? 环路复杂度是可加的。例如,模块 A的复杂度为 3,模
块 B的复杂度为 4,则 模块 A与 模块 B的复杂度是 7。
? McCabe建议,对于复杂度超过 10的程序,应分成几个
小程序,以减少程序中的错误。 Walsh用实例证实了这
个建议的正确性。在 McCabe复杂度为 10的附近,存在
出错率的间断跃变。
? McCabe环路复杂度隐含的前提是,错误与程序的判定
加上例行子程序的调用数目成正比。 加工复杂性、数
据结构、录入与打乱输入卡片的错误可以忽略不计。
McCabe度量的缺点
? 对于不同种类的控制流的复杂性不能区
分
? 简单 IF语句 与 循环语句 的复杂性同等看
待
? 嵌套 IF语句 与 简单 CASE语句 的复杂性
是一样的
? 模块间接口 当成 一个简单分支 一样处理
? 一个 具有 1000行的顺序程序 与 一行语句
的复杂性相同
Halstead的软件科学
? Halstead软件科学研究确定计算机软件开发中的一
些定量规律,它采用以下一组基本的度量值。
? 这些度量值通常在程序产生之后得出,或者在设
计完成之后估算出。
? 程序长度 (预测的 Halstead长度 )
令 n1表示程序中不同运算符 (包括保留字 )的个数,
令 n2表示程序中不同运算对象的个数,令 H表示
“程序长度”,则有
H=n1?log2 n1+n2 ? log2n2
? 这里,H是程序长度的预测值,它不等于程序中语
句个数。
Halstead的软件科学
? 在定义中,运算符包括:算术运算符;赋值符(=或,=);逻辑运算符;分界符 (,或;或,);关
系运算符;括号运算符;子程序调用符;数组操作符;循环操作符等。
? 特别地,成对的运算符,例如, begin…end”,,for…to”,,repeat …until”,,while…do”,
,if…then…else”,“( … )” 等都当做单一运算符。
? 运算对象包括变量名和常数。
Halstead的软件科学
? 实际的 Halstead长度,设 N1为程序中实际出现的运算符总个数,N2为程序中实际出现的运算对象总个数,
N为实际的 Halstead长度,则有,N = N1 + N2
? 程序的词汇表,Halstead定义程序的词汇表为不同的运
算符种类数 n1和不同的运算对象种类数 n2的总和。若令 n为程序的词汇表,则有,n = n1+n2
? 程序量,V = N ? log2n
? 它表明了程序在 词汇上的复杂性 。其最小值为
V* = (2+n2*) ? log2(2+n2*)
这里,2表明程序中至少有两个运算符:赋值符 = 和函数调用符
f ( ), n2*表示输入/输出变量个数。
示例程序
SUBROUTINE SORT ( X,N )
DIMENSION X( N )
IF ( N,LT,2 ) RETURN
DO 20 I=2,N
DO 10 J=1,I
IF ( X(I),GE,X(J) ) GO TO 10
SAVE = X(I)
X(I) = X(J)
X(J) = SAVE
10 CONTINUE
20 CONTINUE
RETURN
END
运算符 计数 运算对象 计数
可执行语句结束 7 X 6
数组下标 6 I 5
= 5 J 4
IF ( ) 2 N 2
DO 2 2 2
, 2 SA VE 2
程序结束 1 1 1
,L T, 1 N 1 = 7 N 2 = 22
,G E,1
GO T O 10 1
n 1= 10 n 2 = 28
示例计算
? H = 10 ? log210+7 ? log27 = 52.87
? N = 28+22 = 50
? V = (28+22) ? log2(10+7) = 204
? 等效的汇编语言程序的 V= 328。
这说明汇编语言比 FORTRAN语
言需要更多的信息量 (以 bit表示 )
Halstead的软件科学
? 程序量比率 (语言的抽象级别 )
L = V* / V 或 L = (2 / n1)?(n2 / N2)
? 它表明了一个程序的最紧凑形式的程序量与实际程序量之比,反
映了程序的效率。其倒数 D = 1 / L 表明了实现算法的困难程度。
? 程序员工作量 E = V / L
? 程序的潜在错误
? Halstead度量可以用来预测程序中的错误。预测公式为
B = (N1+N2)?log2(n1+n2) / 3000
B为该程序的错误数。它表明程序中可能存在的差错 B 应与程序
量 V成正比。
示例
? 例如,一个程序对 75个数据库项共访问
1300次,对 150个运算符共使用了 1200次,
那么预测该程序的错误数,
B = (1200+1300)?log2(75+150)/3000
? 6.5
即预测该程序中可能包含 6~ 7个错误
Halstead的重要结论
? 程序的实际 Halstead长度 N可以由词汇表
n算出。即使程序还未编制完成,也能预
先算出程序的实际 Halstead长度 N,虽然
它没有明确指出程序中到底有多少个语
句。
? 这个结论非常有用。经过多次验证,预
测的 Halstead长度与实际的 Halstead长度
是非常接近的。
Halstead度量的缺点
? 没有区别自己编的程序与别人编的程序。 这是与实际
经验相违背的。这时应将外部调用乘上一个大于 1的的
常数 Kf (应在 1~ 5之间,它与文档资料的清晰度有关 )。
? 没有考虑非执行语句。 补救办法:在统计 n1,n2,N1、
N2时,可以把非执行语句中出现的运算对象,运算符
统计在内。
? 在允许混合运算的语言中,每种运算符与它的运算对
象相关。
? 如果一种语言有整型、实型、双精度型三种不同类型
的运算对象,则任何一种基本算术运算符 (+、-,×,
/ )实际上代表了 6 种运算符。 在计算时应考虑这种因
数据类型而引起差异的情况。
Halstead度量的缺点
? 没有注意调用的深度。 Halstead 公式应当对调用子程
序的不同深度区别对待。在计算嵌套调用的运算符和
运算对象时,应乘上一个调用深度因子。这样可以增
大嵌套调用时的错误预测率。
? 没有把不同类型的运算对象,运算符与不同的错误发
生率联系起来,而是把它们同等看待。 例如,对 简单 if
语句 与 while语句 就没有区别。
? 忽视了嵌套结构 (嵌套的循环语句、嵌套 IF语句、括号
结构等 )。 一般地,运算符的嵌套序列,总比具有相同
数量的运算符和运算对象的非嵌套序列要复杂得多。
解决的办法是对嵌套结果乘上一个嵌套因子。
静态分析
? 静态分析是通过分析、但不执行软件的程序,找出其
中的错误和疑点
? 静态分析手段之一,就是使用工具对软件单元甚至软
件所有成分的源代码进行分析,获得以下主要信息,
? 语法错误信息;每个语句中标识符的引用分析,如
变量、参数等;每个模块调用的子模块;未给初值
的变量;已定义的但未使用的变量;未经说明或无
用的标号;对任何一组输入数据均不可能执行到的
代码段
? 可以根据所获信息对程序进行分析,发现其中可能存
在的缺陷
? 还可以通过静态分析确定程序的分支、路径、转移等
结构关系,为进行动态测试产生测试数据提供方便
代码审查
? 代码审查是由代码审查员根据软件详细设计说
明,按照规定的代码审查单,对被审查的程序
代码逐条进行审查,以确保编码与设计的一致
并确保编码的正确性
? 代码审查应着重审查错误代码而不是多余代码
或代码的遗漏
? 各软件开发机构应根据自身情况和所开发的软
件的特点,形成并不断补充完善自己使用的代
码审查单
通用的代码审查单
格式,
— 嵌套的 IF是否已正确地缩进?
—— 注释准确并有意义吗?
—— 是否使用了有意义的标号?
—— 代码是否基本上与开始时的模块模式
一致?
—— 是否遵循全套的编程标准?
通用的代码审查单
入口和出口的连接,
—— 初始入口的最终出口正确吗?
—— 对另一个模块的每一次调用,
全部所需的参数是否已传送给每
一个被调用的模块?
被传送的参数值是否正确地设置?
通用的代码审查单
程序语言的使用,
—— 是否使用一个或一组最佳的动词?
—— 模块中是否使用完整定义的语言的有限子集?
存储器使用,
—— 每一个域在第一次使用前正确地初始化了吗?
—— 规定的域正确吗?
—— 每个域是否已由正确的变量类型声明?
通用的代码审查单
测试和转移,
—— 测试条件正确吗?
—— 用于测试的是正确变量吗?
—— 每个转移目标正确并至少执行一次吗?
性能,
—— 逻辑是否被最佳地编码?
—— 提供的是一般的错误还是异常的例程?
通用的代码审查单
维护性,
—— 所提供的列表控制是否有利于提高可读性?
—— 标号和子程序名符合代码的意义吗?
逻辑,
—— 全部设计是否均已实现?
—— 编码是否做了设计所规定的内容?
—— 每个循环是否执行正确的次数?( END)
调试
? 调试的任务:诊断和改正程序中的错误
? 调试的活动,
? 根据迹象确定错误的正确位置
? 仔细分析研究以确定问题的原因并进行改正
? 确定错误位置占调试 95%的工作量
? 调试工作是一个具有很强技巧性的工作
? 软件运行失效或出现问题,往往只是潜在错误的外部
表现
? 调试是通过现象,找出原因的一个思维分析的过程
错误改正后要进行回归测试
? 修改程序可能带来新的错误
错误的特征和调试的困难
? 症状和原因可能是相隔很远的
? 症状可能在另一个错误被纠正后消失或暂时性消失
? 症状可能实际上并不是由, 错误, 引起的(如舍入
误差)
? 症状可能是由不太容易跟踪的人工错误引起的
? 症状可能是和时间有关的,而不是处理问题
? 很难重新产生完全一样的输入条件(如实时应用)
? 症状可能时时有时无的
? 症状可能是由分布在许多不同任务中的原因引起的
调试的步骤
(1) 从错误的外部表现形式入手,确定程序
中出错位置 ;
(2) 研究有关部分的程序,找出错误的内在
原因 ;
(3) 修改设计和代码,以排除这个错误 ;
(4) 重复进行暴露了这个错误的原始测试或
某些有关测试 。
调试技术
? 输出存储器内容
? 打印中间结果
? 自动工具(自动插装)
调试策略
? 试探法
? 回溯法
? 对分查找法
? 归纳法
? 演绎法
强行排错
? 使用较多,效率较低,
? 通过内存全部打印来调试
? 在程序特定部位设置打印语句
? 自动调试工具
? 应用以方法之前,都应当对错误的征兆进行全
面彻底的分析,得出对出错位置及错误性质的
推测,再使用一种适当的调试方法来检验推测
的正确性。
试探法
? 分析错误征兆
? 猜想故障的大致位置
? 采用某些调试技术获得程序中被怀疑地
方附近的信息
? 试探法通常是缓慢而低效的
回溯法
? 检查错误征兆,确定最先发现, 症状, 的地
方
? 用人工沿程序的控制流往回追踪源程序代码,
直到找出错误根源或确定故障范围为止
? 另一种形式是正向追踪,使用输出语句检查
一系列中间结果,以确定最先出现错误的地
方
? 回溯法比较适用于小程序,大规模程序使彻
底回溯不可能
回溯法调试示例
? 例如,程序中发现错误处是某个打印语
句。通过输出值可推断程序在这一点上
变量的值。再从这一点出发,回溯程序
的执行过程,反复考虑:, 如果程序在
这一点上的状态(变量的值)是这样,
那么程序在上一点的状态一定是这
样,..”,直到找到错误的位置。
对分查找法
? 如果已经知道每个变量在程序内若干关
键点的正确值,则可以用赋值语句或输
入语句在程序中点附近, 注入, 这些变
量的正确值,然后检查程序的输出
? 如果输出结果正确,故障在程序的前半
部分;反之在程序的后半部分
? 重复使用直到定位或范围小到容易诊断
归纳法
? 归纳法是一种从特殊推断一般的系统化
思考方法
? 归纳法调试的基本思想是:从一些线索
(错误征兆 )着手,通过分析它们之间的
关系来找出错误。
归纳法四个步骤,
? 收集有关的数据:列出已经知道的关于程序哪
些事做得对哪些事做得不对的一切数据
? 组织数据:整理数据,发现规律,发现矛盾,
即在什么条件下出现错误,什么条件下不出现
错误
? 导出假设:分析线索之间的关系,提出故障的
假设
? 证明假设:解释所有原始的测试结果
归纳法数据组织
? 归纳法常以 3W1H形式组织可用的数据,
?, What” 列出一般现象;
?, Where”说明发现现象的地点;
?, When” 列出现象发生时所有已知情
况;
?, How” 说明现象的范围和量级;
演绎法
? 演绎法是一种从一般原理或前提出发,
经过排除和精化的过程来推导出结论的
思考方法。
? 演绎法排错是测试人员首先根据已有的
测试用例,设想及枚举出所有可能出错
的原因做为假设;然后再用原始测试数
据或新的测试,从中逐个排除不可能正
确的假设;最后,再用测试数据验证余
下的假设确是出错的原因
演绎法四个步骤
? 设想可能的原因
? 用已有的数据排除不正确的假设
? 精化余下的假设:具体化和定位
? 证明余下的假设
演绎法流程图
调试原则,确定错误性质和位置的原则
? 用头脑去分析思考与错误征兆有关的信
息
? 避开死胡同
? 只把调试工具当做辅助手段来使用。利
用调试工具,可以帮助思考,但不能代
替思考
? 避免用试探法,最多只能把它当做最后
手段
调试原则,修改错误的原则
? 在出现错误的地方很可能还有别的错误
? 修改错误的一个常见失误是只修改了这个错
误的征兆或这个错误的表现,而没有修改错
误的本身
? 当心修正一个错误的同时有可能会引入新的
错误
? 修改错误的过程将迫使人们暂时回到程序设
计阶段
? 修改源代码程序,不要改变目标代码
修改的问题
? 在改错时回答以下问题,
? 这个错误在程序的其它地方也会产生
吗? 举一反三
? 将要进行的修改可能会引发的, 下一
个错误, 是什么? 消除影响
? 为了防止这个错误,首先应当做什么?
缺陷预防
谢谢!
68389085( O)
68389504( H)
mdtang@btamail.net.cn