资讯详情

编程(代码、软件)规范(适用嵌入式、单片机、上位机等)

前言

第1章 文件

1.1头文件

1.2定义文件

第2章 注释规范

2.1 共性注释规范

2.2 文件注释规范

2.3 C语言风格注释规范

第3章 排版规范

3.1 缩进与对齐 风格

3.2 空行

3.3 代码行

3.4 代码行中的空间

3.5 长行拆分

第4章 标识符命名规范

4.1 共性命名规则

4.2 文件命名规则

4.3 变量命名规则

4.4 函数命名规则

4.5 宏的命名规则

第5章 变量

第6章 常量、宏

第7章 数据类型

附件1 缩写(英语)(元音:aeiou)

附件2 常用的反义词组(英语)

附件3 Doxygen关键字表常用于格式

附件4 标识符命名个人风格

附件5 常见标识符号命名风格类型


前言

本编程规范是个人工作十多年,近年来形成相对稳定的版本,已被公司采用为公司编程规范!本规范主要借鉴一些大公司(华为、百度、腾讯、阿里巴巴、谷歌、苹果、微软、ARM,ST...)编程规范和风格,吸收它们的共性和个别优点。我咨询了一些几十年的老工程师和华为等大公司的软件工程师!

我创造了自己的编程风格,也想创造世界上最完美的编程规范和风格。在当时的技术水平和行业限制下,我感觉很好!随着经验的丰富,接触的技术越来越多,发现一些规则和适用的环境不再适用,甚至一些鸡肋也增加了负担!这方面也花费了很多毫无价值的时间!后来痛苦地改变了编程规范和风格。不再原创,符合世界,符合大行业更受欢迎,大公司一般,大公司个人优秀的风格规范作为自己的编程规范和风格,只是吸收每个人的优势和共同点,所以换工作也可以适用,新可以快速适应,因为大多数人的规范和风格,少数服从大多数。

不要试图创建最完美的编程规范和风格,每种风格都有优缺点,这些都不是我们工作的重点!编程规范和风格是给人看的,方便人们交流和阅读。每个人都有不同的喜好,很难满足和统一,就像世界很难统一一一样。求同存异,兼容并包!芯片的编程规格和风格都是一样的,最终会被翻译成机器代码!

每个程序通常分为两个文件。用于保存程序声明的文件(),称为头文件。另一个文件用于实现保存程序(),称为定义()文件。

程序的头文件以“”为后缀,程序的定义文件以“”为后缀,程序的定义文件通常以“”为后缀(也有一些系统以“”或“”为后缀)。

        对于C语言来说,头文件的设计体现了大部分的系统设计。不合理的头文件布局是编译时间过长的根因。头文件主要由三部分内容组成:头文件注释;预处理块;函数声明等。

        通过头文件来调用库功能。在很多场合,源代码不便(或不准)向用户公布,只要向用户提供头文件和二进制的库即可。用户只需要按照头文件中的接口声明来调用库功能,而不必关心接口怎么实现的。编译器会从库中提取相应的代码。

        某产品曾经做过一个实验,把所有函数的实现通过工具注释掉,其编译时间只减少了不到10%,究其原因,在于A包含B,B包含C,C包含D,最终几乎每一个源文件都包含了项目组所有的头文件,从而导致绝大部分编译时间都花在解析头文件上。

        某产品更有一个“优秀实践”,用于将.c文件通过工具合并成一个比较大的.c文件,从而大幅度提高编译效率。其根本原因还是在于通过合并.c文件减少了头文件解析次数。但是,这样的“优秀实践”是对合理划分.c文件的一种破坏。

        大部分产品修改一处代码,都得需要编译整个工程,对于TDD之类的实践,要求对于模块级别的编译时间控制在秒级,即使使用分布式编译也难以实现,最终仍然需要合理的划分头文件、以及头文件之间的包含关系,从根本上降低编译时间。

    《google C++ Style Guide》1.2 头文件依赖 章节也给出了类似的阐述:若包含了头文件aa.h,则就引入了新的依赖:一旦aa.h被修改,任何直接和间接包含aa.h代码都会被重新编译。如果aa.h又包含了其他头文件如bb.h,那么bb.h的任何改变都将导致所有包含了aa.h的代码被重新编译,在敏捷开发方式下,代码会被频繁构建,漫长的编译时间将极大的阻碍频繁构建。因此,我们倾向于减少包含头文件,尤其是在头文件中包含头文件,以控制改动代码后的编译时间。

