При преобразовании низкоуровневой ошибки в доменное исключение как сохранить исходную причину для диагностики?
Передайте исходное исключение как причину нового доменного исключения. Обычно это делают через конструктор, принимающий Throwable, чтобы сохранить исходный тип, сообщение и цепочку стека вызовов.
Простая замена исходной ошибки новым исключением без причины теряет диагностический контекст и затрудняет поиск источника сбоя.
Цепочка причин исключений появилась в Java для разделения уровней абстракции. Низкоуровневый компонент может работать с файловой системой, сетью или базой данных, тогда как вызывающему коду важнее получить понятную доменную ошибку.
Механизм cause позволяет скрыть детали реализации от API, не уничтожая исходную информацию. Это поддерживает преобразование исключений между слоями приложения без потери данных для журналирования и расследования проблем.
Например, репозиторий получает исключение драйвера базы данных и должен сообщить сервисному слою о невозможности загрузить заказ. Если выбросить только новое исключение с текстом вроде «не удалось загрузить заказ», исходный код ошибки, SQL-причина и стек вызовов могут быть потеряны.
Без цепочки причин оператор видит лишь верхний уровень сбоя. В результате сложнее отличить временную недоступность базы данных от нарушения схемы или ошибки преобразования данных, а обработчики вынуждены анализировать нестабильные тексты сообщений.
Создайте исключение более высокого уровня и передайте исходный Throwable в качестве причины. Метод getCause() нового исключения вернёт исходную ошибку, а стандартный вывод стека обычно покажет цепочку Caused by.
Причина не обязана иметь тот же тип, что и новое исключение: Throwable позволяет связать ошибки разных уровней. При этом новый тип должен отражать контракт текущего слоя, а исходная причина — оставаться доступной для диагностики.
Не следует без необходимости оборачивать исключение несколько раз на каждом уровне. Каждый слой должен преобразовывать ошибку только тогда, когда меняется смысл или публичный контракт; иначе цепочка становится шумной. Также нельзя полагаться на текст сообщения как на стабильный API: для программной обработки используйте типы исключений и структурированные данные.
Причина отличается от подавленного исключения. Причина описывает исходную ошибку, из-за которой возникло новое исключение, а подавленное исключение обычно отражает дополнительный сбой, например при закрытии ресурса. Эти механизмы не следует смешивать.
Сервис заказа обращается к репозиторию. Репозиторий может использовать PostgreSQL сегодня и другой источник данных завтра, поэтому наружу не должен передавать SQLException или исключение конкретного драйвера.
Рассматривались два варианта. Передавать низкоуровневое исключение напрямую проще, но это связывает сервис с реализацией хранилища. Создать новое исключение без причины лучше скрывает детали, но лишает диагностики. Оборачивание с сохранением причины сохраняет границу слоёв и исходную информацию.
Выбран вариант с доменным исключением и исходным Throwable в качестве причины. Сервис обрабатывает стабильный тип OrderLoadException, а журналирование получает полную цепочку причин. В результате замена драйвера не меняет контракт сервиса, а расследование ошибок не требует воспроизводить проблему локально.
Нет. Сообщение обычно содержит только текст и не сохраняет тип исходного исключения, его стек вызовов и вложенную причину. Передача исходного объекта как cause сохраняет гораздо больше диагностической информации и позволяет исследовать всю цепочку.
Да, если причина не была задана конструктором и объект допускает инициализацию причины. Для этого существует initCause, но повторная установка причины или установка причины самому себе приводит к ошибке состояния. На практике предпочтительнее конструктор с cause: он делает обязательное наличие причины явным и предотвращает ошибку при поздней инициализации.
Нет. Оборачивание оправдано, когда текущий слой меняет смысл ошибки, формирует более стабильный контракт или добавляет полезный контекст. Если тип уже подходит вызывающему коду и дополнительный контекст не нужен, бездумная обёртка увеличит глубину цепочки и может усложнить обработку; в таком случае исключение можно пробросить дальше без изменения.