9146|16

7815

帖子

56

TA的资源

裸片初长成(中级)

楼主
 

以TI的一个SimpliciTI(点对点简单无线数传协议)例程为例,说一下重构 [复制链接]

重构,用最简单的说法就是: 在不改变代码现有功能的前提下,优化和改善代码。

对代码而言,它的优化和改善是多方面的,为了避免这个话题过于庞杂,并且超出我们的能力范围。
我们只着重考虑我们所关心的 部分代码质量问题。

这里说的 “代码质量”,是楼主临时想到的一个提法,如字面意思。

现阶段,楼主所考虑的 代码质量 比较简单,只包含:
1.代码以功能为单位,尽可能地模块化,子函数化,更低耦合性,更独立——你可以理解成,一旦达成这样的目的,你可以很轻易地把在某个在PC上使用的代码直接放到51程序里而甚至无需修改数据类型;

2.代码更易于理解,具体而言,楼主关心的是:
   1.注释量少到非注释不可——作为一个关心代码规范,能为其他代码阅读者着想的我们,估计都有过非常认真写大量注释的时期,这种行为属于本意良好,却未必有效果的善意行为。
      相信我,不必要的注释不仅是对注释者的繁琐工作,更是对阅读者的极大困扰。(尤其是那种喜欢 用 /**/ 单行注释的亲,要知道大量这种的注释符号插在代码中,会造成企图通过注释关掉部分代码的操作变得非常麻烦,因为注释符号是不嵌套的。)
      而减少非必要注释量的具体做法可以简单地概括为:   通过更达意的命名取代一句注释,比方说下面这个极端的例子:
  1. /* This function use to get a random number */
  2. char fun1(void);

  3. //其实,它可以写成
  4. char getRandom(void);
复制代码
2.良好命名,包括变量,宏,函数,结构体 等等等等,所有可以命名的东西。
     这个话题可以从上一点引申开来。
     我们要集体鄙视 类似于 char  a,b,c; typedef {...}Str1,Str2....... 这一类的命名。
     因为它什么信息都不能告诉我们,如果满世界的人都叫 张三1,张三2,李四1,李四2,你是什么感觉?——是的,上面那两种典型命名给我们的感觉就和这个类似。
     3.前一个问题说到宏的命名,这里再引申说宏,避免使用直接的数值,而把数值定义成宏,它的好处 除了我们众所周知的 “修改一处就可以不漏下任何都用到的地方” 这种浅显的目的以外,更重要的是,它让这个常数带上了自己的意义。
     
     以上谈到的几点属于比较表面的(但是很重要)问题,下面谈的是更深层次的,在软件架构框架上的问题.

3. 减少不必要的宏嵌套层次
         前面我们提到通过宏来避免使用直接数,实际上,宏还有另一种重要的功能:如果是一个带参宏,它在功能上会相当于一个函数(确切地说是相当于一个C++的内联函数)。它的存在,自然是可以提供一些函数实现以外的作用,但这种功能,个人觉得大多数时候其实是被人为夸大,其实没有非要不可的紧张气氛。
然而,我却常看到这种手法的滥用。比如在一些厂商提供的例程里。

        下面我们简单看一个例子:
  1. #define IS_BUTTON1() BUTTON1_PORT & BV(3)
  2. 往下追查这两个宏
  3. #define BUTTON1_PORT P1
  4. #define BV(n) (1<

  5. 关于这个带参宏,不知道你是怎么看?
  6. 比如对于 移位操作 它煞费心思弄成一个宏,这个意义到底有多大?
  7. #define IS_BUTTON1() BUTTON1_PORT &(1<<3)
  8. 这种写法和上面那种写法在表达这个开关的IO是连在P1口的第3号脚上 这个意思 难道有任何差别吗?
  9. 答案是没有。不仅表达意义没任何不同,就连编译时的速度也稍优于前者;
复制代码
这只是楼主凭记忆随便写的一个只有两层的 宏嵌套,实际上,在(比如说前面提到的 TI的CC2530例程)中,诸如这种的嵌套多了去,而且嵌套层次,复杂程度远远超出举例,为了看懂一句宏,我常常要ctrl+F好几次,一层层庖丁解牛 才能看懂。
         
         这个话题引申开去,还包括,减少任何不必要的嵌套。特别是 循环结构 和 条件判断结构 以及更复杂恐怖的 循环+判断(而实际上,这种结构更常见).
     
