代码之家  ›  专栏  ›  技术社区  ›  Ethan Heilman

我如何处理一个IOException,我知道它永远不会被抛出,以一种安全和可读的方式?

  •  18
  • Ethan Heilman  · 技术社区  · 17 年前

    “事物之间的主要区别 可能会出错 不可能出错的是 不可能出错的事情 出错了,通常是 不可能到达或修理。” - Douglas Adams

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

    事实:

    1. 已检查该文件是否存在,但极不可能存在这样的可能性:由于某些重大的现实错误,在调用此方法之前可能会删除该文件。
    2. 由于发生1的概率非常不同并且不可恢复,所以我宁愿定义一个未检查的异常。
    3. 文件已经被发现存在,迫使其他程序员编写代码并捕获选中的fileNotFoundException,这看起来既单调又无用。程序应该在那一点上完全失败。例如,计算机总是有可能 catch fire, but no one is insane enough to force other programmers to handle that as a checked exception .
    4. 我经常遇到这种异常问题,每次遇到这个问题(我以前的解决方案)时定义自定义未检查的异常都是令人厌烦的,并且会增加代码膨胀。

    当前代码如下

     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?

    8 回复  |  直到 12 年前
        1
  •  49
  •   Henning    17 年前

    通常的处理方式是 exception chaining . 只需将fileNotFoundException包装在RuntimeException中:

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

    这种模式不仅适用于特定情况下无法发生异常的情况(如您的情况),而且也适用于您没有办法或意图真正处理异常的情况(如数据库链接失败)。

    编辑 :要小心这种类似的反模式,我在野外经常看到这种反模式:

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

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

    另一个编辑: 正如托尔比安徒生在他的回答中正确指出的那样,在评论中或者更好的是,作为例外信息,说明你为什么要将例外情况链接起来并不伤人:

    catch(FileNotFoundException e) {
        throw new RuntimeException(
            "This should never happen, I know this file exists", e);
    }
    
        2
  •  11
  •   Emil H    17 年前

    通过将选中的异常嵌套在RuntimeException中,可以将其转换为未选中的异常。如果异常在堆栈中被捕获得更高,并使用printstacktrace()输出,那么原始异常的堆栈也将显示出来。

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

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

        3
  •  10
  •   Thorbjørn Ravn Andersen    17 年前

    考虑使用表单

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

    相反。这允许您向维护人员传达要遵循的含义,无论是在读取代码时还是在某些奇怪的场景中引发异常时。

    编辑:这也是一种通过机制传递异常的好方法,该机制不需要这些异常。例如,如果您有“从数据库中获取更多行”迭代器,迭代器接口不允许抛出,例如,fileNotFoundException,这样您就可以像这样包装它。在使用迭代器的代码中,然后可以捕获RuntimeException,并使用getcause()检查原始异常。在通过遗留代码路径时非常有用。

        4
  •  7
  •   Yishai    17 年前

    如果您的立场是,这是如此不可能的,应该只是结束程序,使用现有的运行时异常,甚至运行时异常本身(如果不是IllegalstateException)。

    try {
      ....
    
    } catch (FileNotFoundException e) {
        throw new RuntimeException(e);
    }
    
        5
  •  1
  •   matt b    17 年前

    您最好还是抛出超类和更一般的异常 IOException 在代码中涉及从文件中读或写的任何点上。

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

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

    等。

    我不想重新发明轮子,我想说只要把IOException重新扔到JDK的任何地方/ java.io 你正在使用的类强迫你这样做。

    还有一个讨厌从构造函数中抛出异常的类——如果我是你,我会去掉这些异常。

        6
  •  1
  •   branchgabriel    17 年前

    我在谷歌上搜索了一下,找到了这个代码。我觉得这种方法比较灵活

    对这个的赞美 article

    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"
        });
      }
    } ///:~
    
        7
  •  1
  •   bohdan_trotsenko    17 年前

    我也遇到过同样的问题。

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

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

    我选择的方法是创建一个“回滚”操作列表。如果工作流成功,则忽略它们。如果发生异常,我将执行回滚并向用户呈现异常。

    这是:

    • 保持数据的完整性
    • 使编码更加容易

    典型功能如下:

    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
    }
    

    这是一个 小的 在现实世界中有点棘手,因为

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

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

        8
  •  1
  •   Christopher Perry tegbird    12 年前

    不要只是用 RuntimeException 当您遇到不太可能出现的错误情况时。 运行期异常 应为程序员错误保留,并且 IOException 很可能不是由程序错误引起的。

    相反,将较低级别的异常封装在较高级别的异常中,然后重新引发。然后在调用链上处理更高级别的异常。

    例如:

    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
      }
    }
    

    很可能引发异常的调用堆栈正在应用程序中执行某些任务。而不是用 运行期异常 找出在任务期间出现问题时要做什么。例如,您试图保存一个文件吗?不要崩溃,而是通知用户有问题。