Этот вопрос предназначен для применения к любому языку программирования ОО, который поддерживает обработку исключений; Я использую C # только в иллюстративных целях.
Исключения обычно предназначены для возникновения, когда возникает проблема, что код не может быть обработан немедленно, а затем для перехвата в catch
предложении в другом месте (обычно это кадр внешнего стека).
Вопрос: Существуют ли допустимые ситуации, когда исключения не генерируются и перехватываются, а просто возвращаются из метода, а затем передаются как объекты ошибок?
Этот вопрос возник у меня, потому что System.IObserver<T>.OnError
метод .NET 4 предполагает именно это: исключения передаются как объекты ошибок.
Давайте посмотрим на другой сценарий, проверка. Допустим, я следую общепринятому мнению и поэтому я различаю тип объекта ошибки IValidationError
и отдельный тип исключения, ValidationException
который используется для сообщения о непредвиденных ошибках:
partial interface IValidationError { }
abstract partial class ValidationException : System.Exception
{
public abstract IValidationError[] ValidationErrors { get; }
}
(Пространство System.Component.DataAnnotations
имен делает нечто очень похожее.)
Эти типы могут быть использованы следующим образом:
partial interface IFoo { } // an immutable type
partial interface IFooBuilder // mutable counterpart to prepare instances of above type
{
bool IsValid(out IValidationError[] validationErrors); // true if no validation error occurs
IFoo Build(); // throws ValidationException if !IsValid(…)
}
Теперь мне интересно, не мог бы я упростить вышесказанное до этого:
partial class ValidationError : System.Exception { } // = IValidationError + ValidationException
partial interface IFoo { } // (unchanged)
partial interface IFooBuilder
{
bool IsValid(out ValidationError[] validationErrors);
IFoo Build(); // may throw ValidationError or sth. like AggregateException<ValidationError>
}
В: Каковы преимущества и недостатки этих двух разных подходов?
AggregateException
вместо? Хорошая точка зрения.Ответы:
Если его никогда не бросают, то это не исключение. Это объект
derived
из класса Exception, хотя он не соответствует поведению. Называть это Исключением - это чисто семантика на данный момент, но я не вижу ничего плохого в том, чтобы не бросать его. С моей точки зрения, не выбрасывать исключение - это то же самое, что и внутренняя функция, которая его перехватывает и предотвращает распространение.Допустимо ли для функции возвращать объекты исключений?
Абсолютно. Вот краткий список примеров, где это может быть уместно:
Разве это не плохо?
Бросок исключения - это что-то вроде: «Иди в тюрьму. Не сдавайся!» в настольную игру монополия. Он говорит компилятору перепрыгнуть через весь исходный код до улова без выполнения какого-либо из этого исходного кода. Это не имеет ничего общего с ошибками, сообщением или устранением ошибок. Мне нравится думать о создании исключения как оператора «супер возврата» для функции. Он возвращает выполнение кода куда-то намного выше, чем исходный вызывающий объект.
Здесь важно понять, что истинное значение исключений заключается в
try/catch
шаблоне, а не в экземпляре объекта исключения. Объект исключения - это просто сообщение о состоянии.В вашем вопросе вы, кажется, путаете использование этих двух вещей: переход к обработчику исключения и состояние ошибки, которое представляет исключение. То, что вы приняли состояние ошибки и поместили его в исключение, не означает, что вы следуете
try/catch
шаблону или его преимуществам.источник
Exception
класса, но не соответствующий поведению». - И это как раз та проблема: законно ли использоватьException
-приведенный объект в ситуации, когда он вообще не будет брошен ни создателем объекта, ни каким-либо вызывающим методом; где он служит только как «сообщение о состоянии», передаваемое без потока управления «супер возврат»? Или я так сильно нарушаю невысказанныйtry
/catch(Exception)
контракт, что никогда не должен этого делать, а вместо этого использую отдельный, неException
тип?No
вы не нарушаете никаких контрактов. Былоpopular opinion
бы то, что вы не должны этого делать. Программирование полно примеров, когда ваш исходный код не делает ничего плохого, но всем это не нравится. Исходный код всегда должен быть написан так, чтобы было ясно, какова цель. Вы действительно помогаете другому программисту, используя исключение таким образом? Если да, то сделай так. В противном случае не удивляйтесь, если это не нравится.Возврат исключений вместо их выдачи может иметь семантический смысл, когда у вас есть вспомогательный метод для анализа ситуации и возврата соответствующего исключения, которое затем вызывается вызывающим (вы можете назвать это «фабрикой исключений»). Создание исключения в этой функции анализатора ошибок будет означать, что во время самого анализа что-то пошло не так, а возврат исключения означает, что тип ошибки был успешно проанализирован.
Одним из возможных вариантов использования может быть функция, которая превращает коды ответов HTTP в исключения:
Обратите внимание, что выбрасывание исключения означает, что метод использовался неправильно или имел внутреннюю ошибку, а возврат исключения означает, что код ошибки был успешно идентифицирован.
источник
return
операторы, чтобы компилятор прекратил жаловаться.System.Exit()
. Код после вызова функции не выполняется. Это не похоже на хороший шаблон дизайна для функции.Концептуально, если объект исключения является ожидаемым результатом операции, тогда да. Однако случаи, о которых я могу вспомнить, в какой-то момент всегда связаны с броском:
Вариации шаблона «Try» (когда метод инкапсулирует второй метод, генерирующий исключение, но перехватывает исключение и вместо этого возвращает логическое значение, указывающее на успешность). Вместо того, чтобы возвращать логическое значение, вы можете возвратить любое выброшенное исключение (ноль, если оно выполнено успешно), что позволяет конечному пользователю получить больше информации, сохраняя при этом указатель успеха или сбоя без перехвата.
Обработка ошибок рабочего процесса. Практически аналогично инкапсуляции метода «try pattern», вы можете иметь шаг рабочего процесса, абстрагированный в шаблоне цепочки команд. Если ссылка в цепочке выдает исключение, часто проще отловить это исключение в абстрагирующем объекте, а затем вернуть его в результате указанной операции, чтобы механизм рабочего процесса и фактический код, выполняемый как шаг рабочего процесса, не требовали тщательной попытки. -поймать логику самостоятельно.
источник
OperationResult
класс соbool
свойством «Successful»,GetExceptionIfAny
методом иAssertSuccessful
методом (который выдает исключение,GetExceptionIfAny
если оно не равно NULL). НаличиеGetExceptionIfAny
метода, а не свойства позволило бы использовать статические неизменяемые объекты ошибок в тех случаях, когда было не так уж много полезного.Допустим, вы поставили задачу, которую нужно выполнить в пуле потоков. Если эта задача выдает исключение, это другой поток, поэтому вы не можете его перехватить. Нить, где он был исполнен, просто умрет
Теперь учтите, что что-то (либо код в этой задаче, либо реализация пула потоков) перехватывает это исключение, сохраняет его вместе с задачей и считает задачу выполненной (безуспешно). Теперь вы можете спросить, завершена ли задача (и либо спросить, сгенерировала ли она, либо она может быть снова добавлена в ваш поток (или, лучше, новое исключение с исходным в качестве причины)).
Если вы делаете это вручную, вы можете заметить, что вы создаете новое исключение, выбрасываете его и перехватываете, а затем сохраняете его, в другом потоке, получая выбрасывание, перехват и реагирование на него. Так что имеет смысл пропустить бросок и поймать и просто сохранить его и завершить задачу, а затем просто спросить, есть ли исключение и реагировать на него. Но это может привести к более сложному коду, если в одном и том же месте могут быть действительно выданы исключения.
PS: это написано с опытом работы с Java, где информация о трассировке стека создается при создании исключения (в отличие от C #, где она создается при генерировании). Таким образом, подход «не выбрасывать исключения» в Java будет медленнее, чем в C # (если только он не создан и не используется повторно), но будет иметь доступную информацию трассировки стека.
В общем, я бы держался подальше от создания исключений и никогда не выбрасывал их (разве что в качестве оптимизации производительности после профилирования указывалось как узкое место). По крайней мере, в Java, где создание исключения стоит дорого (трассировка стека). В C # это возможно, но IMO это удивительно, и поэтому его следует избегать.
источник
Это зависит от вашего дизайна, обычно я бы не возвращал исключения вызывающей стороне, скорее, я бы бросил и поймал их и оставил это на этом. Как правило, код написан так, чтобы быстро и быстро проваливаться. Например, рассмотрим случай открытия файла и его обработки (это C # PsuedoCode):
В этом случае мы выдали бы ошибку и прекратили обработку файла, как только он обнаружил плохую запись.
Но допустим, что мы хотим продолжить обработку файла, даже если в одной или нескольких строках есть ошибка. Код может начать выглядеть примерно так:
Таким образом, в этом случае мы возвращаем исключение обратно вызывающей стороне, хотя я, вероятно, не вернул бы исключение обратно, а вместо этого использовал какой-либо объект ошибки, поскольку исключение было перехвачено и уже обработано. Это не вредно при возврате исключения, но другие вызывающие могут повторно вызвать исключение, которое может быть нежелательным, так как объект исключения имеет такое поведение. Если вы хотите, чтобы вызывающие абоненты имели возможность перебрасывать, возвращайте исключение, в противном случае извлеките информацию из объекта исключения и создайте меньший облегченный объект и верните его взамен. Быстрый сбой обычно более чистый код.
Для вашего примера проверки я бы, вероятно, не наследовал от класса исключений и не генерировал исключения, потому что ошибки валидации могут быть довольно распространенными. Возвращать объект было бы меньше, чем выдавать исключение, если 50% моих пользователей не смогли правильно заполнить форму с первой попытки.
источник
Да. Например, недавно я столкнулся с ситуацией, когда обнаружил, что сгенерированные исключения в доноте веб-службы ASMX включают элемент в получающееся сообщение SOAP, поэтому мне пришлось сгенерировать его.
Иллюстративный код:
источник
В большинстве языков (afaik) исключения сопровождаются некоторыми дополнительными преимуществами. В основном, снимок текущей трассировки стека хранится в объекте. Это хорошо для отладки, но может иметь последствия для памяти.
Как уже говорилось @ThinkingMedia, вы действительно используете исключение в качестве объекта ошибки.
В вашем примере кода кажется, что вы в основном делаете это для повторного использования кода и во избежание композиции. Лично я не думаю, что это хорошая причина для этого.
Другая возможная причина - обмануть язык, чтобы получить объект ошибки с трассировкой стека. Это дает дополнительную контекстную информацию к вашему коду обработки ошибок.
С другой стороны, мы можем предположить, что хранение трассировки стека требует затрат памяти. Например, если вы начнете собирать эти объекты где-то, вы можете увидеть неприятное влияние на память. Конечно, это во многом зависит от того, как трассировки стека исключений реализованы на соответствующем языке / движке.
Так это хорошая идея?
До сих пор я не видел, чтобы его использовали или рекомендовали. Но это не много значит.
Вопрос в том, действительно ли трассировка стека полезна для обработки ошибок? Или, в целом, какую информацию вы хотите предоставить разработчику или администратору?
Типичный шаблон throw / try / catch делает необходимым иметь трассировку стека, потому что в противном случае вы не знаете, откуда он берется. Для возвращаемого объекта это всегда происходит из вызываемой функции. Трассировка стека может содержать всю информацию, которая нужна разработчику, но, возможно, подойдет что-то менее тяжелое и более конкретное.
источник
Ответ - да. См. Http://google-styleguide.googlecode.com/svn/trunk/cppguide.xml#Exceptions и откройте стрелку для руководства по стилю Google C ++ для исключений. Вы можете видеть там аргументы, против которых они раньше принимали решение, но говорите, что если бы им пришлось сделать это снова, они, вероятно, решили бы.
Однако стоит отметить, что язык Go также не использует идиоматически исключения по аналогичным причинам.
источник