打开APP
userphoto
未登录

开通VIP,畅享免费电子书等14项超值服

开通VIP
一线研发之声之C程序的软件分层,看您躺枪没?
 本帖最后由 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里面,这里我要很明确的不怕得罪人地表达: 我鄙视这种不成熟的做法
第二枪: 你的设备驱动程序移植起来是不是巨艰难,因为它的插头不利索。详见上述的举例说明。


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









本站仅提供存储服务,所有内容均由用户发布,如发现有害或侵权内容,请点击举报
打开APP,阅读全文并永久保存 查看更多类似文章
猜你喜欢
类似文章
作用域--python
C语言知识复习资料
其实,VBA很简单!
Python入门基础(章节一)
Python学习Tip摘录
python的模块和包
更多类似文章 >>
生活服务
热点新闻
分享 收藏 导长图 关注 下载文章
绑定账号成功
后续可登录账号畅享VIP特权!
如果VIP功能使用有故障,
可点击这里联系客服!

联系客服