异常是耗时操作,用坏了整个系统就不好了

2018-09-30  本文已影响0人  等你足够强了再说吧

软件工程领域的大师级人物 Robert C. Martin在《Clean Code》中讲道:
错误处理是十分必要的,但是如果对错误处理使用不当则会让代码变得十分臃> > 肿,让阅读者看不清代码的逻辑,更严重的是,这也会让程序变得十分脆弱。

使用Exception而不是返回码

返回码是一个历史遗留问题,在以前的没有Exception的语言(比如c语言)中,它是有效且必要的,但是在有Exception的语言中使用返回码是没有任何益处的。
对于使用返回码的函数,调用者在得到调用结果(这里是返回码)之后要立即去验证返回码,这对于代码的可读性和结构的合理性都是极大的挑战,使用「异常处理」能让业务逻辑和错误处理在代码结构上分离,代码的结构和逻辑会更清晰。

========华丽的分割线========

使用异常来取代层层传参来解决状态传递问题,的确是一种进步,但如果滥用,把本该使用返回值传参的地方也使用异常,得不偿失了,特别是TPS要求比较高的接口。

现在我们就来实测下这种抛异常的策略,是否会影响性能:
代码

/**
 * 相同条件下,同样的业务逻辑和IO下,比较抛异常和不抛异常场景下,
 * 在性能上有什么区别
 */
@Test
public void givenTwoScenario_whenOneHasExceptionAndAnotherNot_thenGetTheElapsedTime() {
    StopWatch stopWatch = new StopWatch("OneHasExceptionAndAnotherNot");
    stopWatch.start("hasNoException");
    int length = 1000000;
    for (int i = 0; i < length; i++) {
        doBizHasNoException(String.valueOf(i));
    }
    stopWatch.stop();

    stopWatch.start("hasException");
    for (int i = 0; i < length; i++) {
        doBizHasException(String.valueOf(i));
    }
    stopWatch.stop();
    log.info("{}", stopWatch.prettyPrint());
}

/**
 * 没有抛异常的场景【也打印个日志】
 *
 * @param i
 */
public void doBizHasNoException(String i) {
    try {
        log.info("result:{}", i);
    } catch (Exception e) {
        log.warn("{}", e.getMessage());
    }
}

/**
 * 抛异常的场景【打印个日志】
 *
 * @param i
 */
public void doBizHasException(String i) {
    try {
        throw new IllegalArgumentException(i);
    } catch (Exception e) {
        log.warn("{}", e.getMessage());
    }
}

实际执行效果:

10:19:26.327 [main] INFO com.tangcheng.learning.syntax.PerformanceAnalysisUseExceptionTest - StopWatch 'OneHasExceptionAndAnotherNot': running time (millis) = 17315
-----------------------------------------
ms     %     Task name
-----------------------------------------
05510  032%  hasNoException
11805  068%  hasException

可以看到,抛异常场景下,会更耗性能。
这个结论是不是很逆天,很不可思议。

当时也觉得百思不得其解,后来偶然在《极客时间》上听了 郑雨迪 讲的《深入拆解Java虚拟机》,感觉豁然开朗,那就不班门弄斧了,直接看看大佬来自jvm的视角:

异常实例的构造十分昂贵。这是由于在构造异常实例时,Java虚拟机便需要生成该异常的栈轨迹(stack trace)。该操作会逐一访问当前线程的Java栈帧,并且记录下各种调试信息,包括栈桢所指向方法的名字,方法所在的类名、文件名,以及在代码中的第几行触发该异常。

上面的文本不能copy,敲着比较费劲,那就直接截图了啊:

explain-1.jpg

评论永远有经典:

ask_1.jpg ask_2.jpg ask_3.jpg ask_4.jpg

这个栏目提供有试读,有兴趣的同学,来扫这个二维码:

2.jpg
上一篇 下一篇

猜你喜欢

热点阅读