合理的头文件划分体现了系统设计的思想,但是从编程规范的角度看,仍然有一些通用的方法,用来合理规划头文件。本章节介绍的一些方法,对于合理规划头文件会有一定的帮助。

说明:头文件是模块(Module)或单元(Unit)的对外接口。变量定义应在.c中定义,在.h中仅声明,尽量不要使用全局变量作为接口,变量是程序内部实现细节,应通过函数接口的方式对外暴露。

个人理解如下:

h文件:包含需用头文件、对外类型和枚举、对外宏(常量)定义、对外变量声明(尽量不用)、对外函数声明。

c文件:包含同名头文件、内部类型和枚举、内部宏(常量)定义、内部变量定义、内部函数定义与实现。延伸阅读材料:《C语言接口与实现》(David R. Hanson 著 傅蓉 周鹏 张昆琪 权威 译 机械工业出版社 2004年1月)(英文版: "C Interfaces and Implementations")

说明:头文件过于复杂,依赖过于复杂是导致编译时间过长的主要原因。很多现有代码中头文件过大,职责过多,再加上循环依赖的问题,可能导致为了在.c中使用一个宏,而包含十几个头文件。

例如: 如下是某平台定义WORD类型的头文件:

#include <VXWORKS.H>

#include <STDIO.H>

#include <STDLIB.H>

#include <SYSLIB.H>

…

typedef unsigned short WORD;

…

        这个头文件不但定义了基本数据类型WORD,还包含了stdio.h syslib.h等等不常用的头文件。如果工程中有10000个源文件,而其中100个源文件使用了stdio.h的printf,由于上述头文件的职责过于庞大,而WORD又是每一个文件必须包含的,从而导致stdio.h/syslib.h等可能被不必要的展开了9900次,大大增加了工程的编译时间。

说明:头文件循环依赖,是指a.h包含b.h,b.h包含c.h,c.h包含a.h之类。

说明:自包含是指任意一个头文件均可独立编译。如果a文件用到b文件内容,本应该a文件包含b.h头文件即可,但要包含b.h头文件前还要先包含c.h头文件才能工作的话,增添不必要的负担。

例如: a文件用到b文件内容

#include "c.h" //要想用b文件,a文件必须先包含c.h文件,这种情况就不是自包含。
#include "b.h" //a文件本应该包含b.h就可以。

说明:没有在宏最前面加上“_",是因为一般以"_"和”__"开头的标识符为系统保留或者标准库使用,在有些静态检查工具中,若全局可见的标识符以"_"开头会给出告警。后面加"_"是为了与程序用的宏定义区别。

例如:

#idndef LED_H_ //很明显这一部分也是为了防止重复引用

#define LED_H_

……

#endif /* LED_H_ */

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

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

错误示例:

extern “C”
{
#include “xxx.h”
...
}

正确示例:

#include “xxx.h”
extern “C”
{
...
}

说明:extern "C"的主要作用就是为了能够正确实现C++代码调用其他C语言代码。加上extern "C"后,会指示编译器这部分代码按C语言(而不是C++)的方式进行编译。由于C++支持函数重载,因此编译器编译函数的过程中会将函数的参数类型也加到编译后的代码中,而不仅仅是函数名;而C语言并不支持函数重载,因此编译C语言代码的函数时不会带上函数的参数类型,一般只包括函数名。

示例:  led头文件

#ifndef LED_H_ //很明显这一部分也是为了防止重复引用
#define LED_H_

#include"xxxx.h"

#ifdef __cplusplus //C++编译环境中(cpp文件定义了)定义了宏__cplusplus (plus plus就是"+ +"的意思)
extern "C"{ // 声明括号内是C语言代码,告诉C++编译器用C语言进行编译,这样C++就可以调用C头文件了
#endif

… //其他代码

#ifdef __cplusplus
}
#endif

#endif  /* LED_H_ */

说明:需要注意的是,这个.h并不是简单的包含所有内部的.h,它是为了模块使用者的方便,对外整体提供的模块接口。

说明:降低接口使用者的编写难度。

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

#include <C标准库头文件>
#include <C++标准库头文件>
#include "平台头文件" //MCU厂家、系统等头文件
#include "第三方头文件" //公司或第三方提供的成熟头文件
#include "自己写的基础全局头文件"
          //base.h基础头文件,提供常用的数据类型、修饰符以及基础操作宏
          //global.h包含了其他基础的应用的头文件,第一个文件包含在应用相关c文件中。

