模块与接口规范综述
合集下载
相关主题
- 1、下载文档前请自行甄别文档内容的完整性,平台不提供额外的编辑、内容补充、找答案等附加服务。
- 2、"仅部分预览"的文档,不可在线预览部分如存在完整性等问题,可反馈申请退款(可完整预览的文档不适用该条件!)。
- 3、如文档侵犯您的权益,请联系客服反馈,我们会尽快为您处理(人工客服工作时间:9:00-18:30)。
5.1. 模块设计过程中,数据结构需适当考虑可扩充性。
例如:
结构体可以设置保留字段,为以后的升级或功能扩展做预留空间。
IP数据报格式及首部中的各字段
5.2.函数的设计,如果相关联的参数较多,可以合并为结构体。 结构体是C语言实现面向对象的关键之一,可以很好的体现面向对象思想。同时 能给功能升级和扩充带来很大的方便。 例如:
耦合的强度依赖于以下几个因素: (1)一个模块对另一个模块的调用; (2)一个模块向另一个模块传递的数据量; (3)一个模块施加到另一个模块的控制的多少; (4)模块之间接口的复杂程度。
耦合按从强到弱的顺序可分为以下几种类型:
(1)内容耦合: 当一个模块直接修改或操作另一个模块的数据,或者直接转入另一个模块 时,就发生了内容耦合。此时,被修改的模块完全依赖于修改它的模块。 (2)公共耦合: 两个以上的模块共同引用一个全局数据项就称为公共耦合。 (3)控制耦合: 一个模块在界面上传递一个信号(如开关值、标志量等)控制另一个模 块,接收信号的模块的动作根据信号值进行调整,称为控制耦合。 (4)标记耦合: 模块间通过参数传递复杂的内部数据结构,称为标记耦合。此数据结构 的变化将使相关的模块发生变化。 (5)数据耦合: 模块间通过参数传递基本类型的数据,称为数据耦合。
从功能上划分模块,保持“功 能独立”是模块化设计的基本 原则。
三. 模块与接口设ห้องสมุดไป่ตู้准则
1.封装与隐藏 接口是对模块功能的声明,只需要指明客户调用程序可能使用的标识符(包 括数据类型,函数原型等)即可,应尽可能隐藏内部细节和算法。
application
Mn
interface1
M2
M1
• t
2.模块的独立性 声明与实现分离
// 波特率 // 数据位 // 校验位 // 停止位 //其它属性
由于标准C语言没有提供命名空间机制,较大规模软件中,不同模块的全局变 量和函数也很可能重名,导致重复定义错误。同时被多个模块使用的全局变 量,是增大模块间耦合性的重要因素之一,为减少模块间耦合性,应尽量少 用或不用多个模块共用的全局变量。
规定:只在本模块内部使用的函数和全局变量,必须用static关键字修饰, 以限制只能在本模块内使用。
这样不同的功能模块,可以根 据自己的需要调用不同的初始 化模块。
int init_graphics(); int init_language(); int init_connect();
函数的功能要单一,不要设计“多功能”函数
。
函数的功能应该是单一而明确的。不要把不同的功能放入同一个 函数,特别是尽量不要用控制开关来让数据处理函数实现不同的功能。 例如:如下是不好的函数设计,函数功能不单一,且实现的功能无必 然联系。
#define TYPE_ADD 0 #define TYPE_SUB 1 int add_or_sub(int a, int b, int flag) { if (TYPE_ADD == flag) { return (a + b); } else { return (a – b); } }
4.弱耦合性 耦合(Coupling) 是模块之间依赖程度的度量。内聚和耦合是密切相关的,与其它模 块存在强耦合的模块通常意味着弱内聚,而强内聚的模块通常意味着与其它 模块之间存在弱耦合。模块设计追求强内聚,弱耦合。
二. 模块划分基本准则
模块的划分以功能为标准,相对独立的功能划分为不同的模块,各模块之间 根据需要可以进行数据通信,但是必须保持功能上的相对独立性。 “功能独立”的模块可以降低开发、测试、维护等阶段的代价。但是“功能 独立”并不意味着模块之间保持绝对的孤立。一个系统要完成某项任务,需 要各个模块相互配合才能实现,此时模块之间就要进行信息交流。
为了保证模块的独立封装性,就必须将声明与实现分离开来。C语言对此 只提供最基本的支持。在标准C语言中,接口在头文件中声明,头文件的扩展 名为.h。
规定:调用程序可使用的宏定义,数据结构类型,变量以及函数原型等 必须在头文件中声明。调用程序使用预处理指令#include导入接口声明。 禁止将函数的实现和变量的定义放在头文件。
extern int control_param_from_B; { some code; if (control_param_from_B == xxx) { do something; } else { do other thing; } } //控制参数,从B模块传递而来 //根据传递过来的参数决定功能。
如果一个软件研发单位和工作组能够在每一次研发过程中都考虑到这些问题,那么程 序员就不会在重复性的工作中耽误太多时间,就会有更多时间和精力投入到创新的代码工 作中去。对于软件公司,也无需重复开发同样的功能,避免“重复设计轮子”。
例如:
在公司很多产品中,都有使用串口通信的功能模块。但是虽然已有多个项目使用了串口通信, 却没有形成串口通信库。每次打开串口,发送数据等,都是重新编写函数,或拷贝代码后做大量 修改,可重用性很低。 应该将串口属性定义为结构体,针对结构体将串口的常用操作如打开串口,设置波特率,收 发数据等做成库函数,函数在头文件中声明,并编译成动态或静态库。以后的项目中只需直接包 含头文件并链接相关库即可。 lib_serial.h typedef struct { unsigned char baud_rate; unsigned char data_len; unsigned char parity_bit; unsigned char stop_bit; ...... }COM_OPTION, *P_COM_OPTION; /*使用指定属性打开串口*/ int open_serial(P_COM_OPTION p_com_option);
假如某函数对一个数据结构进行处理,可以定义为以下形式: func_name(int option1, int option2, char option3, char *option4) typedef struct { int option1; int option2; char *option4; char option3; }OPTION, *P_OPTION; func_name(P_OPTION p_option);
如果模块间需要进行数据通信,尽量采用文本形式的简单数据格式。不要定 义复杂二进制数据格式,除非文本格式无法满足功能要求。
不少模块间需要进行数据通信,通信数据格式尽量采用简单文本形式。 二进制通信格式既不便于使用,也不便于调试,不建议使用。 数据格式应尽可能简单,且必须能满足可扩充性。 采用简单格式的文本数据通信,也有助于减少模块间耦合。一般来说, 二进制通信协议如果数据格式发生变化,则通信的各个模块编解码都需要 相应变化。而文本通信能保持较好的兼容性。
二进制编码一般情况下能提高时间和空间效率,当需要传递大量数据 且对性能要求较高,或必须传递二进制数据时,才可以考虑二进制编 码协议。普通情况下,二进制编码带来的效率提升甚至无法抵消编解 码带来的额外开销。
5.可复用性与可扩充性
如果一个模块可以被扩充,则称模块 具有可扩充性。当着手一个新模块时, 很难一次性解决所有问题,应该先纵 观问题的一些重要方面,同时作好以 后补充的准备。
减少模块间接口复杂度,函数参数不得超过7个。
复杂的函数接口,既不便于使用,也会增加调用时的出错几率。如果函数 功能复杂,参数过多,可以考虑分解成几个函数来完成,或将有逻辑关联的参 数构建成结构体来传递参数。
减少模块间控制参数,模块通信尽量只使用数据通信。 模块控制参数是指,某个模块的功能,部分或完全取决于另一模块传递过来 的参数。控制参数会增大模块耦合,使得模块不便于理解和调试,应尽量避免。
防止函数或模块内出现随机内聚。在编程时,经常遇到在不同函数中使用相 同的代码,许多开发人员都会把这些代码提出来,并构成一个新函数。若这些代 码关联较大并且是完成一个功能的,那么这种构造是合理的,否则这种构造将产 生随机内聚的函数。
例如:
很多软件系统在启动的时候都需要初始化一些参数,因此不少系统会把初始化 模块做成一个或多个函数。但是要防止把毫不相关的参数初始化放入同一个函数。
3.强内聚性 内聚是一个模块内部各成分之间相关联程度的度量。根据内聚性的强弱,有 以下几种类型:
(1)随机内聚: 如果一个模块的各成分之间毫无关系,则称为随机内聚。
(2)逻辑内聚: 几个逻辑上相关的功能被放在同一模块中,则称为逻辑内聚。如一个模块读取各 种不同类型外设的输入。尽管逻辑内聚比偶然内聚合理一些,但逻辑内聚的模块各成分在功能上 并无关系,即使局部功能的修改有时也会影响全局,因此这类模块的修改也比较困难。 (3)时间内聚: 如果一个模块完成的功能必须在同一时间内执行(如系统初始化),但这些功 能只是因为时间因素关联在一起,则称为时间内聚。 (4)过程内聚: 如果一个模块内部的处理成分是相关的,而且这些处理必须以特定的次序执行, 则称为过程内聚。 (5)通信内聚: 如果一个模块的所有成分都操作同一数据集或生成同一数据集,则称为通信内聚。 (6)顺序内聚: 如果一个模块的各个成分和同一个功能密切相关,而且一个成分的输出作为另一 个成分的输入,则称为顺序内聚。 (7)功能内聚: 模块的所有成分对于完成单一的功能都是必须的,则称为功能内聚。
模块与接口规范
模块及模块接口设计原则
一.何为模块
模块由接口和实现两部分组成。 --接口指明模块的功能,即模块能做什么。它声明了使用该模块代码的标 识符,类型和函数原型等; --实现指模块是如何完成起接口声明的功能。一个具体的功能模块接口是 唯一固定的,但是它的实现可能会有很多种。每个实现可能使用不同的算法 和数据结构,但是都必须符合接口给出的使用声明。
int init_env() { init_graphics...; init_language...; init_connect...; .... }
//初始化图形设置 //初始化语言设置 //初始化网络连接
假如模块A需要使用a语言,无网络 连接,而模块B需要使用b语言,有网络 连接。则init_env函数无论怎么改动都无 法同时满足两个模块要求。此时函数 init_env就是随机内聚函数,正确的修改 方法应该是根据初始化的模块不同,分 解成相对独立的函数。 修改init_env()为三个独立的函数:
(6)非直接耦合: 模块间没有信息传递时,属于非直接耦合。
4.1. 设计弱耦合性模块 减少多个模块间的全局变量
多个模块间都能使用的全局变量会增大模块间耦合,给程序的调试和维护 带来很大的不便。尽量采用参数传递来取代多个模块间的全局变量。
注意:使用static修饰的模块内全局变量不会增加模块间耦合,在 参数传递极为频繁的情况下,可以将需要频繁传递的参数提取为 模块内部全局变量,以减少函数参数个数和参数传递。多个模块 间如果存在全局变量,尽量让该全局变量只被一个模块修改,而 其它模块只读。
也可以将四个数 据项定义为一个 结构体,函数采 用结构体做参数
假如由于功能升级, 需要增加一个功能选 项,第一种方案除了 修改函数本身,还不 得不修改每个函数调 用。而第二种方案仅 仅需要修改函数本身 和结构体的声明。
5.3. 设计和编码的过程中,要有复用性设计和模块化的思维。 复用性设计,模块化思维就是要程序员在完成任何一个功能模块或函 数的时候,要多深入考虑,不要局限在完成当前任务的简单思路上,想 想看该模块或函数是否可以脱离这个系统存在,是否可以通过简单的修 改参数的方式在其他系统和应用环境下直接引用,这样就能极大避免重 复性的开发工作.
好的模块设计要有尽可能高的内聚性,尽量避免随机内聚,禁止将毫无 关系的成分放入同一模块。随机内聚会使模块的功能不明确,破坏模块的功能独 立性。也给模块的维护、测试及升级等造成不便。
Interface n
m1 m4 m3
Mn
m5 m6 m2
3.1. 设计高内聚性模块
防止把没有关联的功能放到一个模块中。