Комментарии (1)
Ответ сгенерирован нейросетью и может содержать ошибки
Иерархия наследования исключений в Java
При проектировании собственных исключений выбор базового класса критически важен для правильной обработки ошибок. Моя стратегия следующая:
Наследование от Exception vs RuntimeException
Всё зависит от того, является ли исключение ожидаемой частью работы программы:
// Наследую от Exception (checked исключение)
public class InsufficientFundsException extends Exception {
public InsufficientFundsException(String message) {
super(message);
}
}
// Используется для предсказуемых ошибок
public class BankAccount {
public void withdraw(BigDecimal amount) throws InsufficientFundsException {
if (balance.compareTo(amount) < 0) {
throw new InsufficientFundsException("Недостаточно средств");
}
balance = balance.subtract(amount);
}
}
Checked исключения (extends Exception)
Использую Exception для исключений, которые:
- Предсказуемы и ожидаемы — например, файл не найден, недостаточно средств на счёте
- Должны быть обработаны вызывающим кодом — компилятор заставляет это сделать
- Представляют бизнес-логику ошибок — это часть контракта метода
// Checked исключение для бизнес-логики
public class PaymentFailedException extends Exception {
private final PaymentErrorCode errorCode;
public PaymentFailedException(String message, PaymentErrorCode code) {
super(message);
this.errorCode = code;
}
public PaymentErrorCode getErrorCode() {
return errorCode;
}
}
Unchecked исключения (extends RuntimeException)
RuntimeException использую для:
- Программных ошибок — нарушение инвариантов, неправильное использование API
- Непредсказуемых сбоев — проблемы с БД, сетью, когда обработка в каждом месте неэффективна
- Критических ошибок, от которых приложение не может восстановиться
// Unchecked исключение для программных ошибок
public class InvalidProductStateException extends RuntimeException {
public InvalidProductStateException(String message) {
super(message);
}
}
// Используется в методе без throws
public class Product {
public void activate() {
if (status == ProductStatus.DELETED) {
throw new InvalidProductStateException(
"Не можно активировать удалённый товар"
);
}
status = ProductStatus.ACTIVE;
}
}
Собственная иерархия для доменного слоя
В архитектуре по DDD я создаю базовые классы исключений для каждого слоя:
// Базовое checked исключение для бизнес-ошибок
public abstract class DomainException extends Exception {
protected DomainException(String message) {
super(message);
}
}
// Конкретные бизнес-исключения
public class OrderNotFoundException extends DomainException {
public OrderNotFoundException(UUID orderId) {
super("Заказ не найден: " + orderId);
}
}
public class InvalidOrderStateException extends DomainException {
public InvalidOrderStateException(String state) {
super("Недопустимое состояние заказа: " + state);
}
}
Исключения инфраструктурного слоя
Для проблем с внешними системами использую RuntimeException:
// Unchecked для ошибок инфраструктуры
public class DatabaseException extends RuntimeException {
public DatabaseException(String message, Throwable cause) {
super(message, cause);
}
}
public class ApiClientException extends RuntimeException {
private final int statusCode;
public ApiClientException(String message, int statusCode, Throwable cause) {
super(message, cause);
this.statusCode = statusCode;
}
}
Правило большого пальца
Если ошибка — это бизнес-событие, которое нужно обработать → Exception (checked).
Если ошибка — это баг или сбой инфраструктуры → RuntimeException (unchecked).
Какое основание NOT выбирать
❌ Не наследую от Throwable напрямую — это очень редко требуется, сбивает с толку.
❌ Не создаю иерархии глубже 2-3 уровней — избыточная сложность, нарушает KISS.
Вывод
Мойсилыподход основан на том, что checked исключения заставляют обработать предсказуемые ошибки, а unchecked исключения используются для непредсказуемых сбоев. Это делает код более надёжным и явным в своём намерении.