关闭

软件工程程序编码

标签: 语言数据结构fortrann2汇编存储
5327人阅读 评论(0) 收藏 举报
 

第六章 程序编码

 

一、复习要求

 

1. 了解什么是结构化程序设计,以及结构化程序设计的原则。

2. 了解程序设计风格4个方面的要求。

3. 了解提高程序效率的方法。

4. 了解程序设计语言的分类和特点。

5. 掌握度量程序复杂性的McCabe方法和Halstead方法。

 

二、内容提要

1. 结构化程序设计

结构化程序设计技术是60年代中期提出来的,它主要包括两个方面:

§ 在编写程序时,强调使用几种基本控制结构,通过组合嵌套,形成程序的控制结构。尽可能避免使用会使程序质量受到影响的GOTO语句。

§ 在程序设计过程中,尽量采用自顶向下和逐步细化的原则,由粗到细,一步步展开。

(1) 结构化程序设计的原则

§ 使用语言中的顺序、选择、重复等有限的基本控制结构表示程序逻辑。

§ 选用的控制结构只准许有一个入口和一个出口。

§ 程序语句组成容易识别的块,每块只有一个入口和一个出口。

§ 复杂结构应该用基本控制结构进行组合嵌套来实现。

§ 语言中没有的控制结构,可用一段等价的程序段模拟,但要求该程序段在整个系统中应前后一致。

§ 严格控制GOTO语句,仅在用一个非结构化的程序设计语言去实现一个结构化的构造,或者在某种可以改善而不是损害程序可读性的情况下才可以使用GOTO语句。

大量采用GOTO语句实现控制路径,会使程序路径变得复杂而且混乱,因此要控制GOTO语句的使用。但有时完全不用GOTO语句进行程序编码,比用GOTO语句编出的程序可读性差。例如,在查找结束时,文件访问结束时,出现错误情况要从循环中转出时,使用布尔变量和条件结构来实现就不如用GOTO语句来得简洁易懂。

对于常用的高级程序设计语言,一般都具备前述的几种基本控制结构。即使不具备等同的结构,也可以采用仿真来实现。下面以FORTRAN77为例进行说明,参看图4.1

 

      基本控制结构

     FORTRAN77模拟基本控制结构

  判断语句

  if ( p ) S1;

  else S2; 

       IF ( p ) THEN

          S1

       ELSE

          S2

       ENDIF

  先判断型循环语句

  while ( p )

     S;

 100   CONTINUE

       IF ( p ) THEN

          S

          GOTO 100

       ENDIF

   后判断型循环语句

   do S; while ( p );

 100   CONTINUE

       S

       IF ( p ) GOTO 100

                 4.1  FORTRAN77语句实现基本控制结构

(2) 程序设计自顶向下,逐步求精

在详细设计和编码阶段,应当采取自顶向下,逐步求精的方法,把一个模块的功能逐步分解,细化为一系列具体的步骤,进而翻译成一系列用某种程序设计语言写成的程序。

例如,要求用筛选法求100以内的素数。所谓筛选法,就是从2100中去掉2,3,,9,10的倍数,剩下的就是100以内的素数。为了解决这个问题,可先按程序功能写出一个框架。

main ( ) {
         
建立2100的数组A[ ],其中A[i]i - - - - - - - - - - - - -- - - - - - - - - - - - 1
         
建立210的素数表B[ ],其中存放210以内的素数;- - - - - - - - - - - - 2
         
A[i]iB[ ]中任一数的倍数,则剔除A[i]- - - - - - - - - - - - - - - - - - - - 3
         
输出A[ ]中所有没有被剔除的数;- - - - - - - - - - - - - - - - - - -- - - - - - - - - - - - 4
}
上述框架中每一个加工语句都可进一步细化成一个循环语句。
main ( ) {

           /*建立2100的数组A[ ],其中A[i]i*/  - - - - - - - - - - - - - - - - - - - - - - - - 1

           for i = 2i <= 100i++A[i] = i

           /* 建立210的素数表B[ ],其中存放210以内的素数*/ - - - - - - - - - - - 2

           B[1] =2  B[2] = 3  B[3] = 5  B[4] = 7

           /*A[i]iB[ ]中任一数的倍数,则剔除A[i]*/ - - - - - - - - - - - - - - - - - - - - 3

           for j = 1 j <= 4 j++

              检查A[ ]所有的数能否被B[j]整除并将能被整除的数从A[]中剔除;- - - - - - - - 3.1

/*输出A[ ]中所有没有被剔除的数*/ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -4
for
i = 2 i <= 100 i++
   
A[i]没有被剔除,则输出之;- - - - - - - - -- - - - - - - - - - - - - - - - - - - - - - - - - -4.1

}
继续对3.14.1细化下去,直到最后每一个语句都能直接用程序设计语言来表示为止。
main ( ) {

for i = 2i <= 100i++A[i] = i

B[1] =2  B[2] = 3  B[3] = 5  B[4] = 7

/*A[i]iB[ ]中任一数的倍数,则剔除A[i]*/

for j = 1 j <= 4 j++

    /*检查A[ ]所有的数能否被B[j]整除并将能被整除的数从A[ ]中剔除*/

    for i = 2 i <= 100 i++

       if A[i]B[j]*B[j] == A[i]A[i] = 0

/*输出A[]中所有没有被剔除的数*/

for i = 2 i <= 100 i++

    /*A[i]没有被剔除,则输出之*/

    if A[i] != 0

       printf(“A[%d]%d/n”,iA[i]);

}

