函数设计PPT课件_第1页
函数设计PPT课件_第2页
函数设计PPT课件_第3页
函数设计PPT课件_第4页
函数设计PPT课件_第5页
已阅读5页,还剩28页未读 继续免费阅读

下载本文档

版权说明:本文档由用户提供并上传,收益归属内容提供方,若内容存在侵权,请进行举报或认领

文档简介

1、一个编写的不好的函数Void HandleStuff(CORP_DATA* pInputRec, int CrntQtr, EMP_DATA EmpRec, float* pEstimRevenue, float YTDRevenue, int ScreenX, int ScreenY, COLOR_TYPE * pNewColor, COLOR_TYPE PrevColor, STATUS_TYPE* pStatus , int ExpenseType) int i;for( i = 1; irevenuei = 0;pInputRec-expensei = corpExpense Crnt

2、Qtri; UpdateCorpDatabase( EmpRec ) ;*pEstimRevenue = YTDRevenue * 4.0 /( float )CrntQtr);*pNewColor = PrevColor;*pStatus = Success;if( ExpenseType = 1) Profiti =Revenuei - Expense.Type1i; else if( ExpenseType = 2) Profiti = Revenuei - Expense.Type2i; else (if ExpenseType = 3) Profiti = Revenuei - Ex

3、pense.Type3i; 第1页/共33页上一个函数的缺点 函数的名字不好,不能说明函数是干什么的 函数进行了全局变量的读写操作。 CorpExpense、profit都是全局变量 函数的功能不是单一的。 它初始化了某些变量;对一个数据库进行写操作;又进行了某些计算工作。而这些功能之间又看不出任何联系 函数中没有采取预防非法数据的措施 如果CrntQtr的值为“0”,那么,表达式YTDRevenue* 4.0real(CrntQtr)就会出现被零除的错误 指针没有检查是否为空 在函数中仅使用了CORP-DATA型参数的两个域,却传入了整个结构化变量 函数中的一些参数没有使用过 :Screen

4、X和ScreenY 函数中的参数太多 函数中使用了几个常数(神秘数字): 100,40,2和 3第2页/共33页函数设计的话题 函数设计概述 大原则 内聚性 耦合性 信息隐藏 不好搞小动作 小细节 函数名 参数 返回值 函数内部设计第3页/共33页函数设计概述系统设计模块设计详细设计编码单体测试单体测试 函数设计主要在详细设计中做第4页/共33页创建函数的理由(1) 降低复杂性 避免重复代码段 限制改动带来的影响 隐含顺序 改进性能 实行集中控制 隐含数据结构 隐含指针操作第5页/共33页创建函数的理由(2) 隐含全局变量 代码段复用 计划开发一个软件族 改善某一代码段的可读性 改善可移植性

5、分隔复杂操作 独立非标准语言函数的使用 简化复杂的布尔测试 第6页/共33页大原则内聚性 内聚性是指一个函数内的各种操作之间互相联系的紧密程度 可以接受的内聚性 功能内聚性 顺序内聚性 通讯内聚性 临时内聚性 不可接受的内聚性 过程内聚性 逻辑内聚性 偶然内聚性 函数设计的目标:强内聚第7页/共33页可以接受的内聚性 功能内聚性 一个好的函数应该具有功能内聚性,即Do one thing and do it well 举例:Sin(),GetCustomerName(),EraseFile()等 顺序内聚性 特点:函数内的操作不能形成完整的功能,只是包含需要按特定顺序进行的、逐步分享数据的一些

6、操作 举例:下面五个操作完成一个功能:打开文件、读文件、进行两个计算、输出结果、关闭文件。如果DoStep1()打开文件、读文件和计算操作,而DoStep2()进行输出结果和关闭文件操作。Dostep1和DoStep2只是把操作分隔开来,并没有产生出独立的功能,所以具有顺序内聚性第8页/共33页可以接受的内聚性 通讯内聚性 特点:函数中的两个操作只是使用相同数据,而没有其它任何联系 举例:GetNameandChangePhoneNumber(),它取得的Name和PhoneNumber在同一个用户记录中,GetName和ChangePhoneNumber之间没有其他的关系 临时内聚性 特点:

