@dmitriylanets

Правильно ли выбрасывать исключения в бизнес логике?

В последнее время встречаю много критики по поводу данного подхода, но все же насколько правильно заложить исключительные ситуации для метода сервиса бизнес логики через вариации Exceptions
try{
   $order = $orderService->createOrderAndNotify($orderRequest);
   return $this->view("success",[
      "orderId" => $order->getId()
   ]);
}
catch(OrderValidationException $ex){
   $logger->warning("error valid order", $ex->getErrors());
   return $this->view("warning",[
       "errors" => $ex->getErrors()
   ]);
}
catch(OrderException $ex){
   $logger->error($ex->getMessage());
   return $this->view("error");
}


иерархия исключений Order\Exceptions
OrderException extends RuntimeException
OrderValidationException extends OrderException
OrderNotFound extends OrderException


вижу приемущества:
1. конкретный ответ метода, а не какой то специализированный DTO отдающий все на свете
2. обработка исключений в зависимости от ситуации и логики приложения
3. тестируемость и документированность (phpunit,docblock)
4. передача доп. данных в исключениях
  • Вопрос задан
  • 549 просмотров
Решения вопроса 2
Я считаю это хорошей практикой. Исключительная ситуация - это та, при которой программа выполняется не так, как ожидалось. Но, позвольте, я ожидаю, что заказ пройдет валидацию. Я ожидаю, что заказ завершится успехом. Поэтому OrderValidationException, OrderException - исключительные ситуации. Исключительной ситуацией не является, например, отмена пользователем заказа - глупо выбрасывать исключение по клику на Cancel.

Исключения гораздо глубже, чем просто "исключительная ситуация". Они помогают развести обработку ошибок по разным уровням абстракции, а использование разных типов исключений позволяет обрабатывать конкретные ошибки там, где их нужно обработать (на нужном слое). Исключения очень помогают в локализации ошибки в дальнейшем, они позволяют избавиться от простыни If .. else, проверок на null, false и прочей ереси. Они, чаще всего, делают код чище.

Но пользоваться ими стоит аккуратно, все-таки стоит разделять исключительную ситуацию и вполне нормальную, штатную ветку исполнения. Так, если заказов нет - это НЕ исключение, тут можно вернуть null - допустимо (хотя некоторые и тут выбрасывают исключение, потому что очень соблазнительно привязать код исключения к HTTP кодам). А если у вас аплоад документов, и юзер пытается загрузить файл слишком большой, то это уже исключение.
Ответ написан
tommy-vercetti
@tommy-vercetti
Да, правильно.

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

Пример кода на моем проекте:
namespace App\Domain\Mission;

class MissionOffer
{
    // ...

    public function __construct(Employee $employee, Mission $mission)
    {
        if ($employee->worksInShop($mission->getShop())) {
            throw new MissionOfferCannotBeCreatedForEmployeeException(
                $employee->getUuidString(),
                $mission->getUuidString()
            );
        }

        $this->initUuid();

        // ...
    }

    // ...

    private function accept(): void
    {
        if (self::STATUS_OFFERED !== $this->status) {
            throw new MissionOfferCannotBeAcceptedException();
        }

        $this->status = self::STATUS_ACCEPTED;
        $this->acceptedAt = new DateTime();
    }

    public function refuse(): void
    {
        if (self::STATUS_OFFERED !== $this->status) {
            throw new MissionOfferCannotBeRefusedException();
        }

        $this->status = self::STATUS_REFUSED;
        $this->refusedAt = new DateTime();

        foreach ($this->offerSlots as $offerSlot) {
            if ($offerSlot->isPending()) {
                $offerSlot->refuse();
            }
        }
    }
}

Эти исключения ловятся в одном месте - exception subscriber'е. В зависимости от эксепшена возвращается статус код + тело ответа.
Ответ написан
Пригласить эксперта
Ответы на вопрос 3
@FanatPHP
Чебуратор тега PHP
Ответ на вопрос из заголовка простой: исключение выбрасывается тогда, когда код не в состоянии выполнить работу, для которой он предназначен. Это простое правило, общее для программирования в целом, и безнес логика в этом смысле ничем не отличается от любого другого кода.

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


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

Но вот код, который лежит внутри catch(OrderException $ex){ является избыточным. Логирование ошибки и вывод стандартного сообщения клиенту - это то, что должен делать централизованный обработчик ошибок, который в любом случае должен присутствовать в приложении. То есть здесь этот код явно лишний.
Ответ написан
php666
@php666
PHP-макака
Нет, это плохая практика.

Исключительная ситуация на то и исключительная. Это ошибка запроса, нет коннекта к БД или отсутствие записи, которая, как мы уверены, должна существовать и тд. Это событие, после которого работа кода НЕВОЗМОЖНА.

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

Войдите, чтобы написать ответ

Войти через центр авторизации
Похожие вопросы