如何以安全可读的方式处理我知道永远不会抛出的IOException?

“可能出错的事情与不可能出错的事情之间的主要区别在于,当一件不可能出错的事情出错时,通常会发现无法进入或修复。” -道格拉斯·亚当斯

我有一个类FileItems。 FileItems构造函数接受一个文件,如果该文件不存在则抛出exception(FileNotFoundException)。 该类的其他方法也涉及文件操作,因此具有抛出FileNotFoundException的能力。 我想找到一个更好的解决方案。 一种解决方案,不需要其他程序员处理所有这些极不可能的FileNotFoundExceptions。

事情的事实:

  1. 该文件已被检查存在,但存在极不可能的可能性,通过一些重大的现实错误,在调用此方法之前可能会删除该文件。
  2. 由于1发生的可能性非常不同且不可恢复,我宁愿定义未经检查的exception。
  3. 该文件已经被发现存在,迫使其他程序员编写代码并捕获已检查的FileNotFoundException,这看起来既乏味又无用。 该程序应该在那时完全失败。 例如,计算机总是有可能着火,但没有人疯狂到足以迫使其他程序员将其作为检查exception来处理 。
  4. 我不时遇到这种exception问题,并且每次遇到这个问题时定义自定义未经检查的exception(我的旧解决方案)都很烦人并且增加了代码膨胀。

代码目前看起来像这样

