第 8 章 程序开发过程

Preview:

DESCRIPTION

第 8 章 程序开发过程. 学习目的: ① 理解软件开发的一般过程; ② 了解软件测试方法; ③ 应用 VC++ 的 IDE 熟练调试程序。. 8.1 软件开发方法概述 8.2 软件设计 8.3 软件编码 8.4 软件测试与调试 8.5 程序运行效率. 8.1 软件开发方法概述. - PowerPoint PPT Presentation

Citation preview

第 8 章 程序开发过程

8.1软件开发方法概述8.2软件设计8.3软件编码8.4软件测试与调试8.5程序运行效率

学习目的:① 理解软件开发的一般过程;② 了解软件测试方法;③ 应用 VC++ 的 IDE 熟练调试程序。

8.1 软件开发方法概述

8.1.1软件生存周期8.1.2 软件开发方法

自从 1968 首次提出“软件工程”的概念以来 , 软件的生产过程一直是一个热门话题,“软件”从最原始的单纯的程序、发展到由程序和设计说明组成的所谓程序系统、乃至后来的软件工程(程序 +文档 + 数据)。其间,无论是开发任务、程序设计语言,还是软件的规模和开发技术与手段都有了质的飞跃,人们越来越认识到一个成功的计算机软件的研制不是一蹴而就的,软件开发需要科学的理论、先进的技术和规范化的管理。时至今日,程序开发已经远远不是传统意义上的程序设计,而变成了涵盖程序设计、文档编制、多种先进开发技术与手段,以及现代软件管理技术的软件工程。

8.1.1 软件生存周期软件的研制,从问题的提出,经过开发、使用、维护、修订,直到最后终止使用或被另一软件取代,如同生命体从孕育、出生、成长,到最后消亡,软件整个状态变化的过程称为生命周期(或生存期)。

可行性研究与计划

需求分析

概要设计

详细设计

实现(包括单元测

试)

组装测试(集成测

试)

确认测试

使用和维护

软件工程国家标准—计算机软件开发规范( GB8566-88 )中将软件生命周期划分为 8 个阶段:

8.1.2 软件开发方法1 瀑布模型开发过程依照固定顺序进行,各阶段的任务与工作结果如图所示。该模型适用于需求明确、开发技术比较成熟、工程管理严格的场合,其缺点是由于任务顺序固定,软件研制周期长,前一阶段工作中造成的差错越到后期越大,而且纠正前期错误的代价高。

计划阶段

开发阶段

维护阶段

可行性研究

问题定义

需求分析

总体设计

详细设计

编 码

测 试

运行维护

阶段 基本任务 工作结果

问题定义 理解问题 系统目标与范围说明书

可行性研究 理解工作范围 项目计划任务书

需求分析 定义用户需求 需求规格说明书

总体设计 建立软件结构 总体设计说明书

详细设计 模块功能实现 程序规格说明书

编码 编写程序 程序清单

测试 发现错误调试 软件产品

维护 运行和管理 改进的软件产品

8.1.2 软件开发方法2 渐进模

型 从一组简单的基本用户需求出发,首先建立一个满足基本要求的原型系统。通过测试和运行原型系统,有用户提出进一步细致的需求, 然后修改和完善原型系统,反复进行这个过程直到用户满意为止。该模型适合于开发初期用户需求不 甚明确, 相关技术和理论需要不断研究、 反复实验,以及开发过程需要经常与用户交互的场合,学习或研究类软件的开发常用此法。由于用户在整个软件开发过程中都直接参与,因此最终的软件产品能够很好地满足用户的需求。

问题描述 需求分析 软件设计与编码 软件运行与测试

用户满意否NO

交用户使用

8.1.2 软件开发方法3 喷泉模

型 该模型主要用于面向对象软件技术开发项目,其特点是各项活动之间没有明显的界限,由于面向对象技术的优点,该模型软件开发过程与开发者对问题认识和理解的深化过程同步。该模型重视软件研发工作的重复与渐进,通过相关对象的反复迭代并在迭代中充实扩展,实现了开发工作的 迭代和无间隙,该开发过程分为:分析、设计、实现、确认、维护、 演化。

分析

设 计

实 现

确 认

维 护

演 化

8.2 软件设计

8.2.1概要设计8.2.2 详细设计

设计阶段是软件开发的重要环节,其主要任务是对软件总体结构、算法及其具体实现进行 描述,由概要设计(亦称为总体设计或结构设计)和详细设计 (亦称程序设计)组成 。

8.2.1 概要设计

1 模块化

