论坛首页 Java企业应用论坛

人在江湖:如何用代码保护好自己

浏览 45283 次
该帖已经被评为良好帖
作者 正文
   发表时间:2011-10-12   最后修改:2011-10-14

  现在上一点规模的系统,特别是金融行业的系统,业务规则复杂,一般是将系统分割成较小的子模块,每个人开发一个或几个模块,模块开发完成后做成一个jar包,供其它的模块调用,待所有模块开发完成后再集成在一起。对于充值系统而言则更为复杂,除了要将系统分解成子模块外,还要与众多外围系统交互,如收单服务商、充值中心、银行等。程序员就是其中一个或几个模块的开发者。

 

本文的讨论的要点是:在系统出现问题时,如何有理有据的保护好自己。

 

     对于软件开发者来说,我们在公司里一般处于弱势群体,每当系统出现问题造成事故的时候,运营人员一般都会将矛头指向研发人员。他们这么做一般是将责任撇清,以防引火上身。当出现事故给公司造成了实际损失,那么公司老板肯定会介入进来,总有人要背这个黑锅的,这个责任会从上级一级一级的压下来,直到推到某个模块具体编码的程序员身上,这时候我们开发者唯一进行反击的武器就是日志,如果日志没有清楚的记录什么时间调用了什么代码,什么时间调用的代码出现异常,异常的原因是什么等详细信息,那么负责编码的程序员也将是一头雾水莫口难辩,屎盆子全都扣到你头上,最后的结果就是背了黑锅走人。

 

     这种情况在我上一家公司已经发生了很多次,在入职的那段时间里Team leader经常提醒我们,编码一定要小心小心再小心,千万不要出错,出事故了我要挨老板的骂,你们有人就得被辞退。在部门svn服务器的文件夹里有专门存放事故的总结报告,一个事故,一个word文档。差不多有11个文档,直接造成经济损失的A级事故也有3个左右,这几个同仁被辞退的时候心情肯定是极其郁闷。前段时间得到上一家公司同事的消息,公司有个技术大牛写的程序和另外一家公司的系统进行业务交互,结果充值报文被人给修改了,金额被修改成了200万,结果这个大牛写的代码不严谨,没有考虑周全,收到响应报文时没有做金额校验就做了转账操作,听说这一下公司就损失了200万,老板娘在公司发飙,把笔记本重重的摔在他办公桌上。最后也是被辞退,无论技术多么牛的人还是小心些好,常言道:小心驶得万年船。

 

 

现在我用做过的一个充值系统的交互图来说明我的观点,如何调用黑洞代码(所谓黑洞代码就是说你将要调用的这个法,对你而言就是一个黑盒子,你不知道这个方法做了哪些操作,你不知道它会出现什么错误)

 

如图:

 

这是一个银联卡充值系统,这个系统的功能是:只要你将可以能够在网上进行支付的银行卡(哪个银行的都可以)和自己的手机号绑定,就可以随时用手机拨打自动语音充值电话给自己的手机号或着他人的手机号充值,这个系统的参与者有IVR语音服务商、收单服务商、电信充值中心。整个系统可划分为IVR语音模块、收单模块、电信指令交互模块、报文加解密模块、支付模块。

 

模块开发明细表:

 

模块名称 开发者 调用模块编号 序号
IVR语音交互模块 张三 2 1
收单模块 李四 5、3 2
电信指令交互模块 王五 4 3
报文加解密模块 那六   4
支付模块 小七   5

 

系统上线时非常的不稳定,充值失败率很高,平均每五笔就有一笔充值失败。问题定位在2和3这两个模块上,两位开发者各就执一词,争的面红耳赤。我们看看李四的代码,李四的代码去调用了王五的代码。

 

李四的代码截图:

 

 

 

用红色框圈起来的代码是王五的代码,王五的代码打成jar包供李四去调用,李四将王五的业务实现用Spring注入进来,然后直接去调用doCharge方法,并将结果返回,看起来没有问题。只不过只是表面看起来没有问题。

 

打问号的代码你敢大胆的这样用吗?

 

风险一:

 

doCharge方法对李四来说就是一个黑洞代码,不知道这个方法作了哪些操作,会不会有错误发生,因为doCharge方法未声明该方法可能要抛出的异常,李四以为这个doCharge方法是安全的,所以没有加try{}catch()代码捕获异常。什么事都怕万一,万一这个doCharge出现了异常怎么办?

 

风险二:

如果doCharge方法因为某种原因产生了死锁,那么你的调用结程死在里面了,永远不返回调用结果,这种情况怎么办?

 

 

风险一的应对措施:

  对于要调用的关键方法,无论它有没有声明要抛出的异常,我们都要对它保持怀疑的态度,加try{}catch捕获,并将捕获到异常,记录日志后,包装下继续抛给上层调用者。让上层调用者知道出错了,异常抛出了你就尽到了通知的义务,系统出问题与你没有关系,不然出了问题问你:你为什么不捕获异常?为什么不抛异常?虽然有很多种理由可以向质问者解释,但还是多一事不如少一事,别偷懒加个try{}catch()捕获可能出现的异常。

 

