嵌入式Linux入门11:编程规范

2019-07-12 22:30发布

作为一名软件工程师,日常与代码打交道,免不了接触到编程规范。而编程规范,可谓是各式各样,不同系统有不同风格,不同公司有不同风格。就笔者经历而言,在学生年代学习单片机编程时已经开始建立自己的“编程规范”。总的来说,笔者认为《高质量C、C++编程指南》、《Google C++ 编码规范》非常好,有指导意思。工作多年,接触了多种风格,并能自由切换。但还有有一些原则性的条例,是自己一直坚持的。 本文关注两大方面:编码风格以及设计原则。

一、编码风格

编码如写文章,有段落,有条理。如书法,有整体布局。本节主要就目录组织形式和书写命名方面进行介绍。
源码组织(目录格式) 将头文件及实现文件分开存放,但内部使用的头文件可以不按此约定。使用第三方库源码亦要与自实现代码分开存放。

命名
1、与当前所使用的代码风格保持一致。 一般地,内核及driverlib层使用linux风格
应用层程序使用 windows风格 借鉴他人(如TI的mcfw代码)
2、源码文件名称需要统一使用一种风格。比如,统一使用小写+下划线,或者统一使用大小写。
形式:动词+名词。 可顺序阅读的。如获取下一帧数据:getNextFrameData()

类:开头、所有词首字母大写 变量:开头小写 宏、枚举:全部大写 成员变量:加不加m?
专用名大写(PI、OSD)

void createNew()

int rtpPortNum

int maxCNAMElen

doEventLoop()

常见: this doit



代码布局 整个文件布局整齐、整洁,不能出现多余的空行,行尾也不应有多余的空格。 原则:如写文章一样,有段落。

注释 注释分为文件注释及函数接口注释,以清楚明白为原则,主要在头文件进行,在实现代码中需要写上重要的、必要的注释。风格不限,但需统一。建议使用doxygen格式的注释。 只对代码无法表达的东西写注释(Comments Only What the Code Cannot Say)

头文件包含 原则:只在必须的文件中进行,比如bar.h头文件不需要foo.h,而bar.c文件需要,则仅在bar.c文件中包含。 不要包含不必要的头文件。 对包含的头文件可以作一些必要的注释。如: #include    /**< bzero */

二、设计原则

上一节的代码,只徒有其表,写得再漂亮的代码,没有内涵,没有条理性,没有逻辑,也是狂然。本节就一些设计原则进行介绍。——至于如架构设计、设计模式、架构模式涉及内容太多,就不在本文讨论范围。

函数设计 1、函数接口返回值一般应该为整型 int,成功返回0,错误返回负数,并在接口注释中标明错误码。 例外:像内存管理类的接口,根实际情况作处理,如申请共享内存,要返回内存地址。

2、一个函数做一件事,把相对独立的事情封装到一个函数中。 举例:从JPEG转为BMP,应分2个函数,解压JPEG得到RGB数据,将RGB数据保存为BMP。

参数设计 若参数为只读字符串,不需要修改,则使用const,如打开文件: void foo_open(const char* file) 若参数过多,可考虑封装为结构体。 对于C++,参数可以使用引用的方式。

------------------------------------------------------------------------------- 宏定义及判断语句 以肯定语气为主,宏判断不宜过多。 示例:

#ifndef FOO_BAR // something #endif

#if (!defined(A_PLATFORM) && !defined(_B_PLATFORM_)) || (defined(_Z_APP_))

不好的示例(原型来自TI某代码): switch (cmd)
 {
     case DISABLE_A:
         break;
     case DISABLE_B:
         break;
     case DISABLE_NONE:
         break;
     default:
         break;
 } 应改为:
switch (cmd)
 {
     case ENABLE_A:
         break;
     case ENABLE_B:
         break;
     case ENABLE_ALL:
         break;
     default:
         break;
 }

-------------------------------------------------------------------------------
信息打印 在调试期间,可以打印许多的信息,但要使用宏或者等级来控制,不要直接出现调用printf的实际函数。 该打印的一直要打印出来,比如错误信息,一些重要的步骤过程(监控代码运行)。 不该打印的不要打印,比如打印传递的参数值、为跟踪代码而打印的(作为debug打印)。 不同的错误原因,不能使用同一打印信息(为省事直接复制代码),打印信息应准确反应错误原因。 不好的示例: if (init_foo() < 0)      printf("foo error. "); if (exit_foo() < 0)      printf("foo error. "); 应改为:
if (init_foo() < 0)      printf("init foo error. "); if (exit_foo() < 0)      printf("exit foo error. ");

------------------------------------------------------------------------------- 1、不要在if()括号中中写大量的函数调用,return也不要写。 如 if (foo() || bar() || a < b) 或 return (foo() ? b : a) || (bar() &&flag) -->原因:1:函数调用不突出,因为在if里面。2:出错不好排查。因为不知道哪个函数出错。

2、不要一个语句写大量的函数。 如 ss* ptr = (ss*)foo()->getname()->file()->state() -->如果中间有非法指针(如getname()返回NULL),可能会挂掉。
---------------------- 1、数组越界,GCC检查不出来的。实际工程的代码示例: int data[2]; // 保存到临时变量
data[0] = param->enable;
 data[1] = param->type;
 data[2] = param->width;
 data[3] = param->height;
 data[4] = param->count;

判断语句,布尔型的可以使用if (!enable),也可以使用if (enable == false)

===========================================

一定要用括号把优先级强调出来。——即使本身优先级已确定。 不要在宏中多次++操作。

================================== C++结构体,直接当类使用、初始化: struct ata_print_options
 { bool drive_info; ata_print_options()
     : drive_info(false), {} }

多用uint8_t、int32_t这类标准写法,不用int(在stdint.h头文件中,如没有,可自行定义)
本文涉及内容,仅为一人之言,不当之处,请方家斧正。 PS:如与参考资料有冲突之处,请以个人/公司实际情况为准。
参考资料: 《高质量C、C++编程指南》 《Google C++ 编码规范》
李迟 2017.9.5 夜