自顶向下,逐步求精方法的优点:

自顶向下,逐步求精方法符合人们解决复杂问题的普遍规律。可提高软件开发的成功率和生产率;

用先全局后局部,先整体后细节,先抽象后具体的逐步求精的过程开发出来的程序具有清晰的层次结构,因此程序容易阅读和理解;

4.2  程序的树形结构

 

程序自顶向下,逐步细化,分解成一个树形结构(如图4.2所示)。在同一层的结点上做的细化工作相互独立。在任何一步发生错误,一般只影响它下层的结点,同一层其它结点不受影响。在以后的测试中,也可以先独立地一个结点一个结点地做,最后再集成。

程序清晰和模块化,使得在修改和重新设计一个软件时,可复用的代码量最大;

每一步工作仅在上层结点的基础上做不多的设计扩展,便于检查;

有利于设计的分工和组织工作。

(3) 数据结构的合理化

H.Mills指出,结构化程序设计主要是想从程序的控制结构入手,消除不适应的、容易引起混乱的GOTO语句。这只是问题的一个方面,而问题的另一方面,过去没有注意到的是数据结构的合理化问题,即数据结构访问的规范化,标准化问题。

假如数据结构中常使用数组、指针等数据类型,则对它们必须采取随机访问,这样势必产生访问上的混乱。例如,要访问数组元素A[i][j],必须先对下标ij访问,造成访问忽前忽后,这与GOTO语句造成的混乱类似,同样是有害的。H.Mills指出,解决这一问题的办法是用栈和队列去代替数组和指针。栈与队列分别是按后进先出(LIFO)和先进先出(FIFO)的原则进行存取的。在程序中用栈和队列代替数组和指针,用合理的规范的顺序存取代替随机存取,将克服随机存取带来的麻烦。而且有人做了证明,所有使用数组和指针的程序,都可以使用栈和队列的程序等价替换。

2.程序设计风格

在软件生存期中,人们经常要阅读程序。特别是在软件测试阶段和维护阶段,编写程序的人与参与测试、维护的人都要阅读程序。因此,阅读程序是软件开发和维护过程中的一个重要组成部分,而且读程序的时间比写程序的时间还要多。70年代初,有人提出在编写程序时,应使程序具有良好的风格。

程序设计风格包括4个方面:源程序文档化,数据说明,语句结构和输入/输出方法,力图从编码原则的角度提高程序的可读性,改善程序质量。

(1) 源程序文档化

符号名的命名

符号名即标识符,包括模块名、变量名、常量名、子程序名、数据区名、缓冲区名等。

§ 这些名字应能反映它所代表的实际东西,应有一定实际意义。

§ 名字不是越长越好,过长的名字会使程序的逻辑流程变得模糊,给修改带来困难。所以应当选择精炼的意义明确的名字,改善对程序功能的理解。

§ 必要时可使用缩写名字,但缩写规则要一致,并且要给每一个名字加注释。

§ 在一个程序中,一个变量只应用于一种用途。就是说,在同一个程序中一个变量不能身兼几种工作。

程序的注释

夹在程序中的注释是程序员与日后的程序读者之间通信的重要手段。正确的注释能够帮助读者理解程序,可为后续阶段进行测试和维护,提供明确的指导。因此,注释决不是可有可无的,大多数程序设计语言允许使用自然语言来写注释,这就给阅读程序带来很大的方便。一些正规的程序文本中,注释行的数量占到整个源程序的1312,甚至更多。

§ 序言性注释 :通常置于每个程序模块的开头部分,它应当给出程序的整体说明,对于理解程序本身具有引导作用。有些软件开发部门对序言性注释做了明确而严格的规定,要求程序编制者逐项列出的有关项目包括:程序标题、有关本模块功能和目的的说明、主要算法、接口说明、有关数据描述、模块位置、开发简历等。

§ 功能性注释 :嵌在源程序体中,用以描述其后的语句或程序段是在做什么工作,不要解释下面怎么做,因为解释怎么做常常是与程序本身重复的,并且对于阅读者理解程序没有什么帮助。

书写功能性注释,要注意:·用于描述一段程序,而不是每一个语句;·用缩进和空行,使程序与注释容易区别;·注释要正确。

视觉组织

利用空格、空行和移行,提高程序的可视化程度。

§ 恰当地利用空格,可以突出运算的优先性,避免发生运算的错误。

§ 自然的程序段之间可用空行隔开;

§ 对于选择语句和循环语句,把其中的程序段语句向右做阶梯式移行。这样可使程序的逻辑结构更加清晰,层次更加分明。

(2) 数据说明

在编写程序时,需注意数据说明的风格。为了使程序中数据说明更易于理解和维护,必须注意以下几点。

§ 数据说明的次序应当规范化,使数据属性容易查找。