#include "自己写的工具头文件"
#include "本项目头文件"
#include "本文头文件"

  

        定义文件主要由三部分内容:定义文件注释;头文件的引用;程序的实现体(包括数据和代码)。

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

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

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

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

说明:本规则仅对新增函数做要求,对已有函数修改时,建议不增加代码行。

说明:本规则仅对新增函数做要求,对已有的代码建议不增加嵌套层次。

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

说明:可重入函数是指可能被多个任务并发调用的函数。在多任务操作系统中,函数具有可重入性是多个任务可以共用此函数的必要条件。共享变量指的全局变量和static变量。

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

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

说明:函数的参数过多,会使得该函数易于受外部(其他部分的代码)变化的影响,从而影响维护工作。函数的参数过多同时也会增大测试的工作量。如果超过了5个建议拆分为不同函数。

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

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

        良好的注释可以提高程序的可读性、易防错性、易维护性等。

说明:优秀的代码不写注释也可轻易读懂,注释无法把糟糕的代码变好,需要很多注释来解释的代码往往存在坏味道,需要重构。

说明:错误的注释不但无益反而有害。

说明:注释的目的是解释代码的目的、功能和采用的方法,提供代码以外的信息,帮助读者理解代码,防止没必要的重复注释信息。 

示例:如下注释意义不大。 

/* if receive_flag is TRUE */
if (receive_flag)

而如下的注释则给出了额外有用的信息。 

/* if mtp receive a message from links */
if (receive_flag)

说明:在使用缩写时或之前,应对缩写进行必要的说明。

说明:可使程序排版整齐,并方便注释的阅读与理解。

说明:除非必要,不应在代码或表达中间插入注释,否则容易使代码可理解性变差。

说明:注释语言不统一,影响程序易读性和外观排版,出于对维护人员的考虑,建议使用中文。

说明:采用工具可识别的注释格式,例如Doxygen格式,方便工具导出注释形成帮助文档。(常见工具:Doxygen,JavaDoc,……)

示例:单行注释格式 1 采用风格:/** …… */  或 ///……  放在代码上行,表示注释下行代码

单行注释格式 2 采用风格:/**< …… */ 或 ///<…… 放在代码后面,表示注释前面代码

多行块注释格式 采用风格:/**

……

*/

示例:以doxygen格式为例(Doxygen格式常用关键字表,见附件1),注释风格如下:

/** @copyright Copyright(c)2014-2011 XXXX Co.,Ltd. All rights reserved.
  ******************************************************************************
  * @mainpage   无人机固件程序
  * <table>
  * <tr><th>Project  <td>A600
  * <tr><th>Author   <td>缪某某(ID:123456)
  * <tr><th>Source   <td>D:\workspace\demo_project\examples\A600\A600-doxygen
  * </table>
  * @section    项目描述
  * 该无人机是主要应用于......
  *
  * @section    功能描述
  * -# 定高悬停
  * -# 路径规划飞行
  *
  * @section    用法描述
  * -# 用遥控器
  *
  * @section    固件更新记录
  * <table>
  * <tr><th>Date        <th>H_Version   <th>S_Version       <th>Author  <th>Description  </tr>
  * <tr><td>2018/08/17  <td>1.0         <td>S02010041808171 <td>lebo    <td>创建初始版本 </tr>
  * <tr><td>2019/06/24  <td>1.1         <td>S02010041906241 <td>lebo    <td>
  * -# 无
  * </tr>
  * </table>
  ******************************************************************************
  */

示例:以doxygen格式为例,注释格式 风格如下:

/** @copyright  Copyright(c)2014-2011 XXXX Co.,Ltd. All rights reserved.
  ******************************************************************************
  * @mainpage   FreeRTOS实验
  * <table>
  * <tr><th>Project  <td>FreeRTOSDemo
  * <tr><th>Author   <td>匠在江湖(ID:1234)
  * <tr><th>Source   <td>F:\BaiduNetdiskWorkspace\Work\FreeRTOSDemo
  * </table>
  * @section    项目描述
  * - 类别1
  *     -# 情况1......
  *     -# 情况2......
  * - 类别2
  *     -# 情况1......
  *     -# 情况2......
  * @section    用法描述
  * 
  * @section    固件更新记录
  * <table>
  * <tr><th>Date        <th>H_Version   <th>S_Version       <th>Author(ID:xxxxxx)   <th>Description  </tr>
  * <tr><td>2018/08/17  <td>1.0         <td>S02010041808171 <td>匠在江湖(ID:1234)   <td>创建初始版本 </tr>
  * </table>
  ******************************************************************************
  */

