[设计原则] 保护本身

      软件开发中的保护本身不是指我的行为上的自我保护,而是指防犯别的模块的错误却最终表如今我所设计的模块上。当某一模块出现错误时,毫无疑问大多情形下模块的做者将会被“招见”。出现软件错误是正常的,但最为郁闷的是最后发现错误是别的模块误用而引发的。偶而这种情形的发生是能够理解的,可是若是这种事情是屡见不鲜,则须要考虑经过设计来“保护本身”。提出“保护本身”这一设计原则,其目的不在于让咱们变得不敢承担责任,更不是鼓励“扯皮”,而在于高效地解决问题以提升工做效率。
      下面以做者所从事过的一个真实的项目为例来讲明“保护本身”这一设计原则是如何起做用的。图1示例了某项目在一个电信级设备上的部署图,这一电信设备使用的是实时Linux操做系统(Monta Vista Linux和WindRiver Linux),图中的SNMP Agent进程正是做者所在的项目组开发的,而Box库是由其它的团队所开发的。图中的FDR(Function Definition Rule)是指一套函数定义规则,是Box库在实现函数时应当遵照的准则。SNMP Agent所实现的功能是使得设备以外的网络管理程序能够经过SNMP Agent上的SNMP接口来管理电信设备。SNMP Agent收到设备外部发送过来的SNMP消息以后,将消息最后转换为函数调用的形式来存取信息,而这些函数的实现是位于Box库中的,注意Box库是由一个印度团队开发的而不是中国团队。读者能够想象到的是,因为Box库是被SNMP Agent进程调用的,所以,一旦Box库中存在严重的缺陷,其最终将直接致使SNMP Agent进程崩溃。对于很多崩溃的情形,经过运用gdb工具查看崩溃时的调用栈能很快地找到问题的根源,这类问题或许也不算大,哪怕当即解决不了也仍是有点方向的。可是,在使用gdb工具查看调用栈时,若是发现任务的堆栈被破坏了(stack corrupted),那问题可就麻烦了,由于这种错误能够说是行业内最难解决的问题。有读者会想到经过使用日志的方式将有助于接近问题的根源,这种作法的前提是问题容易重现,不然不可能运用打印调试日志的方式让软件在用户那运行,要知道不少问题在实验室是没法重现的,由于其所模拟的环境不如真实的那样复杂。另外,采用日志的方式并不经济,由于它须要占用大量的处理器时间,进而可能改变运行环境使得问题难以重现,所以这种方法有它极大的局限性。另外,还有一个更为重要的问题,这个完整的软件是由两个团队开发的,一个问题的出现,究竟是出在SNMP Agent侧呢?仍是Box库侧?在现实中,不可避免地因为是SNMP Agent出现了崩溃,Box库的开发人员天然但愿SNMP Agent开发团队去找问题。不难想象,当问题是出在Box库侧时,不管SNMP Agent如何努力都发现不了问题的根源,而最终将致使一个问题被拖延很长时间也毫无结果。若是出现的问题很是的严重和紧急,那就意味着这一问题对于高层管理人员具备很高的可见性,也可能高层都将关注问题的解决过程,那意味着SNMP Agent —— 做者所在的团队,将面临很大的压力。读者可能看到这会想,是否是做者把问题给假设得太严重了?不是,这是真实发生过的事情!
