第十五天、代码避坑指南

194 阅读8分钟

对于一些隐藏的坑如何优雅的避过。

1. 关于浮点数的处理

   定义:浮点数是属于有理数中某特定子集的数字表示,在计算机中用以近似表示任意某个实数。

1. 货币类型的存储

货币类型的存储推荐使用最小货币单位且整型类型来进行存储。 例如人名币的最小单位是分,那么存在数据库里面就是以分来存,1块就是100分

2. 浮点数据类型的问题

最重要的问题:精度丢失 单精度的数据的存储格式为:一个符号为,8个指数位,23个有效数字,如下图 image.png 单精度数据在存储的时候,首先会把整数部分改写成十二进制,然后把小数部分采用乘二取整来计算二进制位数,比如5.2,首先会把5-->101,然后把0.2采用乘二取整来转为二进制,这样转换出来其实它是一个无限循环的小数,而单精度的有效长度只有23位,这样就会造成截取,就造成了精度的损失。

3. 避坑

  1. 浮点数之间的等值判断,基本数据类型不能用==来比较,包装数据类型不能用equal比较。因为浮点数之间的计算并不是准确的某个值,我们可以设定一个区间,当两个浮点数的差值在这个区间的时间,就认为它是相等的。
  2. BigDecimal的等值比较应该使用compareTo方法,而不是equals方式。因为equals方法会比较值和精度(1.0与1.00返回结果位false),但是compareTo会忽略精度。
  3. 禁止使用构造方法BigDecimal(double)的方式直接把double值转为BigDecimal对象。因为构造方法能够保留double的最长长度。

2. 日期类型数据的处理

1. 新老API对比

  • Java传统日期API
    • Date: 表示特定的瞬间,精确到毫秒
    • SimpleDateFormate: 继承DateFormat类,主要用来格式转换
    • Calender: 一个工具类,为特定瞬间和一组日历字段之间的转换以及操作日历段提供了方法
    • 问题:所有的日期都是线程不安全的。
  • Java8新增的日期API
    • LocalDate: 日期 2021-11-09
    • LocalTime: 时刻 12:00:00
    • LocalDateTime: 具体时间 2021-11-09 12:00:00
    • 线程安全的,并且每个类型都有单独的操作类

2. 避坑

  1. 日期转换时,传入的pattern的日期表示年份的统一用小写的y。因为大写的Y在跨年的那一周的时候,可能会被格式化为下一年的日期。比如21年的12月31号,就有可能被格式化成22年的12月31号。
  2. 搞清楚日期中各个字母的作用,并且为了防止出错,建议在工具类中定义好所有的pattern。 - M:月份 - m:分钟 - H:24小时 - h:12小时
  3. 使用System.currentTimeMillis(),而不要多走一步用new Date().getTime()
  4. 要使用更加精确的纳秒级的话,推荐使用System.nanoTime
  5. 在JDK8中,针对统计时间等场景,推荐使用Instant

3. 控制语句的避坑规范

1. switch避坑

  1. 每个Case要么使用continue/break/return等来终止,要么注释说明程序执行到哪一个case会被终止
  2. 在一个Switch块内,都必须包含一个default在最后
  3. 当switch括号内的判断为String,并且属于外部传参是,必须先进行控制判断
  4. if/else/for/while/for语句都必须使用大括号,这样可读性会高一点
  5. 表达异常的分支时,少使用if-else方式

2. 三目运算符踩坑

三目运算符在运算的时候会自动进行装箱,拆箱操作,在下面两种类型下面会执行拆箱操作

  1. 表达式1或者表达式2的值只要有一个是原始类型,如果一个是int一个是Integer,Integer会被拆箱为int
  2. 表达式1和表达式2的类型不一样时,会强制拆箱成表示范围更大的那个值 例子:
int a = 1;
Integer b = null;
Integer c = false?a:b

此时会把b进行拆箱,但是因为Integer为空,所以此时会报NPE的错误。

