前不久,部门做了一次codereview。代码整体比较简单,该吹B的地方已经吹好了。无非是ifelse的一些老毛病。翻到定时任务的一步执行代码时,我眼前一亮,觉得是时候让BB说点什么了。没想到这群家伙在审稿的时候给了我满满的认可感,但是审稿没多久就给了我B的称号。今天我就把当时的这些话整理一下,让大家说说是否我是不是有问题。一个任务处理示例代码的结构大致是这样的。通过定时,这段代码需要每天晚上和凌晨对数据库中的记录进行调和。主要逻辑是使用独立的线程逐步读取数据库中的相关记录,然后在循环中对这些记录进行一条一条的处理。ExecutorService服务=Executors.newFixedThreadPool(10);...service.submit(()->{while(true){if(CollectionUtils.isEmpty(items)){break;}Listitems=queryPageData(start,end);//分页逻辑for(Dataitem:items){try{Thread.sleep(10L);}catch(InterruptedExceptione){//noop}processItem(item);}}});ExecutorServiceservice=Executors.newFixedThreadPool(10);...service.submit(()->{while(true){if(CollectionUtils.isEmpty(items)){break;}Listitems=queryPageData(start,end);//分页逻辑for(Dataitem:items){try{Thread.sleep(10L);}catch(InterruptedExceptione){//noop}processItem(item);}}});等一下。马上翻代码的时候,我停了下来,这里的processItem并没有捕获到异常。通常,这不是问题。但平静的岁月总是偶尔被一些偶然的意外打断。如果这是您任务的完整代码,那么它有一种非常神秘的失败方式。即使你的单元测试写得很好,我们仍然可以通过远程投毒和问题记录来导致这段代码出现问题。是的。上面代码的根本原因是processItem函数可能产生的异常没有被捕捉到。如果在记录处理过程中有任何item抛出异常,不管是checkedexception还是uncheckedexception,都会终止整个task的执行!不要认为这很容易。踩过这个坑的同学,记得扣个666。或者翻查你的任务执行代码,看看你有没有这个问题。Java编译器在很多情况下会提示你捕获异常,但总有一些异常会逃逸,比如空指针异常。如下图所示,RuntimeException和Error都是uncheckedexception。RuntimeException不用try...catch也可以处理,但是如果发生异常,会导致程序的执行中断,JVM会统一处理这些异常。抓不到,自然也就结束了你的任务。如果要异步执行一些任务,不如在异常设计上多花点功夫。这车翻车的同学不少,这车不介意再带一辆。审稿人很谦虚,马上当场修改了代码。不要吞下原始异常并查看修改后的代码。ExecutorService服务=Executors.newFixedThreadPool(10);...service.submit(()->{while(true){if(CollectionUtils.isEmpty(items)){break;}Listitems=queryPageData(start,end);//分页逻辑for(Dataitem:items){try{Thread.sleep(10L);}catch(InterruptedExceptione){//noop}try{processItem(item);}catch(Exceptionex){LOG.error(...,ex);}}}});...service.shutdownNow();为了控制任务执行的频率,睡眠方法是一种有效的方法。代码很贴心,按照我们上面说的方式捕获了异常。同时,它也非常贴心地捕获睡眠相关的异常。这里要是不体贴也没办法,因为如果这部分代码没有完成的话,编译是不会通过的。让我们认为开发人员的水平不够好。由于sleep抛出InterruptedException,代码什么也不做。这也是我们代码中常见的操作。不信你打开你的项目,肯定有很多代码忽略了InterruptedException。此时,你去执行这段代码。虽然线程池使用了暴力的shutdownNow函数,但是你的代码还是不能终止,它还会继续运行。因为你忽略了InterruptedException异常。当然,我们可以在捕获到InterruptedException时终止循环。try{Thread.sleep(10L);}catch(InterruptedExceptione){break;}虽然这可以满足预期,但通常不会这样处理InterruptedException。正确的处理方式是:while(true){ThreadcurrentThread=Thread.currentThread();if(currentThread.isInterrupted()){中断;}尝试{Thread.sleep(1L);}catch(InterruptedExceptione){currentThread.interrupt();}}除了捕获它,我们还要重新设置中断状态,否则它会随着捕获一起被清除。InterruptedException在很多场景下都非常重要。当某些方法一直阻塞线程时,比如耗时计算,整个线程就会卡在那里,什么也做不了。InterruptedException可以中断任务的执行,非常有用。但是对我们现在代码的逻辑没有任何影响。被评论的朋友不满意。还是有问题!有没有效果是一回事,是不是好习惯又是另一回事。我尝试尽可能地安装B。其实你的异常处理代码还有其他隐藏的问题。还有问题吗?大家都一改往日懒洋洋的表情,该说说了。下面来看看小伙伴们现场改的问题。他这里直接用catch捕获了异常,然后记录了相应的日志。我想说的问题是这里的Exception粒度不对,太粗暴了。try{processItem(item);}catch(Exceptionex){LOG.error(...,ex);}processItem函数抛出IOException,也抛出InterruptedException,但是我们都把它当作普通Exception,这不能体现出本意上层函数抛出异常。比如processItem函数抛出一个TimeoutExcepiton,希望我们可以根据它做一些重试;或者抛出SystemBusyExcption,希望我们可以睡一会儿,给服务器一些时间。这种粗粒度的异常一次捕获它们。当新增异常时,根本找不到这些代码,就会出现风险。一时间,会议室里鸦雀无声。我觉得你说的对。一位资深老手说,你的意思是把所有的异常情况单独抓取,进行细粒度的处理。但是最后还是得用Exception来捕获RuntimeException,异常还是捕获不到。这真是一个非凡的问题。优秀的、规范的代码写法,无法实现的重要因素之一就是项目中的其他代码根本不按规矩办。如果我们的下层代码进行了正确的空指针判断,数组越界操作,或者使用了Guava的Preconditions等API进行了异常的预翻译,以上问题根本不需要回答。但是在上面代码的情况下,我们需要手动捕获RuntimeException并单独处理。在你的项目中,坏代码太多,不好改。我虽然有情商,但是脾气比较大。大家分手了。完了我实在想不通,codereview是用来找问题的。结果这篇评论一开,大家都在背后讽刺我。这是我的问题吗?还是团队的问题?这很混乱。当你纠结于使用Integer还是int时,我什么也没说。下面说说异常处理,玻璃心到受不了。你不能假装所有这些B。什么?你想审查我的代码吗?看看我有没有按照我说的写代码,有没有做出示范?对不起,我是一名建筑师。我已经很多年没有写过代码了。你这个愿望落空了!
