函数设计的精髓:编写整洁函数,同时把代码有效组织起来
原则2.1:一个函数仅完成一件功能。
一个函数实现多个功能会给开发,使用,维护都带来很大的困难。
案例:realloc,在标注的C语言中,realloc是一个典型的不良设计,这个函数的基本功能是重新分配内存,但它承担了天多的其他任务。如果传入的指针参数为NULL,就分配内存,如果传入的大小参数为0,就释放内存,如果可行则就地重新分配,如果不行则到其他地方分配,如果没有足够可用的内存来完成重新分配(扩大原来的内存块或分配新的代码块),则返回NULL,而原来的内存块保持不变,这个函数不易扩展,容易导问题,例如下面代码容易导致内存泄漏; char *buffer=(char *)malloc(XXX_SIZE)
建议1.1:一个模块通常包含多个.c文件,建议放在同一个目录下,目录名即是模块名,为方便外部使用者,建议每一个模块提供一个.h,文件名为目录名。
说明:需要注意的是,这个.h并不是i简单的包含所有内部的.h,他是为了模块使用者的方便,对外提供的模块接口。
以Google test 为例,GTest作为一个整体对外提供C++单元测试框架,其1.5版本的gtest工程下有6个源文件,和12个头文件,但是他对外只提供一个gtest.h,只要包含gtest.h即可使用GTest提供的所有对外提供的功能,使用者不必关心GTest内部各个文件的关系,即使以后GTest的内部实现改变了,例如把一个源文件c拆成两个源文件,使用者也不必关心,甚至如果对外功能不变,连重新编译都不需要。
对于有些模块,其内部功能相对松散,可能并不一定需要提供这个.h,而是直接提供各个子模块或者.c头文件。
比如产品普遍使用的VOS,作为一个大模块,其内部有很多子模块,他们之间的关系相对比较松散,就不适合提供一个vos.h,而V OS的子模块,如Memory,其内部实现高度内聚,虽然其内部是新可能有多个.c和.h,但是相对外只需要提供一个Memory.h声明接口。
建议1.2:如果一个模块包含多个子模块,则建议每一个子模块提供一个对外的.h,文件名为子模块名,说明:降低接口使用者的编写难度
建议1.3 头文件不要使用非习惯用法的扩展名,如inc。
说明:目前很多产品中使用了.inc作为头文件扩展名,这不符合c语言的习惯用法。在使用.inc作为头文件扩展名的产品,习惯于用于表示此头文件为私有文件。但是从产品的实际代码来看,这一条并没有被遵守,一个.inc文件被多个.c包含比比皆是,本贵方不提倡将私有定义单独放在头文件中。
原则2.2 重复代码应该尽可能被提炼成函数
说明:重复代码提炼成函数可以带来成本的降低。
重复代码是我司代码最典型的特征之一。在“代码能通用就不改”的原则之下,大量设计及其实现充斥着各产品代码之中。新需求增加来的代码拷贝和修改,随着时间推移,产品中堆砌着许多类似或者重复的代码。
项目组应当使用代码重复度检查工具,在持续集成环境中检查代码重复度指标变化趋势,并对新增重复代码及时重构。当一段代码重复两次时,即应考虑消除重复,当代码重复超过三次时,应当立刻着手消除重复
规则2.3可重入函数应避免使用共享变量;若需要使用,则应通过互斥手段(关中断 ,信号量)对其加以保护。
(可重入函数主要用于多任务环境中,一个可重入的函数简单来说就是可以被中断的函数,也就是说,可以在这个函数执行的任何时刻中断它,转入OS调度下去执行另外一段代码,而返回控制时不会出现什么错误;而不可重入的函数由于使用了一些系统资源,比如全局变量区,中断向量表等,所以它如果被中断的话,可能会出现问题,这类函数是不能运行在多任务环境下的。)
规则2.4 对参数的合法性检查,由调用者负责还是由接口函数负责,应在项目组/模块内应统一规定,缺省由调用者负责。
说明:对于模块间接口函数的参数的很发行检查这一问题,往往有两个极端现象,即:要么是调用者和被调用者对参数均不作合法性检查,结果就遗漏了合法性检查这一必要的处理过程,造成问题隐患;要么就是调用者和被调用者均对参数进行合法性检查,这种情况虽不会造成问题,但产生了冗余代码,降低了效率。
规则2.1 避免函数过长,新增函数不超过50行(非空非注释行)
说明:本规则仅对新增函数做要求,对已有函数修改时,建议不增加代码行。过长的函数往往意味着函数功能不单一,过于复杂。
函数的有效代码行,即NBNC(非空非注释行) 应当在[1,50]区间
例外:某些实现算法的函数,由于算法的聚合性与功能的全面性,可能会超过50行。
延伸阅读材料,业界普遍认为一个函数的代码行不要超过一个屏幕,表面来回翻页影响阅读,一般的代码度量工具建议都对此进行检查。
规则2.2避免函数的代码块嵌套过深,新增函数的代码块嵌套不超过4层。
规则2.5对函数的错误返回码要全面处理
说明:一个函数(标准库中的函数/第三方库函数/用户定义的函数)能提供一些提示错误发生的方法,这可以通过使用错误标记 ,特俗的返回数据或者其他手段,不管什么时候函数提供了这样的机制,调用程序应该再函数返回时立刻检查错误错误提示。
规则2.6设计高扇入,合理扇出(小于7)的函数
说明:扇出是指一个函数直接调用(控制)其他函数䣌数目,而扇入是指有多少上级函数调用它
扇出过大,表明函数越复杂,需要控制和协调过多的下级函数,而扇出过小,例如:总是1,表明函数的调用层次可能过多,这样不利于程序阅读和函数结构的分析,并且程序运行时会对系统资源如对战空间造成压力,通常函数比较合理的扇出(调度函数除外)通常是3-5.
扇出太大,一般是由于缺乏中间层,可适当增加中间层次的函数。扇出太小,可把下级函数进一步分解多个函数,或合并到上级函数中,当然分解或合并函数时,不能改要实现的功能,也不能违背函数间的独立性,扇入越大,表明使用此函数的上级函数越多,这样的函数使用效率高,但不能违背函数间的独立性
扇入越大,表明使用此函数的上级函数越多,这样的函数使用效率高,但不能违背函数间的独立性而单独追求高扇入。公共模块中的函数及底层函数应有较高的扇入。较良好的软件结构通常是顶层函数的扇出较高,中层函数的扇出较少,而底层函数则扇入到公共模块中。
规则2.7 废弃代码(没有被调用的函数和变量)要及时清除
说明:程序中的废弃代码不仅占用额外的空间,而且还常常影响程序的功能和性能,很可能给程序的测试,维护造成不必要的麻烦。
建议2.1函数不变参数使用const
说明:不变的指更易于理解/跟踪和分析,把const作为默认选项,在编译时会对其进行检查,使代码更加牢固/更安全
实例:C99标准,strncmp的例子,不变参数声明为const
int strcmp(const char *s1,const char *s2,register size_t n)
{
........
}