嵌入式软件开发之程序编程规范(四)

前言

作为一个软件开发人员,应养成良好的编程习惯,随着编码越来越多,内容也会变得越来越多,规范化自己的编程有助于为了在程序代码量很大的时候,便于自己阅读,也便于别人阅读(团队合作),特别是作为一个合格的开发者,更需要规范自身写的程序代码,形成一种良好的习惯。

记得之前看过一本书,其中我感触最深的一句话就是“代码是写给人看的,不是写给机器看的,只是顺便计算机可以执行而已”

在C语言中不遵守编译器的规定,编译器在编译时就会报错,这个规定叫作规则。但是有一种规定,它是一种人为的、约定成俗的,即使不按照那种规定也不会出错,这种规定就叫作规范。

下篇:嵌入式软件开发之程序编程规范(五)

参考文献

  • 《代码整洁之道》
  • 《华为C编程规范》

规范化的好处

 1. 看着很整齐、很舒服,可读性高。

假如你现在用不规范的方式写了一万行代码,现在能看得懂,但等过了三个月你再回头看时就很吃力了,更不要说给别人看了。

 2. 程序不容易出错,方便排查编码问题

代码写规范的话,能避免很多语法错误,即使出错了查错会很方便。


如何规范化

首先我们先看一下非规范和规范后的代码,你更喜欢看哪一种呢?

 

总体原则

1、清晰第一

清晰性是易于维护、易于重构的程序必需具备的特征,一般情况下,代码的可阅读性高于性能,只有确定性能是瓶颈时,才应该主动优化

2、简洁为美

代码越简单越好,代码一行只做一件事,一个函数只完成一个功能等,代码越长越难以看懂,也就越容易在修改时引入错误

3、选择合适的风格,与代码原有风格保持一致

因为软件开发不可避免要维护他人代码或者和他人合作开发,此时就需要尽可能和与原有代码风格保持一致或者和合作者达成共识的代码风格,否则每个人都使用自己的代码风格,在阅读时变得十分吃力


排版风格

代码规范化中的排版基本上有七大风格,主要体现在缩进、空行、代码行、空格、成对书写、对齐、注释七方面的书写规范上。

【规范一】缩进

程序块采用缩进风格编写,缩进可以使程序更有层次感,每次缩进一般为 4 个空格(部分要求为一个制表符Tab)

原则是:如果地位相等,则不需要缩进;如果属于某一个代码的内部代码就需要缩进

【规范二】空行

空行起着分隔程序段落的作用。空行得体将使程序的布局更加清晰。空行不会浪费内存,虽然在文档中比较多,但是值得

1、定义变量后要空行。尽可能在定义变量的同时初始化该变量,即遵循就近原则;如果变量的引用和定义相隔比较远,那么变量的初始化就很容易被忘记。若引用了未被初始化的变量,就会导致程序出错

2、每个函数定义结束之后都要加空行

3、结合1、2两点综合来说,相对独立的程序块之间、变量说明之后必须加空行。比如上面几行代码完成的是一个功能,下面几行代码完成的是另一个功能,那么它们中间就要加空行。这样看起来更清晰

【规范三】代码行

1、一行代码只做一件事情,如只定义一个变量,或只写一条语句。这样的代码容易阅读,并且便于写注释

2、if for do while case switch default 等语句独占一行,执行语句不得紧跟其后(不论执行语句有多少行,就算只有一行也要加{},并且遵循对齐的原则,这样可以防止书写失误)

3、一条语句不能过长,如不能拆分需要分行写,对于目前大多数的PC来说,132 比较合适(80/132是VTY常见的行宽值);对于新PC宽屏显示器较多的产品来说,可以设置更大的值

【规范四】空格

