代码Review,瑞出事来了!

IT科技2025-11-05 13:50:2654331

不久之前,代码部门进行了一次代码评审。瑞出

代码整体比较简单,代码该吹B的瑞出地方都已经吹过了,无非是代码些if else的老问题而已。当翻到一段定时任务的瑞出一步执行代码时,我的代码双眼一亮,觉得该BB两句了。瑞出

谁知这群家伙,代码评审的瑞出时候满满的认同感,但评审结束不久,代码就给我冠了个事B的瑞出称号。

今天我就把当时的代码这些话儿整理整理,让大家说道说道,瑞出我到底是代码不是个事B。

一个任务处理例子

代码的结构大体是这样的。

通过定时,这段代码每天晚上凌晨都要对数据库的记录进行一遍对账。主要的服务器托管逻辑,就是使用独立的线程,渐进式的读取数据库中的相关记录,然后把这些记录,放在循环中逐条进行处理。

ExecutorService service = Executors.newFixedThreadPool(10);

...

service.submit(()->{

while(true){

if(CollectionUtils.isEmpty(items)){

break;

}

Listitems = queryPageData(start, end); // 分页逻辑

for(Data item : items){

try {

Thread.sleep(10L);

} catch (InterruptedException e) {

//noop

}

processItem(item);

}

}

});ExecutorService service = Executors.newFixedThreadPool(10);

...

service.submit(()->{

while(true){

if(CollectionUtils.isEmpty(items)){

break;

}

Listitems = queryPageData(start, end); // 分页逻辑

for(Data item : items){

try {

Thread.sleep(10L);

} catch (InterruptedException e) {

//noop

}

processItem(item);

}

}

});

等一下。在代码马上被翻过去的时候,我叫停了,这里的processItem没有捕获异常。

通常情况下,这不会有什么问题。但静好的岁月,总是偶尔会被一些随机的事故打断。如果这是你任务的完整代码,那它就有一种非常隐晦的故障处理方式。即使你的单元测试写的再好,这段代码我们依然可以通过远程投毒的方式,通过问题记录来让它产生问题。IT技术网

是的。以上代码的根本原因,就是没有捕捉processItem函数可能产生的异常。如果在记录处理的时候,有任何一条抛出了异常,不管是checked异常还是unchecked异常,整个任务的执行都会终止!

不要觉得简单哦,踩过这个坑的同学,请记得扣个666。或者翻一下你的任务执行代码,看看是不是也有这个问题。

Java编译器在很多情况下都会提示你把异常给捕捉了,但总有些异常会逃出去,比如空指针异常。如下图,RuntimeException和Error都属于unchecked异常。

RuntimeException可以不用try...catch进行处理,但是如果一旦出现异常,则会导致程序中断执行,免费源码下载JVM将统一处理这些异常。

你捕捉不到它,它自然会让你的任务完蛋。

如果你想要异步的执行一些任务,最好多花一点功夫到异常设计上面。在这上面翻车的同学比比皆是,这辆车并不介意再带上你一个。

评审的小伙很谦虚,马上就现场修改了代码。

不要生吞异常

且看修改后的代码。

ExecutorService service = Executors.newFixedThreadPool(10);

...

service.submit(()->{

while(true){

if(CollectionUtils.isEmpty(items)){

break;

}

Listitems = queryPageData(start, end); // 分页逻辑

for(Data item : items){

try {

Thread.sleep(10L);

} catch (InterruptedException e) {

//noop

}

try{

processItem(item);

}catch(Exception ex){

LOG.error(...,ex);

}

}

}

});

...

service.shutdownNow();

为了控制任务执行的频率,sleep大法是个有效的方法。

代码里考虑的很周到,按照我们上述的方式捕捉了异常。同时,还很贴心的把sleep相关的异常也给捕捉了。这里不贴心也没办法,因为不补齐这部分代码的话,编译无法通过,我们姑且认为是开发人员的水平够屌。

由于sleep抛出的是InterruptedException,所以代码什么也没处理。这也是我们代码里常见的操作。不信打开你的项目,忽略InterruptedException的代码肯定多如牛毛。

此时,你去执行这段代码,虽然线程池使用了暴力的shutdownNow函数,但你的代码依然无法终止,它将一直run下去。因为你忽略了InterruptedException异常。

当然,我们可以在捕捉到InterruptedException的时候,终止循环。

try {

Thread.sleep(10L);

} catch (InterruptedException e) {

break;

}

虽然这样能够完成预期,但一般InterruptedException却不是这么处理的。正确的处理方式是这样的:

while (true) {

Thread currentThread = Thread.currentThread();

if(currentThread.isInterrupted()){

break;

}

try {

Thread.sleep(1L);

} catch (InterruptedException e) {

currentThread.interrupt();

}

}

除了捕捉它,我们还要再次把interrupt状态给复位,否则它就随着捕捉给清除了。InterruptedException在很多场景非常的重要。当有些方法一直阻塞着线程,比如耗时的计算,会让整个线程卡在那里什么都干不了,InterruptedException可以中断任务的执行,是非常有用的。

但是对我们现在代码的逻辑来说,并没有什么影响。被评审的小伙伴不满意的说。

还有问题!

有没有影响是一回事,是不是好的习惯是另一回事 。我尽量的装了一下B,其实,你的异常处理代码里还有另外隐藏的问题。

还有什么问题?大家都一改常日慵懒的表情,你倒是说说。

我们来看一下小伙伴现场改的问题。他直接使用catch捕获了这里的异常,然后记录了相应的日志。我要说的问题是,这里的Exception粒度是不对的,太粗鲁。

