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

  现在上一点规模的系统,特别是金融行业的系统,业务规则复杂,一般是将系统分割成较小的子模块,每个人开发一个或几个模块,模块开发完成后做成一个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:应网友要求,贴出猎狗代码,优势和缺点请大家比对

普通调用:

猎狗调用:

http://www.iteye.com/topic/1116449

这个仁兄想法很全面,如果你做Team leader我相信大部分研发人员都愿意跟着你干,因为有系统出事故你不会把问题直接推到下属那。这种想法和建议有人提过,我记得有个新员工刚入职的时候,他认为架构组设计的代码很搓就群发邮件和架构组交流,但是不知道为什么这种交流变了味,最后变成了口水仗。他的建议没有被采纳反而得罪了架构组的人。 
  我们无力改变这种研发管理混乱的局面,只能去适应。 
  我们能做的是: 
   1、小心谨慎的写好代码,将能想到的问题做好应对措施。 
      2、异常处理好,该抛的抛,该捕获的捕获,详细的记录日志。出事故责任不会推到我头上,我们只关注每个月的薪水能否打到工资卡上。 
   3、有合适的机会,离开这个尔虞我诈的公司。

学到很多,原来项目小,一两个人做,几乎没记录过日志,现在项目大了,人多了,各个模块集成,很有必要记录日志,不是说为了找责任人,而是为了更高效的工作。而且对于黑洞方法要持有一定的怀疑态度,我也认为非常有必要

个人感觉,从纯技术角度上看,楼主的做法其实属于治标不治本,实在是丑陋的架构和管理手法下不得已采用的丑陋做法。

从风险一的应对措施来看,这段代码一次性不管三七二十一把所有Exception都catch起来,加上了一段没有任何业务语义的描述 (An error occured during XXX之类,如果方法名称起得好的话,从异常的调用栈里就能得到同样的信息。如果你觉得这种异常信息有用,有三种可能:1. 你打算让这条异常一直抛到用户界面上去,显示异常信息。或者 2.你所在的团队没有仔细看异常调用栈的习惯。或者 3.你们的代码类名和方法名起得不知所谓。) ,然后包装成另一个Unchecked Exception抛出来。正常来说,只要在架构的高层位置有一个大的try catch统一来干这个事情,这里就根本不用管。

通用的异常处理应该放在高层,通常底层代码是不用管更底层的代码抛出的RuntimeException的,除非: 
1. 你有处理这个异常的具体方法(也就是在你的层面看,这个根本不是异常,而是业务的一部分)。 
2. 你认为这段代码的调用者有能力处理这个异常,你就应该把这个异常封装成一个Checked Exception再抛出去。 
3. 你为这个异常加入更具业务语义的message再抛出去。

从发现错误的角度看,一前一后两个log已经解决所有问题了,这里的异常处理纯属多余。除非,你的上层代码把异常吃掉了(既没日志又没继续往上抛),这纯属是做架构的人胡闹,害人不浅(如果你能拿到他们的代码,把吃异常的地方找出来,追究责任时可以拿来说事)。

不过就算是上层代码不可控,楼主也应该在可控的范围内把这种通用的异常处理往高层放,不用在底层搞那么多层层叠叠的try catch。

第二个风险的应对也不是正路,你开了新线程去调别人的东西,超时了你的线程就异常终止了,另一条线程还在跑呀(就算你强行kill了这条新线程,你也不知道黑箱里有没有再开新线程,或者通过异步方式干其他东西,例如读写数据库之类),它跑的结果怎么样,有没有什么副作用你都不管了。你这个异常终止之后,整个系统就处于一种未知状态了,后面的操作还怎么做,干脆直接Runtime.exit()再重新启动还安全一点。

既然别人的包本来预期是同步使用的,而且你不知道它的运行细节,把它强行改为异步没什么好处。如果你怕别人的包死锁,一般来说也是一前一后两个log就能发现问题了。感觉你在这里有点贪心,除了想发现错误,还想搞点错误恢复,但异步调用不能这么简单处理的。但如果要处理好,成本太高了。

从管理角度看,也很有问题:

1. 自己人的代码还搞什么黑箱。我一般都要求程序员如果调用不是自己写的接口,至少要往下看三层实现代码,包括别人的代码和开源代码。如果要修改代码,需要先往上找出所有调用关系并阅读代码,保证不会修改了一条调用路径上的bug却把另外的调用路径搞死了。如果是黑箱代码(我所在的项目基本没有),除非有完善的技术支持或者口碑非常好,否则一律不用,而且调用黑箱代码的前后必须做好日志。违反者一经发现,口头鄙视。

2. 找人负责也不是不行,但也不能老板越过技术主管直接找程序员的麻烦吧。不懂技术(甚至懂技术,但不了解具体上下文)的人直接批评程序员,往往只找到了表面现象就开骂(例如楼主说的李四),等到找到真正原因,又挂不住面子,反而更不爽你。楼主举的例子,我想象的场景是这样的:老板问张三,你这个语音模块怎么回事,这么卡。张三就说,具体不清楚,不过我调用了李四的包,可能跟这个有关系(典型的推卸责任)。然后老板就问李四,你怎么回事,李四说具体不清楚,能不能告诉我怎样重现错误,我调试一下(典型的认真负责)?老板一个笔记本拍李四脸上:去死吧你,就是你这个害群之马,人头猪脑。。。。(省略一百字)。这种搞法怎么能管好技术团队?

