|
以TI的一个SimpliciTI(点对点简单无线数传协议)例程为例,说一下重构
[复制链接]
重构,用最简单的说法就是: 在不改变代码现有功能的前提下,优化和改善代码。
对代码而言,它的优化和改善是多方面的,为了避免这个话题过于庞杂,并且超出我们的能力范围。
我们只着重考虑我们所关心的 部分代码质量问题。
这里说的 “代码质量”,是楼主临时想到的一个提法,如字面意思。
现阶段,楼主所考虑的 代码质量 比较简单,只包含:
1.代码以功能为单位,尽可能地模块化,子函数化,更低耦合性,更独立——你可以理解成,一旦达成这样的目的,你可以很轻易地把在某个在PC上使用的代码直接放到51程序里而甚至无需修改数据类型;
2.代码更易于理解,具体而言,楼主关心的是:
1.注释量少到非注释不可——作为一个关心代码规范,能为其他代码阅读者着想的我们,估计都有过非常认真写大量注释的时期,这种行为属于本意良好,却未必有效果的善意行为。
相信我,不必要的注释不仅是对注释者的繁琐工作,更是对阅读者的极大困扰。(尤其是那种喜欢 用 /**/ 单行注释的亲,要知道大量这种的注释符号插在代码中,会造成企图通过注释关掉部分代码的操作变得非常麻烦,因为注释符号是不嵌套的。)
而减少非必要注释量的具体做法可以简单地概括为: 通过更达意的命名取代一句注释,比方说下面这个极端的例子:- /* This function use to get a random number */
- char fun1(void);
- //其实,它可以写成
- char getRandom(void);
复制代码 2.良好命名,包括变量,宏,函数,结构体 等等等等,所有可以命名的东西。
这个话题可以从上一点引申开来。
我们要集体鄙视 类似于 char a,b,c; typedef {...}Str1,Str2....... 这一类的命名。
因为它什么信息都不能告诉我们,如果满世界的人都叫 张三1,张三2,李四1,李四2,你是什么感觉?——是的,上面那两种典型命名给我们的感觉就和这个类似。
3.前一个问题说到宏的命名,这里再引申说宏,避免使用直接的数值,而把数值定义成宏,它的好处 除了我们众所周知的 “修改一处就可以不漏下任何都用到的地方” 这种浅显的目的以外,更重要的是,它让这个常数带上了自己的意义。
以上谈到的几点属于比较表面的(但是很重要)问题,下面谈的是更深层次的,在软件架构框架上的问题.
3. 减少不必要的宏嵌套层次
前面我们提到通过宏来避免使用直接数,实际上,宏还有另一种重要的功能:如果是一个带参宏,它在功能上会相当于一个函数(确切地说是相当于一个C++的内联函数)。它的存在,自然是可以提供一些函数实现以外的作用,但这种功能,个人觉得大多数时候其实是被人为夸大,其实没有非要不可的紧张气氛。
然而,我却常看到这种手法的滥用。比如在一些厂商提供的例程里。
下面我们简单看一个例子:- #define IS_BUTTON1() BUTTON1_PORT & BV(3)
- 往下追查这两个宏
- #define BUTTON1_PORT P1
- #define BV(n) (1<
- 关于这个带参宏,不知道你是怎么看?
- 比如对于 移位操作 它煞费心思弄成一个宏,这个意义到底有多大?
- #define IS_BUTTON1() BUTTON1_PORT &(1<<3)
- 这种写法和上面那种写法在表达这个开关的IO是连在P1口的第3号脚上 这个意思 难道有任何差别吗?
- 答案是没有。不仅表达意义没任何不同,就连编译时的速度也稍优于前者;
复制代码 这只是楼主凭记忆随便写的一个只有两层的 宏嵌套,实际上,在(比如说前面提到的 TI的CC2530例程)中,诸如这种的嵌套多了去,而且嵌套层次,复杂程度远远超出举例,为了看懂一句宏,我常常要ctrl+F好几次,一层层庖丁解牛 才能看懂。
这个话题引申开去,还包括,减少任何不必要的嵌套。特别是 循环结构 和 条件判断结构 以及更复杂恐怖的 循环+判断(而实际上,这种结构更常见).
4.项目文件的组织要层次清晰
这个原则,是目前为止提到的第一个不以“代码”为着眼点的问题。还记得前面我提到过的“一旦达成这样的目的,你可以很轻易地把在某个在PC上使用的代码直接放到51程序里而甚至无需修改数据类型” 吗?
这是我在说到 模块化 的时候提到的,没错,层次清晰是实现模块化的必然结果,也是模块化的根本动力。
模块化 这个词经常被提到,甚至在非软件,非IT行业,然而,回到程序代码上,很多声称模块化的代码却一点也不模块化,或者模块化程度很低。举个例子,如果一个最简单的FIFO函数(不是硬件上的FIFO寄存器,而是指一个数组或者什么结构体,数据的存取顺序是 先进去的数据最先被取出。)
如果一个软FIFO函数,从PC端程序搬到51单片机上使用,要作不小的一番修改,那这叫什么模块化?
而导致这些的原因可能只是简单到 直接使用int这个数据类型,而不是使用 U32或者uint32_t这一类的重定义类型;(因为这个问题很难再复杂,除非他根本没有单独抽离出这一部分FIFO功能的代码!)
对于整个项目文件的组织也是一样的。除了我们刚用VC6初学C语言那会,我们大概都不会写那种只有一个c源文件的项目了。
我们都是按照各种分类的原则和目的(而它们都被统称为“模块化”,虽然有些模块化还不如不模块化)把所有的代码分成好几个甚至十几个,几十个源文件,并为之配备相应的 头文件。
这样的源文件应该和前面提到的那个 软FIFO函数 一样,理想的情况下,是要做到它们随意搬到一个同种语言的项目里可以几乎不做任何修改就直接使用。
这样一来,下一次,当我们写另外的项目时,如果需要使用到相同的功能,我们至少也可以通过直接复制这个源文件去直接实现,更高端的方法是编译成一个库链接使用;
另一方面,或者是更重要的目的:因为这些模块,这些源文件是经过长时间的验证,或者严格的测试,使我们对它们足够信任,那么,下次当发现什么bug时,我们就不会轻易怀疑这类通用功能的实现源码,可以大幅度的减轻我们的调试强度。
说得够多了,暂时就这么多——但要做到还是很不容易的,不过不要紧,我们总是在实践中一点一点增进自己对程序代码的掌控能力,一点一点追求更完美的代码。
|
|