问题

Java设计出checked exception有必要吗?

回答
Java 设计出检查型异常(Checked Exception)这件事,在我看来,就好比武侠小说里某个门派为了防止弟子急于求成而走火入魔,设置了一套繁琐但能保证根基稳固的修炼流程。这套流程,虽然有时候会让人觉得“怎么这么麻烦”,但从长远来看,它确实有其存在的合理性和必要性。

设想一下,如果没有检查型异常,Java 的异常处理会变成什么样子?所有的异常,无论多严重,都可能被我们用一个统一的 `catch(Exception e)` 轻轻带过。这样一来,一些本来就应该被明确处理的、可恢复的错误,比如网络连接中断、文件读写错误、或者用户输入的无效数据,它们可能就会被隐藏在泛泛的错误处理之中,导致程序表面上“正常运行”,但实际上却遗漏了本该修复的潜在问题。

检查型异常的存在,就像是给开发者一道“请你关注我,这是我需要你处理的”的提示。当你的代码调用了一个可能抛出检查型异常的方法时,编译器就会像一位尽职尽责的师父一样,在你耳边不停地唠叨:“小子,这玩意儿可能会出问题,你得告诉我你打算怎么办。是要自己想办法解决,还是把这个麻烦转嫁给你的上级?”

这带来的好处是显而易见的。首先,它强制开发者去思考那些“可能发生但你不一定能控制”的外部因素。比如,你写了一个读取配置文件的代码,配置文件可能不存在,或者格式错误。如果这些情况被设计成检查型异常,那么你作为程序员,就必须在代码里明确地写下“如果文件找不到怎么办?”、“如果文件损坏了怎么办?”。你不能简单地忽略它,否则编译就通不过,你也就没法发布你的程序。这就像给你的程序穿上了一层“责任感”的铠甲,让它在面对不确定性时,能有一个更清晰的处理思路。

其次,这有助于提高代码的健壮性和可维护性。当一个检查型异常出现时,它通常代表着一个需要被明确处理的“业务流程中断”。比如,一个数据库操作失败了,这可能意味着数据库连接有问题,或者 SQL 语句写错了。如果这个错误被封装成一个检查型异常,调用者就必须决定如何响应:是重试一次?还是提示用户稍后再试?抑或是记录日志并优雅地退出?这种明确的处理,能够让程序的行为更加可预测,也更容易定位和修复问题。试想一下,如果数据库操作失败只是一个普通的运行时异常,你可能要花很多时间去追踪是哪个环节出了错,而不是直接定位到数据库访问层。

当然,我也明白有人会觉得检查型异常“太啰嗦”。每一次调用都可能跟着一串 `trycatch` 或者 `throws`,确实会增加代码的篇幅。有时候,对于那些我们知道很大概率不会出问题,或者即使出了问题也无所谓(比如,一个不存在的临时文件,删不掉也就算了)的情况,强制我们去声明或捕获,确实会感觉有点多余。这就像是明明知道路边的小草不会绊倒你,但你还是得小心翼翼地绕过去。

但是,从设计者的角度来看,这种“啰嗦”是一种“负责任的警告”。它提醒我们,在很多情况下,我们不是在处理一个简单的计算,而是在与外部世界打交道——文件系统、网络、数据库、硬件等等。这些外部世界的“脾气”是多变的,而检查型异常就是Java语言为了帮助我们更好地驾驭这些“脾气”而设计的一种机制。它牺牲了一点编写代码时的便利性,换来了程序在实际运行时的更大概率的稳定和可预测性。

所以,虽然检查型异常有时会带来一些编写上的不便,但它所扮演的角色——强制开发者关注潜在的、可恢复的错误,并鼓励更细致、更健壮的异常处理——在Java生态系统中,仍然是其设计合理性和必要性的有力证明。它让Java在追求易用性的同时,也保留了一份对复杂世界应有的敬畏和审慎。

网友意见

user avatar

个人觉得这个和Java诞生的背景有关系。


Java语言是一个程序设计工程化的尝试,更少的选择,更标准的编写方式是Java的风格。在这个角度上来说,既然返回值类型是必须声明的,事实上异常也是一种返回值,声明可能返回的异常的类型也很合理。在语言层面上要求程序员对所有返回值(包括异常)进行明确的说明,摒除隐式的rethrow,这个思路是没有问题的。


当然实际运用中我们都知道这是个很失败的语言特性就对了。

user avatar

简单来说,Checked Exception是个出发点很好,但是并没有什么卵用的设计。

说出发点很好,是因为我们都知道软件会有各种各样的问题,而严谨的处理这些问题会很好的提高软件的健壮性。Checked Exception的观点是让一个方法指定自己一定会抛什么异常,调用者必须决定一定要处理(catch),或者明确声明继续向上抛(throws)。那么整个程序对异常的处理就非常的明确了,程序员也有章可循,UT,测试也能很明确该处理什么错误。