好在楼主说是上一家公司,估计已经脱离苦海了吧,恭喜恭喜。

======================================= 
补充一点。。。 
我本来想这种方法调用的日志其实最好用debug或trace级别,放在info级别,岂不是逼人在生产环境下关闭info级别的日志(我循环调用一个黑箱方法100次就会看到200条重复的日志,再来50个用户并发访问。。。)。不过后来想想也有道理,这里的日志不是为了查错,而是为了在第一时间撇清责任,等到发现错误再打开日志就太晚了。所以说丑陋的管理导致丑陋的代码。。。

很喜欢这样的帖子,谈谈我的观点。 
从技术角度上: 
1. 内部调用可以通过约定,大家都是白盒,避免使用无用的try/catch。外部调用的业务异常尽量提前约定好,不要自己猜测;系统级别异常根据情况决定是否捕获,是否有必要继续向上层抛出。外部调用日志信息要记录完整,便于线上问题定位。 
2. 猎狗模式是一种好的远程调用方式,对于经常出问题的服务提供方,可以这样做防御性编码。但是如果每处调用都这么写,那太蛋疼了。

从管理上: 
1. 出问题后,要对事不对人。大呼大叫,赶人,这是下下策。这样大家都战战兢兢,人心迟早要散的。出问题后,通过分析找到深层次原因,可以让当事人做案例分享,让其他同学从中吸取经验教训,防止后人犯同样错误。长期坚持下去,可以积累一份“经验教训宝典”,让大家定期学习,减少犯同类错误概率。 
2. 工作中鼓励大家大胆写代码,不要怕犯错误,对于不合理的代码能够及时重构。

在位一天,就要尽最大努力去改变。如果你改变不了,还是乘早闪人。

应该配合拦截器机制使用,否则因为对方的异常、死锁或者更多的隐患而为对方补漏。代价实在太大了。

1. 真正安全的应该是try catch(Throwable ex) 如果只是exception 照样也有一场泄露。
2. 就算要开人,也应该开掉测试而不是开发。
3. 遇见这样的公司,可见是人数不多的公司,极有可能开发和测不分。
4. 这样的软件根本不是什么银行软件,应该各种移动电信广电运营商的小应用拿来收钱的。对应的叫法有BSS,BOSS,支付平台,呼叫中心,银行托收,银行代缴,银行代扣等等。这样的软件不能算是金融软件。
5. 能够随便划掉200万不大可能,银行这种小额支付通常都有限额的。自动取款机上一天才只能取几万块钱,超过十万就需要到银行预约。网上银行和电话银行的转账也没有200万这么高的额度。高额度必须要到柜台办理。
6. 我不懂报文被修改了是什么意思,如果文档上规定是已对方传回的金额为准,那就直接充值呗。从流程上来说,这个充值200万也是充值到某个客户的账本。这笔钱也只是划款到了电信移动运营商,而运营商只是修改了一下客户的账本金额(话费余额)而已。换句话说:银行扣200万给运营商,运营商只是给客户的话费的额度调高了一点。处理办法运营商退200万给银行,并且把客户的余额调整回来就可以了。200万不是损失,损失是一包烟一瓶茅台吧。
8. 我觉得真正的情况应该是客户明明只从银行扣款了200块钱,确被别人把账本金额改成200万,变成客户好像有了200万话费一样。 那这样一条sql把数据改回来就得了

最后一句话,第三方集成商真可怜。

时间: 2024-10-14 18:22:54

人在江湖:如何用代码保护好自己(转)的相关文章

大型.NET商业软件代码保护技术 技术与实践相结合保护辛苦创造的劳动成果

列举工作以来遇到的各种类型的软件所采用的代码保护技术,只讲原理不涉及技术细节实现,以避免产生法律问题.有些朋友说直接把代码放在Github开源下载,开源可以促进技术交流与进步,然而值钱的代码都积压在硬盘里面,即使很烂的代码都卖了很多钱,赢得了许多客户与市场.珍惜爱护自己写的代码,他们都是宝贵的财富. 以下保护技术主要测重于脱机验证与保护,不涉及联网(连接到许可证服务器)验证. 1 程序集混淆 Asembly obfuscate CLR代码的运行是即时编译执行的,.NET编译器只是将源代码文件编译

黄聪:如何用代码设置控制自己网站的网页在360浏览器打开时强制优先使用极速模式,而非兼容模式

最近用360浏览器访问自己的网站,发现都是被优先选用兼容模式打开,这使得网站很难看.为了让360浏览器打开网站的时候优先试用极速模式,找了一下官方论坛,发现了解决方案. 在head标签中添加一行代码: <html> <head> <meta name="renderer" content="webkit|ie-comp|ie-stand"> </head> <body> </body> <

