打印

求C/C++编程(代码)规范

[复制链接]
4050|22
手机看帖
扫描二维码
随时随地手机跟帖
跳转到指定楼层
楼主
沙发
tyw| | 2021-9-6 14:24 | 只看该作者
华为软件设计规范.pdf (528.45 KB)
《C 编程规范 101条规则、准则与最佳实践》笔记.pdf (3.89 MB)
【精品】C 编程规范101条规则与最佳实践1.pdf (2.55 MB)
C 、 C编程规范总则.pdf (10.89 MB)
C 编程规范101条规则与最佳实践0.pdf (1.4 MB)
C语言代码规范(编程规范).pdf (1.02 MB)
华为C语言编程规范总则.pdf (183.8 KB)
华为的编程规范和范例.pdf (531.81 KB)
华为软件编程规范和范例(20200310185149).pdf (399.06 KB)
华为软件编程规范培训实例与练习.pdf (217.14 KB)


使用特权

评论回复
评论
keer_zu 2021-9-6 16:10 回复TA
谢谢T叔 
板凳
keer_zu|  楼主 | 2021-9-7 14:35 | 只看该作者
9、禁止在头文件中定义变量

在头文件中定义变量,将会由于头文件被其他.c文件包含而导致变量重复定义。

10、只能通过包含头文件的方式使用其他 .c 提供的接口,禁止在.c 中通过 extern 的方式使用外部函数接口、变量

若a.c使用了b.c定义的foo()函数,则应当在b.h中声明extern int foo(int input);并在a.c中通过#include <b.h>来使用foo。禁止通过在a.c中直接写extern int foo(int input);来使用foo,后面这种写法容易在foo改变时可能导致声明和定义不一致。

11、禁止在 extern "C" 中包含头文件

在extern "C"中包含头文件,会导致extern "C"嵌套,Visual Studio对extern "C"嵌套层次有限制,嵌套层次太多会编译错误。
在extern "C"中包含头文件,可能会导致被包含头文件的原有意图遭到破坏。

错误示例:
extern “C”
{
#include “xxx.h”
...
}

正确示例:
#include “xxx.h”
extern “C”
{
...
}


使用特权

评论回复
地板
keer_zu|  楼主 | 2021-9-7 14:36 | 只看该作者
12、一个模块通常包含多个 .c 文件,建议放在同一个目录下,目录名即为模块名。为方便外部使用者,建议每一个模块提供一个 .h ,文件名为目录名

需要注意的是,这个.h并不是简单的包含所有内部的.h,它是为了模块使用者的方便,对外整体提供的模块接口。以Google test(简称GTest)为例,GTest作为一个整体对外提供C++单元测试框架,其1.5版本的gtest工程下有6个源文件和12个头文件。但是它对外只提供一个gtest.h,只要包含gtest.h即可使用GTest提供的所有对外提供的功能,使用者不必关系GTest内部各个文件的关系,即使以后GTest的内部实现改变了,比如把一个源文件c拆成两个源文件,使用者也不必关心,甚至如果对外功能不变,连重新编译都不需要。对于有些模块,其内部功能相对松散,可能并不一定需要提供这个.h,而是直接提供各个子模块或者.c的头文件。

比如产品普遍使用的VOS,作为一个大模块,其内部有很多子模块,他们之间的关系相对比较松散,就不适合提供一个vos.h。而VOS的子模块,如Memory(仅作举例说明,与实际情况可能有所出入),其内部实现高度内聚,虽然其内部实现可能有多个.c和.h,但是对外只需要提供一个Memory.h声明接口。

13、如果一个模块包含多个子模块,则建议每一个子模块提供一个对外的 .h,文件名为子模块名

降低接口使用者的编写难度

14、头文件不要使用非习惯用法的扩展名,如 .inc