4.项目文件的组织要层次清晰
         这个原则,是目前为止提到的第一个不以“代码”为着眼点的问题。还记得前面我提到过的“一旦达成这样的目的,你可以很轻易地把在某个在PC上使用的代码直接放到51程序里而甚至无需修改数据类型” 吗?
         这是我在说到 模块化 的时候提到的,没错,层次清晰是实现模块化的必然结果,也是模块化的根本动力。
         
         模块化 这个词经常被提到,甚至在非软件,非IT行业,然而,回到程序代码上,很多声称模块化的代码却一点也不模块化,或者模块化程度很低。举个例子,如果一个最简单的FIFO函数(不是硬件上的FIFO寄存器,而是指一个数组或者什么结构体,数据的存取顺序是 先进去的数据最先被取出。)
         如果一个软FIFO函数,从PC端程序搬到51单片机上使用,要作不小的一番修改,那这叫什么模块化?
        而导致这些的原因可能只是简单到 直接使用int这个数据类型,而不是使用 U32或者uint32_t这一类的重定义类型;(因为这个问题很难再复杂,除非他根本没有单独抽离出这一部分FIFO功能的代码!)

         对于整个项目文件的组织也是一样的。除了我们刚用VC6初学C语言那会,我们大概都不会写那种只有一个c源文件的项目了。
         我们都是按照各种分类的原则和目的(而它们都被统称为“模块化”,虽然有些模块化还不如不模块化)把所有的代码分成好几个甚至十几个,几十个源文件,并为之配备相应的 头文件。

          这样的源文件应该和前面提到的那个 软FIFO函数 一样,理想的情况下,是要做到它们随意搬到一个同种语言的项目里可以几乎不做任何修改就直接使用。
这样一来,下一次,当我们写另外的项目时,如果需要使用到相同的功能,我们至少也可以通过直接复制这个源文件去直接实现,更高端的方法是编译成一个库链接使用;
          另一方面,或者是更重要的目的:因为这些模块,这些源文件是经过长时间的验证,或者严格的测试,使我们对它们足够信任,那么,下次当发现什么bug时,我们就不会轻易怀疑这类通用功能的实现源码,可以大幅度的减轻我们的调试强度。
         

       说得够多了,暂时就这么多——但要做到还是很不容易的,不过不要紧,我们总是在实践中一点一点增进自己对程序代码的掌控能力,一点一点追求更完美的代码。
此帖出自编程基础论坛

最新回复

谢谢楼主的分享,内容特别特别好,学习到了。  详情 回复 发表于 2016-4-27 14:16
点赞 关注
 

回复
举报

1149

帖子

3

TA的资源

五彩晶圆(初级)

沙发
 

回复 楼主 辛昕 的帖子

好文章,顶一下!:carnation:
此帖出自编程基础论坛
 
 
 

回复

7815

帖子

56

TA的资源

裸片初长成(中级)

板凳
 

重构的代价和危险

前面说的内容很容易激动人心,这的确是一项迷人的技术。
不过,最好还是丑话说在前头。
任何事情都有代价,危险,重构也是。

从前面的简单解释就可以知道,重构的一个特征 “不改变任何代码的功能”。
这就是说,对于你的上司,老板,以及客户,这件事情没有任何实际意义。

所以说,你的所有努力,在别人看来都是 浪费时间。
尽管你很清楚,这对于整个开发过程很重要,对于最终完成的代码质量有很大提高。更长远的说,它还会对后期维护带来极大的好处......
只不过,所有这些,都抵不过你现在被指责在干无意义的事情,你要面对的仍然是紧张的进度。
这就是重构的代价。

更加不幸的一个消息是,重构也有危险,重构尽管可以优化代码,但同时也能在事实上恶化代码。
如果你花了很多时间来重构,并且破坏了代码,那你同时付出了不菲的时间代价和承担了不幸的风险。

但我希望你仍然要耐着性子往下看,因为,通过系统的方法和步骤,我们是有办法把 这些代价 和 风险 降到最低,从而达到 收益最大化 的。
此帖出自编程基础论坛
 
 
 

回复

7815

帖子

56

TA的资源

裸片初长成(中级)

4
 

动手1: 找到要做的第一件事

我非常担心我再啰嗦下去,你们都会失去兴趣。
就好像,在SimpliciTI的例程搞到手以后,我纠结了很久如何把这个例程抽取出来,在别的文件夹下编译和试验以前一样。
最后我花了不少时间,甚至不慎破坏了安装文件,不得不两三次重新安装,重头再来,浪费了不少时间。而此时,我的上司已经开始咨询我进度,并传达了一定的进度压力。
而解决这个事情的关键在于,我下定决心,先在我的开发板上验证这个例程确实起到点对点收发的作用,再考虑其他事情。

而对于重构而言(这里免去解释如何验证这个例程的过程)
我们先考虑要实现的第一个目标是什么?

先观察这个例程,看看它有什么让人不满意的?