小试.NET代码保护软件(代码混淆、加密)

有着微软人性化的开发工具VISUAL STUDIO和MSDN详尽的帮助,.NET 的开发效率的确高. 但是由于.NET同JAVA一样都采用中间语言.虚拟机/SDK等诸多特质,而且高等语言的类库编码规范,MSIL微软中间语言可读性很高,所以代码很容易就能被反编译,或许这是一种“另类的开源”. 到目前为止.NET开发的桌面软件或者说共享软件真的很少,大多都是一些管理系统.ERP.等一些数据库软件,这些软件一般结构复杂,采用了架构或者平台等一些机制,而且一个功能都可以写N多个类,即使被反编译了别人也要

QWaitCondition 的正确使用方法(通过 mutex 把有严格时序要求的代码保护起来,同时把 wakeAll() 也用同一个 mutex 保护起来)

简单用法 QWaitCondition 用于多线程的同步,一个线程调用QWaitCondition::wait() 阻塞等待,直到另一个线程调用QWaitCondition::wake() 唤醒才继续往下执行. 为了描述方便,这里假设主线程调用Send()往通信口发送一个数据包,然后阻塞等待回包才继续往下执行.另一个线程(通信线程)不断从通信口中接收数据并解析成数据包,然后唤醒主线程.下面是按网上给的最简单的方法: // 示例一 // 主线程 Send(&packet); mutex.lock(

Android | 教你如何用代码一键实现银行卡绑定

前言 ??小编前面几期文章分别给大家介绍了用代码实现微笑抓拍.证件照DIY.拍照翻译的功能开发(链接见文章末尾),本次小编给大家带来的是用代码一键实现银行卡识别与绑定功能. 银行卡识别的应用场景 ??介绍开发步骤前,我们先来谈谈银行卡识别的具体应用场景,银行APP.移动支付.缴费类APP.电商类APP或者其它带支付功能的APP在使用过程中往往会遇到如下常见的几个应用场景: 绑卡支付 ??常用于支付类APP,或者带支付功能的APP,用来绑定信用卡.银联借记卡,提供在线支付功能. 转账汇款 ??常用

纯干货:微软漏洞中国第一人黄正——如何用正确姿势挖掘浏览器漏洞(附完整 PPT)

导语:黄正,百度安全实验室 X-Team 掌门人.他在这里分享了诸多漏洞挖掘的干货,目标成为大神黑客的你不能错过. 浏览器就像一扇窗,通过这扇窗,黑客可以攻入电脑的心脏. 就像情场高手,通过眼睛,融化一个人的心灵. 黄正,百度安全实验室 X-Team 掌门人.2016年,这个信仰"技术可以改变世界"的低调黑客大牛以一己之力挖掘无数浏览器漏洞,创下了排名微软 MSRC 2016 年度黑客贡献榜中国区第一(世界第八)的壮举. 从一个安全开发工程师华丽转身,成为安全研究员,黄正最终站在了中国

C#懒人包-我的辅助代码库

喜爱编程,尽管编程开发并非自己的主要工作,但多年来,也一直没有间断开发.既有工作单位的一些项目,也有纯粹自己的一些想法而做的程序(我的原创).在使用C#开发各类项目的过程中,把一些常用的编程的思路方法总结起来,慢慢就形成了一个辅助代码库.随着内容的完善,我自我感觉对于快速完成开发效果非常明显. 一直以来也想把这个辅助代码库发布出来,对有些情况类似的开发者也许有用,对自己同时也是个促进.但是每次当我看到园子里面的高手们推出的各种框架,顿时自惭形秽.后背冒汗,立马打消念头了.和他们相比,我的代码整理

感悟:百度万人协同规模下的代码管理架构演进

百度代码管理的挑战 百度拥有万人开发团队,近十万项目,每周代码自动检出的问题超二十万,每天发起评审超1万次.为了保证代码质量,我们要求代码提交前和提交后都进行自动化检查.为了加速编译和集成,我们有大规模的分布式编译系统和持续集成系统.百度C/C++语言是源码依赖,编译系统需要检出所有的依赖代码,这样代码库的访问压力呈指数级增长.这些都是百度代码管理面临的挑战,总结起来就是这三点:代码质量.规模协同和安全稳定的服务. 面对这三大挑战,代码开发协作平台重点解决代码管理五个方面的问题:代码托管.协同开

多人协作项目如何保持代码风格统一

原文链接:https://liushiming.cn/2020/02/20/keep-same-code-style/ 概述 多人在同一个项目协作的时候保持统一的编码风格很重要.但说起来容易做起来难,每个人都有自己的编码习惯,不去对比自己都难以意识到.本文我推荐一些做法.工具可以帮助我们尽量保持同样的风格. 选择go语言 如果可以,选择go语言作为编程语言. go语言自带代码格式化命令gofmt,要求所有代码都有一致的代码风格,甚至不允许未使用的import和未使用的变量定义在程序里,否则编译不