CherieLi Student

编程规范

2020-07-03
CherieLi

https://blog.51cto.com/waleon/5743410 依赖:分为so依赖和.h依赖
模块依赖:回调

1 头文件


1-1:头文件中需要使用#ifndef/#define/#endif,避免头文件重复包含。define格式为“FILE_NAME_H”。

1-2:头文件中的结构体定义,声明需要通过EXTERN_C_START/EXTERN_C_END。

1-3:头文件中只允许包含当前头文件依赖的其他头文件,禁止包含无关的头文件。

1-4:头文件中不允许出现变量定义,对外暴露变量,通过extern全局变量暴露。

1-5:.c文件中包含文件顺序依次为:self.h -> 系统头文件 -> 其他库头文件 -> 本项目头文件。

2 格式


2-1:函数的开始、结构的定义及循环、判断,case等语句中的代码都要采用缩进风格编写,缩进的空格数为4个。

2-2:函数/结构体/变量禁止使用过多单词命名,函数建议<=5个,变量建议<=3个。

2-3:缩进对齐只使用空格键,不使用TAB键。

2-4:相对独立的程序块之间、变量说明之后必须加空行。

2-5:if、for、do、while、case、switch、default等语句自占一行,且if、for、do、while等语句的执行语句部分无论多少都要加括号{}。

2-6:函数体的开始、结构、枚举的定义,应各独占一行并且位于同一列,同时与引用它们的语句左对齐。

2-7:当代码不能通过名字和逻辑自注释时,必须增加注释;注释通过//或者/**/说明。

2-8:对用户接口、头文件,必须加注释,注释格式见第4章。

2-9:避免无意义的空行,文件末尾保留空行。

说明:

在正确的做法中,函数与函数之间有且仅有一行空行,文件尾有且仅有一行空行。

2-10:不允许把多个短语句写在一行中,即一行只写一条语句。

3 可读性


3-1:编码采用驼峰式命名法,数据结构首字母大写;变量、函数名首字母小写;宏、常量全大写加下划线。全局变量加”g”前缀。

3-2:对外头文件统一放到src/include目录中,所有产品共有的以yac为前缀,产品特有的以yac+产品名为前缀。

3-3:内部文件,建议加前缀(如“cbo_”等),前缀需统一。

3-4:除0/1等含义非常明确的数字外,避免使用魔鬼数字,用有意义的标识来替代。

说明:

魔鬼数字的定义是:在代码中没有具体含义的数字、字符串。魔鬼数字主要影响了代码可读性, 阅读者可能无法理解字符的含义, 从而难以理解程序的意图。当程序中的魔鬼数字过多时,代码的可维护性将会下降。

3-5:程序中关系较为紧密的代码应尽可能相邻,仅限本文件使用的,结构定义,宏,函数声明应该放在.c文件,而不是.h文件中。

3-6:非必要不使用难懂的技巧性很高的语句,如果使用要有代码注释。

说明:

不要使用短时间内难以明白的技巧性很高的语句,高技巧的语句不一定是高效率的,高效率的关键在于算法设计本身。

3-7:注意运算符的优先级,并用括号明确表达式的操作顺序,避免使用默认优先级。

说明:

默认的优先级可能与程序设计的思路或思想不符合导致出错。

3-8:文件名采用小写加下划线方式命名,命名应该与文件的功能尽量匹配。

说明:

程序文件、头文件、源文件的命名采用小写加下划线的形式,注意命名应与文件功能高度匹配。

3-9:基本数据类型使用自定义的类型定义文件;跨平台的公共函数使用自定义的infra库,保持独立性。

3-10:禁止使用goto语句。

说明:

goto语句是一种无条件转移语句,goto语句的使用格式为:goto 语句标号。执行goto语句后,程序将跳转到该标号处并执行其后的语句。goto语句会扰乱程序逻辑,使程序的可读性变差。

3-11:不使用无意义的缩写,不使用不能理解的缩写,同一单词缩写保持一致。

说明:

通用的缩写的使用应该参考abbr.md。程序中用于处理逻辑的语句应该少用缩写,不用无意义和无法理解的缩写,同一单词保持一致的缩写。