概要设计决定软件系统的层次结构,最终成果为概要设计说明书,该说明书描述软件系统的基本处理流程、组织结构、功能分配、模块划分、接口设计、运行设计、数据结 构设计和出错处理设计等方面的规定。

可分解性 复杂软件系统模块化的基础和前提。将复杂系统划分成不同模块,这些模块的复杂程度降低,可再继续划分为 更简单的模块,如此下去直至各模块足够简单,易于实现。可组合性 划分 得到的模块实现后至 少应能按照某种方式组合在一起,形成其上一层模块,最终构成整个软件系统,否则模块化就无意义。从长期考虑,划分出的模 块应具某种通用性,以至于还 能由它们构造出其它软件系统。可理解性 模块的可理解性的 重要性是 显然的,只有可理解 才能便于实现,只有可理解 才能便于重用,只有可理解 才能便于维护。

将软件分解成 若干个可以单 独命名和编址的组成部分, 这些组成部分设计完成后,将它们以某种方式相互连接可形成满足设计需求的软件系统。

8.2.1 概要设计2 概要设计

软件系统模块化应按某种够符合人类认知习惯的规则进行, 得到的模块及其间的相互联系将反映出这种划分规 则,模块及联系以图示方式表示出来,可以 给出直观形象的软件系统内部各组成部分和 层次结构。

进行软件 层次结构设计的方法很多,常见的有 SD ( Structured Design ,结构化设计方法)、 SC(Structure Chart ,程序结构图 ) 、 Jackson 方法等,它们都给出相对规范的设计步骤、描述方法等。

A

B

C D

D DD

D

E

F

G H

B B

F F

8.2.2 详细设计在概要设计阶段得到软件系统的总体结构、各模块功能及其相互关系后,接下来的软件设计工作就是需要考虑如何实现 每个模块的具体功能,这个工作阶段称为软件的详细设计阶段,通 常也称为程序设计。详细设计阶段软件开发的 主要任务就是对概要设计中每个模块的功能进行分析,建立 每一个待实现功 能的数学模型,将实际问题转化为数学问题,然后选择或制定解决相应数学问题的算法,并将该算法以适当的方式(比如流程图)描述出来,通过具体的描述形式直观、明确地反映程序设计思想,以待进入编码阶段。

详细设计阶段的成果 主要为软件系统的详细设计说明 书,该说明书描述了软件系统各层次中每一个程序(模块、函数等)的设计细节,包括功 能描述、模块性能、实现算法及其 逻辑流程、接口定义、存储分配、测试计划、 待解决问题等等各方面的信息。关于软件详细设计的主要思想、原理、步骤和过程等方面的内容第 1 章中做了详细 讲解,此处不再重复。

8.3 软件编码

8.3.1 程序设计方法8.3.2 程序设计语言8.3.3 编码风格

8.3.1 程序设计方法目前流行的程序设计方法有 很多,但真正具有广泛意义的是结构化程序设计、面向对象程序设计以及 20世纪 90年代后逐渐发展起来的基于构件的程序设计方法。这些方法各有自己的特点,面向对象技术和构件技术是目前程序设计领域的热点,但是,仅从程序编码的角度讲,它们主要的特长在于程序的组织结构、信息封装以及软件 重用方面,因此它们对于规模足够小模块的编码并没使人得到更多教益,反而是结构化程序设计方法对面向对象程序设计中每个小模块(成员函数)的设计起到关键作用。

8.3.2 程序设计语言

从理论上讲,对于设计阶段的输出,无论采用哪一种风格的设计方法,都可以用任 何一种程序设计语言来编码实现, 但实际上对于具体的任务和设计风格,我们总可以 在众多的编程语言中挑选出一种最适合的,使用它能够在程序运行效率、开发效率、软件可维护性 等方面达到令人满意的折衷。

8.3.3 编码风格1 关于名字

易理解

