从概念上讲,我一直在决定我的项目的异常处理结构.
假设你有一个例子:
public abstract class Data { public abstract String read(); }
还有两个子类FileData,它从一些指定的文件读取你的数据,而StaticData,它只返回一些预定义的常量数据.
现在,在读取文件时,可能会在FileData中抛出IOException,但StaticData将永远不会抛出.大多数样式指南建议将Exception传播到调用堆栈,直到有足够的上下文可用于有效处理它.
但我真的不想在抽象的read()方法中添加throws子句.为什么?因为Data和使用它的复杂机器对文件一无所知,所以它只知道数据.此外,可能还有其他数据子类(以及更多这些子类)从不抛出异常并完美地传递数据.
另一方面,IOException是必要的,因为如果磁盘不可读(或某些此类),则必须抛出错误.所以我看到的唯一出路就是捕获IOException并在其位置抛出一些RuntimeException.
这是正确的哲学吗?
你是对的.
异常应该在使用的相同抽象级别.这就是为什么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分钟内显示重试消息),那么应该使用检查异常.
如果异常失控程序员,但可以完成NOTHING,则可以使用RuntimeException.例如,您应该写入文件,但文件已被删除,您无法重新创建它或重新尝试该程序应该失败(您无法做任何事情),最有可能是运行时.
请参阅Effective Java中的这两项:
对可恢复条件使用已检查的异常,对编程错误使用运行时异常
抛出适合抽象的异常
我希望这有帮助.
如果你没有明确说明read()
可以抛出异常,那么当它发生时你会给开发人员带来惊喜.
在您的特定情况下,我会捕获基础异常并将它们重新抛出为新的异常类DataException
或DataReadException
.