程中。随机内聚给函数或过程的维护、测试及以后的升级等造成了不便,
同时也使函数或过程的功能不明确。使用随机内聚函数,常常容易出现在一种应用场合需
要改进此函数,而另一种应用场合又不允许这种改进,从而陷入困境。
在编程时,经常遇到在不同函数中使用相同的代码,许多开发人员都愿把这些代码提出来,
并构成一个新函数。若这些代码关联较大并且是完成一个功能的,那么这种构造是合理的,
否则这种构造将产生随机内聚的函数。
示例:如下函数就是一种随机内聚。
void init_var( void )
{
rect.length = 0;
rect.width = 0; /* 初始化矩形的长与宽 */
point.x = 10;
point.y = 10; /* 初始化“点”的坐标 */
}
矩形的长、宽与点的坐标基本没有任何关系,故以上函数是随机内聚。
应如下分为两个函数:
void init_rect( void )
{
rect.length = 0;
rect.width = 0; /* 初始化矩形的长与宽 */
}
void init_point( void )
{
point.x = 10;
point.y = 10; /* 初始化“点”的坐标 */
}
.6-21:如果多段代码重复做同一件事情,那么在函数的划分上可能存在问题。
说明:若此段代码各语句之间有实质性关联并且是完成同一件功能的,那么可考虑把此段
代码构造成一个新的函数。
.6-22:功能不明确较小的函数,特别是仅有一个上级函数调用它时,应考虑把它合并到上级
函数中,而不必单独存在。
说明:模块中函数划分的过多,一般会使函数间的接口变得复杂。所以过小的函数,特别
是扇入很低的或功能不明确的函数,不值得单独存在。
.6-23:设计高扇入、合理扇出(小于7)的函数。
说明:扇出是指一个函数直接调用(控制)其它函数的数目,而扇入是指有多少上级函数
调用它。
扇出过大,表明函数过分复杂,需要控制和协调过多的下级函数;而扇出过小,如总是1,
表明函数的调用层次可能过多,这样不利程序阅读和函数结构的分析,并且程序运行时会
对系统资源如堆栈空间等造成压力。函数较合理的扇出(调度函数除外)通常是3-5。扇
出太大,一般是由于缺乏中间层次,可适当增加中间层次的函数。扇出太小,可把下级函
数进一步分解多个函数,或合并到上级函数中。当然分解或合并函数时,不能改变要实现
的功能,也不能违背函数间的独立性。
扇入越大,表明使用此函数的上级函数越多,这样的函数使用效率高,但不能违背函数间
的独立性而单纯地追求高扇入。公共模块中的函数及底层函数应该有较高的扇入。
较良好的软件结构通常是顶层函数的扇出较高,中层函数的扇出较少,而底层函数则扇入
到公共模块中。
.6-24:减少函数本身或函数间的递归调用。
说明:递归调用特别是函数间的递归调用(如a->b->c->a),影响程序的可理解性;递
归调用一般都占用较多的系统资源(如栈空间);递归调用对程序的测试有一定影响。故
除非为某些算法或功能的实现方便,应减少没必要的递归调用。
.6-25:仔细分析模块的功能及性能需求,并进一步细分,同时若有必要画出有关数据流图,
据此来进行模块的函数划分与组织。
说明:函数的划分与组织是模块的实现过程中很关键的步骤,如何划分出合理的函数结构,
关系到模块的最终效率和可维护性、可测性等。根据模块的功能图或/及数据流图映射出
函数结构是常用方法之一。
.6-26:改进模块中函数的结构,降低函数间的耦合度,并提高函数的独立性以及代码可读性、
效率和可维护性。优化函数结构时,要遵守以下原则:
(1)不能影响模块功能的实现。
(2)仔细考查模块或函数出错处理及模块的性能要求并进行完善。
(3)通过分解或合并函数来改进软件结构。
(4)考查函数的规模,过大的要进行分解。
(5)降低函数间接口的复杂度。
(6)不同层次的函数调用要有较合理的扇入、扇出。
(7)函数功能应可预测。
(8)提高函数内聚。(单一功能的函数内聚最高)
说明:对初步划分后的函数结构应进行改进、优化,使之更为合理。
.6-27:在多任务操作系统的环境下编程,要注意函数可重入性的构造。
说明:可重入性是指函数可以被多个任务进程调用。在多任务操作系统中,函数是否具有
可重入性是非常重要的,因为这是多个进程可以共用此函数的必要条件。另外,编译器是
否提供可重入函数库,与它所服务的操作系统有关,只有操作系统是多任务时,编译器才
有可能提供可重入函数库。如dos下bc和msc等就不具备可重入函数库,因为dos是
单用户单任务操作系统。
.6-28:避免使用bool参数。
说明:原因有二,其一是bool参数值无意义,ture/false的含义是非常模糊的,在调
用时很难知道该参数到底传达的是什么意思;其二是bool参数值不利于扩充。还有null
也是一个无意义的单词。
.6-29: 对于提供了返回值的函数,在引用时最好使用其返回值。
.6-30:当一个过程(函数)中对较长变量(一般是结构的成员)有较多引用时,可以用一个
意义相当的宏代替。
说明:这样可以增加编程效率和程序的可读性。
示例:在某过程中较多引用thereceivebuffer[firstsocket].bydataptr,
则可以通过以下宏定义来代替:
# define psockdata thereceivebuffer[firstscoket].bydataptr
7 可测性
17-1:在同一项目组或产品组内,要有一套统一的为集成测试与系统联调准备的调测开关及相
应打印函数,并且要有详细的说明。
说明:本规则是针对项目组或产品组的。
17-2:在同一项目组或产品组内,调测打印出的信息串的格式要有统一的形式。信息串中至少
要有所在模块名(或源文件名)及行号。
说明:统一的调测信息格式便于集成测试。
17-3:编程的同时要为单元测试选择恰当的测试点,并仔细构造测试代码、测试用例,同时给
出明确的注释说明。测试代码部分应作为(模块中的)一个子模块,以方便测试代码在模块中
的安装与拆卸(通过调测开关)。
说明:为单元测试而准备。
17-4:在进行集成测试/系统联调之前,要构造好测试环境、测试项目及测试用例,同时仔细
分析并优化测试用例,以提高测试效率。
说明:好的测试用例应尽可能模拟出程序所遇到的边界值、各种复杂环境及一些极端情况
等。
17-5:使用断言来发现软件问题,提高代码可测性。
说明:断言是对某种假设条件进行检查(可理解为若条件成立则无动作,否则应报告),
它可以快速发现并定位软件问题,同时对系统错误进行自动报警。断言可以对在系统中隐
藏很深,用其它手段极难发现的问题进行定位,从而缩短软件问题定位时间,提高系统的
可测性。实际应用时,可根据具体情况灵活地设计断言。
示例:下面是c语言中的一个断言,用宏来设计的。(其中null为0l)
#ifdef _exam_assert_test_ // 若使用断言测试
void exam_assert( char * file_name, unsigned int line_no )
{
printf( "\n[exam]assert failed: %s, line %u\n",
file_name, line_no );
abort( );
}
#define exam_assert( condition )
if (condition) // 若条件成立,则无动作
null;
else // 否则报告
exam_assert( __file__, __line__ )
#else // 若不使用断言测试
#define exam_assert(condition) null
#endif /* end of assert */
17-6:用断言来检查程序正常运行时不应发生但在调测时有可能发生的非法情况。
17-7:不能用断言来检查最终产品肯定会出现且必须处理的错误情况。
说明:断言是用来处理不应该发生的错误情况的,对于可能会发生的且必须处理的情况要
写防错程序,而不是断言。如某模块收到其它模块或链路上的消息后,要对消息的合理性
进行检查,此过程为正常的错误检查,不能用断言来实现。
17-8:对较复杂的断言加上明确的注释。
说明:为复杂的断言加注释,可澄清断言含义并减少不必要的误用。
17-9:用断言确认函数的参数。
示例:假设某函数参数中有一个指针,那么使用指针前可对它检查,如下。
int exam_fun( unsigned char *str )
{
exam_assert( str != null ); // 用断言检查“假设指针不为空”这个条件
... //other program code
}
17-10:用断言保证没有定义的特性或功能不被使用。
示例:假设某通信模块在设计时,准备提供“无连接”和“连接” 这两种业务。但当前
的版本中仅实现了“无连接”业务,且在此版本的正式发行版中,用户(上层模块)不应
产生“连接”业务的请求,那么在测试时可用断言检查用户是否使用“连接”业务。如下。
#define exam_connectionless 0 // 无连接业务
#define exam_connection 1 // 连接业务
int msg_process( exam_message *msg )
{
unsigned char service; /* message service class */
exam_assert( msg != null );
service = get_msg_service_class( msg );
exam_assert( service != exam_connection ); // 假设不使用连接业务
... //other program code
}
17-11:用断言对程序开发环境(os/compiler/hardware)的假设进行检查。
说明:程序运行时所需的软硬件环境及配置要求,不能用断言来检查,而必须由一段专门
代码处理。用断言仅可对程序开发环境中的假设及所配置的某版本软硬件是否具有某种功
能的假设进行检查。如某网卡是否在系统运行环境中配置了,应由程序中正式代码来检查;
而此网卡是否具有某设想的功能,则可由断言来检查。
对编译器提供的功能及特性假设可用断言检查,原因是软件最终产品(即运行代码或机器
码)与编译器已没有任何直接关系,即软件运行过程中(注意不是编译过程中)不会也不