【问题标题】:Java Style: Properly handling exceptionsJava 风格:正确处理异常
【发布时间】:2009-01-08 18:10:37
【问题描述】:

在为我的项目决定异常处理结构时,我一直在概念上陷入困境。

假设你有,例如:

public abstract class Data {
   public abstract String read();
}

还有两个子类 FileData,它从某个指定的文件中读取您的数据,以及 StaticData,它只返回一些预定义的常量数据。

现在,在读取文件时,可能会在 FileData 中抛出 IOException,但 StaticData 永远不会抛出。大多数样式指南建议将异常向上传播到调用堆栈,直到有足够多的上下文可用以有效处理它。

但我并不想在抽象的 read() 方法中添加 throws 子句。为什么?因为数据和使用它的复杂机器对文件一无所知,它只知道数据。此外,可能还有其他 Data 子类(以及更多)从不抛出异常并完美地传递数据。

另一方面,IOException 是必需的,因为如果磁盘不可读(或类似情况),则必须抛出错误。所以我看到的唯一出路是捕获 IOException 并在其位置抛出一些 RuntimeException。

这是正确的哲学吗?

【问题讨论】:

    标签: java exception


    【解决方案1】:

    你是对的。

    异常应该与使用的抽象级别相同。这就是从 java 1.4 Throwable 开始支持异常链接的原因。对于使用数据库的服务或与“存储”无关的服务,没有必要抛出 FileNotFoundException。

    可能是这样的:

    public abstract class Data {
       public abstract String read() throws DataUnavailableException;
    }
    
    class DataFile extends Data { 
        public String read() throws DataUnavailableException {
            if( !this.file.exits() ) {
                throw new DataUnavailableException( "Cannot read from ", file );
             }
    
             try { 
                  ....
             } catch( IOException ioe ) { 
                 throw new DataUnavailableException( ioe );
             } finally {
                  ...
             }
     }
    
    
    class DataMemory extends Data { 
        public String read()  {
            // Everything is performed in memory. No exception expected.
        }
     }
    
     class DataWebService extends Data { 
          public string read() throws DataUnavailableException {
               // connect to some internet service
               try {
                  ...
               } catch( UnknownHostException uhe ) {
                  throw new DataUnavailableException( uhe );
               }
          }
     }
    

    请记住,如果您在编程时考虑到继承,则应针对特定场景进行仔细设计并使用这些场景测试实现。显然,如果编写通用库更难,因为您不知道如何使用它。但大多数时候,应用程序都被限制在一个特定的领域。

    您的新异常应该是运行时还是已检查?视情况而定,一般规则是针对编程错误抛出运行时并检查可恢复的条件。

    如果可以通过正确编程(例如 NullPointerException 或 IndexOutOfBounds )避免异常,请使用 Runtime

    如果异常是由于程序员无法控制的某些外部资源(例如网络中断)并且可以做一些事情(在 5 分钟内显示重试消息或其他内容),则检查异常应该使用。

    如果异常超出了程序员的控制,但无能为力,您可以使用 RuntimeException。例如,您应该写入一个文件,但该文件已被删除,您无法重新创建它或重试,那么程序应该失败(您对此无能为力)很可能是运行时。

    参见 Effective Java 中的这两项:

    • 对可恢复条件使用检查异常,对编程错误使用运行时异常
    • 抛出适合抽象的异常

    我希望这会有所帮助。

    【讨论】:

    • 如果 DataUnavailableException 在您的示例中不是 RuntimeException,则您的示例(特别是 DataMemory)将无法编译。阅读你的笔记让我认为它应该是一个检查异常,因为对于数据不可用可以做一些事情..
    【解决方案2】:

    如果您没有明确声明 read() 可以引发异常,那么当它发生时,您会让开发人员感到惊讶。

    在您的特定情况下,我会捕获底层异常并将它们作为新的异常类 DataExceptionDataReadException 重新抛出。

    【讨论】:

    • 我也会让 DataException 在其中保存 IOException,因此如果需要,处理异常的级别可以向下钻取。
    • Paul,您不需要显式执行此操作 - 从 Java 1.4 开始,您可以使用 initCause(Throwable) 方法或通过实现包含 Throwable 作为参数的构造函数来链接异常。跨度>
    【解决方案3】:

    抛出 IOException 包装在适合“Data”类的异常类型中。事实是read 方法并不总是能够提供数据,它可能应该指出原因。包装异常可能会扩展 RuntimeException,因此不需要声明(尽管应该适当记录)。

    【讨论】:

      【解决方案4】:

      使用运行时异常,并结合顶部的爆炸式捕获所有内容。一开始有点害怕,习惯了就好了。

      在我的 Web 应用程序中,抛出的所有内容都是运行时。几乎没有“抛出”子句,而且我只有在我真正可以(或想要)处理异常的地方有 catchblocks。在最高级别,有一个 catch Throwable,它呈现一个技术错误页面,并写入一个日志条目。

      Log4J 邮件程序向我发送日志条目和它前面的 10 个日志条目。所以当客户来电时,我通常已经知道有问题了。

      通过适当的(单元)测试和简洁的编程,增加的简洁性和可读性超过了随时检查异常的损失。

      【讨论】:

        【解决方案5】:

        应该在抽象的 read() 方法上声明某种异常。抽象类实际上是在声明一个接口 - 您已经从您的两个具体子类中知道,由于异常情况,实现很可能无法成功返回。

        因此在抽象的 Data.read() 方法中声明一些异常是完全正确的。不要试图简单地声明它抛出 IOException,因为它不应该与特定实现绑定(否则你必须声明它也可能抛出 SQLException,以防你决定拥有一个数据库 -读取子类,SAXException 以防您曾经有一个基于 XML 的阅读器(使用 SAX),等等)。您需要自己的自定义异常,以便在抽象级别充分捕获这一点 - 类似于上面推荐的 DataException,或者如果有意义的话,可能会重用同一包中的现有自定义异常。

        【讨论】:

        • Java 是否允许子类中的覆盖方法删除 throws 声明?如果是这样,如果您通过它们的实际类型(而不是通过父类多态)使用它们,则可以使用永远不会抛出的子类而无需处理异常。不知道这是否可行。
        猜你喜欢
        • 2010-12-26
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2016-08-21
        • 2023-03-07
        • 2012-04-08
        • 2013-07-24
        • 1970-01-01
        相关资源
        最近更新 更多