除非万不得已,别 Catch! - V2EX
V2EX = way to explore
V2EX 是一个关于分享和探索的地方
现在注册
已注册用户请  登录
请不要在回答技术问题时复制粘贴 AI 生成的内容
OneAPM
V2EX    程序员

除非万不得已,别 Catch!

  •  7
     
  •   OneAPM
    oneapm 2015-07-16 18:41:14 +08:00 6688 次点击
    这是一个创建于 3792 天前的主题,其中的信息可能已经有所发展或是发生改变。

    [编者按] 作者 Yegor Bugayenko 是 Teamed.io 的软件架构师,热衷于软件质量研究和有效的项目管理方法探索。在本文中,Yegor 就「异常被捕获但并未重新抛出」这个问题进行了深入讨论,并分享了一些建议。

    对异常只捕获但并未重新抛出究竟是 anti-pattern,还是个普通而且非常流行的错误确实无从考究。但毫无疑问的是,在所有异常捕获代码中,它基本无处不在,正如下面这段代码:

    try { stream.write(data); } catch (IOException ex) { ex.printStackTrace(); } 

    Catch Me If You Can (2002) by Steven Spielberg
    注意:下面的代码并没有反对。

    try { stream.write('X'); } catch (IOException ex) { throw new IllegalStateException(ex); } 

    这叫做 exception chaining,是一个非常有效的构造。

    那么,捕获异常并记录究竟存在什么样的问题?首先,从宏观着手,这里正在谈论的是面向对象编程意味着需要处理的是对象。一个对象(准确的说,是它的类)应该是这个样子:

    final class Wire { private final OutputStream stream; Wire(final OutputStream stm) { this.stream = stm; } public void send(final int data) { try { this.stream.write(x); } catch (IOException ex) { ex.printStackTrace(); } } } 

    这里这样来应用这个类的:

    new Wire(stream).send(1); 

    看起来不错,对么?当调用 send(1) 时,并不需要担心出现 IOException ,它将在方法内部处理。同时,如果出现异常,异常信息会被记录。但是这么做的理念是完全错误的,它传承自没有异常处理的语言,比如 C。

    异常的发明是为了将整个错误处理代码从主要逻辑中移除,以此来简化设计。同时,它们不仅仅是被移走,而且被集中在一个地方在 main() 方法中,即整个应用的入口。

    一个异常的主要目的是搜集尽可能多的错误信息并将它抛到最上层,在这里用户能够针对它做一些处理。Exception chaining 则可以帮助更多,它允许在异常抛至上层的过程中扩充错误信息。在这个过程中,实际上非常类似于每次捕获到泡泡(即异常)后,所做的只是将它添加到一个更大的泡泡中然后重新抛出。当它到达最高层的时候,那里就有许多泡泡,像一个 Russian Doll,一个嵌套着另外一个。最原始的异常就是最小的那个泡泡。

    当捕获一个异常而并没有重新抛出时,等同于你捏碎了这个泡泡。其中包含的大量信息,包括最原始的异常和所有其它带有信息的泡泡,都被你牢牢的抓在手中。你杜绝为上层呈现它,同时你如何处理和使用上层也毫无察觉。这一切都像是暗箱操作,一些潜在的重要信息被隐藏。

    因此,在这里直接导致的是 send() 方法无法得到信任,同样基于 send() 方法的操作也无法得到信任,对象之间的信任链被破坏殆尽。这里的建议是尽可能少捕获异常,同时一旦捕获则必须抛出。

    不幸的是,Java 在很多地方的设计违背了这个原则。例如,Java 有需检查和不需检查的异常两类,但是在我看来,只应该有需检查的异常(这些异常必须被捕获或者声明为 throwable)。而且,Java 允许在一个方法中将多个异常类型声明为 throwable 这是另一个错误;坚持只声明一种类型。在层次结构的顶部有一个通用的 Exception 类,在我看来也是错误的。除此之外,一些内置的类不允许抛出任何需检查的异常,比如说Runnable.run()Java 还有许多关于异常的问题。

    但是尝试记住这些原则,你的代码将会更加整洁:catch 除非你别无选择。

    P.S.这个类应该是这个样子:

    final class Wire { private final OutputStream stream; Wire(final OutputStream stm) { this.stream = stm; } public void send(final int data) throws IOException { this.stream.write(x); } } 

    原文链接:Catch Me If You ... Can't Do Otherwise

    本文系 OneAPM 工程师编译整理。OneAPM 是应用性能管理领域的新兴领军企业,能帮助企业用户和开发者轻松实现:缓慢的程序代码和 SQL 语句的实时抓取。想阅读更多技术文章,请访问 OneAPM 官方博客

    30 条回复    2015-07-18 22:14:05 +08:00
    hitsmaxft
        1
    hitsmaxft  
       2015-07-16 21:54:46 +08:00
    基础库一般是尽可能地抛出异常,不然日志会很乱, 而业务模块尽可能地 catch 异常和合理地记录异常日志,保证不影响其他模块

    因为一个小问题把页面500了,老板也只能 throw 掉你了。
    zonghua
        2
    zonghua  
       2015-07-16 22:08:33 +08:00
    @hitsmaxft 菜鸟好累,做的东西都是虫,代码质量十分不堪。
    xcv58
        3
    xcv58  
       2015-07-16 22:21:36 +08:00   1
    作者吐槽了一通,并没给出解决办法。
    invite
        4
    invite  
       2015-07-16 22:25:44 +08:00
    呵呵, send() 不能信任这个结论, 就这么突然间的出来了? 这文章也不能信任.
    dndx
        5
    dndx  
       2015-07-17 03:36:50 +08:00 via iPhone
    就让它崩溃原则。
    Yiph
        6
    Yiph  
       2015-07-17 09:26:35 +08:00
    神一样的翻译系列,建议大家看后面附的原文链接。
    vivisidea
        7
    vivisidea  
       2015-07-17 09:31:38 +08:00
    @hitsmaxft 赞同这个说法,Controller 还往页面上 throw exception 是作死
    mouhong
        8
    mouhong  
       2015-07-17 10:22:53 +08:00
    作者其实也提到了在最上层 (main) catch 住。

    不过什么是真的“异常”也要分清楚,有些是“可预期的错误”,这种并不是异常,无法预期的才是异常。当一个不可预期错误导致应用进入不可知的状态,自然要提示错误咯 (Web中友好地进行500错误提示)。正确的 throw 和 catch 异常,是不会导致一个小问题把页面 500 的。Controller 通常是最上层,再往页面抛异常是不太对的,但是在有 AOP 支持的框架里,还是可以往上抛的 (上面还有一层可以统一处理)。
    zhicheng
        9
    zhicheng  
       2015-07-17 10:27:58 +08:00 via Android
    除非万不得已,别 Code !
    Agromania
        10
    Agromania  
       2015-07-17 10:47:15 +08:00
    除非万不得已,别当程序员!
    run2
        11
    run2  
       2015-07-17 10:57:42 +08:00
    除非万不得已,别看“翻译”的!
    bengol
        12
    bengol  
       2015-07-17 11:01:31 +08:00 via Android
    这个公司的工程师翻译不行啊
    bobai
        13
    bobai  
       2015-07-17 11:07:16 +08:00
    @sobigfish 看了原文,感觉除了翻译的有些怪异外,没什么不同啊?基本意思没看出来有变化
    run2
        14
    run2  
       2015-07-17 11:12:46 +08:00
    @bobai "注意:下面的代码并没有反对。" 是真的很怪啊。。。
    asj
        15
    asj  
       2015-07-17 11:35:35 +08:00   1
    除非万不得已 {
    不要throw exception
    除非万不得已 {
    不要catch exception
    }
    }

    结论是只有亿不得已的情况才catch
    Air_Mu
        16
    Air_Mu  
       2015-07-17 11:37:22 +08:00
    only if you can
    buru
        17
    buru  
       2015-07-17 12:37:46 +08:00
    我觉得作者说的很对
    Controller的基类应该有一个入口统一进行catch
    具体的Controller就可以往上抛异常了
    supernova16
        18
    supernova16  
       2015-07-17 12:53:50 +08:00
    说翻译的,我觉得技术分享能做到不出错误就值得支持并鼓励了,这样才有更多的好文章得到翻译和传播,能这样做的公司不多
    williamx
        19
    williamx  
       2015-07-17 13:59:04 +08:00
    一种基于异常的语言,很难想象其本质正常。
    incompatible
        20
    incompatible  
       2015-07-17 14:01:56 +08:00
    @williamx 还有什么比异常更好的方式?
    tt7
        21
    tt7  
       2015-07-17 14:13:30 +08:00 via iPad
    标题党, 不是不 catch 而是不要 silent catch.
    alangz
        22
    alangz  
       2015-07-17 17:08:07 +08:00
    什么才是万不得已的时候
    huijiewei
        23
    huijiewei  
       2015-07-17 18:16:46 +08:00
    @invite 因为 send 方法内部把异常消化掉了。造成了就算出现了异常,外面调用的代码也以为这个调用是成功的,所以说 send 方法是不可信的。
    nevermlnd
        24
    nevermlnd  
       2015-07-17 18:19:51 +08:00
    只看出图片是猫鼠游戏
    tinkerer
        25
    tinkerer  
       2015-07-17 19:22:16 +08:00 via iPhone
    注意:下面的代码并没有反对
    invite
        26
    invite  
       2015-07-17 20:25:19 +08:00
    @huijiewei 成功与否,不应该看返回值么?为什么不是返回void导致的,而一定要归结于异常捕获?
    msg7086
        27
    msg7086  
       2015-07-17 20:29:49 +08:00
    @invite
    @williamx
    不能滥用异常,但是也不应该完全没异常啊。
    stevegy
        28
    stevegy  
       2015-07-17 21:19:00 +08:00
    我们规定:界面上*不允许*出现Stack Print。
    - - 这样的规定会直接迫使所有*最外层*的函数使用try/catch/finally,然后catch内部*必须*使用*统一*提供的logger来记录异常的stack message。统一提供的logger不需要每个程序员关心,一般有资深的工程师提供,根据应用的情况,logger有可能是文件,也可能是专用的logger server,但接口一致。
    Comdex
        29
    Comdex  
       2015-07-18 18:21:23 +08:00
    异常和错误如何区分?
    huijiewei
        30
    huijiewei  
       2015-07-18 22:14:05 +08:00 via iPhone
    @invite 他举例的代码可以看到是基础层的代码,和业务无任何关联,抛出异常明显比用返回值更好,因为错误信息更加全面
    关于     帮助文档     自助推广系统     博客     API     FAQ     Solana     3829 人在线   最高记录 6679       Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 26ms UTC 05:13 PVG 13:13 LAX 21:13 JFK 00:13
    Do have faith in what you're doing.
    ubao msn snddm index pchome yahoo rakuten mypaper meadowduck bidyahoo youbao zxmzxm asda bnvcg cvbfg dfscv mmhjk xxddc yybgb zznbn ccubao uaitu acv GXCV ET GDG YH FG BCVB FJFH CBRE CBC GDG ET54 WRWR RWER WREW WRWER RWER SDG EW SF DSFSF fbbs ubao fhd dfg ewr dg df ewwr ewwr et ruyut utut dfg fgd gdfgt etg dfgt dfgd ert4 gd fgg wr 235 wer3 we vsdf sdf gdf ert xcv sdf rwer hfd dfg cvb rwf afb dfh jgh bmn lgh rty gfds cxv xcv xcs vdas fdf fgd cv sdf tert sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf shasha9178 shasha9178 shasha9178 shasha9178 shasha9178 liflif2 liflif2 liflif2 liflif2 liflif2 liblib3 liblib3 liblib3 liblib3 liblib3 zhazha444 zhazha444 zhazha444 zhazha444 zhazha444 dende5 dende denden denden2 denden21 fenfen9 fenf619 fen619 fenfe9 fe619 sdf sdf sdf sdf sdf zhazh90 zhazh0 zhaa50 zha90 zh590 zho zhoz zhozh zhozho zhozho2 lislis lls95 lili95 lils5 liss9 sdf0ty987 sdft876 sdft9876 sdf09876 sd0t9876 sdf0ty98 sdf0976 sdf0ty986 sdf0ty96 sdf0t76 sdf0876 df0ty98 sf0t876 sd0ty76 sdy76 sdf76 sdf0t76 sdf0ty9 sdf0ty98 sdf0ty987 sdf0ty98 sdf6676 sdf876 sd876 sd876 sdf6 sdf6 sdf9876 sdf0t sdf06 sdf0ty9776 sdf0ty9776 sdf0ty76 sdf8876 sdf0t sd6 sdf06 s688876 sd688 sdf86