§ 当多个变量名用一个语句说明时,应当对这些变量按字母的顺序排列。

§ 如果设计了一个复杂的数据结构,应当使用注释来说明在程序实现时这个数据结构的固有特点。

(3) 语句结构

在设计阶段确定了软件的逻辑流结构,但构造单个语句则是编码阶段的任务。语句构造力求简单,直接,不能为了片面追求效率而使语句复杂化。

§ 在一行内只写一条语句,并且采取适当的移行格式,使程序的逻辑和功能变得更加明确。

§ 程序编写首先应当考虑清晰性,不要刻意追求技巧性,使程序编写得过于紧凑。

§ 程序编写得要简单,写清楚,直截了当地说明程序员的用意。

§ 除非对效率有特殊的要求,程序编写要做到清晰第一,效率第二。不要为了追求效率而丧失了清晰性。事实上,程序效率的提高主要应通过选择高效的算法来实现。

§ 首先要保证程序正确,然后才要求提高速度。反过来说,在使程序高速运行时,首先要保证它是正确的。

§ 让编译程序做简单的优化。

§ 尽可能使用库函数。

§ 避免使用临时变量而使可读性下降。

§ 尽量用公共过程或子程序去代替重复的功能代码段。

§ 用调用公共函数去代替重复使用的表达式。

§ 使用括号来清晰地表达算术表达式和逻辑表达式的运算顺序。

§ 避免不必要的转移。同时如果能保持程序的可读性,则不必用GOTO语句。

§ 尽量只采用三种基本的控制结构来编写程序。

§ 用逻辑表达式代替分支嵌套。

§ 避免使用空的ELSE语句和IFTHEN IF…的语句。

§ 避免使用ELSE GOTOELSE RETURN结构。

§ 使与判定相联系的动作尽可能地紧跟着判定。

§ 避免采用过于复杂的条件测试。

§ 尽量减少使用“否定”条件的条件语句。不要让读者绕弯子想。

§ 避免过多的循环嵌套和条件嵌套;

§ 不要使GOTO语句相互交叉。

§ 避免循环的多个出口。

§ 使用数组,以避免重复的控制序列。

§ 尽可能用通俗易懂的伪码来描述程序的流程,然后再翻译成必须使用的语言。

§ 数据结构要有利于程序的简化。

§ 要模块化,使模块功能尽可能单一化,模块间的耦合能够清晰可见。

§ 利用信息隐蔽,确保每一个模块的独立性。

§ 从数据出发去构造程序。

§ 不要修补不好的程序,要重新编写。也不要一味地追求代码的复用,要重新组织。

§ 对太大的程序,要分块编写、测试,然后再集成。

§ 对递归定义的数据结构尽量使用递归过程。

§ 注意计算机浮点数运算的特点,例如,浮点数运算 10.0*0.1 通常不等于1.0

§ 不要单独进行浮点数的比较。用它们做比较,其结果常常发生异常情况。

§ 避免不恰当地追求程序效率,在改进效率前,要做出有关效率的定量估计。

§ 在程序中应有出错处理功能,一旦出现故障时不要让机器进行干预,导致停工。

此外,对于程序中的变量,标号,注释等,还需要给予一些注意。例如,

§ 变量名中尽量不用数字。

§ 显式说明所有的变量。

§ 确保所有变量在使用前都被初始化。

§ 确保注释与代码完全一致。

§ 不仅对代码做注释,而且对每条注释都加以编号。

§ 不注释不好的代码,要重新编写。

§ 程序格式的安排应有助于读者理解程序。

§ 注释不要过于繁琐。

§ 遵循国家标准。

§ 经常反躬自省:“如果我不是编码的人,我能看懂它吗?”考虑它的可理解性达到什么程度。

(4) 输入和输出 (I / O)

输入和输出信息是与用户的使用直接相关的。输入和输出的方式和格式应当尽可能方便用户的使用。因此,在软件需求分析阶段和设计阶段,就应基本确定输入和输出的风格。系统能否被用户接受,有时就取决于输入和输出的风格。

不论是批处理的输入/输出方式,还是交互式的输入/输出方式,在设计和程序编码时都应考虑下列原则:

§ 对所有的输入数据都进行检验,从而识别错误的输入,以保证每个数据的有效性;

§ 检查输入项的各种重要组合的合理性,必要时报告输入状态信息;

§ 使得输入的步骤和操作尽可能简单,并保持简单的输入格式;

§ 输入数据时,应允许使用自由格式输入;

§ 应允许缺省值;

§ 输入一批数据时,最好使用输入结束标志,而不要由用户指定输入数据数目;

§ 在以交互式输入/输出方式进行输入时, 要在屏幕上使用提示符明确提示交互输入的请求,指明可使用选择项的种类和取值范围。同时,在数据输入的过程中和输入结束时,也要在屏幕上给出状态信息;

§ 当程序设计语言对输入/输出格式有严格要求时, 应保持输入格式与输入语句的要求的一致性;

§ 给所有的输出加注解,并设计输出报表格式。

输入/输出风格还受到许多其它因素的影响。如输入/输出设备(例如终端的类型,图形设备,数字化转换设备等)、用户的熟练程度、以及通信环境等。