目前很多产品中使用了.inc作为头文件扩展名,这不符合c语言的习惯用法。在使用.inc作为头文件扩展名的产品,习惯上用于标识此头文件为私有头文件。但是从产品的实际代码来看,这一条并没有被遵守,一个.inc文件被多个.c包含比比皆是。

除此之外,使用.inc还导致source insight、Visual stduio等IDE工具无法识别其为头文件,导致很多功能不可用,如“跳转到变量定义处”。虽然可以通过配置,强迫IDE识别.inc为头文件,但是有些软件无法配置,如Visual Assist只能识别.h而无法通过配置识别.inc。

使用特权

评论回复
5
keer_zu|  楼主 | 2021-9-7 14:37 | 只看该作者

15、同一产品统一包含头文件排列方式

常见的包含头文件排列方式:功能块排序、文件名升序、稳定度排序。

正确示例1:以升序方式排列头文件可以避免头文件被重复包含:

-#include <a.h>
#include <b.h>
#include <c/d.h>
#include <c/e.h>
#include <f.h>

正确示例2:以稳定度排序,建议将不稳定的头文件放在前面,如把产品的头文件放在平台的头文件前面:

#include <product.h>
#include <platform.h>

相对来说,product.h修改的较为频繁,如果有错误,不必编译platform.h就可以发现product.h的错误,可以部分减少编译时间。



使用特权

评论回复
6
keer_zu|  楼主 | 2021-9-7 14:37 | 只看该作者
2、函数
函数设计的精髓:编写整洁函数,同时把代码有效组织起来。

整洁函数要求:代码简单直接、不隐藏设计者的意图、用干净利落的抽象和直截了当的控制语句将函数有机组织起来。

代码的有效组织包括:逻辑层组织和物理层组织两个方面。逻辑层,主要是把不同功能的函数通过某种联系组织起来,主要关注模块间的接口,也就是模块的架构。物理层,无论使用什么样的目录或者名字空间等,需要把函数用一种标准的方法组织起来。例如:设计良好的目录结构、函数名字、文件组织等,这样可以方便查找。

1、一个函数仅完成一件功能

一个函数实现多个功能给开发、使用、维护都带来很大的困难。

将没有关联或者关联很弱的语句放到同一函数中,会导致函数职责不明确,难以理解,难以测试和改动。

2、重复代码应该尽可能提炼成函数

重复代码提炼成函数可以带来维护成本的降低。

重复代码是我司不良代码最典型的特征之一。在“代码能用就不改”的指导原则之下,大量的烟囱式设计及其实现充斥着各产品代码之中。新需求增加带来的代码拷贝和修改,随着时间的迁移,产品中堆砌着许多类似或者重复的代码。

项目组应当使用代码重复度检查工具,在持续集成环境中持续检查代码重复度指标变化趋势,并对新增重复代码及时重构。当一段代码重复两次时,即应考虑消除重复,当代码重复超过三次时,应当立刻着手消除重复。

3、避免函数过长,新增函数不超过 50 行 (非空非注释行)

过长的函数往往意味着函数功能不单一,过于复杂。

函数的有效代码行数,即NBNC(非空非注释行)应当在[1,50]区间。

例外:某些实现算法的函数,由于算法的聚合性与功能的全面性,可能会超过50行。

延伸阅读材料: 业界普遍认为一个函数的代码行不要超过一个屏幕,避免来回翻页影响阅读;一般的代码度量工具建议都对此进行检查,例如Logiscope的函数度量:"Number of Statement" (函数中的可执行语句数)建议不超过20行,QA C建议一个函数中的所有行数(包括注释和空白行)不超过50行。

使用特权

评论回复
7
keer_zu|  楼主 | 2021-9-7 14:37 | 只看该作者
4、避免函数的代码块嵌套过深,新增函数的代码块嵌套不超过4层

