我遇到一个Bug,金额大于一千万就报错

Author Avatar
Pang Jian 1月 13, 2016
总字数:1,117 预计阅读:4 min
  • 在其它设备中阅读本文章

这是一个在测试环境发现的一个BUG,感觉很有趣,便写出来分享一下。本文的灵感以及标题都来自于《我遇到一个BUG,每逢周三就崩溃》。
我在银行做系统开发,“金额”基本上是最常见的字段,也是最不能出错的字段了。每一个错误都代表着实打实的资金损失,无论是客户的还是银行的。所以,作为开发人员,对这个字段也相对敏感一些。金额这个字段的规则也相对其他字段更复杂一些。举个例子来说,金额是一个数字,可以是这样12;当然也有小数点的情况,比如这样12.34;人们还有这样的习惯,每隔3位有一个逗号分隔符,比如这样1,000.23。可是一千万是个什么特殊情况,为什么会有问题呢?

目前系统为了校验前端上送的数据,有一套统一的校验机制。大概过程就是,先使用jackson框架将前端上送的JSON数据进行解析,然后根据这个字段配置的正则表达式进行校验,校验不通过,就会报错。
这是一个工作日,在我们内部通讯工具上弹出来一个同事的窗口。“我们有一个交易上送金额,80099690.89元,校验就报错,但是8009969.89元校验就不报错。”我见到这个问题后,第一反应是:“怎么可能,这个校验规则在生产环境运行了那么久都没有出错,怎么会突然有问题呢?会不会是哪里搞错了?”然后,这位同事又发来“前端上送的JSON报文中,金额使用的数字类型amount:80099690.89,但我们平时都上送的字符串类型amount:'80,099,690.89'”。于是,我开始怀疑那个正则表达式是否正确,还在埋怨是谁写的正则,居然要求必须有逗号分隔符的时候,我看到了这正则:^(\d*|\d{1,3}(,\d{3})*)(\.\d{1,2})?。居然是对的!完美的考虑了带逗号和不带逗号的场景。这时候,这位同事又发来消息“把金额按string上送就不报错了amount:'80099690.89'
WTF?!下面这个表情就是我当时的表情。
WTF
我们知道,在JavaScript中,stringnumber是可以随时互转的,难道amount:80099690.89amount:'80099690.89'上送到后端会有不同的逻辑?几千万大小的数字也没有超过精度呀。
于是我开始看这篇文章开头提到的那个字段校验公共机制,先使用Jackson解析,然后再利用正则表达式校验。难道是Jackson有问题?看过官方文档,Jackson会将number默认解析成Java的Double型,string则转换为Java的String型,而Double在进行正则校验之前需要先转换为String

Double amount1 = new Double("80099690.89");
Double amount2 = new Double("8009969.89");
System.out.print("amount1 = " + amount1.toString());
System.out.print("amount2 = " + amount2.toString());

打印结果为:

amount1 = 8.009969089E7
amount2 = 8009969.89

相信大家已经看出来了,原来是这样!Java里DoubletoString方法当大于7位数的时候会取科学计数法。所以科学计数法的结果是无法匹配这个正则的^(\d*|\d{1,3}(,\d{3})*)(\.\d{1,2})?
从那以后,我终于明白编码规范中为什么要求前端都是用string类型了。我还专门看了接口文档,将类型都调整为了string
其实无论一个bug看上去多么随机和不可思议,如果你挖的足够深的话,总能找到一个符合逻辑的解释,极少有真的“不相关”的错误,几乎都是你自己的错。这就是为什么我微博的简介里会写这么一句话“电脑确实比人靠谱,你和他们打交道时一旦有问题,肯定是你的问题。不像人,即使他们有问题也不轻易承认。”

写在最后:文中提到的那位同事,现在已经去新的岗位提升自己去了,写这篇文章也算纪念一下我们一起共事的时光。^_^~
EOF

转载请注明出处:http://www.pangjian.me/2016/01/13/a-strange-bug/

访问原文「我遇到一个Bug,金额大于一千万就报错」获取最佳阅读体验并参与讨论