3-12:单个文件不允许超过2000行,单行不超过120个字符。

说明:增强程序的可读性。

3-13:较长的语句(>120字符)要分成多行书写,长表达式要在低优先级操作符处划分新行,操作符放在新行之首,划分出的新行要进行适当的缩进,使排版整齐,语句可读。

说明:

增强程序的可读性。

4 文档化注释

4-1:对外接口自动生成的注释定义需符合doxygen的标准(可以使用:visual studio 2019配置注释自动生成命令)

4-2:不需要文档自动生成的注释(如函数内部注释等),单行注释使用//,多行注释使用/* … */。

4-3:每行注释建议不超过120个字符,可以换行或使用多行注释,注释块内注释换行示例:

4-4:数据结构声明(包括数组、结构、类、枚举,宏等),如果其命名不是充分自注释的,必须加以注释。对数据结构的注释应放在其上方相邻位置,对结构中的每个成员的注释放在此成员的右方。

4-5:全局变量,函数宏必须通过评审。如必须添加需要有较详细的注释,包括对其功能、取值范围、存取时注意事项等的说明。

4-6:边写代码边注释,修改代码同时修改相应的注释,以保证注释与代码的一致性。不再有用的注释要删除。

4-7:注释应与其描述的代码相近,对代码的注释应放在其上方,需与其上面的代码用空行隔开。

4-8:注释与所描述内容进行同样的缩排。

4-9:注释使用的语言使用英文。

5 宏


5-1:函数宏尽量使用inline函数替代,不能替代的需评审备案。

说明:

#define发生在预处理阶段采用符号直接替换原则,不能进行参数有效性的测试。当设计这方面代码出错,有时候会令人费解。对于单纯常量,最好以const对象替代#difine,对于形似函数的宏,最好改用inline函数替换#define

5-2:用宏定义表达式时,要使用完备的括号。例 #define foo (bar)。

说明:

宏只是简单的代码替换,不会像函数一样先将参数计算后,再传递。在使用带参数的宏的时候要注意:所有的参数都加括号,然后整个表达式在加上一个括号

6 函数


6-1:函数的参数建议不超过5个,函数的参数禁止超过7个,有效代码行数原则上不超过100行,禁止超过200行。特殊情况需要评审。

6-2:谨慎使用含义不明的结构体代替多参数传递,如assist/manager/context等。

6-3:对所调用函数的错误返回码要全面地处理。

说明:

为了不遗漏调用函数返回的错误信息,从而导致遗漏相关错误处理。需要对全面地对错误码进行处理。

6-4:防御性编程,对用户提供的接口,必须检查函数所有参数输入(包括全局变量)的有效性。

说明:

用户输入的合法性通常难以保证,因为存在恶意攻击和误操作导致的非法输入,因此通常需要校验来保证安全,特别是当用户输入作为循环条件、数组下标、格式化字符串、拼装sql语句等情况。

6-5:内部函数,应遵循“调用者负责”的编码原则,调用者检查参数的有效性。

说明:

统一规定由调用者负责检查参数合理性,避免遗漏合法性检查和重复检查造成冗余代码。

6-6:函数名应准确描述函数的功能。

说明:

函数名应该尽量清晰明了,使用完整的单词或可以理解的缩写,避免产生误解和歧义

6-7:功能不明确较小的函数,特别是仅有一个上级函数调用它时,应考虑把它合并到上级函数中,而不必单独存在。

说明:

函数设计的过多,会使得函数间的接口变得复杂,降低代码可读性,所以过小的函数和功能不明确的函数不需要单独存在。

6-8:函数功能要满足单一职责原则,即一个函数仅完成一个功能。

说明:

一个函数最好只实现一个对应的功能,将没有关联的语句放在用一个函数中会导致函数职责不明确,难以理解

6-9:不对内容修改的指针,应定义为const。

说明:

如果参数是指针型参数,且内容不会被修改,请定义为const类型,以避免指针在运行时被篡改。

6-10:inline函数原则上不应超过10行。

说明:

inline函数带来的运行效率提升是以代码膨胀为代价的,如果执行函数体内的代码的时间比函数调用的开销大,那么inline的收益就会很小。此外,每一处inline函数的调用都是以代码复制方式,将导致生成的可执行文件体积变大。

