嵌入式C语言代码风格简述

文摘   2024-09-22 19:10   广东  

关注+星号公众号,不容错过精彩

作者:HywelStar

Hi, 经常可以看到招聘条件上看到,代码具有良好风格,友好,什么叫做良好风格,相信对于有些大厂工作的朋友,存在一些公司代码编程风格规范,比如华子,还有很多,关于华子的编程风格这里就不说了。关于代码风格都是在自己编码过程中慢慢形成,但如果自己没有一套这样的规范,可以看看本章节,不过需要提醒的是,代码风格没有固定,各个团队都可能存在一些差异,自行选择。

1. 代码格式化工具

想要具有良好的代码风格,首先对于格式需要一定的约束,这里简单来是对代码格式的检查,在写代码的过程中,总会出现一些格式化的问题,比如空多少格的问题,符号与变量是否具有空格,对于这种微小的问题,其实在自己写代码的过程中往往会忽略,这个时候你就需要使用到IDE的自动化格式工具,它将帮助你一键格式化。

2. 自动给文件或函数写注释

关于这个问题,写代码都会发现一个.c 文件都会存在一个开头的说明,当你存在上百个甚至上千上万个时候,当然不可能一个一个去复制黏贴去写,并且这种往往都是需要一定的格式,在你发生改动的时候,可能你需要去修改说明,这里强烈建议采用IDE的自动添加文件说明,不仅能够加快效率,统一的格式化也对整体代码提升质量。

关于这个如何添加文件或者函数的注释,可以查看文章【传送门:给代码生成文件头部与函数注释

3. 代码风格

软件人员怎么也不能逃离写代码这一步,由于代码都是英语,所以这里建议从写函数开始到变量一律用英文。

3.1 命名规范

无论是变量命名,函数命名等相关命名,不要写中英文混搭!保持英文方式去写,对于关键字与库函数重叠名字都不适合使用去命名;变量命名与函数命名根据自己情况选择驼峰命名或者下划线命名方式。

3.1.1 变量命名

关于变量命名,变量名不得使用关键字,这个应该大家都已经知道;

变量名尽可能有描述性,不要使用过于非通用简短的缩写,不适用毫无意义的字符;尽量使用具有实际意义的词汇,让代码更易于理解。

int return = 0;   // badint a, b;    // badfloat cnt, x1z;    // bad
int age, item_count; // good float temperature, height_in_meters; // good

3.1.2 函数命名

函数命名需要注意不得采用关键字进行命名,什么意思呢?比如interruput, exit, public等等,换句话说,不能使用系统已经存在的关键字进行命名。

函数命令采用可以采用大小写混排, 比如GetDevName , 还可以采用小写加下划线方式, 比如get_dev_name, 但是这两种不能混用,统一风格(但现实中也存在一些差异,比如你使用某个库的风格和你原本代码不一样,这个时候可以选择保留自己的风格);

函数命名颖明确表达函数功能,避免使用不明确的缩写或过长的函数名,推荐使用动词加名词方式;

函数名避免与全局命名冲突,可以考虑与项目或者模块相关联,在函数前添加前缀;

# 1. 不得使用关键字或者系统存在的函数接口void exit(); // bad :与标准库函数冲突void interrupt(); // bad :容易引起误解
# 2. 函数名采用清晰表达void gdn(); // bad : 简写不清楚void calc(); // bad : 简写不清楚void get_the_name_of_the_current_active_device_in_the_network(); // bad : too long
void get_device_name(); // good : 函数名清晰表述功能void calculate_sum(); // good : void get_device_name(); // goog :
# 3. 避免冲突可以在模块或者文件名,函数名添加前缀# 比如当前文件为network模块,可以考虑添加前缀void network_send_packet(); // 带模块前缀的函数名
# 比如当前文件为dev_config.c 可以考虑添加前缀int dev_config_init()int dev_config_save()

3.3.3 文件命名

文件名的命名方式,应具备描述性,表达清晰,避免使用模糊的名称或缩写;

文件名不宜过长,不要包含多余的信息。如果文件名过长,不仅难以管理,也会增加书写和引用时的负担。

文件命名应保持一致,可以使用以下几种常见的命名风格,但不建议混用。

文件名应避免使用过于通用的词汇,以免引起混淆。特别是当项目变得庞大时,通用文件名会导致文件冲突或开发者误解文件内容。

# 1. 文件命名能够清除表达内容和功能a.c  // bad : 缺乏描述性,无法通过名称知道文件内容temp.c  // bad : 过于笼统,不说明文件具体作用
device_driver.c // good : 具体描述文件作用network_utilities.c // good : 表明是网络相关工具函数
# 2. 文件名不宜过长device_driver_for_usb_network_interface_card_version.c // bad : 文件名过长usb_network_driver.c // good : 保持简洁的同时表达清楚功能
# 3. 风格一致network_utilities.c, device_driver.c // good : 统一小写加下划线DeviceDriver.c, NetworkUtilities.c // good : 大小写混排
# 4. 避免使用过于通用名称(针对大型项目)util.c // bad : 文件名过于通用,内容不明确test.c // bad : 不清楚具体的测试内容
memory_utilities.c // good : 明确表示是内存相关的工具函数network_test.c // good : 表示进行网络功能测试

