二、编写高质量的代码—数据类型(笔记)
本博文为《编写高质量代码—改善Java程序的151个建议》一书的阅读笔记。该书从很多方面给予了编写高质量代码的宝贵经验。而且该书应该是那种开发经验越丰富,体会越深的书籍。在阅读过程中,从该书中收获良多,这里主要作下书籍笔记,有体会的地方加点自己的想法。受限于知识水平,部分内容还没能深刻体会,所以更多更好的内容和具体实例还需要从书中去找寻。
一、不要用浮点数处理货币
货币在单位是元的时候常常都是带小数的,比如角分。这时候货币的数值就是小数,有些系统为了定义方便,直接定义成float或者double类型,然后运算运算就发现数据不对劲了,小数位总好像差点。我前两个月接手了一个老系统就是这样,非常地坑。
浮点数由于是二进制存储,对于部分小数根本无法准确表示,只能是近似存储,因此在运算过程中可能出现“意外”的结果。所以对于小数的定义,尤其是涉及到货币相关的处理,数据类型最好定义成BigDecimal,这是专门为了弥补浮点数无法精确计算而设计的类。另外还有种思路是先将小数放大成整数,再作运算。
以个人的经验,认为定义成BigDecimal最佳,放大成整数还要放大缩小,多一些多余的操作,字段的含义也发生了变化。
二、不要让类型默默转化
在数据类型默认转化时可能出现意想不到的Bug,例如书中所举的例子:long dis = 30 * 10000 * 1000 * 60 * 8;结果dis的值是个负数(-2028888064),因为dis右边运算的结果是Int类型,但是超过最大长度了,所以变成了负值(越界从头算起)。
这种int类型默默转换成long类型可能就会出现问题,所以尽量避免默认转换,比如上面的例子可以在30后面加L,让计算结果为long类型。
三、边界,还界,还是边界
在代码编写的过程中一定要考虑数据类型的边界,不然可能出现意想不到的问题。比如书中所举的例子int order = 用户输入的订单数,if( order >0 && order+1000<=2000 )就预定成功,这段逻辑初看起来没有任何问题,可是如果order=2147483674竟然可以预定成功。根据原因就是因为order=2147483674时再加1000就已经超过Int最大值,得到负数肯定比2000小。
所以在编写程序一定要注意边界值,另外单元测试时也要做边界测试,假如输入参数是int类型,就要考虑正最大、负最小、0这些边界值。
四、不要让四舍五入亏了一方
四舍五入是一种常见的计算方法,通常的做法都是小数位小于等于4则舍弃,大于等于5则进位后舍弃。但是这种算法对于银行的利息计算就会出现亏损,书中举了个非常好的例子,结论是考虑银行存款足够多,数字均匀发布的情况下每10笔利息银行就会亏损0.005元。
所以在类似银行利息计算的时候,需要使用修正的四舍五入算法,即银行家舍入近似算法,具体规则为:
1. 舍入位小于5直接舍弃;
2. 舍入位数值大于等于6则进位后舍弃;
3. 舍入位等于5时,若5后面还有其它非0数字,则进位后舍弃,若5后面是0(或最后一个数字),则根据5前一位的奇偶性判断是否需要进位,奇数则进位,偶数则舍弃。
简言之,就是四舍六入五考虑,五后非零则进一,五后为零看奇偶,五前为偶应舍弃,五后为奇要进一。
使用银行家的舍入算法,可以通过设置舍入模式为RoundingMode.HALF_EVEN实现。在普通项目中舍入模式不会有太大影响,可以直接使用Math.round方法,但是在大量与货币数字交互的项目中,则需要根据场景选择合适的舍入算法了(RoundingMode有多种舍入模式)。
五、提防包装类型的null值
包装类型在参与运算时要做null值校验,不然若包装类型为空,取值运算时会抛出空指针异常。因此为了程序的健壮性考虑,运算时需做校验。
六、谨慎包装类型的大小比较
包装类型不要直接通过比较运算符(>,<,=)进行比较,因为包装类型是对象应该通过compareTo就去进行比较。
七、优先使用整型池
通过包装类的valueOf生成包装实例可以显著提高空间和时间性能,以Integer为例,Integer会有一个内部的缓存,缓存-128到127之间的对象,而使用valueOf方法生成实例时,如果在范围内就直接返回缓存的对象了,这样就提高了性能。
另外包装类型进行相等判断时最好使用equals,而非==。
八、优先选择基本类型
无论从安全性、性能、稳定性考虑,基本类型相比于其对应的包装类型都是应该优先考虑选用的类型。
九、不要随便设置随机种子
在Java项目中,通常使用Math.random方法和Random类来生成随机数,其中Random类可以设置随机数种子,而一旦设置随机数种子,则在同一台机器 上不论运行多少次随机数总是相同的。因为随机数与种子有如下规则:
1. 种子不同,随机数不同;
2. 种子相同,即使实例不同,也产生相同的随机数。
而Random的默认构造类,种子是seedUniquifier() ^ System.nanoTime(),这样保证了种子的不同。所以想生成“真正的”随机数,不要随便设置随机种子。
十、正确使用String、StringBuffer、StringBuilder
三种字符序列的不同使用场景:
1. String类:由于String对象是不可变的,所以String类应该在字符串不经常变化的场景中使用,例如常量的声明、少量变量运算等。
2. StringBuffer类:在频繁进行字符串运算(如拼接、替换、删除等),而且运行在多线程环境中时,应该考虑使用StringBuffer,例如XML解析、HTTP参数解析和封装。
3. StringBuilder类:在频繁进行字符串运算(如拼接、替换、删除等),而且运行在单线程环境中时,应该考虑使用StringBuilder,如SQL语句的拼装、JSON封装。
推荐阅读
-
高质量Python代码编写的5个优化技巧
-
[编写高质量iOS代码的52个有效方法](十)Grand Central Dispatch(GCD)
-
[编写高质量iOS代码的52个有效方法](十一)系统框架
-
[编写高质量iOS代码的52个有效方法](一)Objective-C基础
-
读书笔记---《编写可读代码的艺术》
-
[编写高质量iOS代码的52个有效方法](五)接口与API设计(下)
-
[编写高质量iOS代码的52个有效方法](八)内存管理(下)
-
作业笔记:基于二次插值的Wolfe-Powell非精确线搜索算法及Python代码实现
-
编写高质量代码的30条黄金守则(首选隐式类型转换)
-
Ruby学习笔记二帮助生成Vim添加代码头的代码