esp-iot-solution 编码规范¶
总体原则¶
简洁明了,结构清晰
统一风格,易于维护
充分注释,易于理解
继承第三方代码已有规范
目录结构¶
components:总体上按照功能分类,大类下如果存在多级目录,应包含一个
README
做综述和索引;docs:
rst
格式文档,包括各个组件的使用指南,API 说明;examples:总体上按照与组件对应的功能分类;
tools:CI 脚本、调试工具。
头文件¶
尽量每一
.c
对应一个同名的.h
文件;单个组件存在多个
.h
,主要对外.h
命名尽量与组件名保持一致;头文件中主要放函数声明,不放函数实现;
尽量不在头文件定义任何形式的变量;
头文件应按照注释规范,对函数接口进行充分注释;
添加宏定义避免重复引用,宏定义名为大写的头文件名加下划线填充:
#ifndef _IOT_I2C_BUS_H_
#define _IOT_I2C_BUS_H_
#endif
函数声明添加 extern “C” 修饰,支持 C/C++ 混合编程:
#ifdef __cplusplus
extern "C"
{
#endif
//c code
#ifdef __cplusplus
}
#endif
注释¶
安装 VSCODE 插件 Doxygen Documentation Generator 可自动生成注释框架;
注释中避免使用单词缩写;
函数声明处注释需要描述函数功能、性能或用法,输入和输出参数、函数返回值说明。
自动生成的注释框架:
/**
* @brief
*
* @param port
* @param conf
* @return i2c_bus_handle_t
*/
i2c_bus_handle_t iot_i2c_bus_create(i2c_port_t port, i2c_config_t* conf);
补充信息和参数方向:
/**
* @brief Create an I2C bus instance then return a handle if created successfully.
* @note Each I2C bus works in a singleton mode, which means for an i2c port only one group parameter works. When
* iot_i2c_bus_create is called more than one time for the same i2c port, following parameter will override the previous one.
*
* @param[in] port I2C port number
* @param[in] conf Pointer to I2C parameters
* @return i2c_bus_handle_t Return the I2C bus handle if created successfully, return NULL if failed.
*/
i2c_bus_handle_t iot_i2c_bus_create(i2c_port_t port, i2c_config_t* conf);
版权声明注释(第三方代码,请保留版权声明信息)
// Copyright 2019-2020 Espressif Systems (Shanghai) PTE LTD
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
函数规范¶
多处重复使用的代码尽量设计为函数;
作用域仅限于当前文件的函数必须声明为静态
static
;设计使用静态全局变量、静态局部变量的函数时,需要考虑重入问题;
尽量在一个固定函数中操作静态全局变量;
如果函数存在重入或线程安全问题,需在注释中说明;
同一组件内的公有函数名,应保持同一前缀;
函数名统一使用
snake_case
格式,只使用小写字母,单词之间加_
;函数命名指引(应保持与已有代码风格一致,不严格约束):
函数名格式 |
函数示例 |
说明 |
---|---|---|
iot_type_xxx |
iot_sensor_xxx; iot_board_xxx; iot_storage_… |
高度抽象的 iot 组件 |
type_xxx |
imu_xxx; light_xxx; eeprom_xxx |
对一类外设的抽象 |
name_xxx |
mpu6050_xxx; |
底层 driver,由于可能来自第三方,不约束函数名 |
xxx_creat / xxx_delete |
创建和销毁 |
|
xxx_read / xxx_write |
数据操作 |
|
变量规范¶
避免使用全局变量,可声明为静态全局变量,使用
get_
set_
等接口进行变量操作;作用域仅限于当前文件的变量必须声明为静态变量
static
;静态全局变量请添加
g_
前缀,静态局部变量请添加s_
前缀;局部变量设计大小时,应考虑栈溢出的问题;
任何变量定义时,必须赋初值;
变量功能要明确,避免将单一变量做多个用途;
句柄类型变量,在对象销毁后,应重新赋值为 NULL;
变量统一使用
snake_case
格式,只使用小写字母,单词之间加_
;避免不必要的缩写,例如
data
不必缩写为dat
;变量应尽量使用有意义的词语,或者已经达成共识的符号或词语缩写;
变量命名指引:
类型 |
规范 |
示例 |
---|---|---|
全局变量 |
避免使用 |
x |
静态全局变量 |
static 标识 , g_ 前缀,赋初值 |
static uint32_t g_connect_num = 0; |
静态局部变量 |
static 标识 , s_ 前缀,赋初值 |
static uint32_t s_connect_num= 0; |
迭代计数变量 |
使用通用的 |
|
常用缩写 |
addr,buf ,cfg , cmd, , ctrl, |
|
常用缩写列表
缩写 |
全称 |
缩写 |
全称 |
缩写 |
全称 |
缩写 |
全称 |
---|---|---|---|---|---|---|---|
addr |
address |
id |
identifier |
len |
length |
ptr |
pointer |
buf |
buffer |
info |
information |
obj |
object |
ret |
return |
cfg |
command |
hdr |
header |
param |
parameter |
temp |
temporary、temperature |
cmd |
command |
init |
initialize |
pos |
position |
ts |
timestamp |
类型定义¶
使用加
snake_case
格式加_t
后缀
typedef int signed_32_bit_t;
枚举应通过 typedef 通过以下方式定义
typedef enum {
MODULE_FOO_ONE,
MODULE_FOO_TWO,
MODULE_FOO_THREE
} module_foo_t;
格式和排版规范¶
该部分继承 ESP-IDF 规范
1. 缩进¶
每个缩进层使用 4 个空格,不要使用制表符进行缩进,将编辑器配置为每次按 tab 键时发出 4 个空格。
2. 垂直间隔¶
在函数之间放置一个空行,不要以空行开始或结束函数。
void function1()
{
do_one_thing();
do_another_thing();
// INCORRECT, don't place empty line here
}
// place empty line here
void function2()
{
// INCORRECT, don't use an empty line here
int var = 0;
while (var < SOME_CONSTANT) {
do_stuff(&var);
}
}
只要不严重影响可读性,最大行长度为 120 个字符。
3. 水平间隔¶
总是在条件和循环关键字之后添加单个空格
if (condition) { // correct
// ...
}
switch (n) { // correct
case 0:
// ...
}
for(int i = 0; i < CONST; ++i) { // INCORRECT
// ...
}
在二元操作符两端添加单个空格,一元运算符不需要空格,可以在乘法运算符和除法运算符之间省略空格。
const int y = y0 + (x - x0) * (y1 - y0) / (x1 - x0); // correct
const int y = y0 + (x - x0)*(y1 - y0)/(x1 - x0); // also okay
int y_cur = -y; // correct
++y_cur;
const int y = y0+(x-x0)*(y1-y0)/(x1-x0); // INCORRECT
.
和 ->
操作符的周围不需要任何空格。
有时,在一行中添加水平间隔有助于提高代码的可读性。如下,可以添加空格来对齐函数参数:
gpio_matrix_in(PIN_CAM_D6, I2S0I_DATA_IN14_IDX, false);
gpio_matrix_in(PIN_CAM_D7, I2S0I_DATA_IN15_IDX, false);
gpio_matrix_in(PIN_CAM_HREF, I2S0I_H_ENABLE_IDX, false);
gpio_matrix_in(PIN_CAM_PCLK, I2S0I_DATA_IN15_IDX, false);
但是请注意,如果有人添加了一个新行,第一个参数是一个更长的标识符(例如PIN_CAM_VSYNC),它将不适合。因为必须重新对齐其他行,这添加了无意义的更改。因此,尽量少使用这种对齐,特别是如果您希望稍后将新行添加到这列中。
不要使用制表符进行水平对齐,不要在行尾添加尾随空格。
4. 括号¶
函数定义的大括号应该在单独的行上
// This is correct:
void function(int arg)
{
}
// NOT like this:
void function(int arg) {
}
在函数中,将左大括号与条件语句和循环语句放在同一行
if (condition) {
do_one();
} else if (other_condition) {
do_two();
}
5. 注释¶
//
用于单行注释。对于多行注释,可以在每行上使用 //
或
/ * * /
块注释。
虽然与格式没有直接关系,但下面是一些关于有效使用注释的注意事项。
不要使用一个注释来禁用某些功能
void init_something()
{
setup_dma();
// load_resources(); // WHY is this thing commented, asks the reader?
start_timer();
}
如果不再需要某些代码,则将其完全删除。如果你需要,你可以随时在 git 历史中查找这个文件。如果您因为临时原因而禁用某些调用,并打算在将来恢复它,则在相邻行上添加解释
void init_something()
{
setup_dma();
// TODO: we should load resources here, but loader is not fully integrated yet.
// load_resources();
start_timer();
}
#if 0 ... #endif
块也是如此。如果不使用,请完全删除代码块。否则,添加注释以解释为什么禁用该块。不要使用#if 0 ... #endif
或注释来存储将来可能需要的代码段。不要添加有关作者和更改日期的琐碎注释。您总是可以查找谁使用 git 修改了任何给定的行。例如,此注释在不添加任何有用信息的情况下,使代码混乱不堪:
void init_something()
{
setup_dma();
// XXX add 2016-09-01
init_dma_list();
fill_dma_item(0);
// end XXX add
start_timer();
}
6. 代码行的结束¶
commit 中只能包含以 LF(Unix风格)结尾的文件。
Windows 用户可以将 git 配置为在本地 checkout 是 CRLF(Windows 风格)结尾,通过设置 core.autocrlf 设置来 commit 时以 LF 结尾。 Github 有一个关于设置此选项的文档 。但是,由于 MSYS2 使用 Unix 样式的行尾,因此在编辑 ESP-IDF 源文件时,通常更容易将文本编辑器配置为使用 LF(Unix 样式)结尾。
如果您在分支中意外地 commit 了 LF 结尾,则可以通过在 MSYS2 或 Unix 终端中运行此命令将它们转换为 Unix(将目录更改为 IDF 工作目录,并预先检查当前是否已 checkout 正确的分支):
git rebase --exec 'git diff-tree --no-commit-id --name-only -r HEAD | xargs dos2unix && git commit -a --amend --no-edit --allow-empty' master
(请注意,这行代码将在 master 上重新建立基,并在最后更改分支名称以在另一个分支上建立基。)
要更新单个提交,可以运行
dos2unix FILENAME
然后运行
git commit --amend
7. 格式化代码¶
您可以使用 astyle 程序根据上述建议对代码进行格式化。
如果您正在从头开始编写一个文件,或者正在进行完全重写,请随意重新格式化整个文件。如果您正在更改文件的一小部分,不要重新格式化您没有更改的代码。这将帮助其他人检查您的更改。
要重新格式化文件,请运行
tools/format.sh components/my_component/file.c
CMake 代码风格¶
缩进是 4 个空格
最大行长为 120 个字符。 分割行时,请尝试尽可能集中于可读性(例如,通过在单独的行上配对关键字/参数对)。
不要在 endforeach()、endif() 等后面的可选括号中放入任何内容。
对命令、函数和宏名使用小写( with_underscores )。
对于局部作用域的变量,使用小写字母( with_underscores )。
对于全局作用域的变量,使用大写( WITH_UNDERSCORES )。
其他,请遵循 cmake-lint 项目的默认设置。