Это плохая практика возвращать исключения из ваших методов - PullRequest
12 голосов
/ 10 июня 2009

Множество методов кода, написанных моими коллегами, выполняют свою собственную обработку ошибок, обычно путем перехвата, уведомления и ведения журнала.

В этих случаях методы возвращают логическое значение, указывающее успех или неудачу.

Иногда, если метод дает сбой, я хочу, чтобы вызывающий код знал, почему, и возврата логического значения недостаточно.

Один из способов обойти это - оставить обработку ошибок в методах, но сделать метод недействительным и заставить методы выдавать свои исключения.

Однако в последнее время у меня появилась привычка возвращать исключения в соответствующих методах, обычно в методах действия, которые в противном случае могли бы быть недействительными, но где я хотел бы узнать статус действия.

Преимущества возврата исключения по сравнению с наличием метода void, создающего исключение, заключаются в том, что я думаю, что это упрощает использование вашей библиотеки другими программистами, и, что еще более важно, это означает, что вы можете безопасно вызывать этот метод без беспокоиться о том, чтобы поймать исключение

Например, если метод просто void, может быть не сразу очевидно, что программист должен обработать успех или неудачу.

Но если в методах конкретно указывается тип возвращаемого значения Exception, то вы знаете, что при желании можете проверить успех или неудачу. Но это также означает, что вам не нужно беспокоиться об обнаружении ошибки, если вы этого не хотите.

Имеет ли это смысл? Может быть, я не использовал лучший пример, , но в целом нормально возвращать исключения или существует лучший шаблон?

UPDATE

Ух ты, подавляющий результат никак . Я так и думал. Я должен сказать, что выполнение (возвращение исключения) своего рода решило проблему, но это было неправильно.

Итак, из некоторых ваших ответов лучшим решением для этих конкретных сценариев (сложный класс или класс с одной или несколькими внешними зависимостями (например, веб-служба)), по-видимому, является пользовательский класс результатов?

UPDATE:

Я действительно ценю все мнения, я читаю все и тщательно обдумываю все входные данные.

В настоящее время я предпочитаю использовать метод void, выбрасывать исключения, а затем ловить их снаружи .... это лучше?

Ответы [ 16 ]

32 голосов
/ 10 июня 2009

Если вы имеете в виду что-то вроде ...

public Exception MyMethod( string foo )
{
   if( String.IsNullOrEmpty() )
   {
      return new ArgumentNullException( "foo" );
   }
}

... а не ...

public void MyMethod( string foo )
{
   if( String.IsNullOrEmpty() )
   {
      throw new ArgumentNullException( "foo" )
   }
}

Тогда абсолютно нет, делать это нехорошо. Вы бы полностью заново изобрели цель исключения и использовали бы его как результат. Вот некоторые стандартные лучшие практики .

Еще одна веская причина этого заключается в том, что стандартные делегаты больше не будут соответствовать сигнатуре вашего метода. Так, например, вы больше не можете использовать Action<string> на MyMethod и вам нужно будет использовать Func<string,Exception> вместо этого.

@ Энди, за комментарий, отвечай слишком долго для комментария: Не совсем. Не беспокойтесь о звонящем. В любом случае, он должен защищать свой замысел. Подумайте о семантике исключения ... «Выполнение этого приложения остановится прямо здесь, если кто-то не знает, что делать с этой проблемой». Если вы можете решить проблему, вы должны решить ее. Если вы не можете, вы должны зарегистрировать его и выбросить на следующий уровень, потому что они могут точно знать, что делать.

Вы должны справиться с тем, что вы можете справиться, и бросить то, что вы не можете. По определению, парень из стека вызовов имеет более широкое представление о мире, чем вы. Ваше приложение должно быть устойчивым, чтобы справляться с исключениями и продолжать работу. Единственный способ сделать это - защитное кодирование и поднять проблемы на более высокий уровень информации, чтобы увидеть, можно ли что-нибудь сделать.

В конце дня, если ответ «нет», то запишите проблему, чтобы понять ее, будьте добропорядочным гражданином коробки и изящно завершите заявку и доживите до следующего дня. Не будьте эгоистичны и постарайтесь скрыть ошибки вашего абонента. Будь оборонительным, и он сделает то же самое. :)

