美文网首页工作生活
Bug隐藏在简单程序背后

Bug隐藏在简单程序背后

作者: 一猿小讲 | 来源:发表于2019-07-02 22:15 被阅读0次

越简单,越玄机,越不简单

曾记得几年前做培训老师的时候,在辅导学员 Java 面试题过程中,总是提醒学员“越是简单的面试题,其中的玄机就越多,需要学员有相当深厚的功力去面对”。多年从事程序开发以来,现在回头想想,还真是那么回事儿。今天转变一下分享角度,就不聊技术架构啦,咱们一起聊聊那些简单的程序代码背后。

01. 刚入猿门(懵懂的小白)

多一点不行,少一点也不行。

从事金融的程序猿都知道,代码实现功能经常跟钱打交道,钱多一点不行,钱少一点也不行。如果你实现的付款功能,向客户少付了一分钱,客户是否能忍?另外信用卡还款,少了 1 分钱,算你违约,你是否能忍?向你女朋友转账 520 元红包,却到账 250 元,你女朋友是否能忍?闲话不多说,直接上代码。

double a = 1;

double b = 0.99;

System.out.println(a - b);

肯定你们中也有一部分,坚信这段代码运行结果很简单不是 0.01 么?!很久之前如果问我结果是什么,我也会毫不犹豫的答道 0.01,然而真实的结果却是:0.010000000000000009。如果该段逻辑实现的是提现功能,那会不会损失大发了。

说说原因:你们都知道,计算机进行的是二进制运算,然而问题在于转换为二进制的时候,有些数字不能完全转换,只能无限接近于原本的值,由于二进制无法准确表示 0.99 ,就像十进制无法准确表示 1/3 一样,所以必定会有精度损失。

讲讲正解:一般遇到这种,需要用到浮点数运算的地方,都可以使用 java.math.BigDecimal。

BigDecimal a = new BigDecimal(1);

BigDecimal b = new BigDecimal(0.99);

System.out.println(a.subtract(b));

程序跑起来看看效果,一看到结果会惊呆你们。

真实的结果输出变成了:0.0100000000000000088817841970012523233890533447265625。

又损失了一点。枉费你们激情满满,从一个坑又带到另一个坑,不靠谱啊。你们,莫着急,我们不妨把参数改成字符串试试。敲黑板,拨云见日水落石出的时刻到了。

BigDecimal a = new BigDecimal("1");

BigDecimal b = new BigDecimal("0.99");

System.out.println(a.subtract(b));

程序跑起来一窥究竟,期待良久的结果 0.01 终于正常算出来了。

我有话说:如果在程序中直接使用 double 进行计算,会造成精度损失,有可能会引起一些莫名奇妙的 bug;如果用 double 来构造 BigDecimal 依然会有精度损失;请你们铭记:直接使用字符串来构造 BigDecimal,是绝对没有精度损失的。

02. 久居猿门(经验丰富的码农)

吐血的 Bug,阴沟里翻船。

我曾经带着兄弟做过一个日志归集的项目,用 elasticsearch 存储采集的日志。由于采集的日志会逐日增多,考虑到系统长期平稳运行,需要每天跑定时任务清理 60 天前的日志信息,用于释放磁盘内存空间。

日志归集项目上线没过多久,突然发现,2 周前的日志数据貌似丢失了,生产无小事,小事更不能忽视,于是就跟兄弟们一起排查、分析代码,但是没发现逻辑上的问题漏洞。但第二天同样的问题,又规律性的再次发生,于是,兄弟们的焦点便集中到了“定时清理的任务”上。左查右查依然没发现问题,只能一步一步的进行 Debug 跟踪调试。

很难令人想象,问题就出现在LOG_DATA_INVALID_DATE 这个 long 型常量上。

说说原因:

public static final long LOG_DATA_INVALID_DATE = 60 * 24 * 3600 * 1000; 

按道理表示 60 天的时间 60 * 24 * 3600 * 1000 的值应该是 5184000000 的,但是它实际值却是 889032704,大约 10 天时间。坑爹啊,最后发现居然是 int 在计算过程中的溢出,太隐晦的 bug 了。排查问题过程很痛苦,解决问题的方式却很简单,任意一个常量上加 L,转成 long 型就好了。

讲讲正解:

public static final long LOG_DATA_INVALID_DATE = 60L * 24 * 3600 * 1000; 

我有话说:现在想想,这种 Bug 确实是挺难查的。不过稍微细心一点或者借助 FindBugs 等一些工具来扫描一下,这样的 Bug 应该都可以避免。编码不易,且码且 Debug。

03. 猿门起飞(装牛 X 的程序员)