Wasserman为“用户软件工程及交互系统的设计”提供了一组指导性原则,可供软件设计和编程参考。

§ 把计算机系统的内部特性隐蔽起来不让用户看到;

§ 有完备的输入出错检查和出错恢复措施,在程序执行过程中尽量排除由于用户的原因而造成程序出错的可能性;

§ 如果用户的请求有了结果,应随时通知用户;

§ 充分利用联机帮助手段,对于不熟练的用户,提供对话式服务,对于熟练的用户,提供较高级的系统服务,改善输入/输出的能力;

§ 使输入格式和操作要求与用户的技术水平相适应。对于不熟练的用户,充分利用菜单系统逐步引导用户操作;对于熟练的用户,允许绕过菜单,直接使用命令方式进行操作;

§ 按照输出设备的速度设计信息输出过程;

§ 区别不同类型的用户,分别进行设计和编码;

§ 保持始终如一的响应时间;

§ 在出现错误时应尽量减少用户的额外工作。

在交互式系统中,这些要求应成为软件需求的一部分,并通过设计和编码,在用户和系统之间建立良好的通信接口。

3. 程序效率

(1) 讨论效率的准则

程序的效率是指程序的执行速度及程序所需占用的内存的存储空间。讨论程序效率的几条准则为:

§ 效率是一个性能要求,应当在需求分析阶段给出。软件效率以需求为准,不应以人力所及为准。

§ 好的设计可以提高效率。

§ 程序的效率与程序的简单性相关。

一般说来,任何对效率无重要改善,且对程序的简单性、可读性和正确性不利的程序设计方法都是不可取的。

(2) 算法对效率的影响

源程序的效率与详细设计阶段确定的算法的效率直接有关。在详细设计翻译转换成源程序代码后,算法效率反映为程序的执行速度和存储容量的要求。

转换过程中的指导原则是:

§ 在编程序前,尽可能化简有关的算术表达式和逻辑表达式;

§ 仔细检查算法中的嵌套的循环,尽可能将某些语句或表达式移到循环外面;

§ 尽量避免使用多维数组;

§ 尽量避免使用指针和复杂的表;

§ 采用“快速”的算术运算;

§ 不要混淆数据类型,避免在表达式中出现类型混杂;

§ 尽量采用整数算术表达式和布尔表达式;

§ 选用等效的高效率算法;

许多编译程序具有“优化”功能,可以自动生成高效率的目标代码。它可剔除重复的表达式计算,采用循环求值法、快速的算术运算,以及采用一些能够提高目标代码运行效率的算法来提高效率。对于效率至上的应用来说,这样的编译程序是很有效的。

(3) 影响存储效率的因素

在大中型计算机系统中,存储限制不再是主要问题。在这种环境下,对内存采取基于操作系统的分页功能的虚拟存储管理,给软件提供了巨大的逻辑地址空间。这时,存储效率与操作系统的分页功能直接有关,并不是指要使所使用的存储空间达到最少。

采用结构化程序设计,将程序功能合理分块,使每个模块或一组密切相关模块的程序体积大小与每页的容量相匹配,可减少页面调度,减少内外存交换,提高存储效率。

在微型计算机系统中,存储容量对软件设计和编码的制约很大。因此要选择可生成较短目标代码且存储压缩性能优良的编译程序,有时需采用汇编程序。通过程序员富有创造性的努力,提高软件时间与空间效率。

提高存储效率的关键是程序的简单性。

(4) 影响输入/输出的因素

输入/输出可分为两种类型:一种是面向人(操作员)的输入/输出;一种是面向设备的输入/输出。如果操作员能够十分方便、简单地录入输入数据,或者能够十分直观、一目了然地了解输出信息,则可以说面向人的输入/输出是高效的。至于面向设备的输入/输出,分析起来比较复杂。从详细设计和程序编码的角度来说,可以提出一些提高输入/输出效率的指导原则:

§ 输入/输出的请求应当最小化;

§ 对于所有的输入/输出操作,安排适当的缓冲区,以减少频繁的信息交换。

§ 对辅助存储(例如磁盘),选择尽可能简单的,可接受的存取方法;

§ 对辅助存储的输入/输出,应当成块传送;

§ 对终端或打印机的输入/输出,应考虑设备特性, 尽可能改善输入/输出的质量和速度;

§ 任何不易理解的,对改善输入/输出效果关系不大的措施都是不可取的;

§ 任何不易理解的所谓“超高效”的输入/输出是毫无价值的;

§ 好的输入/输出程序设计风格对提高输入/输出效率会有明显的效果。

4. 程序设计语言

程序编码阶段的任务是将软件的详细设计转换成用程序设计语言实现的程序代码。因此,程序设计语言的性能和设计风格对于程序设计的效能和质量有着直接的关系。

(1) 程序设计语言特性的比较

软件心理学的观点

因为从设计到编码的转换基本上是人的活动,因此语言的性能对程序员的心理影响,将对转换产生重大影响。程序员总是希望选择简单易学、使用方便的语言,以减少程序出错率,提高软件可靠性。从心理学的观点,影响程序员心理的语言特性有如下六种:

