作为一名软件工程师,日常与代码打交道,免不了接触到编程规范。而编程规范,可谓是各式各样,不同系统有不同风格,不同公司有不同风格。就笔者经历而言,在学生年代学习单片机编程时已经开始建立自己的“编程规范”。总的来说,笔者认为《高质量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 夜