多年来,我一直无法对以下问题找到一个恰当的答案:为什么一些开发人员如此反对检查异常?我有过无数次的对话,在博客上读过一些东西,读过布鲁斯·埃克尔(Bruce Eckel)说过的话(我看到的第一个公开反对他们的人)
我目前正在编写一些新代码,并非常仔细地注意如何处理异常。我正试图了解的观点是;我们不喜欢检查例外情况”;人群拥挤,我仍然看不见
我的每一次谈话都以同一个问题结束,而这个问题没有得到回答。。。让我来设置一下:
总的来说(从Java的设计方式来看)
错误
是指不应该被抓到的东西(VM对花生过敏,有人把一罐花生掉在上面)RuntimeException
是针对程序员做错的事情(程序员从数组末尾走出来)Exception
(除了RuntimeException
)用于程序员无法控制的事情(写入文件系统时磁盘已满,进程的文件句柄已达到限制,您无法再打开任何文件)Throwable
只是所有异常类型的父级
我听到的一个常见论点是,如果发生异常,那么开发人员要做的就是退出程序
我听到的另一个常见论点是,检查异常会使重构代码变得更加困难
至于;我要做的就是退出“;论点我说,即使您退出,您也需要显示一条合理的错误消息。如果你只是在处理错误上下赌注,那么当程序在没有明确说明原因的情况下退出时,你的用户不会太高兴
至于;这使得重构变得很困难;群组,这表明没有选择适当的抽象级别。与其声明一个方法抛出一个IOException
,不如将IOException
转换成一个更适合当前情况的异常
使用catch(Exception)
(或者在某些情况下使用catch(Throwable)
包装Main以确保程序可以正常退出,我没有任何问题,但是我总是捕获我需要的特定异常。这样做至少可以显示适当的错误消息
人们永远不会回答的问题是:
如果抛出
RuntimeException
子类而不是Exception
子类,那么你怎么知道呢
你应该接住吗
如果答案是catchException
,那么您处理程序员错误的方式与处理系统异常的方式相同。我认为这是错误的
如果您捕获了Throwable,那么您正在以相同的方式处理系统异常和VM错误(以及类似的错误)。这在我看来是错误的
如果答案是只捕获已知抛出的异常,那么如何知道抛出了哪些异常?当程序员X抛出一个新异常并忘记捕获它时会发生什么?这对我来说似乎非常危险
我想说,显示堆栈跟踪的程序是错误的。不喜欢检查异常的人不会有这种感觉吗
所以,如果你不喜欢检查异常,你能解释一下为什么不喜欢,并回答一个没有得到回答的问题吗
我不是在寻找关于何时使用这两种模型的建议,我要寻找的是为什么人们从RuntimeException
扩展,因为他们不喜欢从异常扩展,和/或为什么他们捕获异常,然后重试
RuntimeException
,而不是向他们的方法添加抛出。我想忍受不喜欢检查异常的动机
我想我读的是和你一样的Bruce Eckel采访,这总是困扰着我。事实上,这个论点是由被采访者(如果这确实是你所说的帖子)Anders Hejlsberg提出的,他是.NET和C#背后的微软天才
虽然我是海尔斯伯格及其作品的粉丝,但这一论点一直让我觉得是假的。它基本上可以归结为:
&“检查过的异常是不好的,因为程序员只是滥用它们,总是捕获它们并将其丢弃,这会导致隐藏和忽略问题,否则这些问题会呈现给用户”
通过“以其他方式呈现给用户”我的意思是,如果使用运行时异常,懒惰的程序员将忽略它(而不是使用空的catch块捕获它),用户将看到它
这个论点的总结是程序员不会正确地使用它们,不正确地使用它们比没有它们更糟糕
这个论点有些道理,事实上,我怀疑高斯林不在Java中使用运算符重写的动机来自一个类似的论点——它们让程序员感到困惑,因为它们经常被滥用
但最后,我发现这是海尔斯伯格的一个虚假论点,可能是一个事后的论点,用来解释这一缺失,而不是一个深思熟虑的决定
我认为,虽然过度使用检查异常是一件坏事,而且往往会导致用户处理不及时,但正确使用它们可以让API程序员为API客户机程序员带来巨大的好处
现在,API程序员必须小心不要到处抛出检查过的异常,否则它们只会惹恼客户机程序员。非常懒惰的客户机程序员会像Hejlsberg警告的那样求助于catch(Exception){}
,所有的好处都将失去,地狱也将接踵而至。
但在某些情况下,没有什么可以替代良好的检查异常
对我来说,典型的例子是文件开放API。语言史上的每一种编程语言(至少在文件系统上)都有一个API,可以让你打开一个文件。每个使用这个API的客户端程序员都知道,他们必须处理他们试图打开的文件不存在的情况。
让我重新表述一下:每个使用此API的客户机程序员都应该知道他们必须处理这种情况。
还有一个问题:API程序员是否可以帮助他们知道应该通过单独评论来处理它,或者他们是否可以坚持客户处理它
在C语言中,这个成语的意思是
如果(f=fopen(";goodluckfindingthisfile";){…}
否则{//找不到文件。。。
其中,fopen
通过返回0表示失败,而C(愚蠢地)让你将0视为一个布尔值……基本上,你学会了这个习惯用法,你就没事了。但是如果你是个傻瓜,你没有学会这个习惯用法呢。当然,你从
f=fopen(“GoodLuckFindingThis文件”);
f、 read();//砰!
并通过艰苦的方式学习
请注意,我们这里只讨论强类型语言:对于强类型语言中的API有一个清晰的概念:它是一个功能(方法)的大杂烩,供您使用,并为每种语言使用一个明确定义的协议
该明确定义的协议通常由方法签名定义。
这里,fopen要求您向它传递一个字符串(在C的情况下是一个char*)。如果您向它传递其他内容,则会出现编译时错误。您没有遵守协议-您没有正确使用API
在某些(模糊的)语言中,返回类型也是协议的一部分。如果在某些语言中尝试调用等价的fopen()
,而不将其分配给变量,则也会出现编译时错误(只能使用void函数执行此操作)
我想指出的一点是:在静态类型语言中,API程序员鼓励客户机正确使用API,防止客户机代码在出现任何明显错误时编译。
(在动态类型语言(如Ruby)中,您可以传递任何东西,比如浮点,作为文件名,它将被编译。如果您甚至不打算控制方法参数,为什么要用选中的异常来麻烦用户呢?这里的参数只适用于静态类型语言。)
那么,检查异常呢
这里有一个可以用来打开文件的JavaAPI
试试看{
f=新文件输入流(“GoodLuckFindingThis文件”);
}
catch(filenotfounde异常){
//处理它。不,真的,处理它!
…//这是我在处理的
}
看到那个陷阱了吗?下面是该API方法的签名:
公共文件输入流(字符串名称)
抛出FileNotFoundException
请注意,FileNotFoundException
是一个选中的异常
API程序员对您说:
";您可以使用此构造函数创建新的FileInputStream,但
a) 必须将文件名作为
字符串
b) 必须接受
文件可能不存在的可能性
“在运行时找到”
这就是我所关心的全部问题
基本上,关键是问题所说的“程序员无法控制的事情”。我的第一个想法是,他/她指的是API程序员无法控制的事情。但事实上,正确使用检查异常时,实际上应该是指客户端程序员和API都无法控制的事情程序员的控制。我认为这是不滥用检查异常的关键
我认为打开的文件很好地说明了这一点。API程序员知道,您可能会给他们一个在调用API时不存在的文件名,他们将无法返回您想要的,但将不得不抛出一个异常