§ 一致性 :它表示一种语言所使用符号的兼容程度、允许随意规定限制、以及允许对语法或语义破例的程度。同是一个符号,给予多种用途,会引起许多难以察觉的错误。

§ 二义性 :虽然语言的编译程序总是以一种机械的规则来解释语句,但读者则可能用不同的方式来理解语句。例如,对于一个逻辑表达式 A ≥“0and A ≤“9”,读者可能对这个逻辑表达式有不同的理解。

如果一个程序设计语言缺乏一致性和存在二义性,那么用这种语言编写出来的程序可读性就差,同时用这种语言编程也容易出错。

§ 简洁性(紧凑性):表示程序员为了用该语言编写程序,必须记忆的有关编码的信息量。可用语言支持块结构和结构化程序的能力、可使用的保留字和缩写字的种类、数据类型的种类和缺省说明、算术运算符和逻辑运算符的种类、系统内标准函数的数目等来衡量。

遗憾的是,语言的简洁性与程序的一致性常常是抵触的。

§ 局部性 :指程序设计语言的联想(综合)特性。综合的特性使人们能够对事物从整体上进行记忆和识别。在编码过程中,由语句组合成模块,由模块组装为程序体系结构,并在组装过程中实现模块的高内聚和低耦合,可使程序的局部性加强。

§ 线性 :指程序的联想(顺序)特性。人们总是习惯于按逻辑线性序列理解程序。如果程序中线性序列和逻辑运算较多,会提高可读性。如果存在大量的分支和循环,就会破坏顺序状态,增加理解上的困难。直接实现结构化程序可提高程序的线性特性。

§ 传统 :人们学习一种新的程序设计语言的能力受到传统的影响。具有Pascal基础的程序人员在学习C语言时不会感到困难,因为C保持了Pascal所确立的传统语言特性。但是要求同一个人去学习APL或者LISP这样一些语言,传统就中断了。

软件工程的观点

从软件工程观点,程序设计语言的特性应着重考虑软件开发项目的需要。为此,对于程序编码,有如下一些工程上的性能要求:

§ 详细设计应能直接地容易地翻译成代码程序 :把设计变为程序的难易程度,反映了程序设计语言与设计说明相接近的程度。所选择的程序设计语言是否具有结构化的构造,复杂的数据结构,专门的输入/输出能力,位运算和串处理的能力,直接影响到从详细设计变换到代码程序的难易程度,以及特定软件开发项目的可实现性。

§ 源程序应具有可移植性 :源程序的可移植性通常有三种解释:① 对源程序不做修改或少做修改就可以实现处理机上的移植或编译程序上的移植;② 即使程序的运行环境改变(例如,改用一个新版本的操作系统),源程序也不用改变;③ 源程序的许多模块可以不做修改或少做修改就能集成为功能性的各种软件包,以适应不同的需要。

为改善软件的可移植性,主要是使语言标准化。在开发软件时,应严格地遵守 ISO ANSIGB的标准,而不要去理会特定编译器提供的非标准特性。

§ 编译程序应具有较高的效率。

§ 尽可能应用代码生成的自动工具 :有效的软件开发工具是缩短编码时间,改善源代码质量的关键因素。使用带有各种有效的自动化工具的“软件开发环境”,支持从设计到源代码的翻译等各项工作,可以保证软件开发获得成功。

§ 可维护性 :源程序的可读性,语言自身的文档化特性(涉及标识符的允许长度、标号命名、数据类型的丰富程度、控制结构的规定等)是影响到可维护性的重要因素。

程序设计语言的技术性能

在计划阶段,极少考虑程序语言的技术特性。但在选定资源时,要规划将要使用的支撑工具,就要确定一个具体的编译器或者确定一个程序设计环境。如果软件开发组的成员对所要使用的语言不熟悉,那么在成本及进度估算时必须把学习的工作量估算在内。

一旦确定了软件需求,待选用的程序语言的技术特性就显得非常重要了。如果需要复杂的数据结构,就要仔细衡量有哪些语言能提供这些复杂的数据结构。如果首要的是高性能及实时处理的能力,就可选用适合于实时应用的语言或效率高的语言。如果该应用有许多输出报告或繁杂的文件处理,最好是根据软件的要求,选定一种适合于该项工作的语言。

软件的设计质量与程序设计语言的技术性能无关(面向对象设计例外)。但在实现软件设计转化为程序代码时,转化的质量往往受语言性能的影响。因而也会影响到设计方法。

语言的技术性能对测试和维护的影响是多种多样的。例如,直接提供结构化构造的语言有利于减少循环带来的复杂性(即McCabe复杂性),使程序易读、易测试、易维护。另一方面,语言的某些技术特性却会妨碍测试。例如,在面向对象的语言程序中,由于实行了数据封装,使得监控这些数据的执行状态变得比较困难;由于建立了对象类的继承结构,使得高内聚、低耦合的要求受到破坏,增加了测试的困难。此外,只要语言程序的可读性强,而且可以减少程序的复杂性,这样的程序设计语言对于软件的维护就是有利的。

总之,通过仔细地分析和比较,选择一种功能强而又适用的语言,对成功地实现从软件设计到编码的转换,提高软件的质量,改善软件的可测试性和可维护性是至关重要的。