7、函数的几个操作因为同时执行才被放入同一个函数 例如:Startup(),CompleteNewEmployee(),Shutdown()等第9页/共33页不可接受的内聚性 过程内聚性 特点:函数中的操作是按某一特定顺序进行的,却不使用相同的数据 举例:按一定的顺序打印报告的函数,打印的内容包括销售收入、开支、雇员电话表。给这个函数命名是非常困难的,模棱两可的名字往往代表着某种警告 逻辑内聚性 特点:函数的几个操作之间没有任何联系,只是用传进来的控制标志来决定执行哪个操作,这些操作往往包括在一个很大的if“或者case语句中 举例:函数InputAll()的输入内容可能是用户名字、雇员时间卡信息

8、或者库存数据,至于到底是其中的哪一个,则由传入函数的控制标志决定 偶然内聚性 特点:同一个函数中的操作之间无任何联系,也叫作“无内聚性” 举例:本文中开头的那个不好的函数第10页/共33页内聚性实例(1) 实例1:一个按给出的生日计算雇员年龄和退休时间的函数n分析:如果它是利用所计算的年龄来确定雇员将要退休的时间,那么它就具有顺序内聚性。而如果它是分别计算年龄和退休时间的,但使用相同生日数据,那它就只具有通讯内聚性。n如何改进成功能内聚性呢?可以分别建立两个函数,一个根据生日计算年龄,另外一个根据生日确定退休时间,确定退休时间函数将调用计算年龄的函数,这样,它们就都是功能内聚性的,而且其它函数

9、也可以调用其中任一个函数,或这两个都调用第11页/共33页内聚性实例(2) 实例2:一个函数将打印季度开支报告、月份开支报告和日开支报告,具体打印哪一个,将由传入的控制标志决定n如何改进程功能内聚性呢?n建立三个函数:一个打印季度报告,一个打印月报告、一个打印日报告,改进原来的子程序,让它根据传进去的控制标志来调用这三个函数之一。调用函数将只有调用代码而没有自己的计算代码,因而具有功能内聚性。而三个被调用的子程序也显然是功能内聚性的n分析 具有逻辑内聚性第12页/共33页内聚性实例(3) 实例3:一个函数先读取雇员的名字,然后是地址,最后是它的电话号码。这种顺序是用户要求的。另外一个函数将读取

10、关于雇员的其它信息。n如何改进程功能内聚性?把它分为几个部分,并把这几部分分别放入程序中。要保证调用程序的功能是单一、完善的。调用程序应该是诸如GetEmployeeData()这样的函数,而不该是像GetFirstPartOfEmployeeData()这类的函数。可能还要改动其余读取数据的子程序n分析 如何雇员信息放到一个记录中,是顺序内聚;否则是过程内聚性第13页/共33页内聚性实例(4) 比如一个函数进行某种复杂计算的前5个操作,并把中间结果返回到调用函数。由于5项操作可能要用好几个小时,因此当系统瘫痪时,函数要把中间结果存入一个文件中,函数还要检查磁盘,以确定其是否有足够空间来存储最

11、后计算结果,并把磁盘状态和中间结果返回到调用程序产生足够数目的文件确定磁盘空间进行安全计算写数据到文件进行前五步存储中间结果进行最后一步n分析 原来的函数是由一系列令人莫名奇妙的操作组成的,与功能内聚性相距甚远n如何改进成功能内聚性? 首先,调用程序应该调用几个独立的函数: 1)进行前5步计算; 2)把中间结果存入一个文件 ; 3)确定可用的磁盘存储空间。如果调用函数叫ComputeExtravagantNumber(),那么它不应该把中间结果写入一个文件,也决不该为后来的操作检查磁盘剩余空间,它所作的就仅限于计算一些数而已。改进这个设计,将至少影响到一到两个层次上的程序, 对于这项任务的较好

12、设计,如下图所示 第14页/共33页内聚性总结 共享数据、各个操作都是为了完成函数的功能而存在功能内聚 共享数据、各个操作顺序进行顺序内聚 共享数据、各个操作之间没有联系通讯内聚 不共享数据、各个操作同时进行临时内聚 不共享数据、根据控制标记决定做哪个操作逻辑内聚 不共享数据、各个操作顺序进行过程内聚 不共享数据、操作之间没有任何关系偶然内聚第15页/共33页大原则耦合性 耦合性是指两个函数之间联系的紧密程度 函数与其它函数之间的联系应该是直接、可见、松散和灵活的,函数应该容易被其他函数调用 函数对外的接口包括参数、函数中使用的全局变量和数据库或文件等 耦合标准 耦合规模 两个函数之间联系的数