函数的代码块嵌套深度指的是函数中的代码控制块(例如:if、for、while、switch等)之间互相包含的深度。每级嵌套都会增加阅读代码时的脑力消耗,因为需要在脑子里维护一个“栈”(比如,进入条件语句、进入循环„„)。应该做进一步的功能分解,从而避免使代码的阅读者一次记住太多的上下文。优秀代码参考值:[1, 4]。

错误示例:代码嵌套深度为5层:
void serial (void)
{
    if (!Received)
    {
        TmoCount = 0;
         switch (Buff)
        {
            case AISG**:
                if ((TiBuff.Count > 3)&& ((TiBuff.Buff[0] == 0xff) || (TiBuf.Buff[0] == CurPa.ADDR)))
                {
                    **7E = false;
                    Received = true;
                }
                else
                {
                    TiBuff.Count = 0;
                    **7D = false;
                    **7E = true;
                }
                break;
            default:
                break;
        }
    }
}

使用特权

评论回复
8
keer_zu|  楼主 | 2021-9-7 14:38 | 只看该作者
5、 可重入函数应避免使用共享变量;若需要使用,则应通过互斥手段(关中断、信号量)对其加以保护

可重入函数是指可能被多个任务并发调用的函数。在多任务操作系统中,函数具有可重入性是多个任务可以共用此函数的必要条件。共享变量指的全局变量和static变量。编写C语言的可重入函数时,不应使用static局部变量,否则必须经过特殊处理,才能使函数具有可重入性。

示例:函数square_exam返回g_exam平方值。那么如下函数不具有可重入性。
int g_exam;
unsigned int example( int para )
{
    unsigned int temp;
    g_exam = para; // (**)
    temp = square_exam ( );
    return temp;
}

使用特权

评论回复
9
keer_zu|  楼主 | 2021-9-7 14:39 | 只看该作者
此函数若被多个线程调用的话,其结果可能是未知的,因为当(**)语句刚执行完后,另外一个使用本函数的线程可能正好被激活,那么当新激活的线程执行到此函数时,将使g_exam赋于另一个不同的para值,所以当控制重新回到“temp =square_exam ( )”后,计算出的temp很可能不是预想中的结果。此函数应如下改进。
int g_exam;
unsigned int example( int para )
{
    unsigned int temp;
    [申请信号量操作] // 若申请不到“信号量”,说明另外的进程正处于
    g_exam = para; //给g_exam赋值并计算其平方过程中(即正在使用此
    temp = square_exam( ); // 信号),本进程必须等待其释放信号后,才可继
    [释放信号量操作] // 续执行。其它线程必须等待本线程释放信号量后
    // 才能再使用本信号。
    return temp;
}

使用特权

评论回复
10
keer_zu|  楼主 | 2021-9-7 14:40 | 只看该作者
6、对参数的合法性检查,由调用者负责还是由接口函数负责,应在项目组/模块内应统一规定。缺省由调用者负责。

对于模块间接口函数的参数的合法性检查这一问题,往往有两个极端现象,即:要么是调用者和被调用者对参数均不作合法性检查,结果就遗漏了合法性检查这一必要的处理过程,造成问题隐患;要么就是调用者和被调用者均对参数进行合法性检查,这种情况虽不会造成问题,但产生了冗余代码,降低了效率。

7、对函数的错误返回码要全面处理

一个函数(标准库中的函数/第三方库函数/用户定义的函数)能够提供一些指示错误发生的方法。这可以通过使用错误标记、特殊的返回数据或者其他手段,不管什么时候函数提供了这样的机制,调用程序应该在函数返回时立刻检查错误指示。

8、设计高扇入,合理扇出(小于7)的函数

扇出是指一个函数直接调用(控制)其它函数的数目,而扇入是指有多少上级函数调用它。


扇出过大,表明函数过分复杂,需要控制和协调过多的下级函数;而扇出过小,例如:总是1,表明函数的调用层次可能过多,这样不利于程序阅读和函数结构的分析,并且程序运行时会对系统资源如堆栈空间等造成压力。通常函数比较合理的扇出(调度函数除外)通常是3~5。

