您好,欢迎访问三七文档
模块与接口规范模块及模块接口设计原则一.何为模块模块由接口和实现两部分组成。--接口指明模块的功能,即模块能做什么。它声明了使用该模块代码的标识符,类型和函数原型等;--实现指模块是如何完成起接口声明的功能。一个具体的功能模块接口是唯一固定的,但是它的实现可能会有很多种。每个实现可能使用不同的算法和数据结构,但是都必须符合接口给出的使用声明。二.模块划分基本准则模块的划分以功能为标准,相对独立的功能划分为不同的模块,各模块之间根据需要可以进行数据通信,但是必须保持功能上的相对独立性。“功能独立”的模块可以降低开发、测试、维护等阶段的代价。但是“功能独立”并不意味着模块之间保持绝对的孤立。一个系统要完成某项任务,需要各个模块相互配合才能实现,此时模块之间就要进行信息交流。从功能上划分模块,保持“功能独立”是模块化设计的基本原则。三.模块与接口设计准则接口是对模块功能的声明,只需要指明客户调用程序可能使用的标识符(包括数据类型,函数原型等)即可,应尽可能隐藏内部细节和算法。1.封装与隐藏applicationM1•tinterface1M2Mn声明与实现分离2.模块的独立性为了保证模块的独立封装性,就必须将声明与实现分离开来。C语言对此只提供最基本的支持。在标准C语言中,接口在头文件中声明,头文件的扩展名为.h。规定:调用程序可使用的宏定义,数据结构类型,变量以及函数原型等必须在头文件中声明。调用程序使用预处理指令#include导入接口声明。禁止将函数的实现和变量的定义放在头文件。由于标准C语言没有提供命名空间机制,较大规模软件中,不同模块的全局变量和函数也很可能重名,导致重复定义错误。同时被多个模块使用的全局变量,是增大模块间耦合性的重要因素之一,为减少模块间耦合性,应尽量少用或不用多个模块共用的全局变量。规定:只在本模块内部使用的函数和全局变量,必须用static关键字修饰,以限制只能在本模块内使用。内聚是一个模块内部各成分之间相关联程度的度量。根据内聚性的强弱,有以下几种类型:3.强内聚性(1)随机内聚:如果一个模块的各成分之间毫无关系,则称为随机内聚。(2)逻辑内聚:几个逻辑上相关的功能被放在同一模块中,则称为逻辑内聚。如一个模块读取各种不同类型外设的输入。尽管逻辑内聚比偶然内聚合理一些,但逻辑内聚的模块各成分在功能上并无关系,即使局部功能的修改有时也会影响全局,因此这类模块的修改也比较困难。(3)时间内聚:如果一个模块完成的功能必须在同一时间内执行(如系统初始化),但这些功能只是因为时间因素关联在一起,则称为时间内聚。(4)过程内聚:如果一个模块内部的处理成分是相关的,而且这些处理必须以特定的次序执行,则称为过程内聚。(5)通信内聚:如果一个模块的所有成分都操作同一数据集或生成同一数据集,则称为通信内聚。(6)顺序内聚:如果一个模块的各个成分和同一个功能密切相关,而且一个成分的输出作为另一个成分的输入,则称为顺序内聚。(7)功能内聚:模块的所有成分对于完成单一的功能都是必须的,则称为功能内聚。好的模块设计要有尽可能高的内聚性,尽量避免随机内聚,禁止将毫无关系的成分放入同一模块。随机内聚会使模块的功能不明确,破坏模块的功能独立性。也给模块的维护、测试及升级等造成不便。m1m4m3m5m6m2InterfacenMn防止把没有关联的功能放到一个模块中。3.1.设计高内聚性模块防止函数或模块内出现随机内聚。在编程时,经常遇到在不同函数中使用相同的代码,许多开发人员都会把这些代码提出来,并构成一个新函数。若这些代码关联较大并且是完成一个功能的,那么这种构造是合理的,否则这种构造将产生随机内聚的函数。例如:很多软件系统在启动的时候都需要初始化一些参数,因此不少系统会把初始化模块做成一个或多个函数。但是要防止把毫不相关的参数初始化放入同一个函数。intinit_env(){init_graphics...;//初始化图形设置init_language...;//初始化语言设置init_connect...;//初始化网络连接....}假如模块A需要使用a语言,无网络连接,而模块B需要使用b语言,有网络连接。则init_env函数无论怎么改动都无法同时满足两个模块要求。此时函数init_env就是随机内聚函数,正确的修改方法应该是根据初始化的模块不同,分解成相对独立的函数。修改init_env()为三个独立的函数:intinit_graphics();intinit_language();intinit_connect();这样不同的功能模块,可以根据自己的需要调用不同的初始化模块。函数的功能要单一,不要设计“多功能”函数。函数的功能应该是单一而明确的。不要把不同的功能放入同一个函数,特别是尽量不要用控制开关来让数据处理函数实现不同的功能。#defineTYPE_ADD0#defineTYPE_SUB1intadd_or_sub(inta,intb,intflag){if(TYPE_ADD==flag){return(a+b);}else{return(a–b);}}例如:如下是不好的函数设计,函数功能不单一,且实现的功能无必然联系。耦合(Coupling)是模块之间依赖程度的度量。内聚和耦合是密切相关的,与其它模块存在强耦合的模块通常意味着弱内聚,而强内聚的模块通常意味着与其它模块之间存在弱耦合。模块设计追求强内聚,弱耦合。4.弱耦合性耦合的强度依赖于以下几个因素:(1)一个模块对另一个模块的调用;(2)一个模块向另一个模块传递的数据量;(3)一个模块施加到另一个模块的控制的多少;(4)模块之间接口的复杂程度。耦合按从强到弱的顺序可分为以下几种类型:(1)内容耦合:当一个模块直接修改或操作另一个模块的数据,或者直接转入另一个模块时,就发生了内容耦合。此时,被修改的模块完全依赖于修改它的模块。(2)公共耦合:两个以上的模块共同引用一个全局数据项就称为公共耦合。(3)控制耦合:一个模块在界面上传递一个信号(如开关值、标志量等)控制另一个模块,接收信号的模块的动作根据信号值进行调整,称为控制耦合。(4)标记耦合:模块间通过参数传递复杂的内部数据结构,称为标记耦合。此数据结构的变化将使相关的模块发生变化。(5)数据耦合:模块间通过参数传递基本类型的数据,称为数据耦合。(6)非直接耦合:模块间没有信息传递时,属于非直接耦合。减少多个模块间的全局变量4.1.设计弱耦合性模块多个模块间都能使用的全局变量会增大模块间耦合,给程序的调试和维护带来很大的不便。尽量采用参数传递来取代多个模块间的全局变量。注意:使用static修饰的模块内全局变量不会增加模块间耦合,在参数传递极为频繁的情况下,可以将需要频繁传递的参数提取为模块内部全局变量,以减少函数参数个数和参数传递。多个模块间如果存在全局变量,尽量让该全局变量只被一个模块修改,而其它模块只读。减少模块间接口复杂度,函数参数不得超过7个。复杂的函数接口,既不便于使用,也会增加调用时的出错几率。如果函数功能复杂,参数过多,可以考虑分解成几个函数来完成,或将有逻辑关联的参数构建成结构体来传递参数。减少模块间控制参数,模块通信尽量只使用数据通信。模块控制参数是指,某个模块的功能,部分或完全取决于另一模块传递过来的参数。控制参数会增大模块耦合,使得模块不便于理解和调试,应尽量避免。externintcontrol_param_from_B;//控制参数,从B模块传递而来{somecode;if(control_param_from_B==xxx)//根据传递过来的参数决定功能。{dosomething;}else{dootherthing;}}如果模块间需要进行数据通信,尽量采用文本形式的简单数据格式。不要定义复杂二进制数据格式,除非文本格式无法满足功能要求。不少模块间需要进行数据通信,通信数据格式尽量采用简单文本形式。二进制通信格式既不便于使用,也不便于调试,不建议使用。数据格式应尽可能简单,且必须能满足可扩充性。采用简单格式的文本数据通信,也有助于减少模块间耦合。一般来说,二进制通信协议如果数据格式发生变化,则通信的各个模块编解码都需要相应变化。而文本通信能保持较好的兼容性。二进制编码一般情况下能提高时间和空间效率,当需要传递大量数据且对性能要求较高,或必须传递二进制数据时,才可以考虑二进制编码协议。普通情况下,二进制编码带来的效率提升甚至无法抵消编解码带来的额外开销。如果一个模块可以被扩充,则称模块具有可扩充性。当着手一个新模块时,很难一次性解决所有问题,应该先纵观问题的一些重要方面,同时作好以后补充的准备。5.可复用性与可扩充性5.1.模块设计过程中,数据结构需适当考虑可扩充性。例如:结构体可以设置保留字段,为以后的升级或功能扩展做预留空间。IP数据报格式及首部中的各字段5.2.函数的设计,如果相关联的参数较多,可以合并为结构体。结构体是C语言实现面向对象的关键之一,可以很好的体现面向对象思想。同时能给功能升级和扩充带来很大的方便。例如:假如某函数对一个数据结构进行处理,可以定义为以下形式:func_name(intoption1,intoption2,charoption3,char*option4)也可以将四个数据项定义为一个结构体,函数采用结构体做参数typedefstruct{intoption1;intoption2;char*option4;charoption3;}OPTION,*P_OPTION;func_name(P_OPTIONp_option);假如由于功能升级,需要增加一个功能选项,第一种方案除了修改函数本身,还不得不修改每个函数调用。而第二种方案仅仅需要修改函数本身和结构体的声明。5.3.设计和编码的过程中,要有复用性设计和模块化的思维。复用性设计,模块化思维就是要程序员在完成任何一个功能模块或函数的时候,要多深入考虑,不要局限在完成当前任务的简单思路上,想想看该模块或函数是否可以脱离这个系统存在,是否可以通过简单的修改参数的方式在其他系统和应用环境下直接引用,这样就能极大避免重复性的开发工作.如果一个软件研发单位和工作组能够在每一次研发过程中都考虑到这些问题,那么程序员就不会在重复性的工作中耽误太多时间,就会有更多时间和精力投入到创新的代码工作中去。对于软件公司,也无需重复开发同样的功能,避免“重复设计轮子”。lib_serial.htypedefstruct{unsignedcharbaud_rate;//波特率unsignedchardata_len;//数据位unsignedcharparity_bit;//校验位unsignedcharstop_bit;//停止位......//其它属性}COM_OPTION,*P_COM_OPTION;/*使用指定属性打开串口*/intopen_serial(P_COM_OPTIONp_com_option);/*设置串口波特率*/intset_serial_baud_speed(intbaud_speed,intserial_fd);........例如:在公司很多产品中,都有使用串口通信的功能模块。但是虽然已有多个项目使用了串口通信,却没有形成串口通信库。每次打开串口,发送数据等,都是重新编写函数,或拷贝代码后做大量修改,可重用性很低。应该将串口属性定义为结构体,针对结构体将串口的常用操作如打开串口,设置波特率,收发数据等做成库函数,函数在头文件中声明,并编译成动态或静态库。以后的项目中只需直接包含头文件并链接相关库即可。模块功能的完善化6.软件模块结构的改进一个完整的模块应当有以下几部分:①执行规定的功能的部分;②出错处理的部分。当模块不能完成规定的功能时,必须回送出错标志,出现例外情况的原因。③如果需要返回一系列数据给它的调用者,在完成数据加工或结束时,应当给它的调用者返回一个结束状态标志。消除重复功能,改善软件结构1完全相似:在结构上完全相似,可能只是在数据类型上不一致。此时可以采取完全合并的方法。2局
本文标题:模块与接口规范
链接地址:https://www.777doc.com/doc-3856371 .html