2 关于注释int MyFunction(char *pName, int Mark){// Function : Demonstrate how to write comment// Parameter : pName, the name of a student// Mark, the score of student pName// Global : xxxx// Algorithm : yyyyyyy// Writer : Wangming// Date : 2002-8-9// note : xxxx… …}

8.3.3 编码风格3 空行与 缩进

语句中表达式的书写要符合人们的习惯,必要时添加空格或括号,提高表达式易读的易读性。

if( ((x-8) & y) || 0x16) 不要写成 if(x-8&y||0x16)

4 语句结构

while(…){ if(…) { … } else if(…) { … } else { … } …}

缩进可以 显示出程序的层次,空行可以 表示出程序的段落感。同一结构的多条语句间保持同样的缩进,不同结构的语句模块之间隔以空行等以提高程序的可读性。

8.4 软件测试与调试

8.4.1 调试工具及使用8.4.2 调试过程8.4.3 错误类型8.4.4 异常处理 *

8.4.5 软件测试

8.4.1 调试工具及使用

(略 )

8.4.2 调试过程

连编 调试 设断点 进入语句 工作区 查看 栈 反汇编跳出函数

停止连编 停止调试 删除断点 单步执行 执行至 输出窗口 寄存器窗口 内存窗口

结合 VC++环境讲解 ! 或留给实验课。

8.4.3错误类型

C++允许在 if 、 for等语句的条件判断中使用赋值符,因为赋值表达式具有值,非 0 可表示 true , 0表示 false ,所以编译并不对此报错,由于 = 和 == 的语义不同,人们经常说“如果 xx等于 xxx” ,所以这种错误十分常见。通常编译时会产生一个警告 "warning C4706: assignment within conditional expression" ,对于编译给出的警告要给予充分注意,它们都说明程序中存在产生错误的隐患。

2. 指针或变量未赋值

未经赋值就直接引用,尤其是指针变量未赋初值。见下面两条语句:int *p;*p=100; // 错误

char* p;cin>>p; // 错误, p未分配存储空间*p=new char[30]; // 错误,应该是 p=new char[30];

1. 关系运算中误用赋值符

8.4.3错误类型

p=new char[30];p=“sdkjfds” // 可能错误

p=new char[30];if(p==0){ cout<<"No enough space!"<<endl; abort();}p="sdkjfds"

4. 内存泄漏int* p=new int[512];p+=255;delete p;

利用 Visual C++6.0 所提供的调试功能检测内存泄漏。

3. 动态分配内存

8.4.3错误类型

顺序错误是由于编程者对运算符的优先级、结合性的理解有误或 疏忽导致的语义理解或语句书写错误。例如 a=b<c?d=e:f=g ,这种复杂的表述很难不出语义错误,最好将它用适当的括号明确地表示出来。

6. 边界错误#include "iostream.h"void A(int a, int b){ int a1=0x11; a=a1+b;}

void main(){ int n1=1; char cc[4]; int n2=2; cin>>cc; //input 1234567 cout<<"n1: "<<n1<<endl; cout<<"cc: "<<cc<<endl; cout<<"n2: "<<n2<<endl;}调试运行此程序

5. 顺序性错误

8.4.4 异常处理 *异常:由于程序顺序控制之外的原因导致程序的非正常运行,比如 资源不足、 I/O 错误等等。

异常处理:程序出现异常时能够产生(或抛出 throw )异常事件,由错误处理函数将之俘获 (catch) ,根据所俘获具体异常事件进行相应处理。

异常是现代程序设计语言中提供的除三种基本控制结构外的一种重要的控制结构,除用于错误处理外,还可用于被调 函数与调用者之间的数据信息交互。

提供异常处理的意义:程序运行过程中出现错误是不可 避免的,因此研究如 何发现错误和定位错误十分必要。软件重用使错误处理变得复杂,库函数开发者能够发现程序中何时发生错误,但对于其中的很多错误他不知道函数的使用者将如何处理;而库函数使用者知道函数调用出现错误将如何进行处理,但是他却无法直接侦知函数何时出现错误。

8.4.4 异常处理 *C++异常处理结构:

try 模块 try 程序块标出了程序中将被监督的代码快,对于不属于某个 try 后面复合语句中的其它语句发生的异常事件不予监视。

catch 模块 catch 模块用来俘获异常事件,进行相应处理。 catch 模块仅随 try 模块之后,每个 try 模块可有多个 catch 模块用于俘获 try模块中抛出的不同异常事件。 catch 后括号中异常说明表明该语句处理异常的类型, catch 后复合语句只能处理所说明类型的异常事件,如写成 catch(…) 的形式,说明该函数处理所有类型的异常事件。

抛出异常事件 根据程序运行情况,在程序中任何地方抛出异常事件。

[ 例 8.1] 异常处理实例,分析 下述程序的运行结果。#include <iostream.h>#include "string.h"void MyFunc( void );class CTest{ public: CTest(int nCause) { m_nCause=nCause; }; ~CTest(){}; void ShowReason() const { cout<<"CTest exception caused by m_nCause = "<<m_nCause<<endl; } private: int m_nCause;};void MyFunc1(){ cout<< "Throwing CTest exception." << endl; throw CTest(1);}void MyFunc2(char* pStr, int n){ int l=strlen(pStr); if(l<n) return; cout<< "Throwing char* exception" << endl;

throw "Exception is caused by out of range";

}

}