3.2  宏定义

关于宏定义的命名,首先大写,然后尽可能表达清楚意思;

宏定义的参数需要采用()保护,结果同时需要采用()保护;

宏定义语言不要采用缩进,建议宏代码尽量使用do...whild(0)包围;不过这里建议,对于过于复杂的函数不建议使用宏。

#define SQUARE(x)         ((x) * (x))	// good#define square(x)           ((x) * (x))	// bad
#define MIN(x, y) ((x) < (y) ? (x) : (y)) // good #define MIN(x, y) x < y ? x : y // bad
/* good */#if defined(XYZ)#if defined(ABC)/* do when ABC defined */#endif /* defined(ABC) */#else /* defined(XYZ) *//* Do when XYZ not defined */#endif /* !defined(XYZ) */
/* bad */#if defined(XYZ) #if defined(ABC) /* do when ABC defined */ #endif /* defined(ABC) */#else /* defined(XYZ) */ /* Do when XYZ not defined */#endif /* !defined(XYZ) */

3.4 结构、枚举、typedef

  • 结构或枚举名称必须为小写,单词之间有可选的下划线字符_
  • 结构或枚举可能包含关键字typedef
  • 所有结构成员都必须为小写
  • 所有枚举成员都应该是大写的
# struct 仅仅用name 声明时候,不要包含_tstruct struct_name {    char* a;    char b;};
# 当仅使用 typedef 声明结构时,它必须在其名称后包含 suffix, _ttypedef struct { char* a; char b;} struct_name_t;
# 当使用 name 和 typedef 声明结构时,它不能包含 for basic name,并且它必须在其名称后包含 suffix for typedef 部分。_t_ttypedef struct struct_name { /* No _t */ char* a; char b; char c;} struct_name_t; /* _t */
# 枚举enum color_type { COLOR_RED, COLOR_GREEN, COLOR_BLUE};

3.5 错误处理

如何处理函数返回值、错误码,常用的错误处理策略,如assert和错误日志。

对于C语言当中,函数都需要返回成功或者失败,但有些是已经约定成熟的错误码不应该乱用。返回 0 是成功!

常见的错误码约定

  • 0:成功(SUCCESS
  • 1:通用错误(GENERAL_ERROR
  • -1:系统级错误(SYS_ERROR
  • -2:其他错误

有些错误码比较多可能需要一些其他的,可以通过枚举进行定义:

enum error_code {    SUCCESS = 0,    ERR_FILE_NOT_FOUND = 1,    ERR_INVALID_ARGUMENT = 2,    ERR_OUT_OF_MEMORY = 3};

关于错误的策略,有时候会发现使用这个assert触发程序的异常,程序出现停止状态。通常在调试时使用,生产环境中则建议禁用。

#include <assert.h>
void divide_by_value(int divisor) { assert(divisor != 0); // 确保除数不为0 int result = 10 / divisor; printf("Result: %d\n", result);}

另外一个是关于日志的错误等级策略,这个就不展开。

3.6 其他

关于代码出现的这种其他问题,多数在写代码习惯上考虑到,尽可能采用规范化减少带来的异常现象:

# 1. 推荐使用显示比较,不推荐隐式比较if (ptr == NULL || ptr != NULL) {	// good 
}
if (ptr || !ptr) { // bad
}
# 2. 始终用于 length 或 size 变量size_t/* OK example */void send_data(const void* data, size_t len) { /* OK */ /* Do not cast `void *` or `const void *` */ const uint8_t* d = data;/* Function handles proper type for internal usage */}
void send_data(const void* data, int len) { /* Wrong, not not use int */}
# 3. 遵循代码原本的样子,比如在使用库,就无需刻意修改
# 4. 零零碎碎size_t length = 5; /* Counter variable */uint8_t is_ok = 0; /* Boolean-treated variable */if (length) /* Wrong, length is not treated as boolean */if (length > 0) /* OK, length is treated as counter variable containing multi values, not only 0 or 1 */if (length == 0) /* OK, length is treated as counter variable containing multi values, not only 0 or 1 */
if (is_ok) /* OK, variable is treated as boolean */if (!is_ok) /* OK, -||- */if (is_ok == 1) /* Wrong, never compare boolean variable against 1! */if (is_ok == 0)     /* Wrong, use ! for negative check */

4. 总结

关于代码风格问题,对于格式的问题,你遇到的代码需要空多少行,运算符和数字是否有空格,采用是否换行,这种问题都可以采用格式化工具进行解决。对于文件或者函数注释也通过工具自动生成一个大概的注释,统一格式。笔者认为可能存在较大差异是在写代码的命名,文件命名,函数命名,不过这个多看看也会了,至于还有一些其他的规范可以通过关注“码思途远”公众号,私信后台发送“规范”获取相关文档链接 。



往期推荐



嵌入式中优先级反转及其解决方案

嵌入式软件的编译知识

嵌入式开发常用技巧

给代码生成文件头部与函数注释

Linux 内核模块加载知多少?

Linux 设备文件如何创建?

公众号目录指引

           
“阅读原文”一起来充电吧!


码思途远
一位码农的日常分享,专注嵌入式领域相关知识。
 最新文章