[英]What should I throw / expect to be thrown?
我已经完全厌倦了这段时间编写的所有错误代码,因此看起来我真的需要了解使用异常的正确方法。
让我们考虑以下示例:
interface IDataSource
{
string ReadData(int offset);
}
class FileDataSource : IDataSource {...}
class DatabaseDataSource : IDataSource {...}
如果我使用的是实现IDataSource的类的对象,如果由于某种原因ReadData()失败,我可以期望捕获哪些异常?
FileDataSource可能会因为没有文件或文件小于偏移量而失败。 DatabaseDataSource可能会失败,因为它无法连接到数据库,或者该数据库中没有必需的表。
当我这样做时:
var data = dataSource.ReadData(10);
我应该抓住什么? 另一方面,如果我要实现一个新的类FakeDataSource
,如果发生不好的情况该怎么办?
我有一种感觉,当我从FileDataSource
抛出FileNotFoundException
或从DatabaseDataSource
SqlException
,这意味着封装违规,因为我知道实现细节。
是否应该将任何IDataSource
抛出的所有异常都绑定到此接口? 我的意思是-当我定义任何新的抽象时,是否还应该定义相关的异常? 像这样:
interface IDataSource { ... }
abstract class DataSourceException : Exception { ... }
因此,当某人决定实现IDataSource
,他应该只抛出DataSourceException
。 那是对的吗? offset
值的错误如何处理-我还是应该抛出DataSourceException
或标准(.NET) ArgumentOutOfRangeException
正常吗?
也将感谢您认为值得的有关错误处理的文章的任何链接。
您应该只捕获可以处理的异常。 如果您的IDataSource抛出FileNotFoundException或SqlException并且您无法处理它,请不要捕获它。
引发异常时,应引发最能描述问题的异常类型。 您想从抛出的异常中了解三件事 。
- 什么地方出了错?
- 哪里出错了?
- 为什么会出错?
当有效地使用异常时,由抛出的异常类型回答什么,由异常堆栈跟踪回答哪里,由异常消息回答为什么。 如果您发现自己的例外没有回答所有三个问题,则很可能是它们没有得到有效利用。 调试程序时,三个规则将帮助您充分利用异常。 这些规则是:具体,早扔和迟到。
在大多数情况下,使用通用定义的异常,而不是创建自己的异常。 人们将不加解释地熟悉它们并理解其含义。 但是,在需要封装特定模块的详细信息的情况下,创建自己的异常类型可能会有所帮助。 采用这种方法时,请将原始异常包装在模块的异常中,以使信息在重新抛出时不会丢失。
仅在您的库/应用程序无法执行被要求执行的操作且需要帮助确定是否以及如何恢复的例外情况下,才应引发异常。
您的例外情况应能准确说明发生了什么。 它们应具有足够的通用性,以通过指出问题确切内容的消息来指示顶级问题。 根据不同类型的操作细分异常类别。
如果在建立连接时遇到任何麻烦,请引发ConnectionException,然后使用该消息指定引发异常的原因。 如果您无法连接到数据库,则该消息应指示连接。 用户名不正确? 密码错误? 让来电者知道。 等等。
如果您的查询存在问题,例如您要查询的表不存在,则抛出QueryException。
将DataSource实现中的每个异常都限制为DataSourceException几乎和抛出Exception类型的抛出每个异常一样糟糕。 你不会那样做,对吗?
我也不会打扰将异常包装在DataSourceException中。 我认为这给您的实现增加了不必要的复杂性。 它还将使可读性稍微复杂一点,因为您将拥有一堆看起来像这样的代码:
new DataSourceException(new ArgumentException("Range is invalid"), "Range is invalid");
或这个:
try {
...
} catch (Exception e) {
throw new DataSourceException(e, e.Message);
}
然后,对于所有消耗实现的类,您必须捕获异常,然后查看内部异常以检查并查看是否要优雅地处理它。
那有意义吗?
为什么要在该级别上捕捉任何东西?
您应该只因为一个原因而捕获:您知道如何处理异常。
有人说如果您想登录就可以捕获,但是在这种情况下应该重新抛出异常。 但这意味着到处重复相同的日志行-更好的方法是在主循环中记录一次。 无论如何,该异常都包含堆栈跟踪。
捕获并重新抛出DataSourceException
是一种典型的Java解决方案,其目的只有一个:满足Java编译器的要求。 毕竟,当您要处理原因时,更有用的是FileNotFoundException
或DataSourceException
吗?
所以我的回答是: 除非您知道如何处理异常,否则不要抓住!
我不认为有任何特定的异常抛出规则。 它可能是更多选择,对程序员来说非常主观。 话虽如此,您可以有不同的选择。 就像您提到的那样,您可以引发内置异常,也可以从属于某个组的类(例如实现接口)引发自定义异常。
在这种情况下,您最好抛出自己的自定义异常,因为您确切知道异常的来源以及如何处理它。 (没有通过接口模型强制执行此操作的实际方法。)再次,这取决于您在异常冒泡时对异常的处理方式。 对此没有硬性规定,但是我们的想法是应该优雅地处理异常。 我怀疑您正在编写错误的代码。 发生异常的原因有很多。 数据库连接失败不在您手中。 在大多数情况下,数据库关闭时,应用程序可能无法运行。 除了捕获异常,记录日志,通知所需组并正常失败之外,您无能为力。 错误的应用程序将向用户显示服务器信息,而不会通知任何人,因此客户将不得不致电对其进行修复。
PS:我不认为抛出从filedatasource(或sqlds中对应的文件)中找不到的文件不违反封装。 由于该类是特定用途的类,因此应该知道实现细节。
您应该抛出任何描述发生了什么以及操作失败的原因的信息。 您应该只期望可以真正处理的异常-绝不要仅仅为了捕获异常就捕获异常。
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.