13、量越少越好:参数越少越好,使用的全局变量越少越好,使用数据库和文件的范围越少越好 密切性 两个函数之间联系越直接越好,使用参数最直接,其次是全局变量,最后的数据库或文件 可见性 两个函数之间联系越显著越好,使用参数可见性最强 灵活性 改变两个函数之间联系越容易越好第16页/共33页耦合层次 简单数据耦合 两个函数之间传递的数据是非结构化的,并且全部都是通过参数表进行的 这通常称作“正常耦合”,这也是一种最好的耦合 数据结构耦合 两个函数之间传递的数据是结构化的,并且是通过参数表实现传递的 如果使用恰当的话,这种耦合也不错 控制耦合 一个函数通过传入另一个函数的数据通知它该作什么 控制耦合是令人

14、不快的, 因为它往往与逻辑内聚性联在一起,并且,通常都要求调用者了解被调函数的内容与结构 全局数据耦合 两个函数使用同一个全局数据 如果所使用的数据是只读的,那么这种耦合还是可以忍受的,但是,总的来说,全局耦合是不受欢迎的第17页/共33页耦合的例子 Tan(float degree) 简单数据耦合n一个函数向另一个函数传递姓名、住址、电话号码、生日和身份证号码等五个变量。 简单数据耦合n一个函数向另一个函数传递变量EmpRec,EmpRec是一个结构化的变量,包括姓名、住址、生日等等五个方面的数据 如果被调用的函数全部使用这五个域,那是可接受的数据结构耦合 如果被调用的函数只使用其中两个域,

15、比如电话号码和生日。这虽然还是数据结构耦合,但却不是个很好的应用 如果生日和电话号码作为简单变量来传递的话,将使联系更加灵活,而且会使它们之间的两个特定域真正联系的可见性更好第18页/共33页耦合的例子 一个函数向另一个函数传递控制标志,通知它到底是打印月报表、季度报表还是年度报 控制耦合n一个函数以全局变量的方式改动一个表的入口,这个表是以雇员的识别卡作为索引的。然后,这个函数又调用另一个函数并把雇员识别卡作为一个参数传递给它,而这个被调用的函数则用雇员识别卡去读全局数据 不可取的全局数据耦合n一个函数把雇员识别卡传递给另一个函数,两个函数都利用这个识别卡从一个全局表中读取雇员的名字,两个函

16、数都没有改变全局数据。 可取的全局数据耦合第19页/共33页大原则封装(信息隐藏) 封装有两层含义 为了隐藏一些信息,往往会形成一个或多个函数 函数的名字和参数应该尽量少的暴露实现的细节,这样可以在不影响用户的情况下方便的修改函数的实现方式 常见需要隐藏的信息 容易被改动的区域 对硬件有依赖的地方 输入和输出 非标准语言特性 难于设计和实现的域 状态变量不要使用逻辑型变量作为状态变量,应使用枚举型变量 使用存取子程序检查变量,而不要对其直接检查 数据规模限制 商业规则 预计得到的其他改动 复杂的数据 复杂的逻辑第20页/共33页大原则不要搞小动作 不要修改调用者的数据Struct Sample

17、int data1;int data2;Int Sum(Struct Sample* pInputRec)if( pInputRec-data1 data1 = 0; /这样做不好return pInputRec-data1 + pInputRec-data2;Int Sum(Struct Sample* pInputRec)/ 修改成这样就好多了int temp =pInputRec-data1 =0)? pInputRec-data1:0;return temp + pInputRec-data2;第21页/共33页大原则不要搞小动作 避免使用全局变量 全局变量的危害 全局数据的疏忽改变。

18、你可以会在某处改变全局变量的值,而在别处又会错误地以为它仍保持着原值TheAnswer = GetTheAnswer();TheAnswer是一个全局变量OtherAnswer = GetOtherAnswer();GetOtherAnswer 改变了TheAnswerAverageAnswer = (TheAnswer + OtherAnswer)/2AverageAnswer错误 伴随全局变量的奇怪的别名问题 Void WriteGlobal (int& InputVar) GlobaIVar = lnputVar + 1; cout“input Varinble ”InputVa