(2) 程序设计语言的分类

目前,用于软件开发的程序设计语言已经有数百种之多,对这些程序设计语言的分类有不少争议。同一种语言可以归到不同的类中。从软件工程的角度,根据程序设计语言发展的历程,可以把它们大致分为4类。

从属于机器的语言(第一代语言):它是由机器指令代码组成的语言。对于不同的机器就有相应的一套机器语言。用这种语言编写的程序,都是二进制代码的形式,且所有的地址分配都是以绝对地址的形式处理。存储空间的安排,寄存器、变址的使用都由程序员自己计划。因此使用机器语言编写的程序很不直观,在计算机内的运行效率很高但编写出的机器语言程序其出错率也高。

汇编语言(第二代语言):汇编语言比机器语言直观,它的每一条符号指令与相应的机器指令有对应关系,同时又增加了一些诸如宏、符号地址等功能。存储空间的安排可由机器解决。不同指令集的处理器系统就有自己相应的汇编语言。从软件工程的角度来看,汇编语言只是在高级语言无法满足设计要求时,或者不具备支持某种特定功能(例如特殊的输入/输出)的技术性能时,才被使用。

高级程序设计语言(第三代语言)

§ 传统的高级程序设计语言 :如FORTRANCOBOLALGOLBASIC等。这些程序语言曾得到广泛应用。目前,它们都已有多种版本。有的语言得到较大的改进,甚至形成了可视的开发环境,具有图形设计工具、结构化的事件驱动编程模式、开放的环境,使用户可以既快又简便地编制出windows下的各种应用程序。

§ 通用的结构化程序设计语言 :它具有很强的过程功能和数据结构功能,并提供结构化的逻辑构造。这一类语言的代表是PL/1PASCALCAda。此外,COBOL78Turbo BASIC等也应归入第三代程序语言范围。

§ 专用语言 :专用语言是为特殊的应用而设计的语言。通常具有自己特殊的语法形式,面对特定的问题,输入结构及词汇表与该问题的相应范围密切相关。有代表性的专用语言有APLLispPROLOGSmalltalkC++FORTH等。从软件工程的角度来看,专用语言支持了特殊的应用,将特定的设计要求翻译成可执行的代码。但是它们的可移植性和可维护性比较差。

第四代语言(4GL):4GL用不同的文法表示程序结构和数据结构,但是它是在更高一级抽象的层次上表示这些结构,它不再需要规定算法的细节。4GL兼有过程性和非过程性的两重特性。程序员规定条件和相应的动作这是过程性的部分,并且指出想要的结果,这是非过程部分。然后由4GL 语言系统运用它的专门领域的知识来填充过程细节。

Martin把第四代语言分为以下几种类型:

§ 查询语言 :用户可利用查询语言对预先定义在数据库中的信息进行较复杂的操作。

§ 程序生成器 :只需很少的语句就能生成完整的第三代语言程序,它不必依赖预先定义的数据库作为它的着手点。

§ 其它4GL :如判定支持语言、原型语言、形式化规格说明语言等。

(3) 程序设计语言的选择

为某个特定开发项目选择程序设计语言时,既要从技术角度、工程角度、心理学角度评价和比较各种语言的适用程度,又必须考虑现实可能性。有时需要作出某种合理的折衷。

在选择与评价语言时,首先要从问题入手,确定它的要求是什么? 这些要求的相对重要性如何? 再根据这些要求和相对重要性来衡量能采用的语言。

通常考虑的因素有 项目的应用范围;② 算法和计算复杂性;③ 软件执行的环境;④ 性能上的考虑与实现的条件;⑤ 数据结构的复杂性;⑥ 软件开发人员的知识水平和心理因素等。其中,项目的应用范围是最关键的因素。

针对计算机的4个主要应用领域,为语言做一个粗略的分类。例如,在科学与工程计算领域内,CC++ 语言得到了广泛的应用,但FORTRAN仍然是应用最广泛的语言。在商业数据处理领域中,通常采用COBOLRPG语言编写程序,当然也可选用SQL语言或其它专用语言。在系统程序设计和实时应用领域中,汇编语言或一些新的派生语言,如BLISSPLSAdaC++等得到了广泛的应用。在人工智能领域以及问题求解,组合应用领域,主要采用LISPPROLOG语言。

新的更强有力的语言,虽然对于应用有很强的吸引力,但是因为已有的语言已经积累了大量的久经使用的程序,具有完整的资料、支撑软件和软件开发工具,程序设计人员比较熟悉,而且有过类似项目的开发经验和成功的先例,由于心理因素,人们往往宁愿选用原有的语种。所以应当彻底地分析,评价,介绍新的语言,以便从原有语言过渡到新的语言。

5. 程序复杂性度量

程序复杂性主要指模块内程序的复杂性。它直接关联到软件开发费用的多少,开发周期的长短和软件内部潜伏错误的多少。同时它也是软件可理解性的另一种度量。

减少程序复杂性,可提高软件的简单性和可理解性,并使软件开发费用减少,开发周期缩短,软件内部潜藏错误减少。

(1) 代码行度量法

