zoukankan      html  css  js  c++  java
  • 【代码修炼系列分享】改掉这些坏习惯,还怕写不出健壮的代码?(二)

    Code Review 是一场苦涩但有意思的修行。书接上篇,本次继续探讨一下,该如何写出健壮的代码?

    一、编码时:看似顺眼,实则不然。

    举个栗子:

    String amount = request.getParameter("amount");
    // 校验金额小数点后最多两位小数
    BigDecimal a = new BigDecimal(amount);
    if (a.doubleValue() * 100 - Math.floor(a.doubleValue() * 100) != 0) {
        System.out.println("交易金额错误");
        // do something ... ...
    }

    摘一段跑在生产环境上的代码,代码咋一看没啥问题,主要功能是获取请求参数;然后完成数据校验。

    看似很顺眼,但是你细品,就会发现其中之奥秘,下面一起在本地跑跑代码,来分析一下到底会存在什么问题?

    问题一:坑死人的 NPE

    输入:

      null(当 amount 输入为空时)

    输出:

    Exception in thread "main" java.lang.NullPointerException
      at java.math.BigDecimal.<init>(BigDecimal.java:806)
      at PayController.main(PayController.java:300)

    分析:

      根据上面异常信息,见 BigDecimal 的源码第 806 行,如下图所示,很显然 BigDecimal 构造不会判断传入的 val 是否为空,所以会出现空指针异常。

    目前没有出现问题,那只能算庆幸,不过终究是个定时炸弹。切记调用 BigDecimal 的构造时,请勿传入 null 值。

    心声:

      身边老码农真真的排查了好长时间,问题场景与此类似,直接阻断了程序后续的流程。

    问题二:同样是传入数字,结果咋就匪夷所思。

    输入:

    6666.66(当 amount 输入为 6666.66)

    当 amount 输入为 6666.66 时,amount 的值校验通过。

    真的是看到的这个样子吗?换个数试试呗。

    输入:

    8888.88(当 amount 输入为 8888.88)

    输出:交易金额错误

    分析:容我拆解一下代码,当 amount 传入为 8888.88 时:

    double d1 = a.doubleValue() * 100;
    double d2 = Math.floor(a.doubleValue() * 100);
    System.out.println(d1); // 输出:888887.9999999999
    System.out.println(d2); // 输出:888887.0
    System.out.println(d1 - d2); // 输出:0.9999999998835847

    很显然, d1 - d2 的值 != 0,那么如下表达式的值则满足,会输出交易金额错误。

    为什么呢?归根揭底是 double 运算时精度丢失而导致程序处理出错,虽然在 Java 中提倡用 BigDecimal 进行四则运算,但是上面的校验实现,貌似跟 BigDecimal 没有啥关系,到底该怎么解决呢?

    不费脑简单实现方式:

    if (amount.contains(".") && amount.substring(amount.indexOf(".") + 1).length() > 2) {
          System.out.println("校验失败 2");
          // do something ... ...
    }

    如上面代码段所示,直接判断传入的 amount 字符串小数点后面的位数就可以啦。

    当然,仁者见仁智者见智,实现方式有很多,不去多深究。

    二、编码时:时间转换也作祟。

    举个栗子:

    public static long convertDaysToMilliseconds(int days) {
        return 1000 * 3600 * 24 * days;
    }

    分析:1000 * 3600 * 24 * days 结果默认为 int 类型,最大值为 2147483647,如果超过 int 范围,则会出现截断,程序不会出错,但是结果却匪夷所思。

    例如:当 days 输入为 30 时,程序输出:-1702967296。

    改进方式一:

    改进方式二:

    再举个栗子:

    public static Date getDate(int seconds) {
        return new Date(seconds * 1000);
    }

    分析:当 seconds * 1000 值为 int 类型,当超过 int 最大值为2147483647 时,程序不会出错,但是结果却匪夷所思。

    改进方式:

    分享一下心声:

    1. 禁止使用 double 直接参与金额运算,会出现意想不到的结果。

    浮点数采用“尾数+阶码”的编码方式,类似于科学计数法的“有效数字+指数”的表示方式。 二进制无法精确表示大部分的十进制小数。 —— 请自行科普,留作业。

    2. 禁止使用构造方法 BigDecimal(double)的方式把 double 值转化为 BigDecimal 对象。

    BigDecimal(double)存在精度损失风险,在精确计算或值比较的场景中可能会导致业务逻辑异常。 如:BigDecimal g = new BigDecimal(0.1f); 实际的存储值为:0.10000000149 优先推荐入参为 String 的构造方法,或使用 BigDecimal 的 valueOf 方法,此方法内部其实执行了 Double 的 toString,而 Double 的 toString 按 double 的实际能表达的精度对尾数进行了截断。 —— 阿里开发手册

    3. 那些看似顺眼的代码,或者线上跑着的代码,未必就没问题,只是没有走到异常分支上去,随着时间的推移,定时炸弹迟早会爆,定期审查代码,以及充分的测试是非常的必要。

    三、编码时:少一点不行。

    坏习惯一:记录日志时,缺失参数。

    反例:

    正解:

      1. 日志打印时,占位符 {} 要严格与参数相对应,如果对应不上,按照截图示意,日志输出则不会打印 queryString 的参数,会直接输出 {},但是某些版本下会出现空指针异常。

      2.说一句废话:图中的 isVarfiy 是什么鬼?莫非是 isVerify,单词好好拼,千万别拼错,不然易被后人拍砖。

    坏习惯二:记录日志时,缺失占位符 {}。

    反例:

    正解:

      类似的这种问题,多数程序员都犯过。记录日志时占位符少,而参数值多,日志输出时想打印的参数,日志中却没有打印。

      如上面截图中代码所示,想输出请求的 queryString,但是由于缺失对应的占位符 {},则不会打印到日志中。

    四、寄语写最后

    老子曰:有道无术,术尚可求也。有术无道,止于术。

    庄子曰:以道驭术,术必成。离道之术,术必衰。

    古人曰:上人用道,中人用术,下人用力。

    小猿曰:管它什么道与术,能助力搬砖采石就足矣,因为我等采石之人心怀大教堂之愿景,哈哈

    常在河边站哪有不湿鞋,金无足赤人无完人,再牛逼的团队,编码都会有出 Bug 的时候。近期微信公众号推出了一个专辑功能,而我迫不及待的想体验。

    谁成想,当我点击创建专辑时,输入专辑名称「码农心声」等信息,然后点击保存,却发现列表页面出现了多个「码农心声」,而且赶紧截了个图,不知道是不是个 Bug?

    But who cares?多出来的直接删除就行啦,又不影响使用。关注同名公众号:一猿小讲,回复「1024」可以获取精心为您准备的职场打怪进阶资料。

    好了,代码修炼的系列分享,本次就谈到这里,不知道有多少是触动了你的心弦,希望有则改之。

    一起聊技术、谈业务、喷架构,少走弯路,不踩大坑。会持续输出原创精彩分享,敬请期待!

  • 相关阅读:
    selenium ide 录制回放link链接报错
    如何打造个人技术影响力
    如何打造个人技术影响力
    三个Bootstrap免费字体和图标库
    程序媛,坚持这几个好习惯让你越来越美
    程序媛,坚持这几个好习惯让你越来越美
    程序媛,坚持这几个好习惯让你越来越美
    程序媛,坚持这几个好习惯让你越来越美
    [慕课笔记]mongodb入门篇
    [慕课笔记]mongodb入门篇
  • 原文地址:https://www.cnblogs.com/socoool/p/13222689.html
Copyright © 2011-2022 走看看