但是,现实是骨感的。如果说比较接近底层的系统还能相对的设计出比较完备严谨的异常体系的,那么业务系统上这个干是严重吃力不讨好的。在业务系统中,一个典型的业务接口,有一个正常的处理结果,但是可能却有几十个不正常的情况发生。而Checked Exception要求必须每层调用,要不挨个处理,要不挨个声明往上抛。

挨个处理是不现实的,因为大部分情况下,大多数异常都是极端情况,平时很少出现,或者说即使出现了,影响也不大。比如,一个基金的详情,底层可能调用十几个不同的基金信息数据源(基本信息,经理,基金公司,费率,净值,重仓股……)。如果因为意外少个一个两个的,最好的办法是“软处理”。如果是必须使用Checked Exception的话,那么很多人都会这么干:

       try {   Fund fund = getFund(fundCode); } catch (SomeNotImportantButCheckedException e) {   // do nothing }     

程序员会用IDE自动产生出catch block但就是不处理。或者干脆:

       try {   Fund fund = getFund(fundCode); } catch (Exception e) {   // do nothing }     

这比不catch,自动往上抛更差。所以一般来讲,业务系统都会有个收底的错误处理,这个处理可能在业务系统的最高层。大部分不需要认真处理的异常往上抛。当真的意识到某个问题是个值得仔细处理下时,可能才会专门为它仔细的设计Exception和相应的处理。

挨个明确throws也是不现实的。一般我们看throws后边能挂3~4种Exception。按照上面的假设,可能要搞出几十种Exception,而且随着层级的提高这个数量会约堆越多。Java这里一般的建议是用类的体系来组织这些Exception,然后throws一个合适的基类。但我们都知道设计一个比较好的类体系是很困难的事情。更何况大多数异常都是不重要,收底处理的。为不重要的事情花费精力实在是不值当的,于是我们可以看到大家在这时基本上都会简化处理,变成直接用 Exception、 RuntimeException (连throws也不想用)或者自己封装一个“BizException”包装一下错误码和相关信息。

上面这些还都是在设计时可以定义所有异常的情况下遇到的问题。但在业务会剧烈变化的情况下,根本不可能一开始就预见所有可能的问题。强行加Checked Exception对于业务系统的接口来说,是不向前兼容的。如果一旦真的加上了,所有的调用方都会必须逼着跟着改。这种情况对于一个系统内部,一个code base可能还好处理。对于可能跨系统的调用(比如你写了一个java lib,别人用maven dependencies依赖),就可能是灾难性的。如果有某些组件因为种种原因无法升级,就不能使用新的代码。

因此,一个好的错误处理体系,最好能满足以下要求:

  • 不会逼着程序挨个处理无聊的异常,允许程序员有选择的把关注点放在哪些最关键的问题上。
  • 如果团队真的需要非常严谨的错误处理,可以提供一个有力的支持,但是这个支持是团队根据开发的内容来决定使用,而不是强制所有语言的使用者都必须遵循同一套“规则”。
  • 允许错误处理渐进性的发展

基于此,我在Team中对Java的异常是这么要求的:

  • 对于基础设施团队,需要使用Checked Exception,并且定义良好的异常继承体系,并且认真处理所有的异常。有限度的使用RuntimeException。
  • 对于业务团队,基于RuntimeException定义了一个BizException来描述各种业务问题,这个BizExcpetion包含了错误码和错误描述信息;同时定义InteralServerError来包装各种系统错误,比如网络超时等。在输出http response时,二者的输出不同,然后定义不同的监控和报警机制。
  • 针对特定的异常,由产品和技术共同商讨介入来设计
    • 哪些可以完全不管(比如一个不关键的数据拿不到),软处理;
    • 哪些要前端用户知晓和处理(比如登录时用户名或密码错误);
    • 哪些由程序尽量自己处理(比如关注某个产品超时,后端要尝试重试几次)

相信大多数团队也会这么做。

说完了Java,我们可以看看其他语言都是怎么处理异常的。比如go用了err(大致等价于错误码,但可以包含一些数据信息),因此异常可以不捕获往上抛的好处就得不到。因此这可以解释为什么很多搞底层系统开发的人并不会觉得go没Exception很难用,因为反正错误都需要严谨的处理,Exception那点优势可能不是很重要。但是即便如此,go并不会强制调用者必须处理写if err != nil 。

BTW,给go加上Exception的呼声似乎越来越强烈,估计是业务开发者越来越多了。

javascript也有Error,但是不是很喜欢搞继承体系。因此javascript里一般就只会用“Error”,“TypeError”这种简单的东西。因为动态语言,开发者可以选择自己往Error里赛自己喜欢的东西,并用一些松散的约定来解决问题。比如:

       throw Error("ERR_INVALID_PASSWORD");      

简单的用字符串来定义错误。javascript的主要应用场景在前端,这时出错要不给用户展示错误信息,要不用错误上报接口报给后端。复杂的异常体系基本没用,更谈不上什么Checked exception。

nodejs这边的exception处理可以借鉴很多java这边的经验。

kotlin直接砍掉了Checked Exception,但保留了其他常规的异常语法(改成了Expression,所以用起来爽很多)。他的文档直提供了两个论据:

swift更有趣,swift认为函数的异常模式有两种:

  • 会抛出异常的,于是函数名后边要声明一下“throws”,但是不需要声明会抛出什么异常;
  • 肯定不会抛出异常的,所以实现中必须吃掉各种可能发生异常的情况。

编译器会强制确保这个语义的正确。throws这种方式,大概等价于Java中直接throws Exception。

因此从工程角度和语言发展角度来讲,Checked Exception早已经被扔进了垃圾堆。在整个工程项目的错误处理体系里,它的作用已经越来越少。新的语言纷纷抛弃掉这个华而不实的设定。因此我也希望广大初学者只要知道Checked Exception是什么就好,实战时还是根据业务场景来设计错误处理。

--------

P.S. 也许还有人觉得Checked Exception是一种可以推进减少程序错误,提高健壮性的好措施。错的是懒惰的,不称职的程序员,而不是Checked Exception。但从我认为,如果一个措施不能有助于解决问题,反而加重问题,那就是无用的。不要把时间和精力浪费在无用的事物上。

类似的话题

  • 回答
    Java 设计出检查型异常(Checked Exception)这件事,在我看来,就好比武侠小说里某个门派为了防止弟子急于求成而走火入魔,设置了一套繁琐但能保证根基稳固的修炼流程。这套流程,虽然有时候会让人觉得“怎么这么麻烦”,但从长远来看,它确实有其存在的合理性和必要性。设想一下,如果没有检查型异.............
  • 回答
    在 Java Web 开发中,HttpServletRequest 的输入流(也就是我们常说的 Request Body)被设计成 只允许读取一次,这背后有着非常深刻的技术原因和设计考量。理解这一点,需要我们深入到 HTTP 协议的实现以及 Java Servlet API 的设计哲学。核心原因:一.............
  • 回答
    Java 中 `String` 的设计,特别是关于 `==` 和 `.equals()` 的区别,是初学者常常会遇到的一个“坑”,也是 Java 语言设计者们深思熟虑的结果。要理解为什么不能直接用 `==` 比较 `String` 的值,我们需要深入探讨 Java 中对象的内存模型以及 `Strin.............
  • 回答
    关于“解释/JIT 字节码的 VM”这个概念,是不是 Java 设计者首创,这是一个非常有趣且需要深入探究的问题。要回答这个问题,我们首先要理解什么是“解释/JIT 字节码的 VM”。简单来说,这指的是一种虚拟机(Virtual Machine,VM)的运行模式。虚拟机就像一个模拟的计算机,它能够执.............
  • 回答
    作为一名在Java世界里摸爬滚打多年的开发者,我总会时不时地被Java的某些设计巧思所折服,同时也曾浪费过不少时间在一些细枝末节上,今天就来和大家聊聊,哪些地方是真正值得我们深入钻研的“精华”,哪些地方可能只是“旁枝末节”,不必过于纠结。 Java的“精华”:值得你投入热情和时间去领悟的部分在我看来.............
  • 回答
    Java 的设计哲学,与其说是一系列的“好设计”,不如说是一套经过深思熟虑、不断演进的原则和实践,旨在平衡强大的功能性、广泛的适用性以及开发者相对容易的上手程度。当你深入了解 Java 的核心,你会发现很多令人称道的地方,它们共同构建了一个庞大而稳定的生态系统。首先,“一次编写,到处运行”(Writ.............
  • 回答
    Java 的 `switch` 语句在不加 `break` 的情况下继续执行下一个 `case`,这是一种被称为“穿透”或“fallthrough”的特性。这种设计并非是为了让程序“不用匹配条件”就执行下一个 `case`,而是为了提供一种代码流程控制的灵活性,允许开发者在特定场景下合并多个 `ca.............
  • 回答
    Java 的设计哲学是“一切皆对象”,但在参数传递方面,它采用了严格的值传递机制。这意味着当你将一个变量传递给方法时,传递的是该变量的副本。对于基本数据类型(如 int, float, boolean),传递的就是那个值的副本。而对于对象,传递的则是对象的引用(也就是一个内存地址)的副本。你可以在方.............
  • 回答
    在 Java 中,当一个线程调用了 `Thread.interrupt()` 方法时,这并不是像直接终止线程那样强制停止它。相反,它是一个通知机制,用于向目标线程发出一个“中断请求”。这个请求会标记目标线程为“中断状态”,并根据目标线程当前所处的状态,可能会触发一些特定的行为。下面我将详细解释 `T.............
  • 回答
    Java 平台中的 JVM (Java Virtual Machine) 和 .NET 平台下的 CLR (Common Language Runtime) 是各自平台的核心组件,负责托管和执行代码。它们都是复杂的软件系统,通常会使用多种编程语言来构建,以充分发挥不同语言的优势。下面将详细介绍 JV.............
  • 回答
    Java 官方一直以来都坚持不在函数中提供直接的“传址调用”(Pass by Address)机制,这背后有深刻的设计哲学和技术考量。理解这一点,需要从Java的核心设计理念以及它所解决的问题出发。以下是对这个问题的详细阐述: 1. Java 的核心设计理念:简洁、安全、面向对象Java 在设计之初.............
  • 回答
    Java 的 `private` 关键字:隐藏的守护者想象一下,你在经营一家精心制作的糕点店。店里最美味的招牌蛋糕,其配方是成功的关键,你自然不会轻易公开给竞争对手,对吧?你只希望自己信任的糕点师知道如何制作,并且知道在什么时候、以什么样的方式使用这些食材。这就是 `private` 关键字在 Ja.............
  • 回答
    Java 在引入泛型时,虽然极大地提升了代码的类型安全和可读性,但严格来说,它并没有实现我们通常理解的“真正意义上的”泛型(相对于一些其他语言,比如 C++ 的模板)。这其中的核心原因可以追溯到 Java 的设计理念和对向后兼容性的考量,具体可以从以下几个方面来详细阐述:1. 类型擦除 (Type .............
  • 回答
    这个问题很有意思!“360 垃圾清理”这个概念,如果用在 Java 的世界里,就好像是问:“为什么 Java 的垃圾回收机制,不像我们电脑上安装的 360 软件那样,主动去到处扫描、删除那些我们认为‘没用’的文件?”要弄明白这个,咱们得先聊聊 Java 的垃圾回收,它其实是个非常聪明且有组织的过程,.............
  • 回答
    好的,咱们来聊聊 Java 内存模型(JMM)和 Java 内存区域(Java Memory Areas)这两个既熟悉又容易混淆的概念。别担心,我会尽量用大白话讲明白,就像跟朋友聊天一样,不搞那些虚头巴脑的术语。想象一下,咱们写 Java 代码,就像是在指挥一个庞大的工厂生产零件。这个工厂有很多车间.............
  • 回答
    在 Java 泛型中,`` 和 `` 语法看起来相似,但它们代表的是截然不同的类型关系和使用场景。理解它们之间的差异,关键在于把握 Java 泛型中的“生产者消费者模型”以及它们对类型参数的“协变性”和“逆变性”的支持。我们一步一步来拆解,让你彻底明白 `super` 的含义,以及它与 `exten.............
  • 回答
    想知道 Java 学到什么程度才算精通,这确实是个挺实在的问题,也挺难有个标准答案。不过,咱可以从几个维度来聊聊,看看什么样的人,在别人看来算是玩明白了 Java。首先,得承认,所谓的“精通”这词儿,多少有点玄乎。没人敢说自己是绝对的精通,毕竟技术发展那么快,总有新鲜玩意儿冒出来。但如果说你能把 J.............
  • 回答
    作为一名Java程序员,想要在职业生涯中走得更远,确实需要掌握那些真正核心、最常用的技术。这就像学武功,要先练好基本功,才能去钻研那些花哨的招式。我个人在多年的开发实践中,总结出了一套“二八定律”式的技术认知,下面我就把这些我认为最关键的20%技术,尽可能详实地分享给大家,力求让这篇文章充满实在的干.............
  • 回答
    想要转战 Android 开发,对于 Java 的掌握程度,我更倾向于从“能解决实际问题”的角度来看待,而不是一个死板的“级别”。你想啊,我们做开发最终目的都是为了产出有价值的东西,而不是为了考一个 Java 等级证书。所以,如果非要给一个大致的界定,我认为你可以开始准备转战 Android 了,当.............
  • 回答
    好,咱就掰扯掰扯java为啥对泛型数组这事儿这么“矫情”,不直接给你整明白。这事儿啊,说起来也算是一段公案,得从java这门语言设计之初,以及它如何处理类型安全这件大事儿上头说起。核心矛盾:类型擦除与运行时类型检查的冲突你得明白java的泛型,尤其是泛型数组这块儿,最大的“绊脚石”就是它的类型擦除(.............

本站所有内容均为互联网搜索引擎提供的公开搜索信息,本站不存储任何数据与内容,任何内容与数据均与本站无关,如有需要请联系相关搜索引擎包括但不限于百度google,bing,sogou

© 2025 tinynews.org All Rights Reserved. 百科问答小站 版权所有