度量程序的复杂性,最简单的方法就是统计程序的源代码行数。此方法基于两个前提:

·程序复杂性随着程序规模的增加不均衡地增长;

·控制程序规模的方法最好是采用分而治之的办法。将一个大程序分解成若干个简单的可理解的程序段。

方法的基本考虑是统计一个程序模块的源代码行数目,并以源代码行数做为程序复杂性的度量。若设每行代码的出错率为每100行源程序中可能有的错误数目, 例如每行代码的出错率为1%,则是指每100行源程序中可能有一个错误。

Thayer曾指出,程序出错率的估算范围是从0.04%~7%之间,即每100行源程序中可能存在0.047个错误。他还指出,每行代码的出错率与源程序行数之间不存在简单的线性关系。Lipow进一步指出,对于小程序,每行代码的出错率为1.3%~1.8%; 对于大程序,每行代码的出错率增加到2.7%~3.2%之间,但这只是考虑了程序的可执行部分,没有包括程序中的说明部分。Lipow及其他研究者得出一个结论:对于少于100个语句的小程序,源代码行数与出错率是线性相关的。随着程序的增大,出错率以非线性方式增长。所以,代码行度量法只是一个简单的,估计得很粗糙的方法。

A 开始

(2) McCabe度量法

 

B

D

E

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

4.3 程序图的例子

C输入


H输入



 


 

K输出

McCabe度量法是一种基于程序控制流的复杂性度量方法。McCabe定义的程序复杂性度量值又称环路复杂度,它基于一个程序模块的程序图中环路的个数。

F

如果把程序流程图中每个处理符号都退化成一个结点,原来联结不同处理符号的流线变成连接不同结点的有向弧,这样得到的有向图就叫做程序图。

L结束

计算有向图G的环路复杂性的公式:

J

 

         V (G)mn2
其中,VG)是有向图G中的环路个数,m是图G中有向弧个数,n是图G中结点个数。

G

 

以图4.3为例,其中,结点数n11,弧数m12,则有 VG)=mn2121123。即McCabe环路复杂度度量值为3 它也可以看做由程序图中的有向弧所封闭的区域个数。

当分支或循环的数目增加时,程序中的环路也随之增加,因此McCabe环路复杂度度量值实际上是为软件测试的难易程度提供了一个定量度量的方法,同时也间接地表示了软件的可靠性。实验表明,源程序中存在的错误数以及为了诊断和纠正这些错误所需的时间与McCabe环路复杂度度量值有明显的关系。

Myers建议,对于复合判定,例如(A0)∩(CD)∪(X'A')算做三个判定。

利用McCabe环路复杂度度量时,有几点说明。

§ 环路复杂度取决于程序控制结构的复杂度。当程序的分支数目或循环数目增加时其复杂度也增加。环路复杂度与程序中覆盖的路径条数有关。

§ 环路复杂度是可加的。例如,模块A的复杂度为3,模块B的复杂度为4,则模块 A与模块B的复杂度是7

§ McCabe建议,对于复杂度超过10的程序,应分成几个小程序,以减少程序中的错误。Walsh用实例证实了这个建议的正确性。他发现,在McCabe复杂度为10的附近,存在出错率的间断跃变。

§ McCabe环路复杂度隐含的前提是:错误与程序的判定加上例行子程序的调用数目成正比。而加工复杂性、数据结构、录入与打乱输入卡片的错误可以忽略不计。

(3) Halstead的软件科学

Halstead软件科学研究确定计算机软件开发中的一些定量规律,它采用以下一组基本的度量值,这些度量值通常在程序产生之后得出,或者在设计完成之后估算出。

程序长度,即预测的Halstead长度

n1表示程序中不同运算符(包括保留字)的个数,令n2表示程序中不同运算对象的个数,令H表示“程序长度”,则有  H = n1 Ÿ log2 n1 + n2 Ÿ log2 n2
这里,H是程序长度的预测值,它不等于程序中语句个数。在定义中,运算符包括:

算术运算符       赋值符(= :=               数组操作符

逻辑运算符       分界符(,或 ;或 :          子程序调用符

关系运算符       括号运算符                             循环操作符等。

特别地,成对的运算符,例如“BEGINEND”、“FORTO”、“REPEAT UNTIL”、“WHILEDO”、“IFTHENELSE”、“(…)”等都当做单一运算符。

运算对象包括变量名和常数。

实际的Halstead长度

N1为程序中实际出现的运算符总个数,N2为程序中实际出现的运算对象总个数, N为实际的Halstead长度,则有 N = N1 + N2

程序的词汇表

Halstead定义程序的词汇表为不同的运算符种类数和不同的运算对象种类数的总和。若令n为程序的词汇表,则有 n = n1 + n2

4.4是用FORTRAN语言写出的交换排序的例子。

     运算符

 计数

 运算对象 

 计数

 可执行语句结束

  7 

    X

  6  

    数组下标

  6

    I

  5

 

  5

    J 

  4

    IF 

  2

    N

  2

       DO 

  2

    2

  2  

       

  2

  SAVE

  2

    程序结束

  1 

    1

  1

       .LT.

  1

 n27  N222

       .GE.

  1

 

    GO TO 10 

  1

 

     n110    N128  

 

 


           SUBROUTINE  SORTXN
           DIMENSION  X
N
           IF
N .LT. 2 RETURN
           DO  20  I = 2, N
              DO  10  J = 1, I
                 IF
XI.GE. XJ))

               GO TO 10
                 SAVE = X