风险二的应对措施:

 

打个比喻:你是猎人,你要在山洞里抓一只狼崽出来,但是你不确定这个山洞里有什么危险,聪明的猎人会放猎狗进去抓狼崽,如果猎狗进去一段时间没有出来,说明里面有危险,猎人再想其它办法。如果猎人自己进去是有风险的,谁知道这个洞里面是狼还是虎。这个比喻想说的是,如果你要调用一个你认为不太安全的方法,不要用主线程调用(猎人),创建一个调用线程(一只猎狗)去调用,这样做的好处是能够监控调用是否成功,还可以设置调用的超时时间。

 

用这个比喻我们创建一个猎狗工具类,调用黑洞方法时,自动生成调用线程,如果调用时间超时,抛出TimeoutException

采用猎狗模式修改后的代码:

 

 

事后终于找到了bug了,问题出在王五的电信指令交互模块上,发送给电信的报文长度必须符合协议,否则电信那端收到非法包后会将Socket连接断开,协议规定充值金额必须是4位数字,不足4位的,左补0,比如说客户要充值10,补全的就是0010

充值100,就是0100.如果客户充值少于10元,要在左侧补3个0,这个bug出现在王五在处理个位充值时,少补了一个0,结果是客户充值2位数金额的话费就成功,一充值个位数的话费Socket连接就断开,李四的调用线程一直堵塞在这里。

 

如果一开始李四采用猎狗模式的话,出现问题一看日志便知道问题出在哪里,有理有据的指出问题所在,也不用背这个黑锅了。

 

上面的代码适合Jdk1.5以上使用,如果想在jdk1.4使用,请自己改造下。

 

PS:应网友要求,贴出猎狗代码,优势和缺点请大家比对

 

普通调用:

 

猎狗调用:

 

 

 

 

   发表时间:2011-10-12   最后修改:2011-10-12
楼主写的很有道理.在多个关键性方法黑盒调用时,确实不能相信任何的行为都是可以正确执行的.

记得以前写一个邮件接收程序给客户.客户的邮件服务器是另一个公司的.
在接收程序使用一段时间后,总是会出现收不下来信的情况.
我们的程序只是个简单的while循环.
分析日志发现,在一段时间后,再也没有任何日志产生.
怀疑为某个环节一直阻塞.
改成每隔一段时间开线程去接收,发现有些线程一直没有退出,并且越挤越多,最终导致任何线程也无法连接到邮件服务器.
这下确认为邮件服务器可能会在接到请求后,不返回任何数据,也不做任何响应....
现在改成线程在一定时间后超时,算是可以正常的工作.
0 请登录后投票
   发表时间:2011-10-12  
i2534 写道
楼主写的很有道理.在多个关键性方法黑盒调用时,确实不能相信任何的行为都是可以正确执行的.

记得以前写一个邮件接收程序给客户.客户的邮件服务器是另一个公司的.
在接收程序使用一段时间后,总是会出现收不下来信的情况.
我们的程序只是个简单的while循环.
分析日志发现,在一段时间后,再也没有任何日志产生.
怀疑为某个环节一直阻塞.
改成每隔一段时间开线程去接收,发现有些线程一直没有退出,并且越挤越多,最终导致任何线程也无法连接到邮件服务器.
这下确认为邮件服务器可能会在接到请求后,不返回任何数据,也不做任何响应....
现在改成线程在一定时间后超时,算是可以正常的工作.

那个充值系统和你的情况差不多是一样的,也是经常发现日志里没有任何数据,当初最原始的方法每隔段时间重启一下服务,很是痛苦。
0 请登录后投票
   发表时间:2011-10-13  
不光内部调用要注意,对外的接口调用也是同样的道理
0 请登录后投票
   发表时间:2011-10-13   最后修改:2011-10-13
学到很多,猎狗工具类代码能不能放出来?
0 请登录后投票
   发表时间:2011-10-13  
lz分析得不错,不过看来lz的公司不太好,一有问题都相互推托
0 请登录后投票
   发表时间:2011-10-13  
学到很多~对于我这个新手而言,很多都是很宝贵的经验~谢谢~
0 请登录后投票
   发表时间:2011-10-13  
个人愚建:
1 王五的doCharge方法本身就应该抛出异常(冲值失败,超时等),那么李四调用时便自动提示try+catch.
2 王五冲值拼0这个功能应该有相关的测试用例,而且调用外部接口应当打印日志,用于证据.
0 请登录后投票
   发表时间:2011-10-13  
bingki 写道
学到很多,猎狗工具类代码能不能放出来?
代码我已经贴到上面了,一会儿我做一个Demo放上来。大家相互学习了。
奇怪,这个文章我发到博客频道了,怎么这里也有
0 请登录后投票
   发表时间:2011-10-13  
代码习注释和做日志都是好习惯,学习lz 的经验
0 请登录后投票
论坛首页 Java企业应用版

跳转论坛:
Global site tag (gtag.js) - Google Analytics