19、r; cout“Global Varinble “next; / 直接使用全局变量Node = nextEmployee(node); / 比较抽象,容易理解 把对数据的所有存取保持在同一抽象水平上 AddEvent(Event) EventCount = EventCount-l / 和上一个语句的抽象程度不同第23页/共33页小细节函数名 函数名要有意义,要能描述函数所做的一切 在函数名字中,应描述所有输出结果及其附加结果 如果很难为一个函数命名,就需要考虑这个函数的功能是不是有问题 实现行为的过程或函数要使用动词或动词短语 如PrintReport(), CheckOrder() 如果函

20、数只有一个返回值,可以用返回值来描述 如Cos(), CurrentPenCount() 如果函数返回布尔值,可以在形容词或名词前加Is或Has,如IsPrinterReady(), HasFirstName() 命名约定 公开的接口函数和全局名字空间的函数选择SmallTalk风格的命名原则(一种首字母大写,字间直接相连而无分隔符的书写风格),如IsPrinterReady() 私有的函数使用C+ 草拟标准工作底稿使用的约定(全部为小写字符、字间以下划线分隔),如is_printer_ready()第24页/共33页小细节参数(1) 函数的参数的命名要有意义stringCopy( char*

21、 str1, char* str2); /*这样的参数命名不好,很难区分*/ 函数参数的顺序要合理 按照输入一修改一输出的顺序排列参数,但是首先要符合使用习惯/*这个参数顺序不符合使用习惯,很容易出错*/StringCopy( char*strSource, char* strDestination)因为我们习惯这样使用:stringCopy( str, “hello”) 如果几个函数中使用了相似的参数,应使用相同的顺序如strncpy和memncpy 把状态和“错误”变量放在最后 参数的个数不要太多,尽量控制在7个以内 如果参数太多,在使用时容易将参数类型和顺序搞错 参数太多,有时也意味着函

22、数功能划分不好 使用所有的参数,删除没有用到的参数第25页/共33页小细节参数(2) 对参数的限制越少越好,但是如果对参数的取值有限制,一定要加上注释来说明 如果函数不是使用结构化变量中的绝大部分,那么就只传递它所用得到的那一部分 结构体如果很大,要用指针或引用传递 参数太大,占用的堆栈空间大,调用的效率也低 如果参数是指针,且仅作输入用,则应在类型前加const,以防止该指针在函数体内被意外修改 第26页/共33页小细节返回值 显式的声明函数的返回值类型 原理:如果不显示的声明返回值类型,不同的编译器可以给与不同的解释 如: TestFunc()最好能显示的声明为int TestFunc(v

23、oid) 函数名字和返回值类型在语义上不可冲突 Int getChar()就是一个不好的设计 不要将正常值和错误标志混在一起返回。正常值用输出参数获得,而错误标志用return返回 有时候函数原本不需要返回值,但为了增加灵活性(如支持链式表达),可以附加返回值 例如 char *strcpy(char *strDest,const char *strSrc) 函数的每种出错返回值的意义要清晰、明了、准确,防止使用者误用、理解错误或忽视错误返回码第27页/共33页小细节函数内部设计 函数体的规模不要太大,否则难以理解。函数过大,也容易出错 资源管理:谁申请谁释放 除非函数的功能就是申请资源(如malloc, openfile等函数),否则尽量不要在函数内部申请资源并返回资源的句炳或指针,如申请内存返回内存指针,打开文件返回文件指针等Void GetData( struct Sample *ppData); /不好Void GetData( struct Sample *pDat

温馨提示

  • 1. 本站所有资源如无特殊说明,都需要本地电脑安装OFFICE2007和PDF阅读器。图纸软件为CAD,CAXA,PROE,UG,SolidWorks等.压缩文件请下载最新的WinRAR软件解压。
  • 2. 本站的文档不包含任何第三方提供的附件图纸等,如果需要附件,请联系上传者。文件的所有权益归上传用户所有。
  • 3. 本站RAR压缩包中若带图纸,网页内容里面会有图纸预览,若没有图纸预览就没有图纸。
  • 4. 未经权益所有人同意不得将文件中的内容挪作商业或盈利用途。
  • 5. 人人文库网仅提供信息存储空间,仅对用户上传内容的表现方式做保护处理,对用户上传分享的文档内容本身不做任何修改或编辑,并不能对任何下载内容负责。
  • 6. 下载文件中如有侵权或不适当内容,请与我们联系,我们立即纠正。
  • 7. 本站不保证下载资源的准确性、安全性和完整性, 同时也不承担用户因使用这些下载资源对自己和他人造成任何形式的伤害或损失。

评论

0/150

提交评论