打印

一线研发之声 之 C程序的软件分层,看您躺枪没?

[复制链接]
24618|156
手机看帖
扫描二维码
随时随地手机跟帖
跳转到指定楼层
楼主
sedatefire|  楼主 | 2013-7-27 17:01 | 只看该作者 回帖奖励 |倒序浏览 |阅读模式
本帖最后由 sedatefire 于 2013-7-28 16:45 编辑

        
       计算机系毕业的人,大概都会有一门专业课,曰为《软件工程》,当年我不仅上了一次中文版的,后来学校搞什么中英教学,又上了一个学期的英文版的。尽管上了两遍,但我回忆起来当年的教学内容。第一个闪念,却是“大锤测试”,纯属是因为名字让人印象深刻而已。
       当年的我,学习也算认真的,上了两遍的同一门课程,也是隐隐约约找到感觉了。什么需求分析啦、瀑布开发模型啦、项目管理啦、小组成员激励啦。当时正处大三下学期,正开始憧憬着未来的就业生活。当时那个信心爆棚啊,只想着出社会后,在软件公司即使领着一帮牛鬼蛇神,也能够如何如何叱咤风云来着。却不曾想,毕业后的第一年,只是查bug查得狗跳**飞而已。总而言之,在这样美好的愿景下,我对这门专业课还是比较上心的。
       如今工作多年后,独立完成的案子也不少,却发现软件工程里面讲的这些东西,恐怕对于90%的国内公司而言,基本形同虚设哎呀呀,偏题啦偏题啦。今日此篇,说说软件分层的事儿。这只是软件工程里面一个小小的分支,而它的理念也基本普及到每个软件工程师的心中。但老实说,做得好的人还不多。(哎,这话该不该讲我还犹豫挺久,说不定是自己公司太弱了,没见世面呢,但不管怎样,语不惊人死不休的目的是达到了)。
        老外的东西,直译过来,说什么高内聚、低耦合,对于没有一定构建系统经验的大学生来说,无亦于催眠良药。哎,再赞一个加拿大,先工作再上学,再工作再上学,随时可进修的教育制度。
        我先用一个比喻来阐述一下软件分层的概念,然后再来讲述常见的错误,看看您躺枪了没?
        好吧,直接一点,先说软件模块吧,它就是一个插座,接口就是插孔。
       什么是软件模块呢? 说白了,你可以理解成一个独立的xxxx.c档和相应的xxxx.h档。.c档里面的东西是经过精挑细选的,主题明确的几个函数和一些变量等等汇集而成。.h档呢,里面放的也是精挑细选的函数声明,供别人调用。这个别人,软件模块叫做“上层”,它还说如果你是第n层,那么调用者就是第n+1层,你就是它的下层。下层不可依赖上层噢。这些函数,就是传说中的接口。
      在这里我急不可耐的要发第一枪: 许多工程师习惯把全局变量extern出来放到.h里面,这里我要很明确的不怕得罪人地表达: 我鄙视这种不成熟的做法。这种感觉如同把插座里面的导线裸露在机壳外一般。也许有人说,为了压缩代码空间,为了提高执行时间。但,我还是表示更加强烈的鄙视:时空效率的提高可不是这么干的。用这种做法压缩时空只是战术上的,小道尔,整个框架的修改调整方为正道。还有些工程师,习惯把只有a.c档会用到的define常量,结构体,IO定义,enum等,放到a.h里面。最郁闷的是,所有的x.c   xx.c    xxx.c都包含有一个all_include.h文档,而a.h就包含在在这个无所不包的all_include.h里面。这感觉,这感觉,就像所有的隐私都泄露在外,家丑,还有各种卡密码...
      回过头来再说插座吧,插孔(接口)许多人还是整理得不错的。可是插座还有一个非常重要的组成部分------插头,如果是一个转换插座的话。更常见的是一个一米五左右的220V连接线,接往另外一个“插座”。在软件上说白了,就是你这个module.c档里面的代码调用了哪些外部函数,所以包含了哪些.h档,没有了它们,你这个module.c就无法编译过了。我们通常关心对上层提供了什么接口,却常常忽略自己依赖了哪些外部接口。这部分,只有在软件移植,特别是重大的平台移植时,才能体现出来它的重要性。
      平台的移植,我们无法做到拔营即走,攻城略地。在51时代,我们无可奈何(源于静态栈),只能放任它们。但如今m3的时代来临,我们可以将所有的这些对下一层的依赖打包起来,整一整,对下层的依赖,可以整得像插头一般爽利。
      针对插头这件事,我要发一个组合枪,装的是霰弹,您可接好了。
      第二枪:你的设备驱动程序移植起来是不是巨艰难,因为它的插头不利索。举例说明如下:
       1.裸露的寄存器名到处都是,比如p1^x = 0,  tcnt = xxx, 这一部分最好用define打包在一起放在文件头。
       2.违法依赖了“上一层”的模块内容。
        在设备初始化函数中,你是否会直接访问系统参数,不管是用全局变量也好还是函数调用。以lcd初始化为例。
                     lcd_init(void)
                    {
                             lcd_contrast_set (sys_para.lcd_contrast);    /* 我最鄙视的全局变量风 */
                             or
                             lcd_contrast_set ( get_sys_para_lcd_contrast() ); /* 我次鄙视的层次不分明的风格 */
                     }
                     这个事儿,比较恰当的做法是。
                     lcd_init (uint_fast8_t contast)
                     {
                             /* 接下来你懂的  */
                     }
       3.没有保持设备驱动的纯洁性,这个同样属于层次不分明的做法。以buzzer为例。
        /*
          input:   0,长哔~~     1,哔      2,哔哔       3,哔哔哔      
         */
         void  buzzer_ctrl (uint_fast8_t    para)     
          {
                  if  (“应用层”状态 == xxx)
                  {
                            return;     
                   }
                   if (其他不相干的设备状态  ==  xxx)
                  {
                            return;
                  }
          }                 
          这就他妈的坑爹,明明按合约说好了,我传入参数1,就叫一声。你说,你说,为何不叫,为何....
          为了获取其他模块的状态,你不得不在buzzer.c包含无关的.h档在里面。这就是污染,玷污了设备的纯洁。
         这事儿也简单,就是把if拉到外面判断。你可能要说,那我要写好多的if语句啊,浪费程序空间。哈哈,你就等着再中枪吧,希望你像红军一样可以中枪不倒,继续冲锋,或者发表一大段的临死表白。


