Почему важно передавать оригинальный exception в конструкторе
Что проверяют
Понимаете ли, что теряется при заворачивании исключения, и умеете ли читать цепочку причин. По сути это вопрос, сможет ли команда починить прод по вашим логам.
Как отвечать
У Throwable есть причина: её передают через super(message, cause) или ставят методом initCause. Когда причина передана, стектрейс печатается цепочкой с блоками Caused by, и видно исходную точку отказа, а не только место, где ошибку переупаковали.
public class StorageException extends RuntimeException {
public StorageException(String message, Throwable cause) {
super(message, cause);
}
}
try {
jdbc.update(sql, args);
} catch (DataAccessException e) {
throw new StorageException("не удалось сохранить документ", e);
}Само заворачивание оправдано: слой отдаёт наверх своё исключение, чтобы вызывающий не зависел от JDBC или конкретного клиента. Терять при этом диагностику незачем, поэтому в собственном исключении сразу объявляют конструкторы, принимающие причину. Без неё в логе остаётся строка вроде «ошибка базы», по которой не отличить таймаут от нарушения уникальности.
Где спотыкаются
Пишут throw new MyException(e.getMessage()). Тип и стек исходного исключения исчезают, остаётся текст, причём часто пустой: у многих исключений сообщение равно null. Тот же эффект даёт перехват с пустым телом, только совсем беззвучно.
Логируют и бросают дальше. Одна ошибка попадает в лог по нескольку раз с разных уровней, и разбор инцидента превращается в поиск первой записи. Правило простое: либо обработали и записали, либо пробросили с причиной, но не то и другое сразу.
Записывают через склейку строк: сообщение плюс e.getMessage(). Стек в лог не попадает вообще, хотя исключение было под рукой. Объект исключения передают вторым аргументом метода логгера, тогда цепочка причин печатается целиком.