14 评论

Java 新手的通病[4]:异常处理使用不当

  上一个帖子讨论了“编程习惯的问题”,今天来聊聊关于异常处理的话题。


★空的 catch 语句块


  犯这种错误的人比较少,一般发生在刚学会 Java 或者刚参加工作不久的人身上。
  所谓“空 catch 语句块”就是在 catch 语句块中没有对异常作任何处理(比如记错误日志),导致异常信息被丢弃/忽略。一旦程序不能正确运行,由于查不到任何 log 信息,只好从头看代码,靠肉眼找 bug。


★没有使用 finally


  很多人在 catch 语句之后不使用 finally 语句。由于在 try 语句中可能会涉及资源的申请和释放。如果在资源申请之后、资源释放之前抛出异常,就会发生资源泄露。
  (资源泄露的严重性,上一个帖子已经聊过了)


★笼统的 catch 语句块


  有些人为了省事,只在自己模块的最外层代码包一个 try 语句块,然后 catch(Exception)。不管捕获到什么异常,都作统一 log 了事。这种做法比“空 catch 语句块”稍好,但由于不能对具体的异常进行具体处理,对一些可恢复的异常(下面会提到),丧失了恢复的机会。而且也可能导致上述提到的资源泄露的问题。


★使用函数返回值进行错误处理


  有些人放着 Java 的异常机制不用,而用函数返回值来表示成功/失败(比如:返回 true 表示成功、false 表示失败),简直是“捧着金碗要饭”。个人感觉,从 C 转到 Java 的人比较容易有此毛病。这种做法会导致如下几个问题:
1. 返回值一般用整数值或布尔值表示,传递的信息过于简陋;
2. 一旦调用者忽略了错误返回码,就会导致和“空 catch 语句块”类似的问题;
2. 对同一个函数的多处调用,都需要对返回值进行重复判断,导致代码冗余(代码冗余的坏处,上一个帖子也已经聊过了)。


★不清楚“Checked Exception”和“Runtime Exception”的区别


  这个现象比较普遍,俺发现很多2年以上 Java 工作经验的人尚未完全搞明白两者的区别。看来这个问题得详细说一下。
当初Java的设计者有意区分这两种异常,是别有深意的。其中“Checked Exception”用于表示可恢复的异常(也就是你必须检查的异常);而“Runtime Exception”表示不可恢复的异常(也就是运行时异常,主要是程序 bug 和致命错误,你【不需要】检查)。不过这种做法引来了很多争议(包括很多 Java 大牛),鉴于本帖子主要针对新手,以后再专门来聊这个争议的话题。
  为了便于理解,下面我举一个例子来说明。假设你要写一个 Download 函数,根据传入的 URL(String 参数)返回对应网页的内容文本。这时候有两种情况你需要处理:
1. 如果传入的 URL 参数是 null,这表明该函数的调用者出 bug 了,而程序本身的 bug 是很难在运行时自我恢复的。这时候 Download 函数必须抛出 Runtime Exception。并且 Download 函数的调用者【不应该】尝试去处理这个异常,必须让它【尽早】暴露出来(比如让 JVM 自己终止运行)。
2. 如果传入的 URL 参数非 null,但是它包含的字符串不是一个合法的URL格式(可能由于用户输入错误导致)。这时候 Download 函数必须抛出 Checked Exception。并且 Download 函数的调用者必须捕获该异常并进行相应的处理(比如提示用户重新输入 URL)。

上面就是几种常见的Java异常处理的误用。下一个帖子我们来聊一下“对虚拟机(JVM)了解不足”。
版权声明
本博客所有的原创文章,作者皆保留版权。转载必须包含本声明,保持本文完整,并以超链接形式注明作者编程随想和本文原始地址:
https://program-think.blogspot.com/2009/02/defect-of-java-beginner-4-exception.html