扇出太大,一般是由于缺乏中间层次,可适当增加中间层次的函数。扇出太小,可把下级函数进一步分解多个函数,或合并到上级函数中。当然分解或合并函数时,不能改变要实现的功能,也不能违背函数间的独立性。扇入越大,表明使用此函数的上级函数越多,这样的函数使用效率高,但不能违背函数间的独立性而单纯地追求高扇入。公共模块中的函数及底层函数应该有较高的扇入。

较良好的软件结构通常是顶层函数的扇出较高,中层函数的扇出较少,而底层函数则扇入到公共模块中。


使用特权

评论回复
11
keer_zu|  楼主 | 2021-9-7 14:41 | 只看该作者
9、废弃代码(没有被调用的函数和变量) ) 要及时清除

程序中的废弃代码不仅占用额外的空间,而且还常常影响程序的功能与性能,很可能给程序的测试、维护等造成不必要的麻烦。

10、函数不变参数使用const

不变的值更易于理解/跟踪和分析,把const作为默认选项,在编译时会对其进行检查,使代码更牢固/更安全。

正确示例:C99标准 7.21.4.4 中strncmp 的例子,不变参数声明为const。

int strncmp(const char *s1, const char *s2, register size_t n)
{
    register unsigned char u1, u2;
    while (n-- > 0)
    {
        u1 = (unsigned char) *s1++;
        u2 = (unsigned char) *s2++;
        if (u1 != u2)
        {
            return u1 - u2;
        }
        if (u1 == '\0')
        {
            return 0;
        }
    }
    return 0;
}

使用特权

评论回复
12
keer_zu|  楼主 | 2021-9-7 14:42 | 只看该作者
11、函数应避免使用全局变量、静态局部变量和 I/O 操作,不可避免的地方应集中使用

带有内部“存储器”的函数的功能可能是不可预测的,因为它的输出可能取决于内部存储器(如某标记)的状态。这样的函数既不易于理解又不利于测试和维护。在C语言中,函数的static局部变量是函数的内部存储器,有可能使函数的功能不可预测。

错误示例:如下函数,其返回值(即功能)是不可预测的。
unsigned int integer_sum( unsigned int base )
{
    unsigned int index;
    static unsigned int sum = 0;// 注意,是static类型的。
    // 若改为auto类型,则函数即变为可预测。
    for (index = 1; index <= base; index++)
    {
        sum += index;
    }
    return sum;
}

使用特权

评论回复
13
keer_zu|  楼主 | 2021-9-7 14:43 | 只看该作者
12、检查函数所有非参数输入的有效性,如数据文件、公共变量等

函数的输入主要有两种:一种是参数输入;另一种是全局变量、数据文件的输入,即非参数输入。函数在使用输入参数之前,应进行有效性检查。

13、 函数的参数个数不超过5个

函数的参数过多,会使得该函数易于受外部(其他部分的代码)变化的影响,从而影响维护工作。函数的参数过多同时也会增大测试的工作量。

函数的参数个数不要超过5个,如果超过了建议拆分为不同函数。

14、除打印类函数外,不要使用可变长参函数。

可变长参函数的处理过程比较复杂容易引入错误,而且性能也比较低,使用过多的可变长参函数将导致函数的维护难度大大增加。

15、在源文件范围内声明和定义的所有函数,除非外部可见,否则应该增加static关键字

如果一个函数只是在同一文件中的其他地方调用,那么就用static声明。使用static确保只是在声明它的文件中是可见的,并且避免了和其他文件或库中的相同标识符发生混淆的可能性。

正确示例:建议定义一个STATIC宏,在调试阶段,将STATIC定义为static,版本发布时,改为空,以便于后续的打热补丁等操作。
#ifdef _DEBUG
#define STATIC static
#else
#define STATIC
#endif

