По вине или не по вине

У меня беседа с коллегой о том, когда выбрасывать ошибки, а когда не выбрасывать ошибки в службе WCF.

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

  • ValidateMember (строковое имя, строковый пароль, строковая страна) -> выдает ошибку, если обязательные параметры не передаются, поскольку сама проверка не может быть выполнена; -> выдаст ошибку, если произойдет какая-то внутренняя ошибка, например, если база данных не работает -> вернет контракт о статусе во всех других случаях, который указывает результат проверки (MemberValidated, WrongPassword, MemberNotKnown,...)

  • GetMember (int memberId) -> выдаст ошибку, только если что-то не так, во всех остальных случаях он вернет член или ноль, если не найден

Другое мнение состоит в том, что мы также должны выдавать ошибки, когда GetMember не находит участника, или в случае ValidateMember пароль неверен.

Как вы думаете?

5 ответов

Решение

Мой взгляд на это...

Есть три причины отказа:

  1. Сервисный код выдал исключение, например, ошибка базы данных, логическая ошибка в вашем коде. Это ваша вина.
  2. Клиентский код не смог правильно использовать ваш сервис в соответствии с вашей документацией, например, он не установил обязательное значение флага, ему не удалось передать идентификатор. Это вина разработчика клиентского программного обеспечения.
  3. Конечный пользователь набрал на экране что-то глупое, например, отсутствие даты рождения, отрицательная зарплата. Это вина конечного пользователя.

Это зависит от вас, как вы решите сопоставить фактические контракты по неисправностям с каждой причиной неисправности. Например, мы делаем это:

  • По причинам 1 и 2 клиентский код должен знать только то, что произошла ошибка службы. Мы определили очень простой контракт ошибки "фатальная ошибка", который содержит только уникальный идентификатор ошибки. Полная информация об ошибке регистрируется на сервере.
  • По причине 3 конечный пользователь должен точно знать, что он / она сделал неправильно. Мы определяем договор об ошибке "Ошибки валидации", содержащий набор дружественных сообщений об ошибках для кода клиента, отображаемого на экране.

Мы заимствуем класс Microsoft EntLib для причины 3 и используем экранирование исключений для декларативной обработки причин 1 и 2. Это делает для очень простого кода.

Чтобы уточнить:

Мы разбираемся с тремя причинами, подобными этим, внутри сервиса:

  1. Неожиданное исключение выдается в сервисном коде. Мы ловим это на верхнем уровне (фактически ловит исключение, но принцип тот же). Записать полную информацию, затем кинуть FaultException<ServiceFault> клиенту, содержащему только идентификатор ошибки.
  2. Мы проверяем входные данные и сознательно выбрасываем исключение. Обычно это ArgumentException, но подойдет любой подходящий тип. После того, как он брошен, он обрабатывается точно так же, как (1), потому что мы хотим, чтобы он выглядел одинаково для клиента.
  3. Мы проверяем входные данные и сознательно выбрасываем исключение. На этот раз это FaultException<ValidationFault>, Мы настраиваем экранирование исключений, чтобы пропустить его через неупакованный файл, чтобы он отображался на клиенте как FaultException<ValidationFault> не FaultException<ServiceFault>,

Конечный результат:

  • Никаких блокировок внутри сервиса (хороший чистый код).
  • Клиенту нужно только поймать FaultException<ValidationFault> если он хочет отображать сообщения для пользователя. Все другие типы исключений, включая FaultException<ServiceFault> обрабатываются глобальным обработчиком ошибок клиента как фатальные ошибки, так как фатальная ошибка в сервисе обычно означает фатальную ошибку и в клиенте.

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

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

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

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

  1. Проверка сообщения - по содержанию сообщения вы можете определить, что данные действительны или недействительны.

    Пример: контент, который должен быть датой рождения - по данным можно узнать, является ли он действительным или нет.

  2. Проверка контекста - вы можете определить, что содержимое недопустимо, по ссылке на сообщение в сочетании с состоянием системы.

    Пример: действительная дата присоединения к компании раньше, чем дата рождения этого лица.

  3. Обманывает систему - вы можете определить, что сообщение было ошибочным, когда более позднее сообщение вызывает аномалию.

    Пример: сохранена действительная дата рождения, и проверка свидетельства о рождении лица показывает, что это неверно. Для исправления лжи в системе обычно требуются действия вне системы, например, применение правовых или дисциплинарных средств правовой защиты.

Ваша система ДОЛЖНА иметь дело со всеми классами ошибок - хотя в случае три это может быть ограничено выдачей предупреждения.

Отказы (исключения), напротив, имеют только одну причину - повреждение данных (которое включает усечение данных). Пример: параметры проверки не переданы.

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

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

Но если вы используете исключения, вы можете уменьшить объем кода, не нужно много операторов if else, просто дайте исключению всплыть.

Это зависит от вашего проекта. Самое главное, что существует стандарт проекта, и все делают это одинаково.

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

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

Я много слышу о проблеме производительности. Я только что сделал простой тест с использованием C# и исключений trow/catch 1000. Время, необходимое для 1К Исключений, составляет 23 мс. Это 23µ за исключение. Я думаю, что производительность больше не является первым аргументом здесь, за исключением того, что вы планируете повышать более 2000 исключений в секунду, и в этом случае у вас будет снижение производительности на 5%, что я могу начать рассматривать.

Мое скромное мнение...

Другие вопросы по тегам