14 条评论

  1. 对我这种菜鸟太有用了,继续关注……

    回复删除
  2. 恩,很是启发啊。发现自己几乎都有,谢谢楼主啊...

    回复删除
  3. 博主应该发现症状以后给我们这些病人下点药
    比如适合新手的书籍,最好是您检验过的
    还有就是源代码。
    书籍,文档,源代码是永远的捷径啊
    苦求中
    来个java,python,c++系列的都可以

    回复删除
  4. 应楼上同学的要求,以后我多写一些针对新手的书评。

    回复删除
  5. 博主很热心啊,支持下,鄙人已经订阅了你的博客了
    很少人愿意出来分享,博主精神可嘉,牛人最优秀的地方不是他们本身的技术,而是他们达到这种境界的,过程,在这个过程中有很多沼泽,如果他们可以把这些沼泽告诉大家,这些新手可以少走写一些弯路,成长也会更快一些

    回复删除
  6. 有楼上同学的理解,我就没白写这些帖子了

    回复删除
  7. 博主那么深的开发功底,又愿意花时间来共享的人很少了。。
    加油

    回复删除
  8. 谢谢...看了之后发现自己差的太多了...

    回复删除
  9. 读了您的文章感受良多
    但是不敢苟同
    以你的一个例子 某个业务需求 要求休息10 秒钟
    我会写sleep(10000),你觉得我的代码太臭了
    最好写在配置文件里,那麽我问 配置文件放在那里
    文件的格式 读写的方式 是否支持热插拔 是否有权限检查
    ...

    “我想要一个苹果,也须你会附送一个地球”这是我想对很多人说的。
    写入配置文件简单,但是带来的是业务所在的系统的功能的添加!带来的问题是配置文件的一系列的问题!
    简单的说,还记得dll地域的问题吗?你的配置文件和我的一样吗?最臭的的写法没有给系统带来任何修改!最好的写法需要修改系统!
    对于一个苹果的需求,附送一个具有太阳能充电,自动调温,甚至防爆,防火,乃至于防生化的冰箱似乎很有必要!
    万一客户是住在撒哈拉沙漠的傻 大 兵 那!

    其实 java dotnet 程序员 有一个最大的特点,对语言平台的依赖,java 要有jdk,同样业务需要平台!这个平台不是windows 不是jboos 不是 iis 不是framerwork ,而是指业务所在的逻辑环境。 脱离逻辑环境的业务那似乎是,怎么看怎么像哲学!
    就像你所说的异常! 就好像抓住了一个小偷,你是警察,就得把他送公安局,如果你是小偷那?合伙算了!如果是家贼那? 呵呵!
    对于异常,平台会必须有一定的处理机制,什么异常该上交,什么异常该自行处理,不同的项目不同的要求!
    (仅仅)读取一个文件,返回内容,不存在返回“”,貌似这个文件就是刚才的配置文件,呵呵你会如何处理那?

    就像你说的Download函数,看逻辑再定,刽子手会不会检查罪犯的事实,如果当前的Download只有一个调用者,并且调用者已经保证了url 的正确性,你还要检查吗?

    回复删除
  10. 首先感谢楼上的同学写了这么长的评论 :)

    ◇对于你提到的“业务需要平台”。
    我是认同这个观念的。由于本系列的帖子主要是面向Java新手,所以主要还是关注于一些比较基础的东东。对于业务以及相关的逻辑环境,或许将来可以专门聊一下。

    ◇对于上一篇帖子关于sleep中涉及的“Magic Number”问题。
    我认为:分两种情况:如果能够确保不需要在软件实施部署之后调整超时值,那我觉得写法2也可以接受;但如果有可能调整超时值(尤其是让用户在运行时调整),那显然需要有类似配置文件的机制。

    ◇对于你提到的“最臭的的写法没有给系统带来任何修改!最好的写法需要修改系统!”
    我认为:诸如“配置信息”之类的东东,属于整个业务系统的基础设施。这些东东应该在平台中统一处理掉。一般来说,在系统框架搭建的时候,就应该把这些基础设施提供出来。所以,当你在写这个sleep代码的时候,可以直接利用现有的“配置信息的机制”,是不需要修改系统的。

    ◇对于你提到的“配置文件放在那里?文件的格式?读写的方式?是否支持热插拔?是否有权限检查?”
    我认为:上述已经提到了,由平台来统一处理诸如“配置文件”的操作。那类似“文件格式、热插拔、权限检查”之类的事情,都应该要封装掉。具体在读写某个配置信息的时候,是感觉不到的。

    ◇对于你提到的“什么异常该上交,什么异常该自行处理,不同的项目有不同的要求”。
    我认为:这要看是哪种异常。对于“Checked Exception”,我赞同你的观点(什么异常该上交,什么异常该自行处理,不同的项目不同的要求!);但是对于“Runtime Exception”,我认为还是不要处理为好,要让它尽快暴露。

    ◇对于你提到的“如果当前的Download只有一个调用者,并且调用者已经保证了url 的正确性,你还要检查吗?”
    我认为:当你抽象出某个Download函数的时候,该函数往往不止在一处调用(这种可能性很大)。一旦调用者多了,当调用者的代码发生修改时,可能会不小心引入一个bug并导致了传递null给Download函数。而在Download函数入口处中对null进行检查的成本很低(也就一两行代码)。所以进行检查还是比较合算的事情。
    反过来,如果某种出错的可能性很低,而针对这种出错的检查成本很高(比如要防止硬盘坏、内存出错等),那就未必进行这种检查了。

    回复删除
  11. 配置文件,全局业务共通的还是写一个为好吧。如果只是针对一个小模块的,就在模块中统一配置吧。
    配置也分层,会更清晰。
    期待楼主的“对虚拟机(JVM)了解不足”。

    回复删除
  12. 楼上的同学,本系列的下一个帖子“对虚拟机(JVM)了解不足”不久就会贴出来。在此帖子之前,我会先写几个铺垫的帖子。

    回复删除