一行代码,蒸发 6,447,277,680 元!

去年由于币圈的疯狂炒作,导致区块链概念深入到每个人的骨髓,就连跳广场舞的大妈、卖书的大爷都参与跟风,喜欢追新的我当然也不会放过。我用两天时间自学了 Solidity 语言,帮别人写了个发行代币的智能合约代码,人家借势赚了个盆满钵满,出于感激,我还赚了个大红包。

跑偏了,咱们今天不聊赚红包这事儿,还是分享区块链业界一个智能合约的普遍漏洞吧。

案例一:随着BEC智能合约的漏洞的爆出,被黑客利用,瞬间套现抛售大额BEC,60亿在瞬间归零。

案例二:距BEC现重大漏洞几近归零仅时隔三天,SMT等多个智能合约再曝漏洞,交易平台迅速停止重提币业务。

说说原因:摘取 BEC 部分智能合约代码进行分析。

稍微写过程序的都能对上图代码理解个八九不离十,就是批量给人转账,函数入参 _receivers 是转给哪些人,_value 是每个人转多少,然后计算一下:你要发送的总金额 = 发送的人数 * 发送的金额,最后从你账户余额中减去你要发送的总金额。

那么问题出现在哪儿呢?

分析一: 你要发送的总金额 = 发送的人数 * 发送的金额

uint256 amount = uint256(cnt) * _value;

当攻击者传入很大的 value 数值,使 uint256(cnt) * value 后超过 unit256 的最大值使其溢出,便可导致 amount 的值变为 0。

分析二:你的账户剩余余额 = 你账户金额 - 你要发送的总金额。

balances[msg.sender] = balances[msg.sender].sub(amount);

那么当 amount 为0时,你的账户显然不会有任何变化。

我有话说:就一个简单的溢出漏洞,蒸发 6,447,277,680 元,导致 BEC 代币的市值接近归 0。而这一切,竟然是因为一个简单至极的程序Bug!

04. 写在最后

最后想分享的是,作为程序猿,诸多看似很简单的程序代码逻辑,跑起来却差强人意,匪夷所思。Coding 是个精细活,所以你们研发过程中一定要仔细,稍微细心一点会屏蔽很多 Bug,会避免很多损失。希望你们能够透过现象看本质、知其然并且知其所以然。

欢迎关注微信公众号“一猿小讲”了解更多精彩分享。

相关文章

  • Bug隐藏在简单程序背后

    越简单,越玄机,越不简单 曾记得几年前做培训老师的时候,在辅导学员 Java 面试题过程中,总是提醒学员“越是简单...

  • 随《搞定》做事(19)2021-12-18组织整理(4)

    我不在乎复杂事物所呈现出的简单表象,我追求隐藏在复杂现象背后的简单本质。 ——奥利弗·温德尔·霍姆斯 是的,追求隐...

  • 优秀测试人,都是这样扼杀掉bug滴!

    BUG种类 一、程序本身语义上的BUG。 运行时BUG。 二、需求理解方面的差异导致的BUG。 简单说,就是程序本...

  • 你可能是个假程序员,除非你做好这6点

    每一个好的程序背后都曾有无数个BUG,有BUG是好事,不要害怕,而我们找不到BUG,那才是害怕的! 程序员心眼儿不...

  • iOS-App调试的几种方式

    要保证自己的App质量,首先就要干掉bug。想要干掉bug就要调试程序,调试程序有以下几种方式。 1、简单粗暴而又...

  • Chapter 1 贝叶斯推断的思想

    你是一名经验丰富的程序员,但是bug仍然暗藏在你的代码中。实现一个极其困难的算法后,你决定在一个简单的例子上测试自...

  • 9个因Bug引发的巨大灾难

    对于程序员来讲,bug就是他们的致命天敌。对于一般的bug来讲,进行简单地修复就解决问题。但以下9个bug,引发了...

  • 在隐芮

    芮,草木生长也。隐,隐藏、隐居,符合夏日假期的心情。 隐芮,藏在芮家坞竹林环抱的山脚下,背后是一个大水库。我和霞住...

  • 【delbug】新成员-代码狗🐶

    delbug 云端BUG管理平台,简单、易用、免费,改善程序员们的工作方式。 Delbug是以改善程序员们的工作方...

  • 这是个伪需求吗?

    引用中国最贵的商业顾问刘润的话: 需求藏在麻烦背后;成长藏在痛苦背后;机会藏在挑战背后。 第一次陪媳妇去吃炒米粉,...

网友评论

    本文标题:Bug隐藏在简单程序背后

    本文链接:https://www.haomeiwen.com/subject/lyjxhctx.html