Зачем нужно создавать свои классы ошибок (исключений)?

Ответ

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

Основные причины:

  1. Семантическая точность: Стандартные исключения (вроде RuntimeException или InvalidArgumentException) часто слишком общие. Свои исключения точно описывают, что пошло не так (например, PaymentDeclinedException, UserNotFoundException, InvalidOrderStateException).
  2. Гранулярная обработка: Позволяет перехватывать и обрабатывать разные типы сбоев по-разному.
  3. Добавление контекста: В кастомный класс можно добавить дополнительные свойства, которые помогут в логировании и отладке (ID транзакции, код ошибки внешнего API и т.д.).
  4. Улучшение читаемости кода: По имени класса исключения сразу понятна природа ошибки.

Пример на PHP:

<?php
// Базовое исключение для домена "Платежи"
namespace AppPaymentException;

class PaymentException extends RuntimeException {
    protected string $transactionId;

    public function __construct(string $message, string $transactionId, int $code = 0, ?Throwable $previous = null) {
        parent::__construct($message, $code, $previous);
        $this->transactionId = $transactionId;
    }

    public function getTransactionId(): string {
        return $this->transactionId;
    }
}

// Конкретные типы ошибок платежа
class InsufficientFundsException extends PaymentException {}
class CardDeclinedException extends PaymentException {}
class GatewayTimeoutException extends PaymentException {}

// Использование в коде
class PaymentProcessor {
    public function charge(Order $order): void {
        $transactionId = $this->generateId();
        // ... логика проверки баланса
        if (!$order->user->hasSufficientFunds($order->total)) {
            throw new InsufficientFundsException(
                "User does not have enough funds.",
                $transactionId
            );
        }
        // ... попытка списания
    }
}

// Обработка на более высоком уровне
try {
    $processor->charge($order);
} catch (InsufficientFundsException $e) {
    // Конкретная обработка: показать сообщение пользователю, предложить другой способ
    $this->logger->warning('Insufficient funds', ['transaction' => $e->getTransactionId()]);
    $this->redirectToPaymentMethodSelection();
} catch (CardDeclinedException $e) {
    // Другая обработка
} catch (PaymentException $e) {
    // Общая обработка всех платежных ошибок
    $this->logger->error('Payment failed', ['exception' => $e]);
    throw new HttpException(500, 'Payment processing error');
}

Такой подход делает поток ошибок в приложении управляемым и понятным для разработчиков.

Ответ 18+ 🔞

А, ну вот, опять про эти ваши исключения. Слушай, а ведь это реально важная тема, хоть и звучит как занудство. Представь, что ты пишешь код, и у тебя всё падает с одной и той же ошибкой RuntimeException. Это как если бы в жизни на любую проблему — сломался лифт, кончился кофе, началась война — ты бы только и говорил: «Ой, блядь!». Неинформативно же, ёпта!

Зачем это, блядь, нужно?

  1. Чтобы не быть распиздяем. Вместо общего «чё-то пошло не так» ты говоришь конкретно: «Карта не прошла, идиот» (CardDeclinedException) или «Пользователь сдох, не найден» (UserNotFoundException). Сразу ясно, где копать.
  2. Чтобы ловить ошибки выборочно, а не всё подряд. Можно отреагировать на «не хватило бабла» (InsufficientFundsException) предложением другой карты, а на «шлюх упал» (GatewayTimeoutException) — повторной попыткой. А не просто орать «ой, всё!» и показывать пользователю пятисотую ошибку. Доверия к такому коду — ноль ебать.
  3. Чтобы запихнуть в ошибку полезные ништяки. В стандартное исключение только сообщение впихнешь, а в своё — ID транзакции, код ошибки от банка, состояние заказа. При дебаге это золото, а не информация.
  4. Читаемость, мать её. Увидел в коде throw new SelfDestructSequenceActivatedException() — и сразу понял, что пора бежать, а не разбираться, что за InvalidArgumentException вылетел.

Смотри, как это выглядит на PHP, без всякой хуйни:

<?php
// Это наше базовое исключение для всего, что связано с баблом.
namespace AppPaymentException;

class PaymentException extends RuntimeException {
    protected string $transactionId; // Вот сюда пихнём ID транзакции, чтобы потом не ебаться.

    public function __construct(string $message, string $transactionId, int $code = 0, ?Throwable $previous = null) {
        parent::__construct($message, $code, $previous);
        $this->transactionId = $transactionId;
    }

    public function getTransactionId(): string {
        return $this->transactionId; // Геттер, чтоб потом вытащить.
    }
}

// А это уже конкретные косяки. Наследуемся и всё.
class InsufficientFundsException extends PaymentException {}
class CardDeclinedException extends PaymentException {}
class GatewayTimeoutException extends PaymentException {}

// Теперь используем это всё в коде, как взрослые.
class PaymentProcessor {
    public function charge(Order $order): void {
        $transactionId = $this->generateId();
        // ... проверяем, есть ли у юзера бабло
        if (!$order->user->hasSufficientFunds($order->total)) {
            // Бросаем ЧЁТКОЕ исключение, а не какую-то хрень!
            throw new InsufficientFundsException(
                "User does not have enough funds.",
                $transactionId
            );
        }
        // ... дальше пытаемся списать
    }
}

// И вот где магия! Ловим ошибки точечно.
try {
    $processor->charge($order);
} catch (InsufficientFundsException $e) {
    // О, не хватило денег! Логируем и предлагаем другую карту.
    $this->logger->warning('Insufficient funds', ['transaction' => $e->getTransactionId()]);
    $this->redirectToPaymentMethodSelection();
} catch (CardDeclinedException $e) {
    // Карту отклонили. Говорим юзеру "звони в банк, пидор".
} catch (PaymentException $e) {
    // Любая другая платежная ошибка. Логируем всё подряд и отдаём 500.
    $this->logger->error('Payment failed', ['exception' => $e]);
    throw new HttpException(500, 'Payment processing error');
}

Вот видишь разницу? Вместо кучи if-ов и непонятных сообщений — чистая, ясная структура. Как будто не код, а инструкция для себя любимого из будущего. И дебажить в разы проще, волнение ебать сразу пропадает. Так что не ленись, создавай свои исключения — терпения на это ноль, но оно того стоит, ёпта.