C# 如何区分意外(愚蠢的)异常和预期(外部的)异常?
在本文中,我使用@Eric Lippert对异常进行分类,您可以在这里找到: 在这种情况下,最重要的是: 愚蠢的异常是您自己的错误,您本可以阻止它们,因此它们是代码中的错误。你不应该抓住他们;这样做就是在代码中隐藏一个bug。相反,您应该编写代码,这样异常就不可能首先发生,因此不需要捕获 外部异常似乎有点像令人烦恼的异常,只是它们不是不幸设计选择的结果。相反,它们是不整洁的外部现实对您美丽、清晰的程序逻辑造成冲击的结果。始终处理指示意外外部条件的异常;一般来说,预测每一次可能的失败都是不值得或不实际的。只需尝试该操作并准备好处理异常 像每个可能经历过的开发人员一样,在大型企业软件中不可能100%地避免愚蠢的异常 在抛出骨头异常的不幸情况下,我想通知用户,以便他向我们报告错误(第三级支持)。此外,在这种情况下,我想记录一条日志级别为“Error”的消息 对于外部异常我想向用户显示一条更具体的消息和一些提示,因为他可能自己解决问题(可能需要一级或二级支持的帮助) 我目前实现这一点的方法是,在低级别组件中只显式捕获外部异常,并将它们包装到自定义异常中。在顶层(在我的例子中是MVVM WPF应用程序的ViewModel),我 显式捕获自定义异常以显示警告。在第二个catch块中,我捕获常规异常以显示错误 在企业应用程序中,这是区分无头异常和外部异常的常见和良好实践吗?有更好的方法吗?或者根本没有必要吗? 阅读本文后,我还想知道,我是否应该将所有(也是骨头)异常包装到自定义异常中,以便在记录它们时提供更多上下文信息? 关于包装我在以下帖子中发现的所有异常:和 这可能会引起相当大的争议,并且取决于用例,所以我不确定我的情况 ViewModel中的高级捕获处理程序示例:C# 如何区分意外(愚蠢的)异常和预期(外部的)异常?,c#,exception,architecture,design-guidelines,C#,Exception,Architecture,Design Guidelines,在本文中,我使用@Eric Lippert对异常进行分类,您可以在这里找到: 在这种情况下,最重要的是: 愚蠢的异常是您自己的错误,您本可以阻止它们,因此它们是代码中的错误。你不应该抓住他们;这样做就是在代码中隐藏一个bug。相反,您应该编写代码,这样异常就不可能首先发生,因此不需要捕获 外部异常似乎有点像令人烦恼的异常,只是它们不是不幸设计选择的结果。相反,它们是不整洁的外部现实对您美丽、清晰的程序逻辑造成冲击的结果。始终处理指示意外外部条件的异常;一般来说,预测每一次可能的失败都是不值得或
public class MainWindowViewModel
{
private readonly ICustomerRepository _customerRepository;
public MainWindowViewModel(ICustomerRepository customerRepository)
{
_customerRepository = customerRepository;
PromoteCustomerCommand = new DelegateCommand(PromoteCustomer);
}
public ICommand PromoteCustomerCommand { get; }
private void PromoteCustomer()
{
try
{
Customer customer = _customerRepository.GetById(1);
customer.Promote();
}
catch (DataStoreLoadException ex)
{
// A expected exogenous exception. Show a localized message with some hints and log as warning.
Log(LogLevel.Warning, ex);
ShowMessage("Unable to promote customer. It could not be loaded. Try to...", ex);
}
catch (Exception ex)
{
// A unexpected boneheaded exception. Show a localized message, so that the users contacts the support and log as error.
Log(LogLevel.Error, ex);
ShowMessage("Unable to promote customer because of an unknown error. Please contact support@example.com", ex);
}
}
}
低级异常包装的示例:
public class SqlCustomerRepository : ICustomerRepository
{
public Customer GetById(long id)
{
try
{
return GetFromDatabase(id);
}
catch (SqlException ex)
{
// Wrap the exogenous SqlException in a custom exception. The caller of ICustomerRepository should not depend on any implementation details, like that the data is stored in a SQL database.
throw new DataStoreLoadException($"Unable to get the customer with id {id} from SQL database.", ex);
}
// All other exceptions bubble up the stack without beeing wrapped. Is it a good idea, or should I do something like this to provide additional context? (Like the id in this case)
/*catch (Exception ex)
{
throw new DataStoreException($"Unknown error while loading customer with id {id} from SQL database.", ex);
}*/
}
}
尽管我们的代码中没有如此精确的分类,但我们的异常处理常常隐含地表明我们是否认为某个特定的异常是可能的(外生的),或者我们只是在解释可能的错误 以Eric为例,如果我们访问一个文件,将其放入一个
try/catch
,并显式捕获FileNotFoundException
,这应该表明我们意识到FileNotFoundException
是一个可能的结果,即使我们在几毫秒之前检查它是否存在
另一方面,如果我们的代码包括:
try
{
// do some stuff
}
catch(Exception ex)
{
// maybe log it
}
…这表明我们正在考虑boneheaded异常,这可能发生在try
中执行的代码中的任何地方
(某种程度上)它们的区别在于,一个表示我们意识到这是可能的,并对其进行了解释,而另一个表示,“希望这里没有任何问题。”
即使是这种区别也不是很清楚。我们的文件访问代码可能位于“vague”try/catch(Exception ex)
块中。我们知道,由于竞争条件的原因,该文件可能不存在。在这种情况下,我们将让模糊的异常处理捕获它。这可能取决于需要发生什么。如果我们删除了这个文件,结果发现它不存在,我们不需要做任何事情。如果我们需要阅读它,但现在它不见了,那只是个例外。如果结果与任何其他异常的结果相同,那么捕获该特定异常可能对我们没有任何好处
类似地,仅仅因为我们显式地捕获异常并不能保证它不是“愚蠢的”。也许我做错了什么,有时我的代码抛出ObjectDisposedException
。我不知道它为什么会这样做,所以我添加了catch(ObjectExposedException ex)
。乍一看,我可能知道代码中发生了什么,但我真的不知道。我应该找出问题并解决它,而不是在不知道为什么会发生异常的情况下捕获异常。如果应用程序偶尔不工作,我不知道为什么,那么我捕获异常的事实充其量是无用的,或者最坏是有害的,因为它隐藏了真正发生的事情
这并不是说我们应该在每个方法中添加
try/catch
语句来捕获“bonehead”异常。这只是一个异常处理的例子,它解释了异常的可能性,而异常可能是错误的,也可能不是错误的。在每种方法中都这样做通常是没有用的。我们可能会在边缘放置足够的位置,以确保抛出的任何异常至少都会被记录下来
至于在新异常中捕获和包装异常,通常归结为您计划如何处理您正在创建的额外信息。答案往往是什么都没有 我们可以在应用程序的某一层抛出各种巧妙包装的自定义异常。然后另一层调用它并执行以下操作:
try
{
_otherLayer.DoSomeStuff();
}
catch(Exception ex)
{
_logger.Log(ex);
}
我们如何处理我们奇特的定制例外?我们只是把它记录下来,就像我们没有包装它一样。当我们看到
throw new DataStoreLoadException($"Unable to get the customer with id {id} from SQL database.", ex);
throw new Exception($"Unable to get the customer with id {id} from SQL database.", ex);