示例1:以doxygen格式为例,注释格式 风格如下:

/**
  ******************************************************************************
  * @brief   发送接收 模拟函数
  * @param   None
  * @retval  None
  * @note    
  ******************************************************************************
  */

示例2:

/**
  ******************************************************************************
  * @brief  xxx 函数
  * @param  None
  * @return 函数执行结果
  * - E_SUCCESS 成功
  * - E_FAIL 失败
  * @par 示例:
  * @code
  *    int ret = register_all(&data, len, RT_TYPE_T1)
  * @endcode
  * @see :: xx表  
  ******************************************************************************
  */

示例3:

/**
  ******************************************************************************
  * @brief  Register a User ADC Callback
  *         To be used instead of the weak predefined callback
  * @param  hadc Pointer to a ADC_HandleTypeDef structure that contains
  *         the configuration information for the specified ADC.
  * @param  CallbackID ID of the callback to be registered
  *         This parameter can be one of the following values:
  *          @arg @ref HAL_ADC_CONVERSION_COMPLETE_CB_ID      ADC conversion complete callback ID
  *          @arg @ref HAL_ADC_CONVERSION_HALF_CB_ID          ADC conversion complete callback ID
  *          @arg @ref HAL_ADC_LEVEL_OUT_OF_WINDOW_1_CB_ID    ADC analog watchdog 1 callback ID
  *          @arg @ref HAL_ADC_ERROR_CB_ID                    ADC error callback ID
  *          @arg @ref HAL_ADC_INJ_CONVERSION_COMPLETE_CB_ID  ADC group injected conversion complete callback ID
  *          @arg @ref HAL_ADC_MSPINIT_CB_ID                  ADC Msp Init callback ID
  *          @arg @ref HAL_ADC_MSPDEINIT_CB_ID                ADC Msp DeInit callback ID
  *          @arg @ref HAL_ADC_MSPINIT_CB_ID MspInit callback ID
  *          @arg @ref HAL_ADC_MSPDEINIT_CB_ID MspDeInit callback ID
  * @param  pCallback pointer to the Callback function
  * @retval HAL status
  ******************************************************************************
  */

示例4:

/**
  ******************************************************************************
  * @brief  Enables ADC, starts conversion of regular group and transfers result
  *         through DMA.
  *         Interruptions enabled in this function:
  *          - DMA transfer complete
  *          - DMA half transfer
  *          - overrun (if available)
  *         Each of these interruptions has its dedicated callback function.
  * @note   Case of multimode enabled (for devices with several ADCs): This
  *         function is for single-ADC mode only. For multimode, use the
  *         dedicated MultimodeStart function.
  * @param  hadc ADC handle
  * @param  pData The destination Buffer address.
  * @param  Length The length of data to be transferred from ADC peripheral to memory.
  * @retval None
  ******************************************************************************
  */

示例1:以doxygen格式为例,注释格式 风格如下:

/** @enum BoolTypeE
  * @brief 布尔类型(逻辑型变量的定义符)
  */
typedef enum
{
    E_FALSE = 0, //假(错误)
    E_TRUE = !E_FALSE //真(正确)
}BoolTypeE;

示例2:以doxygen格式为例,注释格式 风格如下:

/** @struct info
  * @brief 信息结构体 \n
  * 定义存储的信息
  */
typedef struct 结构体名字
{
   成员1, ///< 简要说明文字, 如果不加< 则会认为是成员2的注释
   成员2, ///< 简要说明文字
   成员3, ///< 简要说明文字
}结构体别名;

示例1:纯定义。模块组号 MCU_Driver, 模块名 MCU Driver

/**
  * @defgroup MCU_Driver MCU Driver
  */

或

/**  @defgroup MCU_Driver MCU Driver */

示例2:定义 + 概要描述。

/** @defgroup MCU_Driver MCU Driver
  * @brief
  */

示例3:定义 + 组范围。

/** @defgroup MCU_Driver MCU Driver
  * @{
  */

......

/** @} end of MCU_Driver */

示例3:定义 + 组范围,加入到 MCU_Driver 模块组中,成为其子组。

/** @defgroup ADC_C_Includes ADC.C Includes
  * @ingroup  MCU_Driver
  * @{
  */

