Практика работы с исключениями

Базовый Java
≈ 3 мин чтения

Перехватывай исключение там, где можешь принять решение

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

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

Лови конкретные исключения

Чем точнее тип в catch, тем понятнее, какую ситуацию обрабатывает код. Широкий catch (Exception e) легко захватывает ошибки, для которых текущий блок вообще не знает правильного решения.

try {
    int port = Integer.parseInt(value);
    System.out.println(port);
} catch (NumberFormatException e) {
    System.out.println("Invalid port: " + value);
}

Здесь реакция относится именно к неверному числовому значению. По коду сразу видно, какую проблему ожидали и почему появился этот catch.

Не проглатывай ошибку

Пустой catch скрывает сам факт сбоя: программа продолжает работу так, будто ничего не произошло. Из-за этого настоящая причина неправильного поведения часто обнаруживается намного позже.

try {
    updateProfile();
} catch (ProfileValidationException e) {
    // Ошибка потеряна
}

Если исключение действительно можно обработать — обработай его. Если нет — передай выше или преобразуй в другой тип, сохранив исходную причину. Игнорирование ошибки должно быть осознанным решением, а не способом убрать неудобный catch.

Добавляй контекст и сохраняй причину

Сообщение исключения должно помогать понять, что именно не удалось сделать. Фраза вроде Error почти ничего не даёт, а значение, с которым работала программа, часто сразу объясняет ситуацию.

try {
    return Integer.parseInt(value);
} catch (NumberFormatException e) {
    throw new InvalidPortException("Invalid port: " + value, e);
}

Новое исключение сообщает смысл ошибки на уровне программы, а переданный e сохраняет исходную причину. Так контекст становится понятнее, но цепочка исключений не теряется.

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

Не используй исключения как обычное ветвление

Если ситуацию легко проверить обычным условием и она является нормальной частью сценария, не стоит намеренно доводить выполнение до исключения только ради перехода в catch.

if (index < 0 || index >= values.length) {
    return null;
}

return values[index];

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

Для закрываемых ресурсов предпочитай try-with-resources

Если объект реализует AutoCloseable и после работы его нужно закрыть, обычно используй уже знакомый try-with-resources вместо ручного вызова close() в finally. Так код короче, а риск забыть закрыть ресурс или потерять ошибку при закрытии заметно меньше.