3. 控制语句习惯

  1. 不要在条件表达式中插入赋值语句
  2. 不要在条件表达式中进行及其复杂的判断语句,以提高可读性
  3. 避免采用反逻辑运算符

4. 高并发场景下的判断

  1. 避免使用等于来作为中断或者推出的条件。 比如在秒杀活动中,会有很大的并发,如果是以等于作为判断的话,此时当库存减少到-1的时候,还会继续去售卖,因为会直接到小于0。所以这里最好使用小于等于0来作为判断。
    当然以上还是有可能多卖出一点的,这里还可以在执行扣减库存sql中添加数量的判断,比如update xx set count = (count - 1) where id = x and (count - 1) > 0

5. 参数的校验

  1. 需要参数校验的场合
  • 调用频次低的方法
  • 执行时间开销很大的方法,因为一个参数的问题导致执行失败,有点得不偿失
  • 需要极高稳定性和可用性的方法
  • 对外提供的接口
  • 敏感权限入口
  • 公共接口需要入参保护,特别是批量处理的
  1. 不需要参数校验的
  • 极有可能会被循环调用的方法。在循环体外就做好参数的判断,减少重复判断
  • 底层调用频次高的方法,因为在外层就已经做好了判断,就不需要在最底层进行判断了
  • 被声明为private的方法,因为私有的,自己已经知道了入参信息,就不需要进行判断了

4. OOP规约

OOP:Object-Oriented Programming 面向对象编程

1. 面向对象的四大特征

  • 抽象:对需求进行业务抽象和建模分析,通过模型的行为组合共同去解决一个问题
  • 封装:对象功能内聚的表现形式,使模块之间的耦合度变低,更加具有维护性
  • 继承:继承使子类能够继承父类,获得父类的部分属性和而行为,使模块更有复用性
  • 多态:使模块在复用基础上更加具有拓展性,使系统更加有想象空间

2. 七个设计原则

  • 单一原则:一个类只负责一个功能领域的相关职责。特点:高内聚,低耦合
  • 里氏替换原则:所有父类能够使用的地方,子类都一定能使用。这里老师举了几个很有意思的例子:鲨鱼和鱼都是鱼,从鲨鱼能够游泳,我们可以推出来鱼也可以游泳。但是我们不能从鲨鱼有牙齿推出来鱼也有牙齿。
  • 接口隔离原则:接口的设计的粒度一定要小,同一个接口的方法要强内聚于同一个特征。
  • 组合复用原则:尽量使用对象组合,而不是用继承来达到复用的目的。要防止“接口污染”
  • 依赖倒置原则:面向接口编程。上层模块不依赖于底层模块,细节信息依赖于抽象。
  • 迪米特原则:接口之间需要关注的信息尽可能的少。使用一个接口,只需要关注他的输入和输出就行。
  • 开闭原则:对拓展开放,对修改要关闭。(ps:最重要的一个原则,但是也是最难实现的一个,因为需求会一直变化,有时候不得不区修改代码。 这里就要考验架构设计的能力了,需要考虑到需求的持续变化,以及识别和隔离各个变化点)这里与开闭原则有个相反的定律,叫做熵增定律。所有的一切都是不断变化的,随着变化会越变越复杂。而开闭原则就是一个熵减,可以减少熵增定律带来的影响。

3. 避坑

  1. 所有的覆写方法,都必须要加上@Override防止写错
  2. 可变参数,必须写在参数的最后面。并且避免使用Object
  3. 所有的整型包装类对象之间值的比较,全部使用equals方法比较
  4. 应该用常量或确定有值的一方最为调用方,防止NPE
  5. 所有的POJO类属性比较使用包装类
  6. 定义POJO类时,不要设定默认值
  7. get方法和set方法中,不要添加逻辑
  8. 禁止在POJO类中,使用isXxx()方法。如果存在is_choose这类的字段,可以在数据对应层进行对应POJO中的值
  9. 构造方法里面不要加业务逻辑,需要的话加载init方法中

注:推荐idea安装阿里规范插件,会自动提示很多不规范的代码,根据规范修改即可,简单方便快捷