Ste*_*oss 7 language-design exception
我很清楚关于检查异常是否是一个好主意的整个论点,我认为它们是......但这不是这个问题的重点.我正在设计一个相当简单的编译OOP语言,我决定使用已检查的异常作为一种返回错误的方法,而不会返回返回错误代码的C路由.
我正在寻找一些有关如何改进已检查异常的Java模型以消除其大多数不良方面的信息,可能是语法更改或略微更改实际功能.对检查异常的主要批评之一是,懒惰的程序员可能会吞下它们,因此不会出现错误.
也许它可以是可选的捕获异常,因此如果没有捕获,程序会崩溃?或者也许可以有特定的表示法来表示没有处理异常(比如C++虚函数'= 0'表示法)?或者,如果异常处理程序为空,我甚至可能导致程序崩溃(虽然这可能会让程序员对该语言不熟悉)
try ... catch语法怎么样,你认为可以用更简洁的方式表达异常被捕获吗?该语言将使用垃圾收集器,就像Java一样,所以是否需要finally子句?最后,还有哪些其他缺点可以检查异常以及存在哪些潜在解决方案(如果有的话)?
Jör*_*tag 13
Checked Exceptions只是Erik Meijer称之为诚实的更为普遍的一个小例子.即程序,方法,功能不应该与他们的类型有关.如果您看到类型签名,则应该能够信任其类型.
今天的Java不是这样(特别是如果你想象一个Java--没有Checked Exceptions).
如果你在Java中有这样的类型签名:
Foo bar(Baz)
Run Code Online (Sandbox Code Playgroud)
它说"我接受Baz输入并产生Foo输出".但那是谎言.
事实上,bar采取任何一个Baz 或 null输入.它也需要在整个全局状态,一流的状态和实例状态输入,以及整个宇宙,真的(例如,通过文件I/O,网络I/O,数据库I/O等).和它不产生一个Baz作为输出之一:它产生任一Foo 或 null 或一个异常或 Bottom(即,什么都没有).此外,它的输出还包括整个全局状态,整个类状态,整个实例状态以及整个宇宙状态.
bar的实际类型是:
(IO<Foo | null | Exception> | Bottom) bar(IO<Baz | null>)
Run Code Online (Sandbox Code Playgroud)
或类似的东西.
这需要修复,Checked Exceptions是(非常小的)部分.我个人认为,其他部分都更加重要和Java的设计者应该集中于固定的,而不是例外(特别是例外只是副作用,反正等你,当你解决实际副作用几乎自动修复免费例外效果).
无论如何,这就是为什么我认为Checked Exceptions 背后的一般理念是Good Thing™,即使Java中的特定实现可能有点麻烦.
如何修复Checked Exceptions很大程度上取决于您认为它们究竟出现了什么问题.
有些人认为Checked Exceptions的问题在于,当您更改方法的内部实现以使用与之前不同的辅助方法时,这会抛出一组不同于旧方法的异常,您需要显式处理这些异常.例外或声明它们,从而打破了所有客户.现在,如果你认为这是Checked Exceptions的错误,那么只有办法解决它们:首先没有它们.更改您抛出的异常是您的API合同中的重大变化,并且您的API合同中的重大更改应该会破坏客户端代码.(或者更准确地说:您不应对API合同进行重大更改,以免破坏客户端代码.)
我认为Java实现的Checked Exceptions的主要问题是它们打破了异常的一个主要特性:非本地错误处理.这里发生了一个错误,并且在那里处理错误,这两个是唯一需要了解它的人.如果在这里发生了不同类型的错误,那么唯一需要了解该新错误的地方以及需要更改的唯一地方就是错误处理程序.
使用Java实现的Checked Exceptions,中间的每一段代码也需要更改.
修复此问题的一个建议是锚定异常声明.(模块化锚定异常声明中的改进.)
锚定异常声明的思想基本上是使用委托进行异常声明,就像在方法体中使用委托一样,这毕竟是首先产生问题的方法.
假设您有一些文件读取器方法委托给另一个方法:
String fileReader(String filename) {
return this.fileHelper.read(filename);
}
Run Code Online (Sandbox Code Playgroud)
现在,您将进入JavaDoc FileHelper#read并将异常列表剪切并粘贴到您的方法中:
String fileReader(String filename) throws IOException, CustomFileReaderEx
Run Code Online (Sandbox Code Playgroud)
现在作者FileHelper#read决定他使用不同的实施策略.现在,他确保实际的文件读取永远不会失败,首先确保文件存在,可以打开,并且格式正确.所以,自然,这组例外会发生变化.不再可能得到一个IOException或一个CustomFileReaderEx.相反,你可以得到一个InvalidFilenameEx或CorruptDataEx.所以,你再次剪切和粘贴:
String fileReader(String filename) throws InvalidFilenameEx, CorruptDataEx
Run Code Online (Sandbox Code Playgroud)
不仅没有,你不得不做出这样的改变,但其他人谁调用fileReader(和大家谁呼吁他们和大家谁称呼和...)为好.太疯狂了!您将呼叫委托给fileHelper首先的原因是您不必关心这些细节.
因此,锚定异常声明的想法是将此委托用于异常声明本身.你没有说出你抛出的精确例外,而是责怪别人."他做到了!":
String fileReader(String filename) throws like this.fileHelper.read
Run Code Online (Sandbox Code Playgroud)
而你的客户只是说:
Foo whatever() throws like fileReader
Run Code Online (Sandbox Code Playgroud)
这样,当FileHelper更改其异常时,那么唯一需要更改的代码就是顶级异常处理代码,就像我上面针对未经检查的情况所描述的那样.
当然有限制.例如,为了不破坏封装,您只能在throws like子句中使用所有客户端都可以访问的标识符.在这种情况下,如果fileHelper是private字段,则无法使用它.你需要一些其他的方式.例如,如果FileHelper该类是public(或者如果它是包私有的并且所有客户端都在同一个包中),那么您可以改为说
String fileReader(String filename) throws like FileHelper.read
Run Code Online (Sandbox Code Playgroud)
本文还列出了其他限制.(其中一个在模块化锚定异常声明文件中被提升.)
无论如何,这是改善Checked Exceptions的一些问题的一种方法.但是,Checked Exceptions已经存在了近40年,我们仍然没有把它们弄清楚,所以这显然是一个难题.
我很清楚关于检查异常是否是一个好主意的整个争论,我站在他们的一边......
FWIW,我同意——总的来说,它们是一件好事。程序员在使用某个功能时的坏习惯并不一定意味着该功能不好。在大多数情况下,我认为程序员不明白他们可以传递异常并将其提升到链上。
也许捕获异常是可选的,因此如果没有捕获异常,程序就会崩溃?
Java 具有该功能(RuntimeException及其子类是未经检查的异常)。
或者也许可能有特定的符号来表示未处理异常......
Java 有这个;throws方法声明中的子句。
try...catch 语法怎么样,您认为可以有更简洁的方式来表达正在捕获异常吗?
当然,它可能感觉有点笨拙,但目标是将异常条件从主线逻辑中剔除。
不过,我确实有一些建议try..catch:
catch..from这是我长期以来在 Java 和相关语言中想要的东西(确实需要正确地编写并提交 JSR):catch...from。
这是一个例子:
FileOutputStream output;
Socket socket;
InputStream input;
byte[] buffer;
int count;
// Not shown: Opening the input and output, allocating the buffer,
// getting the socket's input stream
try
{
while (/* ... */)
{
count = input.read(buffer, 0, buffer.length);
output.write(buffer, 0, count);
// ...
}
}
catch (IOException ioe from input)
{
// Handle the error reading the socket
}
catch (IOException ioe from output)
{
// Handle the error writing to the file
}
Run Code Online (Sandbox Code Playgroud)
正如您所看到的,这里的目标是将套接字读取错误和文件写入错误的不相关错误处理分开。非常基本的想法,但涉及很多微妙之处。例如,由套接字或文件输出流实例在幕后使用的其他对象引发的异常需要像由该实例引发的那样进行处理(不是那么难,只需要确保实例信息位于堆栈跟踪中)。
这是人们可以利用现有机制和严格的编码指南来完成的事情,但这是非常困难的。
A'la 计划中的 JDK7 增强功能。
比上面的提供起来非常非常困难,而且人们也更容易解决,所以价值要低得多。但是:提供“重试”语义:
try
{
// ...stuff here...
if (condition)
{
foo.doSomething();
}
// ...stuff here...
}
catch (SomeException se)
{
if (foo.takeRemedialAction() == Remedy.SUCCESS)
{
retry;
}
// ...handle exception normally...
}
Run Code Online (Sandbox Code Playgroud)
在这里,doSomething可能会以一种特殊的方式失败,但也takeRemedialAction可能会以一种能够纠正的方式失败。这延续了将特殊条件排除在主逻辑线之外的主题。当然,retry将执行返回到失败的操作,这可能位于doSomething其调用的某个子方法的深处。你明白我所说的挑战是什么意思。
对于团队来说,使用现有机制更容易做到这一点:只需制作一个doSomething附加takeRemedialAction异常的子例程,然后将该调用放入主线逻辑中即可。所以,这个在列表中排名靠后,但是嘿......