存个草稿,未完待续

插孔         -----  对上层接口函数,放.h档,基本的要有,init, read/recv,  write/send,其他的可以尽量的丰富多彩。放心,不调用就不链接(得懂链接器原理和操作方式噢),不浪费空间。
插座座体  -----  主题明确的软件模块,
插头         -----  对下层的依赖,常常被忽略的地方,导致移植困难。m3/m0时代的到来,这一切终将改变,但对于函数指针的使用得有一定的造诣。

第一枪: 许多工程师习惯把全局变量extern出来放到.h里面,这里我要很明确的不怕得罪人地表达: 我鄙视这种不成熟的做法
第二枪: 你的设备驱动程序移植起来是不是巨艰难,因为它的插头不利索。详见上述的举例说明。


我的其他系列观点,顺便来新手园地逛逛吧
写给在路上的新手---研发之声系列汇集
https://bbs.21ic.com/forum.php?mod=viewthread&tid=576478&fromuid=567930









评分
参与人数 3威望 +13 收起 理由
老钟叔 + 1 很给力!
12864 + 6 很给力!
dong_abc + 6 赞一个!

相关帖子

沙发
refee| | 2013-7-27 17:04 | 只看该作者
好呢 抢个沙发先 :lol

使用特权

评论回复
板凳
dirtwillfly| | 2013-7-27 17:09 | 只看该作者
本帖最后由 dirtwillfly 于 2013-7-27 17:54 编辑

没学过,~~~

使用特权

评论回复
地板
huangxz| | 2013-7-27 17:15 | 只看该作者
非计算机专业毕业的飘过

使用特权

评论回复
5
sedatefire|  楼主 | 2013-7-27 17:19 | 只看该作者
dirtwillfly 发表于 2013-7-27 17:09
没学过,软件工程可以说是最有用的课程,也可以说最没用

嘻嘻,那是你还没到那个位置,机缘一到,你就悟了。

使用特权

