我所理解的代码整洁之道(一)

1. 代码评审的成长

在这里插入图片描述

大家好,我是方圆。最近针对近半年的代码评审做了一次总结分享,主要是一些很简单的问题及代码优化注意事项。

2. 详细问题及注意事项

2.1 Integer, Long, String 类型要使用equals来判断相等

在这里插入图片描述

为什么IntegerLong多数情况下用==比较结果也正确呢?

  • intInteger类型比较,都为true,因为会把Integer拆箱后再去比
  • 两个非new出来的Integer(eg: Integer a = 5),使用 ==比较且大小在-128 ~ 127之间,则为true,否则为false。
    这个范围可以使用 -XX:AutoBoxCacheMax=?来进行调整
  • 两个Integer进行比较,其中一个是new出来的==比较的话为false,因为==比较在对引用类型比较时比较的是存放在堆中的地址值

2.2 加密异常需要抛出来

在这里插入图片描述

这种情况下如果加密出现异常被catch住之后,它还会让代码继续向下执行,所以应该在catch块中把异常抛出来

2.3 double类型加减运算精度丢失问题

在这里插入图片描述

这个是老生常谈的问题,在《Effective Java》这本书中有过关于浮点数计算的提醒:浮点数不用于精确计算,可用在科学计数取近似值,所以在代码中进行小数运算要使用BigDecimal

2.4 count(字段)替换为count(0)

如果想在字段值为null的时候同样也计数,需要使用count(0),count(字段)当字段为null时不计数

2.5 事务注解需要加上rollbackFor

在这里插入图片描述

如果不加的话,则只针对RuntimeException回滚

2.6 集合转数组

  • 使用集合转数组的方法,必须使用toArray(T[] array),传入类型完全一样的数组
List<Integer> list = new ArrayList<>();
list.add(1);

Integer[] ints = (Integer[]) list.toArray(new Integer[0]);

其中数组空间大小的length有以下几种情况

  1. 等于0,动态创建与size相同的数组,性能最好
  2. 大于0但小于size,重新创建大小等于size的数组,增加GC负担
  3. 等于size,在高并发情况下,数组创建完成之后,size正在变大的情况下, 负面影响与2相同
  4. 大于size,空间浪费,且在size处插入null值,存在NPE隐患

2.7 数组转集合

  • 使用Arrays.asList()把数组转换成集合时,不能使用其修改集合相关的方法(add/remove/clear),否则会抛出UnsupportedOperationException 异常

因为Arrays.asList()使用的是适配器模式,返回的对象是Arrays内部类,它只是做了转换,而后台的数据仍然是数组。

2.8 ArrayList的subList

  • ArrayList的subList结果不可强转成ArrayList,否则会抛出 ClassCastException异常

subList 返回的是ArrayList的内部类SubList,并不是ArrayList而是ArrayList的一个视图,对于SubList子列表的所有操作最终会反映到原列表上

  • subList场景中,高度注意对原列表的修改,否则会导致子列表的遍历、增加、删除产生ConcurrentModificationException异常

ArrayList 创建SubList时,此时它们的modCount值一致。当ArrayList增加或者删除时会修改modCount,这时子列表中的modCount是没有被修改的。所以当子列表遍历时判断subList的modCount和ArrayList的modCount不一致,这就会抛出ConcurrentModificationException异常

2.9 double转换BigDecimal

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

BigDecimal(double)存在精度损失风险,在精确计算或值比较的场景中可能会导致业务逻辑异常。
如:BigDecimal g = new BigDecimal(0.1f); 实际的存储值为:0. 1000000000000000055511151231257827021181583404541015625

优先使用入参为String的构造方法new BigDecimal("0. 1")BigDecimal.valueOf方法

2.10 线程工厂

  • 创建线程或线程池时需要指定有意义的线程名,方便排查问题,如下为南网异步线程池线程工厂的应用
    public class WSServiceThreadFactory implements ThreadFactory {

        private final String namePrefix;

        private final AtomicInteger nextId = new AtomicInteger(0);

        public WSServiceThreadFactory(String threadPoolName) {
            namePrefix = "From WSServiceThreadFactory's " + threadPoolName + "-Worker-";
        }

        @Override
        public Thread newThread(@NotNull Runnable r) {
            String name = namePrefix + nextId.getAndIncrement();

            return new Thread(null, r, name, 0);
        }
    }

2.11 SimpleDateFormat是线程不安全的

  • 不要定义为static变量,如果定义 为static,必须加锁,或者使用JDK8中的DateTimeFormatterLocalDateTime
  • 测试用例如下
public class TestSimpleDateFormat {
    private static final SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");