打开安装文件夹下的example,我们首先可以观察到它的组成部分。
三个主文件夹 Component  Doc Project
Component包含的是实现SimpliciTI协议的底层库的所有源文件。
Doc是一些说明文档,其中有两个文档对我们来说是最重要的。
Project包含了各类不同芯片不同开发板的例程项目文件。
打开我们的SRF05(因为我使用的是cc2530芯片,在所有的开发版本里,它最接近CC2530EM)。
打开发现了它还分四个文件夹,分别对应四种不同的网络结构。
而我选择了PEER-TO-PEER,因为我只需要一对一传输;

就我而言,这就是第一个不满意的地方。
从TI官方的角度出发,它这么做是必须的。
因为它的这套协议是在它的好几个系列 无线射频 芯片上 都通用的。

而它本身也针对各个芯片系列做了各自的开发板,它们自然有许多来自芯片底层寄存器,操作方式,甚至开发板上配置的不同。
所以,TI官方这么做,是为了让这个例程更加通用。

但是,从我的角度来说就不一样。
首先,我完全不了解这个协议,我也不了解这些芯片之间,这些开发板之间的不同。

我只希望在我自己的2530上面,实现,而且,只是 一对一,也就是peer-to-peer.

那么,这个复杂的项目文件,这些分离的底层库,和上层应用,就会分散我的注意。
而且,对于我的角度来说,那些其他开发板其他芯片的内容,对我来说,都是“多余”的代码。

所以,为了更加集中精神在我要做的 2530 的 peer-to-peer 这唯一的一个点上,我首先要做的事情,就是重新安排项目文件的组织。
——这样做的另一个目的是,我可以随时保存当前工作的最后状态,假设那天我不小心破坏了所有工作,我也可以恢复到最后一个可工作版本,减轻毁灭带来的损失。

——在此之前我就深刻体会一个代价。
因为我的重构有时有一些危险性比较大的动作,造成了好几次破坏了安装文件夹下的源代码,使得我不得不重新来过。

好,现在,第一项工作,单独分离出 针对cc2530的 peer-to-peer.
此帖出自编程基础论坛
 
 
 

回复

7815

帖子

56

TA的资源

裸片初长成(中级)

5
 

动手2:重构项目文件结构

在动手重构以前,一定要记住,重构的原则是,不要改变代码的功能。
那么,首先我们就要先知道代码的功能。

这个例程是 烧录两快cc2530板子,一块作为 监听方,一块作为 发送方。如果成功点对点传输数据,两块板子就会交替闪烁LED。

好,按照 动手1 的要求,我们先来调整 项目文件夹。
我们要把所有必须用到的 源文件,头文件 全部集中在这个文件夹里,但是又不要单纯二三十个文件杂乱无章都和项目文件堆放在一起。

我个人的划分层次是按照 硬件抽象层 功能模块层 公用函数层 应用层(也就是主调层,一般就是main()函数所在的 主源文件 以及各类 和它同等地位的 比如 中断服务程序。)

这里,由于存在一个独立的simpliciTi库,我们单独给他安排一个文件夹,名字不妨就叫做 Component(确实不要紧,过后我们可以改名字)

这种重构,会带来一个比较麻烦的技术问题。
我们可以在项目里直接添加散乱在几个文件夹里的c文件,但是编译时却要指定搜索 头文件 的路径。

在有的时候这会是一个很麻烦的问题,不过我们不必要太过于纠结,我们只要能保证在自己使用的编译器或者ide下解决这个问题就可以了——
很多时候,解决问题的方法并不重要,重要的是这个问题的意义和提出这个问题。

因为cc2530开发主要用的是IAR,IAR的option选项中有一个 C compilier下的预处理选项,通过在这里添加相对路径,这个问题会非常容易解决。

具体的细节,请自行搜索引擎。

在其他的IDE下或者编译器,比如说在vc6啊,keil啊,或者你在使用的某个很小众的第三方ide......
我就试过,我直到现在都没想明白怎么做,于是我不得不真的把所有源文件头文件都放在一个文件夹下。

但是,这并不重要,重要的是,至少在这个cc2530,在IAR下,我们可以很轻易解决问题。
考虑得太多,不知取舍,有时会造成更大的麻烦。
完美是拖延的一大温床。

好了,调整好后,重新添加新位置的c文件,修改预编译器路径,然后重新烧写进片子,看看是否还有两个LED在交替闪烁吧。
此帖出自编程基础论坛
 
 
 

回复

7815

帖子

56

TA的资源

裸片初长成(中级)

6
 

动手3:从main()函数开始

现在,我们要回到代码上来动手了。
重构代码,一定要从 发生调用 的地方开始着手。
这是因为 主调处是最接近用户语境的地方,这里我们根本不关心具体的实现细节,只要知道到底要干什么,到底做了什么。