使用特权

评论回复
14
keer_zu|  楼主 | 2021-9-7 14:44 | 只看该作者
3、标识符命名与定义
标识符的命名规则历来是一个敏感话题,典型的命名风格如unix风格、windows风格等,从来无法达成共识。实际上,各种风格都有其优势也有其劣势,而且往往和个人的审美观有关。我们对标识符定义主要是为了让团队的代码看起来尽可能统一,有利于代码的后续阅读和修改,产品可以根据自己的实际需要指定命名风格,规范中不再做统一的规定。

1、标识符的命名要清晰、明了,有明确含义,同时使用完整的单词或大家基本可以理解的缩写,避免使人产生误解

尽可能给出描述性名称,不要节约空间,让别人很快理解你的代码更重要。

正确示例:
int error_number;
int number_of_completed_connection;


错误示例:
int n;
int nerr;
int n_comp_conns;

使用特权

评论回复
15
keer_zu|  楼主 | 2021-9-7 14:45 | 只看该作者
2、除了常见的通用缩写以外,不使用单词缩写,不得使用汉语拼音

较短的单词可通过去掉“元音”形成缩写,较长的单词可取单词的头几个字母形成缩写,一些单词有大家公认的缩写,常用单词的缩写必须统一。协议中的单词的缩写与协议保持一致。对于某个系统使用的专用缩写应该在注视或者某处做统一说明。

正确示例:一些常见可以缩写的例子:

argument 可缩写为 arg
buffer 可缩写为 buff
clock 可缩写为 clk
command 可缩写为 cmd
compare 可缩写为 cmp
configuration 可缩写为 cfg
device 可缩写为 dev
error 可缩写为 err
hexadecimal 可缩写为 hex
increment 可缩写为 inc
initialize 可缩写为 init
maximum 可缩写为 max
message 可缩写为 msg
minimum 可缩写为 min
parameter 可缩写为 para
previous 可缩写为 prev
register 可缩写为 reg
semaphore 可缩写为 sem
statistic 可缩写为 stat
synchronize 可缩写为 sync
temp 可缩写为 tmp


使用特权

评论回复
16
keer_zu|  楼主 | 2021-9-7 14:46 | 只看该作者
2、除了常见的通用缩写以外,不使用单词缩写,不得使用汉语拼音

较短的单词可通过去掉“元音”形成缩写,较长的单词可取单词的头几个字母形成缩写,一些单词有大家公认的缩写,常用单词的缩写必须统一。协议中的单词的缩写与协议保持一致。对于某个系统使用的专用缩写应该在注视或者某处做统一说明。

正确示例:一些常见可以缩写的例子:

argument 可缩写为 arg
buffer 可缩写为 buff
clock 可缩写为 clk
command 可缩写为 cmd
compare 可缩写为 cmp
configuration 可缩写为 cfg
device 可缩写为 dev
error 可缩写为 err
hexadecimal 可缩写为 hex
increment 可缩写为 inc
initialize 可缩写为 init
maximum 可缩写为 max
message 可缩写为 msg
minimum 可缩写为 min
parameter 可缩写为 para
previous 可缩写为 prev
register 可缩写为 reg
semaphore 可缩写为 sem
statistic 可缩写为 stat
synchronize 可缩写为 sync
temp 可缩写为 tmp


使用特权

评论回复
17
keer_zu|  楼主 | 2021-9-7 14:48 | 只看该作者
3、产品/项目组内部应保持统一的命名风格

Unix like和windows like风格均有其拥趸,产品应根据自己的部署平台,选择其中一种,并在产品内部保持一致。

4、用正确的反义词组命名具有互斥意义的变量或相**作的函数等

正确示例:
add/remove begin/end create/destroy
insert/delete first/last get/release
increment/decrement put/get add/delete
lock/unlock open/close min/max
old/new start/stop  next/previous
source/target show/hide  send/receive
source/destination copy/paste up/down

