是最后一块没有赶上的框java防模式?
-
03-07-2019 - |
题
我只有一个非常痛苦的故障排除经验在解决一些代码,是这样的:
try {
doSomeStuff()
doMore()
} finally {
doSomeOtherStuff()
}
问题是很难解决,因为doSomeStuff()把一个异常的,这又引起doSomeOtherStuff()也要扔一个例外。第二种例外情况(扔通过的最后块)被扔到我的代码,但它没有处理在第一个例外(扔从doSomeStuff()),这是真正的根本原因的问题。
如果代码说了这话,而不是,问题已经显而易见:
try {
doSomeStuff()
doMore()
} catch (Exception e) {
log.error(e);
} finally {
doSomeOtherStuff()
}
因此,我的问题是这个:
是最后一块使用,没有任何捕捉块一个众所周知java防模式?(它当然似乎是一个不容易-显而易见的子显然众所周知的反模式"不要狼吞虎咽例外!")
解决方案
一般来说,不,这不是反模式。 finally块的重点是确保无论是否抛出异常,都会清理内容。异常处理的重点在于,如果你不能处理它,你可以通过相对干净的带外信令异常处理提供给它的人。如果你需要确保在抛出异常时清理了东西,但是无法在当前范围内正确处理异常,那么这是正确的做法。你可能想要更加小心确保你的finally块不会抛出。
其他提示
我认为真正的“反模式”这是在 finally
块中执行的操作,可以抛出,而不是没有捕获。
完全没有。
最终内部的代码出了什么问题。
请记住,最终总是会被执行,并且只是冒着风险(正如你刚刚目睹的那样)放置可能会在那里引发异常的东西。
尝试最终没有任何问题绝对没有错。请考虑以下事项:
InputStream in = null;
try {
in = new FileInputStream("file.txt");
// Do something that causes an IOException to be thrown
} finally {
if (in != null) {
try {
in.close();
} catch (IOException e) {
// Nothing we can do.
}
}
}
如果抛出异常并且此代码不知道如何处理它,那么异常应该将调用堆栈冒泡到调用者。在这种情况下,我们仍然希望清理流,所以我认为有一个没有捕获的try块是完全合理的。
我认为这是远远不是一个反模式,是我做的事情非常频繁时,它是至关重要的做释放资源过程中获得的方法执行。
有一件事我当处理与文件处理(对于写作)是冲洗的流关闭它之前使用的IOUtils.closeQuietly方法,该方法不会扔的情况除外:
OutputStream os = null;
OutputStreamWriter wos = null;
try {
os = new FileOutputStream(...);
wos = new OutputStreamWriter(os);
// Lots of code
wos.flush();
os.flush();
finally {
IOUtils.closeQuietly(wos);
IOUtils.closeQuietly(os);
}
我喜欢这样做,原因如下:
- 它不完全可以忽略的一个例外时关闭一个文件-如果有字节没有写入文件,然后将文件可能不在国家主叫方所期望;
- 因此,如果一个例外是中提出的flush()方法,它将会传播到的来电,但是我仍然将确保所有文件都是关闭的。该方法IOUtils.closeQuietly(...)小详细然后相应的尝试...抓住...忽视我块;
- 如果使用多个流输出为了flush()方法是重要的。流创建的通过传递的其他流以构造应该是冲第一次。同样的事情是有效的close()方法,但flush()更清楚我的意见。
我会说没有catch块的try块是反模式。说“没有最后没有捕获”是“没有捕获的尝试”的子集。
我以下面的形式使用try / finally:
try{
Connection connection = ConnectionManager.openConnection();
try{
//work with the connection;
}finally{
if(connection != null){
connection.close();
}
}
}catch(ConnectionException connectionException){
//handle connection exception;
}
我更喜欢try / catch / finally(在finally中使用+嵌套的try / catch)。 我认为它更简洁,我不复制catch(Exception)。
try {
doSomeStuff()
doMore()
} catch (Exception e) {
log.error(e);
} finally {
doSomeOtherStuff()
}
不要那么做......你只是隐藏了更多的bug(好吧并没有完全隐藏它们......但是更难以处理它们。当你捕获Exception时,你也会捕获任何类型的RuntimeException(如NullPointer)和ArrayIndexOutOfBounds)。
通常,捕获必须捕获的异常(检查异常)并在测试时处理其他异常。 RuntimeExceptions旨在用于程序员错误 - 程序员错误是在正确调试的程序中不应发生的事情。
在我看来,更多的情况是 finally
带有 catch
表示某种问题。资源习惯很简单:
acquire
try {
use
} finally {
release
}
在Java中,您可以在任何地方获得异常。通常,获取会抛出一个经过检查的异常,处理这种情况的合理方法是捕获大量的数据。不要尝试一些可怕的空检查。
如果你真的是肛门,你应该注意到例外中隐含的优先权。例如,ThreadDeath应该破坏所有,无论是来自获取/使用/释放。正确处理这些优先事项是不雅观的。
因此,使用Execute Around成语来抽象你的资源处理。
Try / Finally是一种正确释放资源的方法。 finally块中的代码应该永远不会抛出,因为它应该只对在进入try块之前获取的资源或状态起作用。
顺便说一句,我认为log4J 几乎是一种反模式。
如果您想要检查运行程序,请使用正确的检查工具(即调试器,IDE,或者极端意义上的字节代码编织器,但不要在每个错误的路径中记录日志!)。
在两个示例中,您展示的第一个看起来是正确的。第二个包括记录器代码并引入了一个bug。在第二个例子中,如果前两个语句抛出一个异常(即你捕获它并记录它但不重新抛出),你可以抑制异常。这是我在log4j使用中常见的东西,它是应用程序设计的一个真正问题。随着你的改变,你使程序失败的方式对于系统来说很难处理,因为你基本上前进就好像你从来没有异常一样(类似VB基础的错误恢复下一个构造)。
return
语句, try-finally
可以帮助您减少复制粘贴代码。请考虑以下示例(Android Java):
boolean doSomethingIfTableNotEmpty(SQLiteDatabase db) {
Cursor cursor = db.rawQuery("SELECT * FROM table", null);
if (cursor != null) {
try {
if (cursor.getCount() == 0) {
return false;
}
} finally {
// this will get executed even if return was executed above
cursor.close();
}
}
// database had rows, so do something...
return true;
}
如果没有 finally
子句,则可能必须编写 cursor.close()
两次:在返回false
之前以及之后周围的 if
子句。
我认为尝试没有捕获是反模式。使用try / catch来处理异常条件(文件IO错误,套接字超时等)不是反模式。
如果您正在使用try / finally进行清理,请考虑改为使用块。