——程序设计的失误,能引发最严重问题的地方就是这里。
这个道理很简单:我不管你是坐飞机还是坐火车还是走路或者实在很糟糕,要先下山转马车到镇里.......但如果我其实想让你去东边,结果你去了西边,那就南辕北辙了。
假如你只是错误选择了交通方案,再严重也不过是花更多的时间或者钱 而已。

所以,不管是重构某一个部分还是整个程序,都必须从主调的地方开始,对于整个程序,自然是main函数。

这里,楼主使用的是IAR环境。
我们知道这个项目,实际上是两个项目,一个是发送方,LinkTo,一个是监听方,LinkListen。
有两个不一样的main函数,通过工作环境切换来完成。

这首先就是一个很别扭的地方。
因为我们更喜欢用 条件编译 来选择编译不同部分代码;

这笔账先记下。
——重构的一个原则就是,发现让你不爽的地方,就像Kent Beck说的,嗅到坏味道,就要把它干掉。

再看。
这两个main源文件 很长很复杂,而且很令人讨厌。
因为它把所有用到的函数实现都放在这里。

我们看函数总是要首先去找main函数,然而,因为这个主源文件里,散乱放着其它函数,加上无处不在无用啰嗦的注释,我们每次都要费不少时间才能找到main函数,这是一件更加不能容忍的事情。

够了,现在我们要动手了。
此帖出自编程基础论坛
 
 
 

回复

7815

帖子

56

TA的资源

裸片初长成(中级)

7
 

动手3:从main()函数开始

查看本帖全部讨论,请登录或者注册
此帖出自编程基础论坛
 
 
 

回复

7815

帖子

56

TA的资源

裸片初长成(中级)

8
 

动手4

查看本帖全部讨论,请登录或者注册
此帖出自编程基础论坛
 
 
 

回复

7815

帖子

56

TA的资源

裸片初长成(中级)

9
 

动手5:增加串口

查看本帖全部讨论,请登录或者注册
此帖出自编程基础论坛
 
 
 

回复

7815

帖子

56

TA的资源

裸片初长成(中级)

10
 

暂时结束 动手6

查看本帖全部讨论,请登录或者注册
此帖出自编程基础论坛
 
 
 

回复

6

帖子

0

TA的资源

一粒金砂(初级)

11
 
查看本帖全部讨论,请登录或者注册
此帖出自编程基础论坛
 
 
 

回复

9

帖子

0

TA的资源

一粒金砂(初级)

12
 
查看本帖全部讨论,请登录或者注册
此帖出自编程基础论坛
 
 
 

回复

9

帖子

0

TA的资源

一粒金砂(初级)

13
 
查看本帖全部讨论,请登录或者注册
此帖出自编程基础论坛
 
 
 

回复

3

帖子

0

TA的资源

一粒金砂(初级)

14
 
查看本帖全部讨论,请登录或者注册
此帖出自编程基础论坛
 
 
 

回复

7815

帖子

56

TA的资源

裸片初长成(中级)

15
 
查看本帖全部讨论,请登录或者注册
此帖出自编程基础论坛
 
个人签名

强者为尊,弱者,死无葬身之地

 
 

回复

7815

帖子

56

TA的资源

裸片初长成(中级)

16
 
查看本帖全部讨论,请登录或者注册
此帖出自编程基础论坛
 
个人签名

强者为尊,弱者,死无葬身之地

 
 

回复

18

帖子

0

TA的资源

一粒金砂(初级)

17
 
查看本帖全部讨论,请登录或者注册
此帖出自编程基础论坛
 
 
 

回复
您需要登录后才可以回帖 登录 | 注册

随便看看
查找数据手册?

EEWorld Datasheet 技术支持

相关文章 更多>>
关闭
站长推荐上一条 1/10 下一条
有奖直播:当AI遇见仿真,会有什么样的电子行业革新之路?
首场直播:Simcenter AI 赋能电子行业研发创新
直播时间:04月15日14:00-14:50

查看 »

 
EEWorld订阅号

 
EEWorld服务号

 
汽车开发圈

 
机器人开发圈

About Us 关于我们 客户服务 联系方式 器件索引 网站地图 最新更新 手机版

站点相关: 国产芯 安防电子 汽车电子 手机便携 工业控制 家用电子 医疗电子 测试测量 网络通信 物联网 4

北京市海淀区中关村大街18号B座15层1530室 电话:(010)82350740 邮编:100190

电子工程世界版权所有 京B2-20211791 京ICP备10001474号-1 电信业务审批[2006]字第258号函 京公网安备 11010802033920号 Copyright © 2005-2025 EEWORLD.com.cn, Inc. All rights reserved
快速回复 返回顶部 返回列表