1、关键字之后要留空格。像 constcase 等关键字之后至少要留一个空格,否则无法辨析关键字。像 ifforwhile 等关键字之后应留一个空格再跟左括号(,以突出关键字

2、函数名之后不要留空格,应紧跟左括号(,以与关键字区别

3、(向后紧跟;),;这三个向前紧跟;紧跟处不留空格

4、,之后要留空格。如果;不是一行的结束符号,其后要留空格

5、赋值运算符、关系运算符、算术运算符、逻辑运算符、位运算符,如 ===!=+=-=*=/=%=>>=<<=&=^=|=><=>>=+*/%&|&&||<<>>^ 等双目运算符的前后应当加空格(运算符“%”是求余运算符,与 printf 中 %d 的“%”不同,所以 %d 中的“%”前后不用加空格)

6、单目运算符 !~++--*& 等前后不需要加空格

7、像数组符号[]、结构体成员运算符.、指向结构体成员运算符->,这类操作符前后不加空格

8、对于表达式比较长的 for 语句和 if 语句,为了紧凑起见,可以适当地去掉一些空格。但 for 和 if 后面紧跟的空格不可以删,其后面的语句可以根据语句的长度适当地去掉一些空格。例如:for (i=0; i<10; i++),即 for 和分号后面保留空格,=<前后的空格去掉

【规范五】成对书写

成对的符号一定要成对书写,如 ()、{}。不要写完左括号然后写内容最后再补右括号,这样很容易漏掉右括号,尤其是写嵌套程序的时候。

【规范六】对齐

对齐主要是针对大括号{}说的

1{ }分别都要独占一行。互为一对的{}要位于同一列,并且与引用它们的语句左对齐

2{ }之内的代码要向内一个缩进,且同一地位的要左对齐,地位不同的继续缩进

【规范七】注释

C语言中一行注释一般采用//…,多行注释必须采用 /*…*/。注释通常用于重要的代码行或段落提示。在一般情况下,源程序有效注释量必须在 20% 以上。虽然注释有助于理解代码,但注意不可过多地使用注释,注释太多会让人眼花缭乱

1、注释用使用对代码的功能做解释,并不是说明是怎么做的

2、对于一些巧妙地、用特殊方式实现功能的代码,可以使用注释说明这样做的目的或好处等

3、代码十分明确的,一目了然的,则不必增加注释,否则就是多余的注释,如 int minValue = 5; // 定义最小值为5

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

5、每一条宏定义的右边必须要有注释,说明其作用

6、相关函数和结构体等需要注释,具体注释方式请参考文档中的“注释规范”内容


命名规范

介绍

标识符的命名规则历来是一个敏感话题,典型的命名风格如unix风格、windows风格等,从来无法达成共识。实际上,各种风格都有其优势也有其劣势,而且往往和个人的审美观有关。对标识符定义主要是为了让团队的代码看起来尽可能统一,有利于代码的后续阅读和修改。

命名完全体现了程序的可阅读性和可理解性,在一定程度上是不需要写注释也能看懂代码。

目前,业界共有四种命名法则:驼峰命名法、匈牙利命名法、帕斯卡命名法和下划线命名法,其中前三种是较为流行的命名法。

printEmployeePaychecks();  // 驼峰命名法

print_employee_paychecks();  // 下划线命名法

PrintEmployeePaychecks(); // 帕斯卡命名法

其中,驼峰命名法和帕斯卡命名法类似,但是区别在第一个字母是否大小写,因此驼峰命名法一般称小驼峰命名法,帕斯卡命名法称大驼峰命名法

【命名法一】驼峰命名法

正如它的名称所表示的那样,是指混合使用大小写字母来构成变量和函数的名字(这样的变量名看上去就像骆驼峰一样此起彼伏,故得名),又叫小驼峰命名法。

当变量名或函数名是由一个或多个单词连结在一起,而构成的唯一识别字时,第一个单词以小写字母开始;从第二个单词开始以后的每个单词的首字母都采用大写字母。

如:printEmployeePaychecks();

【命名法二】匈牙利命名法

通过在变量名前面加上相应的小写字母的符号标识作为前缀,标识出变量的作用域、类型等。这些符号可以多个同时使用,顺序是先m_(成员变量)、再指针、再简单数据类型、再其它。这样做的好处在于能增加程序的可读性,便于对程序的理解和维护

匈牙利命名法的规则是:作用域(属性)+类型+描述。

如:定义一个全局变量,指向类型为int的最小值指针,变量命名为 int g_pMinValue = NULL;

分类 前缀 描述
作用域(属性) 局部变量
m_ 类成员变量(C++)
sm_ 类的静态成员变量(C++)
s_ 静态变量
g_ 全局变量
sg_ 静态全局变量
c 常量
类型 b bool 变量,不过我一般是用 is,如定义一个数据接收完成标志,则 bool isRecvEnd;
sz 以 \0 结束的字符串
str string 类型字符串(C++)
p 指针变量
uc 无符号单字节整数型(signed char)
us 无符号双字节整数型(signed short)
ui 无符号四字节整数型(signed int)
c 有符号单字节整数型(char)
s 有符号双字节整数型(short)
i 有符号四字节整数型(int)
arr 数组
l 长整型
f 浮点型变量
pfn 函数指针
stu 结构体变量
e 枚举变量
... ...
类型前缀可以组合使用,例如"arrc"表示字符数组,"ppn"表示指向整型的指针的指针等等。

【命名法三】帕斯卡命名法

是指混合使用大小写字母来构成变量和函数的名字,每个单词的第一个字母都大写,又叫大驼峰式命名法;

如:PrintEmployeePaychecks();

【命名法四】下划线命名法

使用下划线(_)连接组成的标识符,即 单词(字母或数字)_单词(字母或数字)_单词(字母或数字)

如:函数print_employee_paychecks();,变量 min_value


标识符命名

根据不同场景使用不同命名规则,业内比较常用的方式。

【文件名】驼峰命名法,有时也使用下划线命名法,根据不同情况使用,尽量和之前的保持一致

因为不同系统对文件名大小写处理会有所不同(如Windows系统不区分大小写,但是Linux系统则区分)

【变量名】匈牙利命名法 + 驼峰命名法两种结合使用

具体规则是:属性+类型+描述( 驼峰命名法)

1、使用名词或者形容词 + 名词方式命名变量(描述)

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

宏定义/枚举】大写 + 下划线命名法

1、对于数值或者字符串等等常量或者枚举的定义,全部采用全大写字母,单词之间加下划线“_”的方式命名。如 #define MIN_VALUE   5

2、除了头文件或编译开关等特殊标识符定义,宏定义不能使用下划线“_”开头和结尾(一般来说,下划线开头或结尾的宏都是一些内部的定义)

【函数名】帕斯卡命名法

以函数要执行的动作命名,一般采用动词或者动词+名词的结构,且符合帕斯卡命名法

【通用规则】除了上述规则外,关于以上标识符的命名还应该符合以下规则

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

如:良好的命名:int error_number;   不好的命名:int n,nerr

2、标识符的长度应当符合“min-length && max-information”原则

如:几十年前老ANSI C规定名字不准超过6个字符,现今的C++/C不再有此限制。一般来说,长名字能更好地表达含义,所以函数名、变量名、类名长达十几个字符不足为怪。那么名字是否越长约好?不一定,例如变量名 maxval 就比 maxValueUntilOverflow 好用

3、除了常见的通用缩写外,不使用单词缩写,不得使用汉语拼音

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

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

如:add/remove,begin/end,creat/destroy等

5、尽量避免名字中出现数字编号,除非逻辑上的确需要编号

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

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

 

更多相关规范可以自行百度C语言编程规范


注释风格

注释的原则是有助于对程序的阅读理解以及提供二次开发所需文档,注释的方式有很多,但是业内常用的规范是Doxygen 代码注释规范。遵循原则为,说明性文件、函数接口必须充分注释说明。全局变量需要说明功能及取值范围,需要自行处理资料函数需要加上使用警告信息。

    不要使用注释来屏蔽代码。

    关于函数和局部变量的注释,当代码已经可自注释时,不用添加多余的注释。

介绍

Doxygen是一种开源跨平台的,以类似 JavaDoc 风格描述的文档系统,完全支持 C、C++、Java、Objective-C 和 IDL 语言,部分支持 PHP、C#。注释的语法与 Qt-Doc、Kdoc 和 JavaDoc 兼容。Doxygen 可以从一套归档源文件开始,生成 HTML 格式的在线类浏览器,或离线的 LATE、RTF 参考手册。

Doxygen 能将程序中的特定批注转换成为说明文档。他可以 依据程序本身的结构,将程序中按规范注释的批注经过处理生成一个纯粹的参考手册,通过提取代码结构或借助自动生成的包含依赖图、继承图以及协作图来可视化文档之间的关系,Doxygen 生成的帮助文档的格式可以是 CHM、 RTF、PostScript、PDF、HTML等。

注释规范

以下只说明部分注释内容,详情可自行搜索 Doxygen

  • 单行注释:///或者//!
  • 多行注释:/**或者/*!

文件注释

文件注释通常放在整个文件开头,如说明文件名、作者、日期、描述或版本等诸多信息,具体参数 Doxygen 的文件注释风格。

/**
 * @file      文件名
 * @brief     简介
 * @details   细节
 * @mainpage  工程概览
 * @author    作者
 * @email     邮箱
 * @version   版本号
 * @date      年-月-日
 * @license   版权
 */

我常用的风格如下:

/**
  ******************************************************************************
  * @file    adcDrive.c
  * @author  const-zpc
  * @date    2020-7-20
  * @brief   该文件提供ADC驱动功能,以管理ADC驱动的以下功能:
  *           + 初始化
  *           + ADC数据
  *
  ******************************************************************************
  * @attention
  * 暂无
  *
  ******************************************************************************
  */

通过工具生成文档的效果

类/结构体注释

类或者结构体定义的注释方式非常简单,使用@brief后面填写类的概述,换行填写类的详细信息

/**
 * @brief   CAN发送帧类型结构体定义.
 */
typedef struct {
    uint32_t id;                            /*!< 帧的标识符ID */
    CAN_IdTypeDef emIdType;                 /*!< 帧的类型 */
    CAN_RtrTypeDef emRtrType;               /*!< 帧的格式 */
    uint8_t lenth;                          /*!< 帧的数据长度 */
    uint8_t data[8];                        /*!< 帧的数据内容 */
} CAN_TxFrameType;

生成文档的效果

枚举注释

/**
  * @brief  CAN使能/禁止枚举定义
  */
typedef enum{
    CAN_DISABLE = 0,                        /*!< (0)禁止 */
    CAN_ENABLE = !CAN_DISABLE               /*!< (1)使能 */
}CAN_EnableTypeDef;

/**
 * @brief   CAN帧的格式枚举定义.
 */
typedef enum {
    CAN_DATA_FRAME = 0,                     /*!< (0)数据帧 */
    CAN_REMOTE_FRAME                        /*!< (1)远程帧 */
} CAN_RtrTypeDef;

生成文档的效果

函数注释

/**
  * @brief      读取指定CAN接收帧的数据信息.
  * @note       建议先调用函数CAN_GetRxFrameUnreadNumber获取未读数目, 否则读取的值为0
  * @param[in]  rxIndex g_arrtCANRxFrameInfo 的索引值
  * @param[out] pData[8] - CAN帧数据内容指针.
  * @retval     数据长度
  */
uint8_t CAN_ReadRxFrameInfo(uint16_t rxIndex, uint8_t pData[8])
{
...
}

 

 

 

posted @ 2022-06-10 19:04  大橙子疯  阅读(437)  评论(0编辑  收藏  举报