В этом посте я использую категоризацию исключений @Eric Lippert, которую вы можете найти здесь: Обидные исключения
Самые важные в этом случае:
Кропотливые исключения - это ваша чертова ошибка, вы могли их предотвратить, и поэтому они являются ошибками в вашем коде. Вы не должны их ловить; это скрывает ошибку в вашем коде. Скорее, вы должны написать свой код так, чтобы исключение не могло произойти в первую очередь, и, следовательно, его не нужно было перехватывать.
Экзогенные исключения выглядят чем-то вроде неприятных исключений, за исключением того, что они не являются результатом неудачного выбора дизайна. Скорее, они являются результатом неопрятной внешней реальности, посягающей на вашу красивую и четкую логику программы. Всегда обрабатывайте исключения, указывающие на неожиданные экзогенные условия; как правило, нецелесообразно и нецелесообразно предвидеть все возможные отказы. Просто попробуйте операцию и будьте готовы обработать исключение.
Как, наверное, испытал каждый разработчик, в программном обеспечении для крупного предприятия невозможно избежать упрямых исключений на 100%.
В неудачной ситуации, когда возникает тупое исключение, я хочу проинформировать пользователя, чтобы он сообщил нам об ошибке (поддержка третьего уровня). Кроме того, в этом случае я хочу записать сообщение с уровнем журнала «Ошибка».
Для экзогенных исключений я хочу показать пользователю более конкретное сообщение с некоторыми подсказками, потому что он, вероятно, сможет решить проблему сам (возможно, с помощью поддержки первого или второго уровня)
В настоящее время я добиваюсь этого путем явного перехвата ТОЛЬКО экзогенных исключений в компонентах низкого уровня и их упаковки в пользовательские исключения. На верхнем уровне (в моем случае ViewModel приложения MVVM WPF) я явно перехватываю настраиваемое исключение, чтобы показать предупреждение. Во втором блоке catch я перехватываю общие исключения, чтобы показать ошибку.
Является ли это общепринятым и передовым методом различения тупых исключений и экзогенных исключений в корпоративных приложениях? Есть ли лучший подход? Или это совсем не обязательно?
Прочитав эту статью dotnetpro - Implementierungsausnahmen, мне тоже интересно, если я должен обернуть все (также тупоголовые) исключения в пользовательские исключения, чтобы предоставить дополнительную информацию о контексте при их регистрации?
О переносе всех исключений я нашел следующие сообщения: stackoverflow - Должен ли я поймать и обернуть общее исключение ? и stackoverflow - Должен ли я улавливать все возможные конкретные исключения или просто общее исключение и оборачивать его в индивидуальное? Это выглядит довольно спорным и зависит от варианта использования, поэтому я не уверен в моем случае.
Пример обработчика catch высокого уровня в 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 [email protected]", 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);
}*/
}
}