评论回复
6
dirtwillfly| | 2013-7-27 17:21 | 只看该作者
sedatefire 发表于 2013-7-27 17:19
嘻嘻,那是你还没到那个位置,机缘一到,你就悟了。

:lol但我学过项目管理之类的课程,相信道理都是相通的

使用特权

评论回复
7
sedatefire|  楼主 | 2013-7-27 17:53 | 只看该作者
dirtwillfly 发表于 2013-7-27 17:21
但我学过项目管理之类的课程,相信道理都是相通的

哈,再来看下啊,看看语气会不会有些过啦

使用特权

评论回复
8
sedatefire|  楼主 | 2013-7-27 17:53 | 只看该作者
dirtwillfly 发表于 2013-7-27 17:21
但我学过项目管理之类的课程,相信道理都是相通的

也看看你躺枪没

使用特权

评论回复
9
dirtwillfly| | 2013-7-27 18:02 | 只看该作者
第一枪,相当于把行李都打包放一个行李箱里,然后全部自己背着

使用特权

评论回复
10
sedatefire|  楼主 | 2013-7-27 18:13 | 只看该作者
闪,吃饭去

使用特权

评论回复
11
hwk612167| | 2013-7-27 19:01 | 只看该作者
最郁闷的是,所有的x.c   xx.c    xxx.c都包含有一个all_include.h文档,而a.h就包含在在这个无所不包的all_include.h里面。这感觉,这感觉,就像所有的隐私都泄露在外,家丑,还有各种卡密码...
ucos源码就是这么干的,,这说明了啥。

使用特权

评论回复
12
eydj2008| | 2013-7-27 20:14 | 只看该作者
有点乱。。。 像倒RAM一样

使用特权

评论回复
13
dirtwillfly| | 2013-7-27 20:49 | 只看该作者
hwk612167 发表于 2013-7-27 19:01
最郁闷的是,所有的x.c   xx.c    xxx.c都包含有一个all_include.h文档,而a.h就包含在在这个无所不包的all ...

大家都被ucos带坏了

使用特权

评论回复
14
chenshuqu| | 2013-7-27 20:54 | 只看该作者
学习了。多谢楼主

使用特权

评论回复
15
hwk612167| | 2013-7-27 20:58 | 只看该作者
dirtwillfly 发表于 2013-7-27 20:49
大家都被ucos带坏了

那好吧,我就是跟着ucos学的。

使用特权

评论回复
16
hwk612167| | 2013-7-27 20:59 | 只看该作者
坐等LZ讲课

使用特权

评论回复
17
sedatefire|  楼主 | 2013-7-27 21:00 | 只看该作者
关于all_include.h这个,我个人并不反对。但是
1.我反对将所有信息都放到.h文档里
2.我建议设备驱动尽可能的只有 <stdxxx.h>  strxxx.h   和 reg.h即可。 而不要包含了应用层的信息。
3.all_include.h里面只含所有设备驱动的device_xxx.h,毕竟应用层的依赖实在太多。

ucos之所有那个样搞,
1,他的所有模块之间联系紧密,
2,它的编译条件巨多,为了使它们畅通无阻,所以...
之所以编译条件巨多,一为了兼容历史包袱,二来为了编译的code空间尽可能少。

使用特权

评论回复
18
sedatefire|  楼主 | 2013-7-27 21:01 | 只看该作者
hwk612167 发表于 2013-7-27 19:01
最郁闷的是,所有的x.c   xx.c    xxx.c都包含有一个all_include.h文档,而a.h就包含在在这个无所不包的all ...

ucos之所有那个样搞,
1,他的所有模块之间联系紧密,
2,它的编译条件巨多,为了使它们畅通无阻,所以...
之所以编译条件巨多,一为了兼容历史包袱,二来为了编译的code空间尽可能少。

使用特权

评论回复
19
zuoxuqi| | 2013-7-27 21:04 | 只看该作者
继续呗,看看有什么新颖的东西

使用特权

评论回复
20
TungTek| | 2013-7-27 22:27 | 只看该作者
占座学习!

使用特权

评论回复
发新帖 我要提问
您需要登录后才可以回帖 登录 | 注册

本版积分规则

个人签名:我为我自己代言... 链接:一线研发之声系列汇集 链接:Sedatefire的笔记 我发心,待人

4393

主题

5935

帖子

33

粉丝