使用特权

评论回复
18
keer_zu|  楼主 | 2021-9-7 14:49 | 只看该作者
5、尽量避免名字中出现数字编号,除非逻辑上的确需要编号

正确示例:应改为有意义的单词命名。
#define EXAMPLE_UNIT_TEST_
#define EXAMPLE_ASSERT_TEST_


错误示例:如下命名,使人产生疑惑。
#define EXAMPLE_0_TEST_
#define EXAMPLE_1_TEST_

使用特权

评论回复
19
keer_zu|  楼主 | 2021-9-7 14:51 | 只看该作者
6、标识符前不应添加模块、项目、产品、部门的名称作为前缀

很多已有代码中已经习惯在文件名中增加模块名,这种写法类似匈牙利命名法,导致文件名不可读,并且带来带来如下问题:

第一眼看到的是模块名,而不是真正的文件功能,阻碍阅读;
文件名太长;
文件名和模块绑定,不利于维护和移植。若foo.c进行重构后,从a模块挪到b模块,若foo.c
中有模块名,则需要将文件名从a_module_foo.c改为b_module_foo.c。
7、平台/ / 驱动等适配代码的标识符命名风格保持和平台

涉及到外购芯片以及配套的驱动,这部分的代码变动(包括为产品做适配的新增代码),应该保持原有的风格。

8、重构/修改部分代码时,应保持和原有代码的命名风格一致

根据源代码现有的风格继续编写代码,有利于保持总体一致。

9、文件命名统一采用小写字符

因为不同系统对文件名大小写处理会不同(如MS的DOS、Windows系统不区分大小写,但是Linux系统则区分),所以代码文件命名建议统一采用全小写字母命名。

10、全局变量应增加“g_” 前缀,静态变量应增加“s_”

首先,全局变量十分危险,通过前缀使得全局变量更加醒目,促使开发人员对这些变量的使用更加小心。

其次,从根本上说,应当尽量不使用全局变量,增加g_和s_前缀,会使得全局变量的名字显得很丑陋,从而促使开发人员尽量少使用全局变量。

11、禁止使用单字节命名变量,但 允许 定义i 、j、k作为局部循环变量

12、 不建议使用匈牙利命名法

匈牙利命名法是一种编程时的命名规范。基本原则是:变量名=属性+类型+对象描述。匈牙利命名法源于微软,然而却被很多人以讹传讹的使用。而现在即使是微软也不再推荐使用匈牙利命名法。历来对匈牙利命名法的一大诟病,就是导致了变量名难以阅读,这和本规范的指导思想也有冲突,所以本规范特意强调,变量命名不应采用匈牙利命名法,而应该想法使变量名为一个有意义的词或词组,方便代码的阅读。

变量命名需要说明的是变量的含义,而不是变量的类型。在变量命名前增加类型说明,反而降低了变量的可读性;更麻烦的问题是,如果修改了变量的类型定义,那么所有使用该变量的地方都需要修改。

13、使用名词或者形容词+名词方式命名变量

使用特权

评论回复
20
keer_zu|  楼主 | 2021-9-7 14:51 | 只看该作者
14、函数命名应以函数要执行的动作命名,一般采用动词或者动词+名词的结构

正确示例:找到当前进程的当前目录:

DWORD GetCurrentDirectory( DWORD BufferLength, LPTSTR Buffer );

15、函数指针除了前缀,其他按照函数的命名规则命名

16、对于数值或者字符串等等常量的定义,建议采用全大写字母,单词之间加下划线“_”的方式命名(枚举同样建议使用此方式定义)

正确示例:

#define PI_ROUNDED 3.14



使用特权

评论回复
发新帖 我要提问
您需要登录后才可以回帖 登录 | 注册

本版积分规则

个人签名:qq群:49734243 Email:zukeqiang@gmail.com

1352

主题

12436

帖子

53

粉丝