Ознакомьтесь с блоком обработки исключений Enterprise Library . Им кажется, что они действительно формулируют великолепное видение того, как обращаться с исключениями в вашей архитектуре.

13 голосов
/ 10 июня 2009

Никто другой не сказал этого: я понимаю, что в .NET трассировка стека исключений не заполняется, пока исключение не будет брошено . См. Рекомендации по обработке исключений , где написано:

  • Трассировка стека начинается в операторе, в котором генерируется исключение, и заканчивается в операторе catch, который перехватывает исключение. Помните об этом факте, когда решаете, где разместить оператор throw.

  • Использовать методы построителя исключений. Обычно класс создает одно и то же исключение в разных местах своей реализации. Чтобы избежать чрезмерного кода, используйте вспомогательные методы, которые создают исключение и возвращают его.

В .NET, если вы просто создадите экземпляр Exception и вернете его, у вас не будет никакой информации о стеке в этом Exception. Это полезно, когда вы используете эту функцию для создания метода Exception Builder, и вы теряете, когда вы используете исключения, как вы спрашиваете. Для сравнения, в Java информация о стеке добавляется при создании экземпляра Exception, независимо от того, было ли оно выброшено или нет.

Таким образом, в .NET, когда вы возвращаете исключение, которое никогда не генерировалось, вы отказываетесь от самой трассировки стека, которая сообщает вам, где возникла проблема. Возможно, вам все равно, в данном случае, но я подумал, что на это стоит обратить внимание.

Снижение производительности при возникновении исключительной ситуации достаточно мало в любой современной JVM и во всех последних версиях .NET. Возврат исключения вместо его выброса - по соображениям производительности - является неуместной оптимизацией ... , если вы не профилировали свою программу и не обнаружили, что конкретный бросок действительно является узким местом и что возврат исключения имеет существенное значение. (Что удивило бы меня, но все возможно.)

Примечание: допустим, вы создали метод, который в противном случае был бы void, который возвращает исключение при ошибке. Это означает, что если вы хотите проверить на наличие ошибок, вы должны проверить по null, чтобы увидеть, была ли ошибка или нет. Как правило, методы, которые возвращают ноль в случае ошибки, вносят вклад в хрупкий код. Например, метод, который возвращает список, должен возвращать пустой список, а не ноль.

Также обратите внимание, что в .NET, когда вы повторно отбрасываете исключение, поскольку стек заполняется в предложении throw, вы обычно теряете исходную трассировку стека. Чтобы избежать этого, используйте голый throw без указания экземпляра Exception. Если вы throw ex, то вы потеряете исходную трассировку стека.

9 голосов
/ 10 июня 2009

Вы никогда не должны возвращать исключения. Вместо этого, если ваша функция достаточно сложна, создайте класс, который является результатом вашей функции. Например, метод GetPatientList возвращает GetPatientListResult. Это может иметь член успеха. Исключения составляют действительно необычные вещи, такие как исчезновение файла в середине операции, неверные или нулевые параметры, установленные для функции, когда база данных закрывается. Кроме того, это нарушает всю модель абстракции - Dog.GoForAWalk должен возвращать DogWalkReport, а не возможное исключение NullDogWalkException.

6 голосов
/ 10 июня 2009

Совершенно определенно неортодоксальное использование исключений. Функция, которая делает исключения мощными, заключается в том, что выбрасываемое исключение может накапливать любое количество уровней в стеке вызовов. Если вы просто вернете его, вы потеряете это свойство (но также и негативное влияние на производительность, связанное с этим).

Я действительно не понимаю, чем это отличается от простого возврата экземпляра класса, который содержит информацию о возможных ошибках. Единственное отличие состоит в том, что возвращение объектов, которые наследуются от класса Exception, может вызвать некоторую путаницу у других разработчиков, которые будут читать ваш код позже.

Так что, хотя нет ничего плохого в том, чтобы возвращать объекты, содержащие информацию об ошибках, я бы предложил использовать для этой цели классы, которые не наследуются от Exception, чтобы избежать путаницы.

6 голосов
/ 10 июня 2009

номер

Исключениями должно быть то, что существует только в исключительных случаях (отсюда и название). Единственный раз, когда я мог видеть, чтобы метод возвращал исключение, был какой-то фабричный метод, целью которого было специально создать исключение, но даже тогда это растягивает вещи.