    public static void main(String[] args) {
        Date now = new Date();

        for (int i = 0; i < 100; i++) {
            new Thread(() -> {
                try {
                    String format1 = dateFormat.format(now);
                    Date parse1 = dateFormat.parse(format1);
                    String format2 = dateFormat.format(parse1);
                    
                    // 理论上format1和format2是一样的
                    System.out.println(format2.equals(format1));
                } catch (ParseException e) {
                    e.printStackTrace();
                }

            }).start();
        }
    }
}

测试输出结果不是都为true,也会出现false。因为在SimpleDateFormat的format方法中,有calendar对象是线程不安全的,多线程调用时被并发修改。

    private StringBuffer format(Date date, StringBuffer toAppendTo,
                                FieldDelegate delegate) {
        // 这里并发修改calendar
        calendar.setTime(date);

		...
    }

3. 代码优化

3.1 通用方法复用

在这里插入图片描述

例子中是计算两个时间间隔天数的逻辑,这种通用方法需要加到工具类中复用

3.2 操作资源用try-with-resources资源

在这里插入图片描述

在这里插入图片描述

3.3 SQL 条件字段不要加函数

在这里插入图片描述

  • 否则会使索引失效

3.4 代码注释和方法拆分复用

在这里插入图片描述

  • 代码注释描述清晰,其他小方法进行拆分,尽可能复用代码使逻辑清晰

3.5 工作中代码复用的样例

项目中对其他平台接口调用的代码结构是这样儿的,它有3层,给大家简单解释一下。

请添加图片描述

  1. 接口层:调用外部接口的方法
  2. 抽象层:通用的同步、异步调用逻辑
  3. 实现层:外部接口方法的业务实现

我们需要照样子,为新平台的接口对接,再搬一套类似的结构,像下边儿这样请添加图片描述

这个结构照抄起来非常的简单,但是当我写到具体接口实现的时候出现问题了:

我们拿同步派车单异常接口来举例子,两个平台都想要这份数据,而且它们的执行逻辑是一样的,我就在想:“如果不写重复的代码,该怎么复用之前的代码把这个接口开发完?”

当然我们复制一下放到新添加的接口实现类中是可以,但是不优雅,而且还是被idea提示代码重复。

我们先看下这个要复用的方法,它在OutProxyServiceImpl里,是私有的。如果我们想复用它,就需要把它抽出来,抽出来放在哪儿又是一个问题。如果不抽出来,把私有方法变成公开的(public)方法,又需要将调用电网管理平台的实现类注入到调用都匀局的实现类中,业务不相关的bean注入,这样也不好。

public class OutProxyServiceImpl extends AbstractOutProxyService {
    ...

    private ShipmentErrorDto initShipmentErrorDto(BuException exception) {
        ShipmentErrorDto errorDto = new ShipmentErrorDto();
        BuShipment shipment = shipmentService.getById(exception.getShipmentId());
        // 派车单号
        errorDto.setShipmentPlanCode(shipment.getCode());
        // 车牌号
        errorDto.setPlateNo(exception.getVehicleLp());
        // 司机姓名
        errorDto.setDriverName(shipment.getDriverName());
        // 是否影响到达时间
        errorDto.setIsAffectArrivalTime(exception.getIsAffectArrivalTime());
        // 异常等级
        errorDto.setExceptionLevel(exception.getExceptionLevel());
        // 异常类别
        errorDto.setExceptionType(exception.getReason().toString());
        // 异常内容,取备注
        errorDto.setExceptionText(exception.getRemark());
        // 发生时间,格式 yyyy-MM-dd HH:mm:ss
        SimpleDateFormat format = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
        errorDto.setTime(format.format(exception.getEventTime()));

        return errorDto;
    }

    ...
}

所以,我想了一个新的代码结构,来放公用的代码,像下边儿这样
请添加图片描述

还是三层:

  • 接口直接被具体实现类实现,不再关联抽象层
  • 抽象层现在被大家公用,其中放的就是这些同步、异步调用方法和需要被复用的业务方法

那么这样,我们把上述赋值方法拿到抽象类里,用protected修饰,就可以复用它了

虽然解决了问题,但是我还是觉得它不够好,一是业务代码放在抽象层里,不合适;二是这个代码结构变化比较大,在这个基础上又想了另一种方法,如下

请添加图片描述

这下也实现了代码复用,而且保持代码结构基本不变,只需在抽象层做一层拓展即可。把所有想要复用的方法都拿出来往里边儿放用protected修饰,非常的方便。


“That’s all”

  • 2
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

方圆想当图灵

嘿嘿,小赏就行,不赏俺也不争你

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值