6-11:资源类函数,调用时需配对使用,如分配的内存、申请的(为打开文件而使用的)文件句柄,锁等,在函数退出之前要关闭、释放。不在一个函数配对使用的需评审。

说明:

资源的申请和释放必须在同一逻辑层,谁申请,谁释放。请保持良好的资源释放习惯,避免出现内存泄露和资源泄露。

6-12:一个函数的圈复杂度累计不超过10。

说明:

圈复杂度反应了一个模块判定结构的复杂程度,圈复杂度大说明该段程序代码的判断逻辑复杂,难以维护和测试。一般认为圈复杂度在10以下可认为代码质量较好,10到20可能需要拆分或重构。

圈复杂度的计算方法有很多,这里给出两种常用的计算方法,记圈复杂度为V(G)。

  1. 点边计算法 V(G) = E - N + 2 其中,E表示控制流图中边的数量,N表示控制流图中节点的数量。
  2. 节点判定法 V (G) = P + 1 其中P为判定节点数,常见的判定节点有:if语句(包括else if,else),while语句,for语句,switch-case语句,match-case语句,try-catch语句

示例:

//下列代码的圈复杂度为 1(for) + 2(if) + 1 = 4
int find (int match)
{
    for (int var in list) {
        if (var == match && var != NAN) {
            return var;
        }
    }
}

6-13:函数内部for/while/if嵌套要小于5层,超过的要进行逻辑封装。

说明:

每级嵌套都会增加代码阅读的难度,因为需要记忆上下文代码的逻辑关系。对于超过5层的嵌套进行进一步的分解,保证代码可读性。

6-14:禁止在.c文件中extern其他文件实现的函数、变量,只能通过头文件包含关系调用。

说明:

禁止在.c文件中直接通过extern int foo(int input)的写法来引用其他文件的函数和变量。这种写法会导致代码混乱,同时在函数改变时导致声明和定义不一致。

6-15:注意递归函数的递归层数限制,层数较多的递归函数需加栈保护,避免系统栈溢出。

说明:

对于可能存在较多递归层数的函数,需要使用ANL_STACK_SAFE_CALL添加栈保护,防止系统栈溢出。

6-16:DEBUG宏代码段,只允许做一定的检查、校验,不能有业务逻辑。

说明:

在Release版本中DEBUG宏的代码段会被直接去掉,如果其中包含业务逻辑,会导致相关的业务逻辑丢失,影响正常功能。

6-17:在协议、接口、持久化上,Debug和Release不能有任何区别。

说明:

协议、接口、持久化等方面若存在差异,会导致release版本无法正常解析传入的变量。

6-18:如果一个结构体、变量只在Debug模式下使用,同样要用Debug声明。

说明:

如果某变量只在Debug模式下使用,但是未使用Debug声明,那么在Release版本中会成为一个未被引用的变量,会导致代码冗余,降低代码可读性。

7 结构体


7-1:持久化的结构体,需要4字节对齐;内存结构8字节对齐。

说明:

执行结构体对齐后可以减少读取内存的次数,I/O操作耗时很高,通过对齐可以减少I/O操作

7-2:明确使用、慎用XXXassist, XXXinfo这类的名字。

说明:

在代码中使用了许多XXXassistXXXinfo格式的结构体,请谨慎命名结构体,避免名称混淆和混乱。

7-3:没有内存对齐的,需要显式对齐。

说明:

内存结构体要求8字节对齐,对于没有对齐的结构体,需要手动添加unused

8 断言


8-1:断言必须使用COD_ASSERT宏,禁止使用系统assert()。

说明:

COD_ASSERT通过定义ifndef,只在测试版本出现,最终发布版本不允许出现assert函数

8-2:外部接口严禁对参数进行ASSERT操作,避免运行时crash。

说明:

对于外部接口参数,必须进行合法性判断,严禁在API实现过程中产生crash。

8-3:运行时可能会导致的错误,严禁使用断言。

说明:

某些函数在运行时结果可能会有变化,如果写死断言会导致程序运行时crash。