I
                 X
I= XJ
                 X
J= SAVE
      10      CONTINUE
      20   CONTINUE
           RETURN
           END

4.4  一个交换排序程序的例子

 

因此有:  预测的词汇量 H = n1 Ÿ log2 n1 + n2 Ÿ log2 n2 = 10 Ÿ log2 10 + 7 Ÿ log2 7 = 52.87

         实际的词汇量 N = N1 + N2 = 28 + 22 = 50

         程序的词汇表 n = n1 + n2 = 10 + 7 = 17                       

程序量V,可用下式算得
         V = ( N1 + N2 )
Ÿ log2 ( n1 + n2 )

它表明了程序在“词汇上的复杂性”。其最小值为

         V* = ( 2 + n2*) Ÿ log2 ( 2 + n2* )

这里,2表明程序中至少有两个运算符:赋值符“:=”和函数调用符“f ( )”,n2* 表示输入/输出变量个数。对于图4.4的例子,利用n1N1n2N2,可以计算得

         V = ( 28 + 22 ) Ÿ log2 ( 10 + 7 ) = 204

等效的汇编语言程序的V328。这说明汇编语言比FORTRAN 语言需要更多的信息量(以bit表示)。

程序量比率(语言的抽象级别)

         L = V*V     L = ( 2n1 ) Ÿ ( n2N2 )
这里,N2 = n2 Ÿ log2 n2。它表明了一个程序的最紧凑形式的程序量与实际程序量之比,反映了程序的效率。其倒数

         D = 1L

表明了实现算法的困难程度。有时,用L表达语言的抽象级别,即用L衡量在表达程序过程时的抽象程度。对于高级语言,它接近于1,对于低级语言,它在01之间。下面列出的是根据经验得出的一些常用语言的语言抽象级别。

      

  L的平均值

 English Prose(英语散文)

       PL/1

     ALGOL68

     FORTRAN  

  Assembler(汇编语言)

2.16
     1.53

2.12

1.14

0.88

 

程序员工作量

         E = VL

程序的潜在错误

Halstead度量可以用来预测程序中的错误。 认为程序中可能存在的差错应与程序的容量成正比。因而预测公式为

         B = ( N1 + N2 ) Ÿ log2 ( n1 + n2 )3000 = V3000
B
表示该程序的错误数。例如,一个程序对75个数据库项共访问1300次,对150个运算符共使用了1200次,那么预测该程序的错误数:

         B = ( 1300 + 1200 ) Ÿ log2 ( 75 + 150 )3000 = 6.5
即预测该程序中可能包含67个错误。 

Halstead的重要结论之一是:程序的实际Halstead长度N可以由词汇表n算出。即使程序还未编制完成,也能预先算出程序的实际Halstead长度N 虽然它没有明确指出程序中到底有多少个语句。

这个结论非常有用。经过多次验证,预测的Halstead长度与实际的Halstead长度是非常接近的。

Halstead度量是目前最好的度量方法。 但它也有缺点:

§ 没有区别自己编的程序与别人编的程序。这是与实际经验相违背的。这时应将外部调用乘上一个大于1的的常数Kf(应在15之间,它与文档资料的清晰度有关)。

§ 没有考虑非执行语句。补救办法:在统计n1n2N1N2时,可以把非执行语句中出现的运算对象,运算符统计在内。

§ 在允许混合运算的语言中,每种运算符必须与它的运算对象相关。如果一种语言有整型、实型、双精度型三种不同类型的运算对象,则任何一种基本算术运算符(+、-、×、/)实际上代表了 种运算符。如果语言中有4种不同类型的算术运算对象, 那么每一种基本算术运算符实际上代表了 种运算符。 在计算时应考虑这种因数据类型而引起差异的情况。

§ 没有注意调用的深度。Halstead公式应当对调用子程序的不同深度区别对待。在计算嵌套调用的运算符和运算对象时,应乘上一个调用深度因子。这样可以增大嵌套调用时的错误预测率。

§ 没有把不同类型的运算对象,运算符与不同的错误发生率联系起来,而是把它们同等看待。例如,对简单IF语句与WHILE语句就没有区别。实际上,WHILE语句复杂得多,错误发生率也相应地高一些。

§ 忽视了嵌套结构(嵌套的循环语句、嵌套IF语句、括号结构等)。一般地,运算符的嵌套序列,总比具有相同数量的运算符和运算对象的非嵌套序列要复杂得多。解决的办法是对嵌套结果乘上一个嵌套因子。

0
0

查看评论
* 以上用户言论只代表其个人观点,不代表CSDN网站的观点或立场
    个人资料
    • 访问:428798次
    • 积分:4685
    • 等级:
    • 排名:第6211名
    • 原创:34篇
    • 转载:140篇
    • 译文:0篇
    • 评论:84条
    文章分类
    最新评论