Если вам нужно вернуть больше информации, чем логическое, создайте пользовательский класс (не производный от исключения), который включает ваш логический + другую информацию.

5 голосов
/ 10 июня 2009

Возвращать исключение в описанном вами сценарии - плохая идея, так как исключения не предназначены для этой цели.

Было бы лучше создать собственную структуру или класс, содержащий необходимую вам информацию, и вместо этого вернуть ее.

public enum MyErrorType
{
    Abc,
    Xyz,
    Efg
};

public struct Result
{
    public bool Success;
    public MyErrorType ErrorType;
};

, а затем метод:

private Result MyMethod()
{
   // code
}
4 голосов
/ 10 июня 2009

Я должен согласиться с тем, что в общем , это действительно плохая идея.

Однако , я могу придумать сценарий или два, где он может иметь смысл.

Например, если вы разрабатываете проект библиотеки классов, вы, возможно, захотите выбросить свои собственные пользовательские исключения, и вам, возможно, захочется выполнить дополнительную работу всякий раз, когда выбрасывается одно из этих исключений (например, телефон дома, если это позволяет пользователь) ,

Чтобы применить это, вы можете использовать фабричный шаблон, такой, что только вы можете создавать их, и вы заставляете создание проходить через одно место (где также живет ваш код "телефонный дом").

В этом случае вы не захотите, чтобы фабрика сама генерировала исключение, потому что это исключило бы отслеживание вашего стека. Вместо этого он вернул бы его к исходному методу throw.

Но подобные ситуации являются скорее исключением ** кашель **, чем правилом. Ни при каких обстоятельствах я бы не хотел, чтобы исключение передавалось через границы библиотеки классов, и ни при каких обстоятельствах я не создавал бы исключение, не будучи уверенным, что оно будет сгенерировано.

2 голосов
/ 10 июня 2009

Я не уверен, вредно ли возвращать исключение, поскольку я делаю это так, чтобы я мог сообщать сообщение об исключении, его тип и связанную информацию в журнал исключений на сервере SQL.

Некоторые «исключения», например, соединение с сервером sql, не удалось сообщить о других исключениях, которые мне не нужны, чтобы перейти на другой уровень; Я могу просто попытаться записать их на сетевой диск вместо сбоя.

Другие типы исключений. Я бы так же как можно скорее проверил, может ли метод, вызвавший функцию, справиться с этой конкретной проблемой без снижения производительности при ее фактическом выбросе

В дополнение к этим методам я включаю логический параметр, который позволяет вызывающей стороне решать, следует ли ему сгенерировать исключение или просто вернуть его.

2 голосов
/ 10 июня 2009

Нет, это совершенно убогий язык (технология, поскольку вы просто указываете .NET).

Я бы также не согласился с тем, что это облегчит использование ваших API другими людьми, и всем, кто изучил и имеет базовые знания по программированию с использованием модели, включающей исключения, будет труднее использовать ваш код.

Что произойдет, если в будущем вашему методу потребуется вернуть значение? Что делать, если существует более одного сценария ошибок? Если вы действительно хотите узнать больше о том, как работает метод, вам нужно создать класс, который будет инкапсулировать как результат, так и любую дополнительную информацию о статусе, которую вы хотите.

1 голос
/ 10 июня 2009

Исключения только для исключительных ситуаций. Оставьте это тому, кто вызвал исключительный случай (звонящий), чтобы решить проблему. Скажите, если у вас есть метод, который делит два числа как

int Div(int x, int y)
{
return x/y;
}

Как бы вы реализовали правильную обработку ошибок для этого метода? Какое значение вы бы вернули к звонку в следующих случаях?

Div(10,0);
or(int.MinValue,-1); //result of int.MinValue == int.MaxValue + 1;

В двух случаях нет «правильного» действия, это зависит от использования метода. Обычно хорошей идеей является прерывание выполнения кода при возникновении непредвиденной ситуации.

Обработка ошибок превышает try{] catch{}. Все, что входит в блок catch, должно принимать разумное решение на основе ошибки. Просто перехват исключения и его возврат не является обработкой ошибок; это скрытие симптомов, и в лучшем случае отладка будет очень сложной и часто кошмарной.

...