#c_sharp #исключения
Есть унаследованный класс: public class Division : BinaryOperation { protected override decimal Calculate(decimal a, decimal b) { return a / b; } public override string Name { get { return "Деление"; } } } Как внутри него красиво и логично обработать ситуацию деления на ноль? Если обернуть в try-catch, что писать в catch? Допустим, я не знаю какие контролы будут на форме, и вообще, будет ли это использоваться в WinForms или WPF или что-то еще, чтобы куда то выводить текст Exception'а. Возможно есть еще какие-то хорошие варианты кроме try-catch?
Ответы
Ответ 1
Сложность в "элегантной" обработке исключений заключается в том, что она сильно зависит от контекста. Вот, например, в одном приложении, нужно выделить UI контрол и попросить пользователя ввести валидное значение. В другом приложении (если оно серверное), нужно бросить исключение, которое должно пересечь границы приложения тем или иным способом. А в третьем приложении, проверка аргументов происхоидт на более высоком уровне и передача 0 в параметр b является багом и должно привести к крэшу приложения. Все это приводит к следующим возможностям: Добавить предусловие в метод Calculate, что b != 0: Contract.Requires(b != 0). В этом случае мы переклаыдваем ответственность за валидацию кода на вызывающую сторону. Мы говорим, что 0 - это невалидное значение, и просим клиента нас не трогать в этом случае. Ничего не делать и пробрасывать DivideByZeroException. Еще одна альтернатива: задокументировать метод Calculate и "сказать", что из него может прилететь это исключение. В этом случае мы опять же перекладываем ответственность на вызывающий код, который лучше знает, что с ним делать. Теперь клиент может выдать сообщение, отправить нужный ответ клиенту или преобразовать это исключение в какое-то другое. Добавить новый уровень косвенности. Еще, можно добавить специальный тип исключения, например, CalculationException или OperationException. В этом случае, новый тип исключения будет описывать семейство ошибок, которое может произойти при работе с абстрактной операцией. Это решение является более расширяемым, поскольку новые наследники могут бросать новые и новые типы исключений, а конкретный тип исключения может содержать более специфичную информацию: public abstract class OperationException { } public class DivideByZeroOperationException : OperationException {} public class Division : BinaryOperation { protected override decimal Calculate(decimal a, decimal b) { if (b == 0) {throw new DivideByZeroOperationException();} return a / b; } } Теперь клиент может решить, нужно ли ему обрабатывать базовое исключение или же более специфичное. Еще одна альтернатива, вернуться к умным кодам возврата и уйти от исклчюений. Пример: public enum Error { DivideByZero, YetAnotherError } // Это такой себе 'Either' тип, который может содержать либо ошибку (код и, // возможно, сообщение), либо валидный результат public class OperationResult { public decimal? Result {get;} public Error? Error {get;} public static OperationResult Success(decimal result) { } public static OperationResult Failure(Error error) { // Может быть стоит добавить сообщение или другую контекстную инфомрацию } } public class Division : BinaryOperation { protected override OperationResult Calculate(decimal a, decimal b) { if (b == 0) {return OperationResult.Error(Error.DivideByZero);} return a / b; } } Это более "функциональный" подход, в то время, как предыдущие являются более каноническими в ОО языках, таких как C#. Главное заключение: не существует вменяемого способа полностью обработать исключение внутри метода Calculate. Информация об ошибке должна быть передана вызывающему коду, поскольку только на более высоком уровне существует достаточно инфомрации (контекста), чтобы обработать эту проблему полноценным образом.Ответ 2
Судя по всему, это какой-то вычислитель общего вида. В этом вычислителе должна быть общая система ошибок, которую и должен использовать этот конкретный класс операции деления. Использование исключений скорее всего недопустимо по многим причинам. В частности, деление на ноль в данной области это во-первых нормальная ситуация, а во-вторых не означает что надо уничтожить что-нибудь по случаю "ошибки" и убрать кого-то со стека. Скорее всего, общая система обработки ошибок вычислений должна предусматривать явно ошибку или даже список (иерархический) ошибок как возможный результат любой операции. Вариантов дизайна такой схемы куча - можно начать с подсказок из ответа Sergey Teplyakov. И ещё не забудьте, что в вычислителях общего вида деление на ноль не всегда ошибка, а иногда infinity да ещё и positive или negative, или даже NaN!
Комментариев нет:
Отправить комментарий