Как правильно реализовать контракт метода с учетом обработки ошибок
Что проверяют
Умеете ли вы явно описать, что метод ожидает, что обещает и как сообщает о нарушении, и различаете ли ошибку вызывающего кода и сбой окружения. Следом спрашивают, когда вы выбираете checked-исключение, а когда unchecked.
Как отвечать
Контракт состоит из трёх частей: предусловия (что должно быть верно на входе), постусловия (что гарантирует результат) и инварианты объекта. Для каждой части есть свой инструмент:
- проверка аргументов в начале метода:
Objects.requireNonNull(user, "user"),IllegalArgumentExceptionдля недопустимых значений,Objects.checkIndex()для индексов. Принцип fail fast: упасть сразу с понятным сообщением, а не через три слоя сNullPointerException; - состояние объекта:
IllegalStateException, если метод вызван не вовремя, например у закрытого ресурса; - checked-исключения: для ожидаемых сбоев, на которые вызывающий может осмысленно отреагировать, классический пример
IOException. Ошибки программиста вроде неверного аргумента делают unchecked; - отсутствие результата:
Optional<T>в возвращаемом значении вместоnull. Для полей и параметровOptionalне предназначен; - документация: Javadoc с
@param,@returnи@throws, аннотации nullness (например,@Nullableиз JSpecify), которые проверяют IDE и статический анализ; - крайние случаи: там, где важен результат,
Math.addExact()броситArithmeticExceptionвместо тихого переполненияint.
Если метод оборачивает низкоуровневое исключение в своё, причина передаётся в конструктор: throw new StorageException("...", e). Иначе стек теряется, и разбирать инцидент приходится вслепую.
Где спотыкаются
Делают собственное checked-исключение под неверный аргумент. Каждый вызывающий обязан писать try/catch на ошибку, которую сам же и допустил, и обычно просто глотает её.
Проверяют аргументы через assert. Утверждения по умолчанию выключены и работают только с флагом -ea, поэтому в проде проверки нет вовсе.
Ловят Exception и возвращают null или пишут в лог и тут же бросают дальше. В первом случае ошибка всплывает в другом месте без контекста, во втором одно событие попадает в лог несколько раз.