8-4:严禁在断言内有改变运行环境的赋值、修改变量、资源操作、内存申请等操作。

说明:

断言在release时并不会写入,在断言内不能包含任何主体逻辑(赋值,修改变量,内存申请等),否则会导致代码逻辑遗漏。

8-5:严禁通过断言替代异常处理,release版本应屏蔽断言。

说明:

  1. 断言只能判断真假,并在假时宕掉,缺少一些异常处理。因此严禁简单地用断言来代替一些需要异常处理的逻辑。

  2. 在release版本应当不启用断言,这主要是为了防止运行时宕机。在COD_ASSERT处已经实现了相关宏定义,因此只需要保证调用COD_ASSERT即可。

    9 变量


    9-1:变量禁止无效初始化。

说明:

变量在声明的时候,不要给变量赋无效的值。在实际需要给变量赋值的时候,再赋予相应的值。

9-2:尽量使用const。

说明:

当变量为常量的时候,应当要在变量声明前面加const。这样可以提高代码的安全性、可读性和可维护性。

9-3:局部变量慎用声明超过1KB,禁止声明超过4KB。

说明:

函数内的局部变量保存在栈中,局部变量过大的时候会导致栈空间不足,引起stackoverflow异常。

9-4:禁止私自增加全局变量/线程私有全局变量,新增加全局变量/线程私有全局变量需评审。

说明:

禁止未经评审增加全局变量和线程私有全局变量。

9-5:运行时逻辑代码,禁止使用malloc/realloc/alloca系统函数分配内存。

说明:

代码禁止使用malloc/realloc/alloca系统函数分配内存。

9-6:使用内存操作类函数如memcpy等,应规避内存覆盖。

说明:

在进行memcpy时,如果源内存区域[src, src + count)和目标内存区域[dst, dst + count)有重叠,同时src < dst < src + count时,就会发生内存覆盖问题。src给dst赋值不超过dst的偏移量时没有什么问题,但是一旦超过偏移量,由于前面的赋值操作已经将内存给改变了,然后再用改变过后的内存再给dst赋值,这就导致了前面内容复制重复了,也就是发生内存覆盖了。

10 逻辑语句


10-1:switch语句必须有default分支。

说明:

switch语句中需要强制加上default分支,default建议处理异常。

11 安全编码


11-1:字符串操作等,必须使用安全函数库里面定义的函数。

说明:

字符串的操作必须使用相应的安全函数库内定义的函数,如:strcpy_sstrcat_sstrtok_s等。

11-2:函数内禁用static变量。

11-3:整数之间运算时必须严格检查,确保不会出现溢出、反转、除0。

说明:

当整数之间进行运算时,可能会超过整数的最大固定长度,导致整数溢出或反转,使得预期结果不符。 如果涉及到除法或者求余操作,必须确保除数不为0。

11-4:整型表达式比较或赋值为一种更大类型之前建议用这种更大类型对它进行求值。

说明:

为了防止在整数表达式比较或赋值为一种更大的类型时,计算出的结果与预期不符。建议先用更大的类型对它进行求值。

11-5:禁止对有符号整数进行位操作符运算。

说明:

位操作符(~、»、«、&、^、|)应该只用于无符号整型操作数

11-6:内存申请大小、循环次数如果由函数参数控制,需要校验合法性。

说明:

内存申请的大小可能来自于外部传入的参数,必须检查其合法性,防止非法地申请内存。

12 gitlab代码提交规范


12-1:在本地仓库进行开发,需要提交代码时,按照如下步骤:

Commit:提交代码到本地仓库。

Fetch(pull):获取主仓库的更新,同步到个人本地仓库。

Merge:合并主仓库的代码和本地的开发代码。代码会自动合并,但如果有冲突,需手动合并代码解决冲突。

Push:提交代码到个人远程仓库。

Merge Request:在GitLab的个人主页,发起Merge Request请求将个人远程仓库代码合并到主仓库的对应分支。

Code Review:处理Merge Request请求,进行code review。如果一切正常,最终合并到主仓库,打Tag上线。如果有问题,则拒绝Merge Request,要求修改。


上一篇 lua 学习

下一篇 正则表达式

Comments

Content