Что такое хороший шаблон "Проверка ошибок" (Java)? - PullRequest
17 голосов
/ 10 июня 2010

Я объясню, что я подразумеваю под проверкой ошибок ввода.

Скажем, у вас есть функция doSomething(x).

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

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

  1. Проверка ошибок флага. Если doSomething(x) успешно завершится, верните null. В противном случае он возвращает логическое значение или строку ошибки. Проблема: Побочные эффекты.

  2. Бросок исключения. Бросок исключения, если doSomething(x) обнаруживает ошибку. Проблема: Если вы выполняете проверку ошибок только для параметров, выброс IllegalArgumentException кажется неуместным.

  3. Проверка ввода перед вызовом функции. Если проверка ошибок предназначена только для параметров функции, то перед вызовом функции doSomething(x) можно вызвать функцию проверки. Проблема: Что если клиент класса забывает вызвать функцию проверки перед вызовом doSomething(x)?

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

Ответы [ 9 ]

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

Бросить исключение - лучший способ.

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

Почему?Это цель этого исключения.

5 голосов
/ 10 июня 2010
  1. Проверка ошибок флага

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

Пример из API: Если вы попытаетесь добавить объект в Set, который уже содержит другой объект, который equals новый объект, метод add как бы «терпит неудачу» и указывает на этовозвращение false.(Обратите внимание, что мы находимся на уровне, на котором технически это даже не «ошибка»!)

2. Выдает исключение

Это опция по умолчанию.

Вопрос теперь в том, стоит ли переходить к проверенному исключению (для которого требуется объявление throws или предложения try / catch) или к непроверенному исключению (исключение, котороерасширяется RuntimeException).Здесь есть несколько практических правил.

Из Java Practices -> Проверенные и непроверенные исключения :

  • Непроверенные исключения: Представляют дефекты в программе (ошибки) - часто недопустимые аргументы, передаваемые в не приватный метод.

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

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

Если вы хотите выбросить проверенное исключение, вы можете A) свернуть свое собственное, расширив Exception, B) использовать цепочку существующих проверенных исключений или C) "«исключение времени выполнения, например, IOException: throw new IOException(new IllegalArgumentException("reason goes here..."));

3. Проверка ввода перед вызовом функции

Полагаясьна то, что клиент должен иметь санитИзмерил / проверил его аргументы до того, как звонок кажется мне плохой идеей.

3 голосов
/ 10 июня 2010

Ваше второе предложение («Бросок исключения») - лучший выбор.Другие две опции основаны на том, что вызывающий либо делает что-то до («Проверка ввода перед вызовом функции»), либо после («Проверка ошибок флага») вызова метода.В любом случае, дополнительная задача не обязательна для компилятора, поэтому кто-то, вызывающий функцию, не обязан вызывать «дополнительную вещь», поэтому проблемы не обнаруживаются до времени выполнения."и предложенная вами" проблема ", хорошо, ответ - бросить соответствующие типы исключений для кода.Если входные параметры являются недопустимыми, генерировать исключение InvalidArgumentException (так как это соответствующая ошибка).Если исключение касается функциональности (например, не удается открыть сетевое соединение), используйте другой тип исключения или создайте свой собственный.

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

Я согласен с бросанием исключений.Я хочу добавить еще одну опцию, которая объединяет # 2 и # 3 - шаблон прокси .Таким образом, ваш код остается достаточно сплоченным - проверка в одном месте и бизнес-логика в другом.Это имеет смысл, если у вас большой набор вызовов, которые необходимо проверить.

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

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

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

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

  2. Выдает исключение, когда ваша функция сталкивается с неопределенным поведением. Если у вас есть математическая функция, которая может работать только с положительными целыми числами, и кто-то передает -1, вы должны вызвать InvalidArguementException. Если вашей функции присвоен идентификатор продукта в базе данных, но продукт не может быть найден по запросу, вы можете вызвать специальное исключение ProductNotFound.

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

Я обычно использую # 2 и # 3. Я давно не писал код с флагами ошибок. Исключением может быть функция, возвращающая перечисление, где одно возможное значение указывает код ошибки. Это было обусловлено в большей степени бизнес-правилом, чем чем-либо еще.

И вообще, постарайтесь сделать это простым.

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

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

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

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

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

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

0 голосов
/ 14 сентября 2015

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

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

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

'Шаблон списка валидаторов'

В качестве примера давайте сначала опишем в кодеанти-шаблон «Флаг проверки», а затем мы преобразуем его в шаблон «Список проверки».

public Optional<String> checkForErrorsUsingFlags( 
       ObjectToCheck objToCheck ) {
  // the small series of checks and if statements represent the
  // anti-pattern. Hard to test and many other problems crop up.
  String errMsg = checkForError1( objToCheck );
  if(errMsg != null ) {
    return Optional.of(errMsg);
  }
  errMsg = checkForError2( objToCheck );
  if(errMsg != null ) {
    return Optional.of(errMsg);
  }
  return Optional.empty();
}

/**** client usage  ****/
ObjectToCheck obj = doSomethingToReadInput(obj);
Optional<String> error = checkForErrors( obj);
if (error.isPresent()) {
  // invalid input, throw object away and request input again
} else {
  // do stuff, we have a valid input
}

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

/** The common validator interface each validator will use */
private interface MyValidator {
  public boolean isValid(ObjectToCheck obj);
  public String getErrorMessage(ObjectToCheck obj);
}

 // this method should look familiar to the above, now we 
 // have a list of validators as an additional parameter
public Optional<String> checkForErrors( ObjectToCheck objToCheck,
     List<MyValidator> validators ) {
  for(MyValidator validator : validators ) {
    if (!validator.isValid(objToCheck)) {
      String errMsg = validator.getErrorMessage(objToCheck);
      return Optional.of(errMsg);
    }
  }
  return Optional.empty();
}

/****** client usage  *****/
// now in this pattern, the client controls when the validators
// are created, and which ones are used.
MyValidator validator1 = new MyValidator() {
  @Override
  public boolean isValid(ObjectToCheck obj) {
    return checkForError1( objToCheck ) != null;
  }
  @Override
  public boolean getErrorMessage(ObjectToCheck obj) {
    return checkForError1( objToCheck );
  }
}
// note: above we call checkForError1 twice, not optimal.
// typical in real examples this can be avoided,
// and the error message generation logic split from the detection
// logic often simplifies things.
MyValidator validator2 = new MyValidator() { ... }

List<MyValidator> validators = 
  ImmutableList.of( validator1, validator2);
Optional<String> error = checkForErrors(objToCheck, validators);
if (error.isPresent()) {
    // invalid input, throw object away and request input again
} else {
    // do stuff, we have a valid input
}

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

0 голосов
/ 11 июня 2010

Создайте пользовательское проверенное исключение.

 doSomething(WithX x ) throws BusinessRuleViolatedException 
...