我最近在写stm32单片机的程序,在写的过程中发现自己编写函数名、类型名、宏定义的时候自己编写的代码总是有点别扭,主要原因就是宏定义、函数名、全局变量名、类型名、局部变量名、枚举值等这些元素的命名上有问题,我查阅了uboot、linux内核源码、以及我以前同事编写的代码,积累了这些元素在命名方面的一些共性,现在记录下来。
(1)宏定义的宏名尽量用三个大写单词以内+下划线方式实现。
#define KEXEC_ARM_ATAGS_OFFSET 0x1000 #define KEXEC_ARM_ZIMAGE_OFFSET 0x8000(2)枚举类型定义中的枚举值尽量用三个大写单词加下划线方式实现。
(3)枚举类型名尽量用三个小写单词加下划线方式实现(uboot和linux内核源码中习惯用小写)
例如:
enum km_type { KM_BOUNCE_READ, KM_SKB_SUNRPC_DATA, KM_SKB_DATA_SOFTIRQ, KM_USER0, KM_USER1, KM_BIO_SRC_IRQ, KM_BIO_DST_IRQ, KM_PTE0, KM_PTE1, KM_IRQ0, KM_IRQ1, KM_SOFTIRQ0, KM_SOFTIRQ1, KM_L1_CACHE, KM_L2_CACHE, KM_KDB, KM_TYPE_NR };(4)结构体、共用体类型名和成员变量名尽量用三个小写单词加下划线方式实现,内部成员变量也尽量用三个小写单词加下划线方式实现。
struct root_device { struct device dev; struct module *owner; };(5)函数名尽量用三个小写单词加下划线方式实现,如下所示。
static ssize_t dev_attr_show(struct kobject *kobj, struct attribute *attr, char *buf) { struct device_attribute *dev_attr = to_dev_attr(attr); struct device *dev = to_dev(kobj); ssize_t ret = -EIO; if (dev_attr->show) ret = dev_attr->show(dev, dev_attr, buf); if (ret >= (ssize_t)PAGE_SIZE) { print_symbol("dev_attr_show: %s returned bad count\n", (unsigned long)dev_attr->show); } return ret; }(6)全局变量名尽力用三个小写单词加下划线方式实现,如下所示。
static const struct sysfs_ops dev_sysfs_ops = { .show = dev_attr_show, .store = dev_attr_store, };(7)函数内部定义的参数或者临时栈变量尽量使用三个小写单词加下划线方式实现。这里容易犯的一个错误是参数名或者临时栈变量名中包含了函数名中已经有的单词,导致参数名或者临时栈变量名特别长,之所以会犯这样的错误是因为“编写程序的时候错误下意识以为临时栈变量的作用域也为全局,因此保证起参数或者临时栈变量名字的时候不能重名”,这其实是错误的。我们都知道函数内部的参数或者临时栈变量的作用域只局限在函数内部,因此就算名字重复了也是没有任何影响的,也可以这样理解:函数名是唯一的前缀,就算函数内部参数和变量名字重复了,整体看来也能做到命名不重复。为了缩短参数或者临时栈变量名的长度,采用比较有效的办法就是在参数名或者临时栈变量名中将函数名中已经表明的英文单词给省略掉。
(8)if条件判断中的反逻辑和正逻辑:方式三编写的函数,也就是返回值为某个错误状态码,在父函数中经过传参和函数调用后,直接或间接利用返回值参与逻辑运算,最常见的就是作为if条件判断的表达式。表达式往往采用反逻辑与OK、SUCCESS等值做比较,也就是使用不等于(!=)OK、SUCCESS等数值,这样做的好处就是采用防御思维编程(卫语句),可以降低代码嵌套级数。
另外值得说明的是,错误状态码中往往只有唯一的一个成功值SUCCESS,OK值,其他错误状态可能有多个,因此与这个唯一的成功值SUCCESS、OK值参与反逻辑或者正逻辑运算,而不是与其他错误状态码进行比较逻辑运算是比较简洁的方式。
当然,返回值使用正逻辑与唯一的OK或者SUCCESS值进行比较也是有的,就是没有以防御卫语句编写方式编写,还与个人编写代码习惯有关。
(9)if-else二分法。在编程中经常使用二分法。也就只有2种情况,真假、成功失败,YES/NO等,二分法编写代码是最简单的,因为情况越多需要处理的情况代码也越多,如果发生嵌套,情况越多写代码难度越大。而且条件判断使用最多的就是if()触发判断和if-else二分法条件判断。这也与我以前以前文章中论述错误状态码只需要两种状态OK/ERROR、SUCCESS/FAILE、YES/NO,而不需要细分错误状态码保持一致。
(10) 什么是应用层程序。所谓应用层程序就是模块与模块之间的资源相关调用,完成业务逻辑功能。这里的模块资源包括全局变量,API函数,公共宏定义、公共数据类型定义。在应用层往往发生模块A的函数调用模块B、C、D等模块的资源(其实就是驱动层的资源)完成应用层资源建立,也就是创建私有/公共数据类型、私有/公共全局变量、私有/公共API函数,私有/公共宏定义等。从宏观上看应用层程序编写与驱动层程序编写(创建模块的各个资源)本质上没有区别,但是应用层程序的内容与业务逻辑相关,并且会交叉调用驱动层的相关资源,这也是应用层比驱动层负载的地方。
(11)函数的复用性:将相似代码段定义成一个函数,关键是不同变量定义成参数,可以减少代码行数,节省代码段大小。最近我在写stm32项目程序的业务应用层程序,其中使用了一个阶梯判断语句switch-case-break-default语句用于判断接收命令行命令ID的具体分析,其中有几个命令ID的处理是非常相似的,只有很少一部分语句不同。当我进行程序优化的时候感觉完全可以将这些命令ID的相似处理包含写成一个含参函数,参数对应处理不同部分。这样函数在被调用时传递不同参数,对应上述代码中不同部分,剩余部分相同。返回值选为void,也就是不参与父函数任何运算。
static void app_USB5Vx_set(USB5V_PWR_X x) { if (cmd_rxbuf[4] == 0x00) USB5Vx_power_set(x, USB5V_PWR_OFF); else USB5Vx_power_set(x, USB5V_PWR_ON); } case CMD_USB5V_1: /**0x55 0xAA 0x03 CMD_USB5V_1 0xXX[4] 0x00 0x0A */ // 第一路5V供电配置 app_USB5Vx_set(USB5V_PWR1); // 回传命令 cmd_send(cmd_rxbuf, cmd_rxlen); break; case CMD_USB5V_2: /**0x55 0xAA 0x03 CMD_USB5V_2 0xXX 0x00 0x0A */ // 第二路5V供电配置 app_USB5Vx_set(USB5V_PWR2); // 回传命令 cmd_send(cmd_rxbuf, cmd_rxlen); break; case CMD_USB5V_3: /**0x55 0xAA 0x03 CMD_USB5V_3 0xXX 0x00 0x0A */ // 第三路5V供电配置: app_USB5Vx_set(USB5V_PWR3); // 回传命令 cmd_send(cmd_rxbuf, cmd_rxlen); break;例如上述代码中几个命令ID的处理就是函数体的内容,不同之处主要是函数体内函数USB5Vx_power_set()的第一个参数不同,因此将这个不同的变量定义成参数变量,返回值为void,这样定义成一个函数,函数调用的时候分别传递不同参数,但是都调用同一段代码,这样可以节省代码段的大小,也体现了函数的复用性。
后面如果发现有新共性,继续补充。