最近,我在看我同事写的代码,因为他内部没有定义子函数,而是使用了一个非常大,非常长的大函数来实现业务逻辑的,但是我看了Uboot源码、linux内核源码中代码书写规范,我认为这种意大利面条式代码编写方式不太好,于是我将一些多次重复调用的代码提取出来,写成了一个个的子函数,将函数中使用的常数我就近在函数上面定义成了宏定义或者枚举类型,这些我修改完一遍后,就遇到了一个问题:宏观上看源文件的的宏、类型定义(包含enum枚举类型)、全局变量、函数是交叉排布的,我这样能够实现业务逻辑,但是各个元素交叉着与Uboot、linux内核源码的编写习惯不同,为了做到统一规范,需要对宏定义、类型定义、全局变量、函数摆放在与按照Uboot和linux内核源码中一致位置的规定。
(1)uboot、linux内核源码中对宏、类型定义、全局变量、函数摆放是怎么规划的呢?
答:大原则:类型定义、全局变量、函数定义这三者以列表的方式实现摆放,首先源文件开头位置是类型定义的列表、然后是以这些类型定义的全局变量的列表,最后是函数定义列表摆放。宏定义按照"最小作用域"原则摆放在调用宏值的函数上面相邻位置,调用完宏,甚至可以使用#undef来取消宏定义,这样此行代码后面的所有代码函数都不能再访问该宏了。
为什么大原则是这样的呢?
首先,我们以列表的方式摆放类型定义、全局变量、函数定义更符合c语言设计原则,与数组、结构体成员变量相邻打包的原则是一致的。
其次,我们使用vscode打开源文件,看看大纲窗口各个元素的摆放。

dm9000.c源文件的大纲窗口中也是以列表的方式将将类型定义、全局变量定义、函数定义依次摆放,因为我们在写代码的时候也需要以这种方式。
最后,我问过AI这样摆放的好处是什么,说是方便后期维护修改,因为相同类型的元素都是依次排布的,也就是都在同一段位置附近,容易查找,我想了一下的确是这样。例如,写好代码后,过了好长时间不看,源文件中使用的类型定义、全局变量、函数定义名字都不记得了,如果它们三种是交叉分部的,需要好好长时间才能再次捋清楚整个源文件的业务逻辑。
(2)那么宏定义为什么不摆放在源代码开始位置,然后依次排布呢?我在这里不讨论这样摆放的好处是什么,无非就是便于维护。我想讨论的是,宏定义摆放在使用它的函数上面按照就近原则摆放的好处。
首先,如果将宏定义统一摆放在源文件开始位置,当我们读到这个宏定义列表的时候是不清楚被哪个函数调用,相反的如果在函数附近分散的排布使用的宏定义,那就比较容易查找并理解了。
其次,vscode的大纲列表中没有体现宏定义的列表排布,暗含的意思就是希望宏定义以"最小作用域"的方式分段按需摆放。
最后,我的原则:如果某一个宏定义被2个以上函数调用的话,将这个宏定义摆在源文件开始位置,对应的如果某一个宏定义只被一个函数内部调用,也就是"局部宏",我的建议就是将宏摆放在离函数最近的位置。
往更宽了考虑,如果某一种类型定义、全局变量定义、函数定义、宏定义需要被2个以上源文件共同拥有,那就需要将类型定义定义在头文件中、全局变量声明放在头文件中使用extern修饰、函数声明在头文件中声明、宏定义放在头文件中,这样所有包含这个头文件的源文件就都拥有。
总之,先往小的范围考虑,优先使用小范围作用域,逐级往大的地方移动。
注意:以上的原则都是"义务"原则,尽量遵守,不是强制的。例如,有些源文件中函数中间插了全局变量定义,这个全局变量就遵守了"最小作用域"原则,在uboot或者linux内核源码中经常看到,也是允许的。
知识延伸记录:
(1)函数或者变量定义的单词个数尽量保持在1个、2个、3个单词之类,意思就是主谓宾的组合,函数名别写单词太多,尤其是不要将函数名写成一句话。
(2)if-else嵌套级数尽量保持在一级、二级、三级这三种情况,如果嵌套级数再多,代码的复杂性就太大了(complex),不建议这样写,需要根据自己的逻辑功能优化自己的代码。将多级嵌套改成多个条件使用逻辑运算(&& || !)的方式是一种降低代码复杂性的不错方式。if-else嵌套形成的所有组合条件,我们往往并不需要全都关心,有时候只需要处理我们比较在意的业务逻辑,那些不可能发生或者理论上存在,现实中根本碰不到的情况,不用理会。
(3)源文件中宏、类型定义、全局变量、函数的摆放位置,也在一定程度上体现了"流水线思维",类型定义、全局变量、函数的摆放在开头位置,类比成流水线就是先准备好模具,然后制作出所需原材料、各个加工机器调试摆放顺序。