try{

processItem(item);

}catch(Exception ex){

LOG.error(...,ex);

}

processItem函数抛出了IOException,同时也抛出了InterruptedException,但我们都一致对待为普通的Exception,这样就无法体现上层函数抛出异常的意图。

比如processItem函数抛出了一个TimeoutExcepiton,期望我们能够基于它做一些重试;或者抛出了SystemBusyExcption,期望我们能够多sleep一会,给服务器一点时间。这种粗粒度的异常一股脑的将它们捕捉,在新异常添加的时候根本无法发现这些代码,会发生风险。

一时间会议室里寂静无比。

我觉得你说的很对 ,一位比较资深的老鸟说, 你的意思是把所有的异常情况都分别捕捉,进行精细化处理。但最后你还是要使用Exception来捕捉RuntimeException,异常还是捕捉不到啊。

果然是不同凡响的发问。

优秀的、标准的代码写法,其中无法实施的一个重要因素,就是项目中的其他代码根本不按规矩来。如果我们下层的代码,进行了正确的空指针判断、数组越界操作,或者使用类似guava的Preconditions这类API进行了前置的异常翻译,上面的这种问题根本不用回答。

但上面这种代码的情况,我们就需要手动的捕捉RuntimeException,进行单独的处理。

你们这个项目,烂代码太多了,所以不好改。我虽然有情商,但我更有脾气。

大家不欢而散。

End

我实在是想不通,代码review就是用来发现问题的。结果这review会一开下来,大家都在背后讽刺我。这到底是我的问题呢?还是这个团队的问题呢?让人搞不懂。

你们在纠结使用Integer还是int的时候,我也没说什么呀,现在就谈点异常处理的问题,就那么玻璃心受不了了。这B不能全都让你们装了啊。

什么?你要review一下我的代码?看看我到底有没有像我说的一样写代码,有没有以身作则?是在不好意思,我可是架构师哎,我已经很多年没写代码了。

你的这个愿望让你落空了!

本文地址:http://www.bzuk.cn/html/538e39999062.html
版权声明

本文仅代表作者观点,不代表本站立场。
本文系作者授权发表,未经许可,不得转载。

全站热门

在 Ubuntu 平台,默认的中文字体有限,而且与 Windows 平台上的字体不相同。这就造成了,在 Ubuntu 平台上用 LibreOffice Writer 打开来自 Windows 平台的 Word 文档,原始的字体不能正常显示。可以采取从 Windows 复制字体文件到 Ubuntu,但这好像侵犯了版权,而且不符合开源精神。解决的办法还是有的,那就是在 LibreOffice 里设置字体替换(仅用于显示),尽量让来自 Windows 平台的 Word 文档显示原来的模样。一、设置仅用于显示的字体替换列表1、Ubuntu 默认的简体中文字体有三种:AR PL UKai 是一种楷体,AR PL UMing 是一种宋体,Droid Sans Fallback 是一种黑体。最后一种负责在 Ubuntu 平台上显示中文。2、现在有一篇来自 Windows 平台的 Word 文档,它里面包含了黑体、宋体、楷体、仿宋等字体。由于两个平台的字体名称不同,因此在 Ubuntu 平台上用 LibreOffice Writer 打开,都被显示成了黑体(也就是 Droid Sans Fallback)。3、那我们开始设置字体替换。点击菜单“工具”——“选项”。4、打开“选项”对话框。在对话框左侧点击“字体”,然后在右侧下方点选“使用替换列表”。5、在右侧上方“替换表”下的“字体”框中,输入需要被替换的字体,在右侧的“替换成”框中,点击下拉箭头,选择替换字体。如图,“字体”框中输入“仿宋”,“替换为”框中选择 AR PL UMing,然后点击最右侧的对勾。6、一条替换规则就加入到替换列表中了。再点选列表项前面的“始终”和“只屏幕显示”两个复选框。后一个复选框的作用是,在对文档进行修改、保存后,再传递到 Windows平台时,仍然保持 Windows 的字体不变。假如不打上这个对勾,文档被传递给 Windows 平台上用 Word 打开后,字体显示也会不正常了,会给用户造成困扰。7、用同样的方法,输入其他字体的替换列表项,如图。由于 Ubuntu 中文字体的局限,只能用 AR PL UKai 这种楷体,来代替显示 Winows 平台 Word 文档的各种楷体,而宋体、仿宋等,也只能用 AR PL UMing 这种宋体来代替显示。8、设置完成后,第1步骤的文档看起来就像如图这样,字体不再全部被单调地显示成黑体,也比较接近它在 Windows 平台上的原貌了。二、假如是新建的文档需要传递给Windows平台1、假如需要在 Ubuntu 平台上用 LibreOffice Writer 新建一篇文档,为确保文档被传递到 Windows 平台后能够正常显示,需要我们在编辑文档时,直接设定成后者的字体。如图,在右侧窗口中的字体名称框里,直接输入 Windows 平台的字体名称,然后在文档工作区输入文字。这样的文档被传递到 Windows 平台后,就会以设定好的字体显示,不会给其他用户带来困扰。

智能建筑如何彻底改变设施管理

如何确保大规模物联网部署数据完整性?

物联网对智能家居的影响:开创科技生活便捷方式

二手iPadmini2的性能和使用体验剖析(了解二手iPadmini2的性能特点和购买建议)

5G网络物联网:研究揭示网络安全风险

物联网的应用以及面临的网络威胁

物联网如何彻底改变铁路行业

友情链接

滇ICP备2023006006号-33