在这篇文章中,我使用了@Eric Lippert 的异常分类,你可以在这里找到: Vexing exceptions
在这种情况下最重要的:
愚蠢的异常是您自己的错误,您可以阻止它们,因此它们是您代码中的错误。你不应该抓住它们;这样做会隐藏代码中的错误。相反,您应该编写代码,使异常不可能首先发生,因此不需要被捕获。
外生异常看起来有点像令人烦恼的异常,只是它们不是不幸的设计选择的结果。相反,它们是杂乱的外部现实影响你美丽、清晰的程序逻辑的结果。始终处理表示意外外部条件的异常;一般来说,预测每一个可能的失败是不值得或不切实际的。只需尝试操作并准备处理异常。
就像每个开发人员可能都经历过的那样,在大型企业软件中不可能 100% 避免愚蠢的异常。
在抛出愚蠢异常的不幸情况下,我想通知用户,以便他将错误报告给我们(第三级支持)。另外,在这种情况下,我想记录一条日志级别为“错误”的消息。
对于外生异常,我想向用户显示带有一些提示的更具体的消息,因为他可能自己解决问题(也许在第一级或第二级支持的帮助下)
我目前实现这一点的方法是在低级组件中显式捕获外生异常并将它们包装到自定义异常中。然后在顶层(在我的情况下是 MVVM WPF 应用程序的 ViewModel)中,我显式地捕获自定义异常,以显示警告。在第二个 catch 块中,我捕获一般异常以显示错误。
这是区分企业应用程序中愚蠢的异常和外生异常的常见且良好的做法吗?有更好的方法吗?或者根本没有必要?
在阅读了这篇文章dotnetpro - Implementierungsausnahmen 之后,我还想知道,是否应该将所有(也是愚蠢的)异常包装到自定义异常中,以便在记录它们时提供更多上下文信息?
关于包装我发现以下帖子的所有异常:stackoverflow - 我应该捕获并包装一般异常吗?和stackoverflow - 我应该捕获所有可能的特定异常还是只捕获一般异常并将其包装在自定义异常中? 它似乎很有争议并且取决于用例,所以我不确定我的情况。
ViewModel 中高级捕获处理程序的示例:
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);
}*/
}
}