Сбой рано против надежных методов
Я постоянно (с годами) задаюсь вопросом, какой самый разумный способ реализовать следующее (для меня это несколько парадоксально):
Представьте себе функцию:
DoSomethingWith(value)
{
if (value == null) { // Robust: Check parameter(s) first
throw new ArgumentNullException(value);
}
// Some code ...
}
Это называется как:
SomeFunction()
{
if (value == null) { // Fail early
InformUser();
return;
}
DoSomethingWith(value);
}
Но, чтобы поймать ArgumentNullException, я должен сделать:
SomeFunction()
{
if (value == null) { // Fail early
InformUser();
return;
}
try { // If throwing an Exception, why not *not* check for it (even if you checked already)?
DoSomethingWith(value);
} catch (ArgumentNullException) {
InformUser();
return;
}
}
или просто:
SomeFunction()
{
try { // No fail early anymore IMHO, because you could fail before calling DoSomethingWith(value)
DoSomethingWith(value);
} catch (ArgumentNullException) {
InformUser();
return;
}
}
?
1 ответ
Это очень общий вопрос, и правильное решение зависит от конкретного кода и архитектуры.
Вообще в отношении обработки ошибок
Основное внимание должно быть уделено исключению на уровне, где вы можете его обработать.
Обработка исключений в нужном месте делает код устойчивым, поэтому исключение не приводит к сбою приложения, и исключение может быть обработано соответствующим образом.
Ранний сбой делает приложение надежным, поскольку это помогает избежать противоречивых состояний.
Это также означает, что должен быть более общий блок try catch в корне выполнения, чтобы перехватить любую нефатальную ошибку приложения, которая не может быть обработана. Что часто означает, что вы как программист не думали об этом источнике ошибок. Позже вы можете расширить свой код, чтобы также обработать эту ошибку. Но корень выполнения не должен быть единственным местом, где вы думаете об обработке исключений.
Ваш пример
В вашем примере относительно ArgumentNullException:
- Да, ты должен потерпеть неудачу рано. Всякий раз, когда ваш метод вызывается с недопустимым нулевым аргументом, вы должны выбросить это исключение.
- Но вы никогда не должны ловить это исключение, потому что должно быть возможно избежать этого. См. Этот пост, связанный с темой: Если перехват исключения нулевого указателя не является хорошей практикой, является ли перехват исключения хорошей?
- Если вы работаете с пользовательским вводом или вводом из других систем, то вам следует проверить ввод. Например, вы можете отобразить ошибку проверки в пользовательском интерфейсе после нулевой проверки без исключения. Как всегда показывать проблемы пользователям, это очень важная часть обработки ошибок, поэтому определите правильную стратегию для вашего приложения. Вам следует избегать исключения в ожидаемом потоке выполнения программы. Смотрите эту статью: https://msdn.microsoft.com/en-us/library/ms173163.aspx
Смотрите общие мысли об обработке исключений ниже:
Обработка исключений в вашем методе
Если в методе DoSomethingWith выдается исключение, и вы можете обработать его и продолжить выполнение без каких-либо проблем, то, конечно, вы должны это сделать.
Это пример псевдокода для повторения операции с базой данных:
void DoSomethingAndRetry(value)
{
try
{
SaveToDatabase(value);
}
catch(DeadlockException ex)
{
//deadlock happened, we are retrying the SQL statement
SaveToDatabase(value);
}
}
Позвольте исключению всплыть
Давайте предположим, что ваш метод публичный. Если происходит исключение, которое подразумевает, что метод завершился ошибкой, и вы не можете продолжить выполнение, тогда исключение должно всплыть, чтобы вызывающий код мог обработать его соответствующим образом. От варианта использования зависит, как вызывающий код отреагирует на исключение.
Прежде чем разрешить всплытие исключения, вы можете поместить его в другое исключение, специфичное для приложения, как внутреннее исключение, чтобы добавить дополнительную контекстную информацию. Вы также можете каким-то образом обработать исключение, например, зарегистрировать его или оставить запись для вызывающего кода, в зависимости от вашей архитектуры ведения журнала.
public bool SaveSomething(value)
{
try
{
SaveToFile(value);
}
catch(FileNotFoundException ex)
{
//process exception if needed, E.g. log it
ProcessException(ex);
//you may want to wrap this exception into another one to add context info
throw WrapIntoNewExceptionWithSomeDetails(ex);
}
}
Документирование возможных исключений
В.NET также полезно определить, какие исключения генерирует ваш метод, и причины, по которым он может его генерировать. Так что вызывающий код может принять это во внимание. См. https://msdn.microsoft.com/en-us/library/w1htk11d.aspx
Пример:
/// <exception cref="System.Exception">Thrown when something happens..</exception>
DoSomethingWith(value)
{
...
}
Игнорирование исключений
Для методов, где у вас все в порядке с ошибочным методом и вы не хотите постоянно добавлять блок try catch, вы можете создать метод с похожей сигнатурой:
public bool TryDoSomethingWith(value)
{
try
{
DoSomethingWith(value);
return true;
}
catch(Exception ex)
{
//process exception if needed, e.g. log it
ProcessException(ex);
return false;
}
}