#include "base.h"
#include "adc.h"

/** @} end of ADC_C_Includes */

示例:多行或单行注释格式 采用风格:/* …… */

只可单行注释格式 采用风格://…… 

/** @copyright  Copyright(c)2014-2011 XXXX Co.,Ltd. All rights reserved.
  ******************************************************************************
  * @file       mcu.c
  * @brief      
  * @details    
  * @author     匠在江湖(ID:1234)
  * @version    V1.0
  * @date       2014/05/08
  ******************************************************************************
  * @attention
  * 
  * @par 修改日志:
  * <table>
  * <tr><th>Date        <th>Version <th>Author(ID:xxxxxx)   <th>Description</tr>
  * <tr><td>2014/05/08  <td>V1.0    <td>匠在江湖(ID:1234)   <td>创建初始版本</tr>
  * </table>
  ******************************************************************************
  */


/* Includes *******************************************************************/
……用户定义内容
/* Private typedef ************************************************************/
……用户定义内容
/* Private constants **********************************************************/
……用户定义内容
/* Private macro **************************************************************/
……用户定义内容
/* Private variables **********************************************************/
……用户定义内容
/* Private function prototypes ************************************************/
……用户定义内容


/**
  ******************************************************************************
  * @brief  MCU 初始化 函数
  * @param  None
  * @return None
  * @note   
  ******************************************************************************
  */
void MCU_Init(void)
{

}

/********************************* END OF FILE ********************************/

/** @copyright  Copyright(c)2014-2011 XXXX Co.,Ltd. All rights reserved.
  ******************************************************************************
  * @file       mcu.h
  * @brief      与硬件相关的寄存器定义,包含头文件等。为了适用平台的转换
  * @details    
  * @author     匠在江湖(ID:1234)
  * @version    V1.0
  * @date       2014/05/08
  ******************************************************************************
  * @attention
  * 
  * @par 修改日志:
  * <table>
  * <tr><th>Date        <th>Version <th>Author(ID:xxxxxx)   <th>Description</tr>
  * <tr><td>2014/05/08  <td>V1.0    <td>匠在江湖(ID:1234)   <td>创建初始版本</tr>
  * </table>
  ******************************************************************************
  */


#ifndef MCU_H_
#define MCU_H_
/* Includes *******************************************************************/
#include "stm32f1xx.h"
#include "stm32f1xx_hal.h"
#include "main.h"
#include "usart.h"
#include "base.h"
#include "iwdg.h"

#ifdef __cplusplus
 extern "C"{
#endif

/* Exported types *************************************************************/
/* Exported constants *********************************************************/
/* Exported macro *************************************************************/
///NOP()
#define NOP()                       __NOP
///清看门狗
#define CLR_WDG()                   HAL_IWDG_Refresh(&hiwdg) //清看门狗


/* Exported functions *********************************************************/
void MCU_Init(void);

#ifdef __cplusplus
}
#endif

#endif /* MCU_H_ */

/********************************* END OF FILE ********************************/

说明:当代码段较长,特别是多重嵌套时,这样做可以使代码更清晰,更便于阅读。 

示例:参见如下例子。 

if (...)
{
    // program code

    while (index < MAX_INDEX)
    {
        // program code
    } /* end of while (index < MAX_INDEX) */ // 指明该条while语句结束

    // program code
} /* end of if (...)*/ // 指明是哪条if语句结束 

        良好的排版可以提高程序的可读性、易防错性、易维护性等。

说明:编辑器可用TAB键代替空格时方可使用,否则程序会因缩进不同而变乱。

说明:在函数体的开始、类的定义、结构的定义、枚举的定义以及if、for、do、while、switch、case语句中的程序都要采用如上的缩进方式。 

示例:

​​​​​​​如下例子不符合规范。 
for (...) {
... // program code
}

应如下书写。 
for (...)
{
... // program code
}

示例:

​​​​​​​如下例子不符合规范。 
rect.length = 0; rect.width = 0;

应如下书写 
rect.length = 0;
rect.width = 0;

示例:

​​​​​​​如下例子不符合规范。 
if (pUserCR == NULL) return;

应如下书写: 
if (pUserCR == NULL)
{
    return;
}

标签: 方头静态扭矩传感器pt124bbk电源连接器

锐单商城拥有海量元器件数据手册IC替代型号,打造 电子元器件IC百科大全!

锐单商城 - 一站式电子元器件采购平台