图1
     除此以外,从业务逻辑来看SNMP Agent相对的稳定,由于它只是翻译收到的SNMP消息并最终调用Box库中的函数完成消息的处理,而Box库会因业务的演进而频繁地变更,于是Box库出错的可能性也更大。那如何经过设计来介定当堆栈被破坏这类严重的问题出现时,应当由哪一个团队去负责追查问题的根源呢?或者说,SNMP Agent团队如何经过设计来保护本身以避免“背黑锅”呢?这种防范是有意义的,它不仅意味着问题出现时能快速的介定责任团队,也能够避免没有必要的团队扯皮,说到底就是能提升工做效率。
     做者所提出来的第一个解决方案的思路是,可否经过设计一种方法记录SNMP Agent程序当前正在运行哪一部分的代码?固然,这种信息只要足于区分是运行Box库中的代码或是SNMP Agent中的就好了。最为直观的作法是,每调用Box库中的函数时,在调用以前打印一行日志,在调用返回后又打印一行日志。这种方法在前面说起了,可能会存在必定的性能问题,但其思路仍是对的,只是须要采用其它的更为高效的方式替代日志记录。做者想到了Linux中的共享内存!Linux中的共享内存有一个特色,一个程序若是分配了一块共享内存,若是程序不主动对其删除,则即便是程序出现了崩溃其中的内容也一直保存在那不会被更改,固然操做系统进行过了重启的话则除外。
     如此一来,经过设计能够将调用Box函数以前和以后的信息经过使用一个×××变量的方式记录程序是不是在调用Box库,固然,这个×××变量是位于SNMP Agent所建立的共享内存中的。好比设置整型值3表示将要调用Box库中的某一个函数,而在调用完了这一函数后将这一值设置成4。当SNMP Agent出现崩溃后,在其下一次启动的初始化阶段时(咱们的系统有进程管理程序,发现一个进程出现崩溃之后,又会自动重启它),经过打开同一个共享内存块就能够知道上一次程序出错时,是不是出如今调用Box库其间,若是是则记录一个错误日志。好比,若是发现整型变量的值为3说明崩溃是发生在Box库内的,则记录一条错误日志。固然,设计考虑到了多线程的问题,且以线程为单位来设计的。有了这种方法,当出现问题时,经过查看日志就能很快地定位责任团队,且几乎彻底不失程序的执行效率。
     这种设计方案在操做中存在必定的运做效率问题。好比,即便是Box库的问题,这一问题一旦在测试部门发现,则必定会将缺陷提交给SNMP Agent团队,SNMP Agent团队在查看日志后,发现是Box库形成的问题,因而将缺陷转交给了Box库团队。能不能又经过设计让Box库一出现问题就让测试部门将缺陷提交给Box库团队呢?图2展现了进一步的设计方案。
图2
     在这一方案中,增长了一个新的Proxy进程,这个进程的程序也是由SNMP Agent团队开发的。SNMP Agent进程和Proxy进程之间采用了IPC(Inter-Process Communication,即进程间通信)进行通信,就是将前一方案中SNMP Agent直接调用Box库函数的形式转化成了经过进程间通信将这一调用发送给Proxy进程,Proxy进程再经过FDR接口调用Box案。固然上一方案中采用共享内存记录哪一部分代码正被调用的方法被运用到了Proxy进程上,以帮助介定问题是发生在Proxy进程自己仍是在Box库内。在这种方案中,其中很重要的一个内容是,Proxy进程的逻辑很是的简单,即接受来自SNMP Agent的消息而后调用Box库函数并经过消息将Box库函数所返回结果传给SNMP Agent,其逻辑不会由于项目的不断变化而变化,除非FDR发生了变化。可能这种方案刚开始部署时,Proxy会有必定的缺陷,但通过必定的时期稳定后,一旦出现Proxy崩溃的问题,则极有可能就是Box库所引发的了,而这种情形下测试部门能够直接将缺陷提交给Box库团队。
     至此,相信读者已明白了“保护本身”这一设计原则是如何起做用的了,可能有的读者也会有一点灵感去解决本身项目中正面临的相似恼人问题。另外,不知读者注意到了没有,这里所谈的共享内存方案,其实能够做为一种通机制去辅助缩小出现问题时程序的出错范围。前面也提到是经过一个位于共享内存中整型变量来记录程序的运行点的,而这个变量能够定义232个值用于表示程序的不一样执行点,固然应用程序须要在合适的地方对其设置不一样的值来指示程序运行到了什么地方。
相关文章
相关标签/搜索