int main(){ cout << "In main." << endl; try { cout << "In try block, calling MyFunc1()." << endl; MyFunc1(); cout << "In try block, calling MyFunc2()." << endl; MyFunc2("Test for exception handling", 5); } catch( CTest E ) { cout << "In catch handler." << endl; cout << "Caught CTest exception type: "; E.ShowReason(); } catch( char *str ) { cout << "Caught char* exception: " << str << endl; } cout << "Back in main. Execution resumes here." << endl; return 0;}

8.4.5 软件测试1 白盒测试白盒测试又称为结构化方法或结构测试。在白盒测试中,参照程序的具体实现过程, 根据程序的结构,选择测试数据,其目标是选择测试数据使程序的每条可执行路径都能够被覆盖。

x==2;x==2; x==4;while(c>0)

process(c)

b==0

a==1 a==3a==2

switch a{ case 1: x=3; break; case 2: if(b==0) x=2; else x=4; break; case 3 while(c>0) process(c); break;}

8.4.5 软件测试2 黑盒测试对于大程序,通常并不关心实现细 节,只在意提供一些数据,程序是否正确输出,测试时选择一些代表性数据, 对于测试者而言,程序就像是一个黑盒子,看不到内部结构,称这种测试方法为黑盒测试法。

在黑盒测试法中,测试数据来自对所解决问题的详细 描述,而不考虑程序实现。编程人 员在进行测试时应该 注意下述方面:

简单值 调试程序应该以简单的数值开始,从程序的最简单的运行情况开始。

典型的、有实际意义的数值 就程序被使用的最一般情况给出测试数据,当然,这些数据应该尽可能简单,以便于程序出错时对程序的调试。

边界值 程序出错可能性最大的 情况是程序运行在其应用范围边界上。

非法值 非法值也是一类测试数据,输入非法值后,程序的运行不应发生异常。

8.5 程序运行效率

8.5.1 适当的算法8.5.2 选择快速运算8.5.3 函数

程序的效率包括 两个方面的内容,即程序运行的时间代价和系统 资源的占用情况。泛泛而言,这两个方面是一对矛盾,可以 牺牲程序的运行速度来减少程序对内存占用,也可以通过 对内存的消耗提高运行速度,例如:可以通过适 当的压缩算法对数据进行压缩,从而提高内存和磁盘的利用率,但解压缩和压缩都需要有时间开销,必然增加程序的运行时间。

因此,归根结底提高程序的运行效率最终将是在速度和内存之间进行一种权衡,但这只是当程序本身各方面已经处于理想状态时人们所能做的,实际上在达到理想程序之前人们还有许多有关程序优化方面的工作可做。

8.5.1 适当的算法提高程序运行效率的根本途径是选择一个好算法。下面的几个实例说明了这一点: 求 2n (n<15) 。算法一: power(2, n)

算法二: int GetPower(n) {

int a=1; for(int i=0; i<n; i++) a+=a; return a;}

算法三: 2<<n

算法四: unsigned long GetPower(unsigned long n){ __asm {

xor eax, eax mov ebx, n; bts eax, bx mov n, eax}

return n;}

8.5.2 选择快速运算1. 加 1 减 1 运算

x=x+1 的运算效率往往低于 x++ 。

2. 其他它运算2*n 的效率低于 n+n ,因为乘法运算速度低于加法计算 n2 时,

pow(n, 2) 的效率低于 n*n 。必要时重新实现 sin( ) 、 cos( )等,列表查值方式可提高运行 速度。

一般来说,库函数的开发都是针对一般应用场合中带有普遍性的问题提供支持,一个库函数可以满足多种 情况下的应用,如果编程时仅仅以极特殊的形式调用某个库函数,而且这种调用位于程序运行的 关键路径上,那么就有必要为这个特殊的操作提供一个“量身定制”的函数,这通常是可行的, 因为通用的运行效率一般比专用的要低。

8.5.3函数1. 函数的使用

调用函数需要进行参数传递、保存 CEP 及 CIP等多个额外操作,一个极简单的函数,其运行时间开 销可能是用户描述语句的几倍,如果函数处于关键路径上,则函数的运行效率可能成为程序运行效率的瓶颈,因此对于一些简单的由一两个简单操作组成的函数不防代之以内联函数或者宏定义。在使用类等语法进行编程时,应该熟练 掌握构造函数、赋值函数等的调用规律,提供必要的构造函数、赋值函数、类型转换函数等提高运行效率。

2. 函数参数及返回值

函数的参数可以是 对象、结构,函数的返回值也可以是 对象,但这样的函数在参数传递上效率低,尤其是当结构和类比较复杂时,应尽量以指针、引用等作为函数参数或函数的返回值,这样可以提高 函数的数据传递效率 。

Recommended