Как я должен обрабатывать ожидаемые ошибки? например. "Имя пользователя уже занято"

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

Однако, что мне делать, если я не могу обработать ошибку из текущей ссылки в цепочке? Например, у меня может быть класс, который используется для управления аутентификацией. Одним из методов может быть createUser($username, $password). Изменить: Этот метод вернет идентификатор пользователя или объект пользователя. Внутри этой функции мне нужно определить, существует ли имя пользователя. Если это так, как мне предупредить код вызова об этом? Возвращение null вместо объекта пользователя является одним из способов. Но как мне узнать, что вызвало ошибку?

Как я должен обрабатывать ошибки таким образом, чтобы код вызова мог легко узнать, что вызвало ошибку? Существует ли шаблон проектирования, обычно используемый для такого рода ситуаций?

Изменить: Я забыл упомянуть: я использую PHP.


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

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

Во-вторых, в ответ на "исключения следует использовать только для исключительных ситуаций, и это не один" аргумент: "Когда я вызываю getFoo(), Я действительно ожидаю получить Foo. Если я не получу его, это по определению исключительное событие" . (через pkainulainen)

Ответ 1

Существует несколько общих шаблонов:

1. Throw a exception.

2. Возврат NULL или FALSE и установка переданного в ссылку на ошибку. Например.

function createUser($user, $password, &$error)
{
      //...
      // You could use any type of object here.  This is just a simple example.
      if(uniqueKeyFailure)
      {
          $error = new UserAlreadyExists();
          return NULL;
      }
      //..
}

Его можно вызвать как:

$userCreateError = NULL;
$res = createUser($user, $pass, $userCreateError);
if($res === NULL)
{
  // do something with $userCreateError
}

3. Возвратите NULL или FALSE и получите последнюю ошибку (например, curl_error).

Я бы порекомендовал 1 или 2. Основная причина, по которой люди избегают исключений для "исключительных" ошибок, таких как ввод данных пользователя, - это производительность. Существует довольно много дискуссий по этому вопросу, например Производительность try-catch в php.

Я не рекомендую 3, поскольку он не реентерабелен.

Ответ 2

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

Например:

<?PHP
$user->name = 'Joe';
$user->password = 'secret';

//try to save the user.
if (! $user->save()){
   $errors = $user->getMessages();
   // ... do something with error messages
}else{
   //the user object set it own ID.
   $id = $user->id;
}

Теперь за пользователем отвлекается много материала. Там может быть целая вселенная объектов, но это зависит от вас.

Большая проблема здесь: почему в мире у вас есть класс аутентификации, создающий пользователя? Создание пользовательских объектов, вероятно, выходит за рамки проверки подлинности.

Может быть разумно иметь некоторый метод, например authenticate($username,$password) вернуть объект типа "пользователь" (представляющий пользователя, который только что прошел проверку подлинности), но даже это немного грязно.

Вместо этого рассмотрите что-то вроде:

<?PHP
$auth = new AuthService();
$u = new User();
$u->username='Joe';
$u->password='secret';

$authResult = $auth->authenticate($user);

if ($authResult->success){
  //$user has properties set as a side effect.
}else{
  //find out why it failed.
  $errors = $authResult->getErrors();
}

Конечно, для этого необходимо определить какой-то класс результата для AuthService:: authenticate() для заполнения и возврата. Но по крайней мере вы не получаете методов, которые иногда возвращают логические значения, а иногда возвращают объекты и т.д.

Ответ 3

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

Ответ 4

Это нужно сделать следующим образом:

Создайте объект ошибки с кодами ошибок в них, текст ошибки и метод, который вызвал ошибку.

Пропустите объект ошибки.

Вы можете отбросить исключения, но исключения дороги

EDIT - на основе редактирования OP. В этом случае вам придется выбросить объект исключения.

Создать класс исключения с кодом ошибки, описанием ошибки, именем класса, именем метода. Отбросьте исключение.

Ответ 5

Если вы управляете типом возвращаемого метода, вы также можете изменить его подпись, чтобы вернуть что-то вроде объекта OperationResult, который будет иметь свойство ErrorDescription и свойство UserID или User object.

Ответ 6

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

например.

function numeric($input) {
    if (!is_numeric($input)) return 'Must be numeric.';
    return NULL;
}

function usernameavailable($input) {
    //query database....
    if ($result) return 'Username already taken, please choose another one.';
    return NULL;
}

Вы также можете использовать функции с параметрами:

function length($input, $min, $max) {
    if (strlen($input) < $min) return "Must be longer than $min characters.";
    if (strlen($input) > $max) return "Must be shorter than $max characters.";
    return NULL;
}

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

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

С помощью этого метода вы можете легко создать класс Form и просто создать элементы с аргументами, являющимися функциями проверки. Вы также можете развернуть это, чтобы включить проверку AJAX, отправляя асинхронные запросы к тем же самым функциям проверки PHP.

Пример из моей рамки:

$Form = new AjaxForm();
$Form->add(new TextBox('Username', 'usernameavailable|length[3,12]'));
$Form->add(new SubmitButton());

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

Ответ 7

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

Есть ли у него стоимость? Да. Достаточно ли это достаточно важно? Скорее всего нет.