V2EX = way to explore
V2EX 是一个关于分享和探索的地方
现在注册
已注册用户请  登录
qwerthhusn
V2EX  ›  Java

Java 中的异常要区分受检查异常和运行时异常,我认为这个机制简直是个败笔?

  •  1
     
  •   qwerthhusn · 2019-05-27 10:58:23 +08:00 · 5081 次点击
    这是一个创建于 2011 天前的主题,其中的信息可能已经有所发展或是发生改变。

    受检查的异常需要捕获或者catch吞掉(一般做法是重新包装一个RuntimeException抛出去),在重载一些接口或类的方法时可能必须要catch(包括 lambda 表达式)

    1. 其他语言应该基本上没有这样做的
    2. 从语义上,其实 Checked Exception 和 Runtime Exception 不好定界,导致不同的框架或第三方库有不同的偏好。举个栗子,Spring 系基本上不会用受检查异常,Jackson 基本上所有 API 都声明了 IOException。
    3. 虽然 Checked Exception 会告诉你需要处理这种异常,但是 Runtime Exception 在有些时候你也需要处理,这个就变成了隐藏的雷。而其实更多时候,Checked Exception 其实也不需要做过多的处理,所以代码中有很多catch (EXception ex) { throw new XXXRuntimeException(ex); }
    第 1 条附言  ·  2019-05-27 13:51:33 +08:00
    突然想到一个库,阿里的 fastjson,这 b 玩意,在 JSON 转对象的时候可能会抛各种不同类型的异常,JSONException,ClassCastException,NullPointerException,IndexOutOfBoundsException。虽然都是 RuntimeException,但是方法注释上没有任何说明(好像根本没有注释),而且层级很深,不是说看一下代码就能知道抛什么异常的那种。
    29 条回复    2019-05-27 19:53:22 +08:00
    xuanbg
        1
    xuanbg  
       2019-05-27 11:07:12 +08:00
    Checked Exception 就是个沙雕概念!我都知道要怎么处理异常了,还不把检查做在前面,非得抛异常来处理?简直沙雕。。。
    GM
        2
    GM  
       2019-05-27 11:34:58 +08:00
    首先,这是个好功能。

    其次,Runtime Exception 不需要 catch,出错了就让他出错,在最外层 catch 住,返回出错信息,并清理现场就好了。

    最后,工作原因,用了一段时间 C#,发现缺了这个功能,用起来非常难受,调用任何一个第三方函数,你都无法知道到底会不会抛异常、会抛什么异常,所以无法使用异常类型来区分不同的情况(除非它的文档有说明并且你仔细阅读了),所以唯一能做的就是 catch (EXception ex) ,非常恶心。
    specita
        3
    specita  
       2019-05-27 11:42:26 +08:00
    这要看使用者对异常的看法是如何的,至少我觉得并不鸡肋
    mooncakejs
        4
    mooncakejs  
       2019-05-27 11:48:19 +08:00
    checked 异常是个好东西,但是问题出在,不同的第三方 lib 对 checked exception 的理解不一样,给使用者造成了困扰,
    szq8014
        5
    szq8014  
       2019-05-27 11:49:21 +08:00
    目前来说倒说不是特别烦 CE, 在 web 开发中我都是声明个 CE 来传异常信息的,比如 用户登录,
    User login(String account, String passwd) throws XXXBaseException,成功了返回值就是 User,不成功 Exception 里面的 message 就是失败信息。。

    问题就在于许多基础模块里面用了 CheckException 导致一些无关紧要的 try catch 影响代码流畅度。应该和你说的 请“ Checked Exception 和 Runtime Exception 不好定界” 感受差不多?

    嗯,个人感觉 CE 是个挺好的东西,就是一开始底层使用过度,导致上层代码都被迫套 try catch 影响心情
    mandy0119
        6
    mandy0119  
       2019-05-27 11:51:00 +08:00   ❤️ 1
    我能说你和一楼都不理解异常么。
    Checked Exception 是代码知道可能会出某个错误,抛出来让 上一级处理 。
    例如我写一个工具类,你传进来的参数有个参数不对,我 catch 到了我一个工具类需要怎么处理呢?我处理了你还是没传这个参数,如果我没有默认的参数,那我就要往上抛一个异常告诉你你用错了,外层调用的人捕捉到这个异常知道自己错在哪,至于你是打特殊的日志还是重新搞一个参数来重新调我那就是你上层需要关注的问题了。
    然后我应该抛 Checked Exception 还是 Runtime Exception 取决于我希望你早早就做好方案处理,如果这个错误很可能不可完全避免,那我就抛 Checked Exception,让你在异常发生时去解决他。如果应该这个错误完全可以通过上层代码检测(例如先判断下这个参数是否为 null )来完全避免, 我就会抛 Runtime Exception,表明这个你这里有 bug,传的东西不对,应该提前处理下。
    当然这都是我自己的一点理解。就是抛哪种异常取决于我认为这个异常是否应该发生,空指针这种可以用代码提前避免的抛 Runtime Exception , 其他例如 URLencode 这种你很难自己提前判断的,会抛出 Checked Exception 告诉你如果真的出错了 你应该提前写好处理方案。
    mandy0119
        7
    mandy0119  
       2019-05-27 11:52:57 +08:00
    这是我自己最近在写一个脚手架,想往上抛异常的时候思索该抛什么异常的时候,产生的一点思考。
    chendy
        8
    chendy  
       2019-05-27 12:04:50 +08:00
    9102 年了…这个话题早就结束了吧,CE 的初衷是好的,但是实际效果并不好,所以后来的东西基本能不用就不用了
    ethego
        9
    ethego  
       2019-05-27 12:07:27 +08:00
    wysnylc
        10
    wysnylc  
       2019-05-27 12:24:25 +08:00
    然而你只知道 CE 是败笔却没有更好的方案解决
    典型的提出问题不提供解决方向,乱扯
    EastLord
        11
    EastLord  
       2019-05-27 12:28:53 +08:00
    看这个标题 想起了王垠
    xiangyuecn
        12
    xiangyuecn  
       2019-05-27 13:10:41 +08:00
    在用了第三方的代码时:同意“这个机制就是个败笔”,俩种类型谁都可以用(存在选择性往往代表存在歧义),最后成了一锅乱炖😂

    鬼才知道有没有人往外面扔了个 Runtime Exception 炸弹😒😒😒,不该用 try catch 的地方也被迫要套个套子才安全😁

    最终结果就 java 会趋向于和 C#类似异常机制,任何异常几乎不看源码压根不知道要不要捕获😒
    SoloCompany
        13
    SoloCompany  
       2019-05-27 13:24:09 +08:00 via iPhone
    我支持 kotlin
    ce 的接口传染性所引发的问题远比它解决的一点微不足道的问题要严重得多
    noli
        14
    noli  
       2019-05-27 13:35:51 +08:00 via iPhone
    都 9102 年了还 CE,返回一个 Either 类型不就行了吗,还得动用语法大杀器
    xuanbg
        15
    xuanbg  
       2019-05-27 13:44:49 +08:00
    @mandy0119 公共库不应该为调用者的错误买单!一个参数不能为空,你非传个 null,我还要告诉你这个参数不能为空?直接抛异常给你自己查去。

    当然,这并不友好,但每个模块都做好自己的,就不会出现这种异常。我要说,烂代码很多时间就是被这么惯出来的毛病。
    sun1991
        16
    sun1991  
       2019-05-27 13:46:59 +08:00
    语言设计要考虑人的因素. CE 的初衷可能是好的, 但是这样一个精巧的设计需要人力的支持.
    对多数人来说, 编程是混口饭吃, 不是什么崇高理想好么, 强迫 CE 的结果就是各种偷懒和绕过的做法导致比没有 CE 更混乱.
    xuanbg
        17
    xuanbg  
       2019-05-27 13:57:21 +08:00
    @mandy0119 上面的空指针的例子不太好,重新举个栗子。

    假设有个参数在你调用的库里面是作为除数使用的,而且文档已经明确地表明该参数不能为 0 亦不可为空。那么该参数为 0 时抛出一个除数不能为 0 的 CE 你觉得你能怎么处理?你也只能抛出异常了事。然后在代码里加上一堆的检查预防该异常的发生,这个时候,CE 和 RE 有多大区别?基本也没啥区别。
    mandy0119
        18
    mandy0119  
       2019-05-27 15:32:21 +08:00
    @xuanbg 我就是这个意思啊。。。而且很奇怪我是回来看这个帖子才发现你回复了我,竟然没有未读提醒。
    然后我对 CE 和 RE 的理解,就是 CE 应该是捕获到了之后可以由程序进行处理,无论是打印特殊日志也好,还是做其他补偿操作重试操作也好,而且是你很可能无法避免这个异常发生,只能就发生了之后做相应操作。
    RE 是程序处理不了的,例如我写了个工具类,你传个对象进来我给你做操作,但是我要求这个对象必须要有 id 这个属性,你没有的话,我给你抛出个 CE,catch 到的程序也无法自己去修正,这个时候我抛 RE,让你自己修改这个类,修改完了这个 RE 也不会再复现,你也不用去 try catch 他。
    passerbytiny
        19
    passerbytiny  
       2019-05-27 16:14:21 +08:00
    异常本来就是见仁见智的东西,所以我只指出楼主不对的地方。
    一,框架或库对异常的偏好与 Checked Exception 和 Runtime Exception 无关,只取决于它们自身的异常设计理念。Spring 有完整的异常体系,故不再需要你去处理 Checked Exception ; Jackson 很可能是因为仅是库所以不打算自己处理异常,故碰到 IOException 就直接抛给上层。
    二,不想处理受检异常的标准做法只有两个:直接抛出它——即添加到方法签名的 throws 中;或者捕获并处理掉它。包装成运行时异常再抛出是一种懒人做法——只在最高层统一处理异常。
    三,下层只抛出运行时异常,最高层统一处理异常,这种懒人做法,不是万能药,在该 try catch 的地方,你仍然需要 try catch。如果把这种懒人做法当成标准做法,那 try catch 就完全没必要了。
    四,Java 的异常,不是分为受检异常和运行时异常两种,而是首先是异常,然后如果不需要强制捕获则是运行时异常;或者换句话说,异常全部是受检的,其中有部分特殊的(允许在编译时不检测),是运行时异常。如果你看一下 Exception 的继承树,会更好理解这一点。

    最后再说一点:Java 是授人以渔的,不是授人以鱼的
    liuxey
        20
    liuxey  
       2019-05-27 16:26:45 +08:00
    有没有优雅的处理方法我参考参考
    passerbytiny
        21
    passerbytiny  
       2019-05-27 16:27:59 +08:00
    @qwerthhusn 你补充的 fastjson,不好用的原因,除了缺少 Javadoc 外,还有一个原因是 RuntimeException 用多了。作为一个类库,经过良好的测试之后,应该是极少抛出运行时异常的。

    顺便告诉我一下我对你的这个 @ 有没有消息提醒。
    qwerthhusn
        22
    qwerthhusn  
    OP
       2019-05-27 17:32:19 +08:00
    @passerbytiny 有,但是感觉今天的消息提醒有时会丢。。。作为一个类库,经过良好的测试之后,应该是极少抛出运行时异常的。这个是 JSON 解析库,是根据用户的输入来的。。。
    passerbytiny
        23
    passerbytiny  
       2019-05-27 17:48:21 +08:00
    @qwerthhusn #21 看来我得静等站长的回复了。作为一个 JSON 解析库,当用户输入不正确的时候,应该明确的抛出表示无法解析的异常,或者更明确地抛出表示为什么解析不了的异常,而不应该是 ClassCastException,NullPointerException,IndexOutOfBoundsException 这种原始异常,这是相当不负责任的。此时抛出的异常,虽然是运行时异常,但也应当放到方法签名的 throws 中。
    WalkingEraser
        24
    WalkingEraser  
       2019-05-27 18:08:24 +08:00 via Android
    这两种异常在鼓励 let it crash 的 Erlang(比 Java 大八岁)里,被用不同的抛出方式区分成 throw 型异常、error 异常(还有第三种 exit,表示进程退出)。但实践中,他喵很可能被码畜通配捕获成同一种处理了...还好是绝大部人只会 throw,问题不大。
    我个人是喜欢这种区分的,如果规范,能有效说明异常发生频率、场景、严重性、是否可处理恢复等。然而业务码畜主观和客观上不会聊这些的。
    归根结底还是码畜的自我修养。
    WalkingEraser
        25
    WalkingEraser  
       2019-05-27 18:14:21 +08:00 via Android
    但在框架或者说接口层面上,Erlang 记得一般会通过不同 API 和注解来告知调用方,所以也挺坑的。后面新出的 Elixir 方言,则会在同个方法名后显示加上!来告知。
    SoloCompany
        26
    SoloCompany  
       2019-05-27 19:07:16 +08:00 via iPhone
    checked exception 简而言之就是把异常当正常来设计,语言层面这就是一个烂的设计

    你看看 java 8 都被迫引入一个 unchecked io exception 就知道这设计有多烂了
    SoloCompany
        27
    SoloCompany  
       2019-05-27 19:12:12 +08:00 via iPhone
    也许有人会争辩一开始就应该把 io exception 设计成 re,但这根本解决不了问题
    到最后会发现所有 exception 都应该设计为 re
    wr410
        28
    wr410  
       2019-05-27 19:26:03 +08:00
    checkexception 的作用就是告诉你很可能会有意料之内的异常,然后让人家用的时候在这里 try 一下。
    runtimeexception 的是意料之外的异常,什么 oom 什么鬼的。
    总之请至少让我在进入一个迭代里面的时候知道需不需要 try 起来。
    SuperMild
        29
    SuperMild  
       2019-05-27 19:53:22 +08:00
    知足吧,想想 Golang,几乎每个函数都给你一个 err,而且还无法统一包裹在 try catch 里,必须每个 err 都用 if 来检查……
    关于   ·   帮助文档   ·   博客   ·   API   ·   FAQ   ·   实用小工具   ·   5441 人在线   最高记录 6679   ·     Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 · 29ms · UTC 08:31 · PVG 16:31 · LAX 00:31 · JFK 03:31
    Developed with CodeLauncher
    ♥ Do have faith in what you're doing.