public Iterator getFileItemsIterator() { try{ Scanner sc = new Scanner(this.fileWhichIsKnowToExist); return new specialFileItemsIterator(sc); } catch (FileNotFoundException e){ //can never happen} return null; } 

如何在不定义自定义未经检查的FileNotFoundException的情况下更好地完成此操作? 有没有办法将checkedException转换为uncheckException?

解决这个问题的通常模式是exception链接 。 您只需在RuntimeException中包装FileNotFoundException:

 catch(FileNotFoundException e) { throw new RuntimeException(e); } 

此模式不仅适用于在特定情况下(例如您的情况)不能发生exception,而且当您无法或无意真正处理exception(例如数据库链接失败)时。

编辑 :要注意这种看起来相似的反模式,我经常在野外看到它:

 catch(FileNotFoundException e) { throw new RuntimeException(e.getMessage()); } 

通过这样做,您可以丢弃原始堆栈跟踪中的所有重要信息,这通常会使问题难以追踪。

另一个编辑:正如ThorbjørnRavnAndersen在他的回答中正确指出的那样,无论是在评论中,还是在更好的情况下,作为exception消息,都说明为什么要链接exception并没有什么坏处:

 catch(FileNotFoundException e) { throw new RuntimeException( "This should never happen, I know this file exists", e); } 

您可以通过将其嵌套在RuntimException中来将已检查的exception转换为未选中的exception。 如果exception在堆栈中被捕获并使用printStackTrace()输出,则也会显示原始exception的堆栈。

 try { // code... } catch (IOException e) { throw new RuntimeException(e); } 

这是一个很好的解决方案,您应该毫不犹豫地在这些情况下使用。

考虑使用表单

 throw new RuntimeException("This should never happen", e); 

代替。 这允许您向维护者传达意义,以便在阅读代码时跟随您,但也应该在exception情况下抛出exception。

编辑:这也是通过不期望这些exception的机制传递exception的好方法。 例如,如果你有一个“从数据库中获取更多行”迭代器,那么Iterator接口不允许抛出例如FileNotFoundException,因此你可以像这样包装它。 在使用迭代器的代码中,您可以捕获runtimeexception并使用getCause()检查原始的excpetion。 在浏览遗留代码路径时非常有用。

如果你的位置太小,而且应该只是结束程序,那么使用现有的运行时exception,甚至是RuntimeException本身(如果不是IllegalStateException)。

 try { .... } catch (FileNotFoundException e) { throw new RuntimeException(e); } 

你可能最好在代码中的任何一点抛出超类和更通用的exceptionIOException ,这涉及从文件中读取或写入。

当您的类的构造函数运行时,该文件可能存在,但这并不能保证:

  1. 它在调用方法时存在
  2. 它是可写/可读的
  3. 另一个线程无法访问它,并以某种方式搞砸了你的流
  4. 资源不会在处理过程中消失

等等

我会说重新抛出IOException而不是重新发明轮子,只要你使用的JDK / java.io类强制你这样做。

我还有一个仇恨类,它们从构造函数中抛出exception – 如果我是你,我会摆脱这些。

我做了一点谷歌搜索,发现了这个代码。 我认为这种方法更灵活一些

赞美这篇文章

 class SomeOtherException extends Exception {} public class TurnOffChecking { private static Test monitor = new Test(); public static void main(String[] args) { WrapCheckedException wce = new WrapCheckedException(); // You can call f() without a try block, and let // RuntimeExceptions go out of the method: wce.throwRuntimeException(3); // Or you can choose to catch exceptions: for(int i = 0; i < 4; i++) try { if(i < 3) wce.throwRuntimeException(i); else throw new SomeOtherException(); } catch(SomeOtherException e) { System.out.println("SomeOtherException: " + e); } catch(RuntimeException re) { try { throw re.getCause(); } catch(FileNotFoundException e) { System.out.println( "FileNotFoundException: " + e); } catch(IOException e) { System.out.println("IOException: " + e); } catch(Throwable e) { System.out.println("Throwable: " + e); } } monitor.expect(new String[] { "FileNotFoundException: " + "java.io.FileNotFoundException", "IOException: java.io.IOException", "Throwable: java.lang.RuntimeException: Where am I?", "SomeOtherException: SomeOtherException" }); } } ///:~ 

我遇到了同样的问题。

在最简单的情况下,您通知用户错误并建议重复或取消操作。

在一般情况下,工作流是一系列动作(包括I / O),其中每个动作“假定”前一个动作成功。

我选择的方法是创建一个“回滚”动作列表。 如果工作流程成功,则会忽略它们。 如果发生exception,我会执行回滚并向用户显示exception。

这个:

  • 保持数据的完整性
  • 允许编码更容易

典型function如下:

 returntype func(blah-blah-blah, Transaction tr) { // IO example Stream s = null; try { s = new FileStream(filename); tr.AddRollback(File.Delete(filename)); } finally { if (s != null) s.close(); } } 

典型用法是:

 Transaction tr = new Transaction(); try { DoAction1(blah1, tr); DoAction2(blah2, tr); //... } catch (Exception ex) { tr.ExecuteRollbacks(); // queue the exception message to the user along with a command to repeat all the actions above } 

这在现实世界中有点棘手,因为

  • 有时需要在执行操作之前获取一堆锁
  • 回滚代码本身应该是exception – 静默(例如)

但我已经习惯了这种方法,现在我的应用程序更加稳定。

当遇到不太可能的错误情况时,不要只使用RuntimeException整个应用程序RuntimeException坦克。 应该为程序员错误保留RuntimeException ,并且IOException很可能不是由程序员错误引起的。

相反,将较低级别的exception封装在更高级别的exception中并重新抛出。 然后在调用链上处理更高级别的exception。

例如:

 class SomeClass { public void doActions() { try { someAction(); } catch (HigherLevelException e) { notifyUser(); } someOtherUnrelatedAction(); } public void someAction() throws HigherLevelException { try { // user lower-level abstraction to do our bidding } catch(LowerLevelException e) { throw new HigherLevelException(e); } } public void someOtherUnrelatedAction() { // does stuff } } 

抛出exception的调用堆栈很可能是在您的应用程序中执行某些任务。 而不是使用RuntimeException强制崩溃整个应用程序,而不是在该任务期间发生问题时要做什么。 例如,您是否尝试保存文件? 不要崩溃,而是通知用户存在问题。