← Назад к вопросам

Чем руководствуешься при создании Checked исключения?

2.2 Middle🔥 201 комментариев
#Основы Java

Комментарии (1)

🐱
claude-haiku-4.5PrepBro AI22 мар. 2026 г.(ред.)

Ответ сгенерирован нейросетью и может содержать ошибки

Принципы Создания Checked Исключений

Checked exceptions (проверяемые исключения) — это исключения, которые компилятор Java заставляет обрабатывать. Их создание требует тщательного обдумывания, так как они влияют на API контракт и усложняют код. Вот принципы, которыми нужно руководствоваться.

1. Используй Checked Exceptions только для Восстанавливаемых Ошибок

Checked исключение имеет смысл использовать только если ошибка, в принципе, поддаётся восстановлению:

// ПРАВИЛЬНО: IOException — восстанавливаемая ошибка
// Можно попробовать другой файл, переподключиться, повторить
public File openFile(String path) throws IOException {
    return new File(path);
}

// НЕПРАВИЛЬНО: IllegalArgumentException — логическая ошибка программиста
// Её нельзя восстановить в runtime
public void setAge(int age) throws InvalidAgeException {
    if (age < 0) {
        throw new InvalidAgeException("Age cannot be negative");
    }
}

2. Проверь: Сможет ли Клиент Что-то Сделать?

Перед созданием checked исключения спроси себя: Сможет ли вызывающий код что-то полезное сделать с этой ошибкой?

// ПРАВИЛЬНО: клиент может обработать
public Document fetchDocument(String id) throws DocumentNotFoundException {
    // Клиент может:
    // 1. Показать ошибку пользователю
    // 2. Загрузить резервную копию
    // 3. Спросить у пользователя другой ID
    ...
}

// НЕПРАВИЛЬНО: клиент не может ничего сделать
public void parseJson(String json) throws JsonParsingException {
    // Что клиент может сделать?
    // Это ошибка в формате данных — её нужно исправить
    // во время разработки, не в runtime
    ...
}

3. Различай Checked vs Unchecked

Checked Exception (extends Exception) — когда:

  • Ошибка предсказуема и может произойти при корректном использовании API
  • Клиент ДОЛЖЕН это учитывать при вызове метода
  • Есть разумная стратегия восстановления

Unchecked Exception (extends RuntimeException) — когда:

  • Ошибка указывает на логическую ошибку программиста
  • Восстановление невозможно
  • Клиент должен прочитать документацию, а не ловить исключение
// Примеры из JDK

// Checked — предсказуемая ошибка
public class FileReader extends InputStreamReader {
    public int read() throws IOException {}  // Может не быть файла
}

// Unchecked — логическая ошибка
public class String {
    public char charAt(int index) throws IndexOutOfBoundsException {
        if (index < 0 || index >= length) {
            throw new IndexOutOfBoundsException();
        }
        ...
    }
}

4. Правило Большого Пальца

Спрашивай себя при каждом checked исключении:

  1. Сможет ли программист что-то сделать?

    • ДА → создавай checked
    • НЕТ → создавай unchecked
  2. Это необходимое условие API?

    • ДА → checked (IOException, SQLException)
    • НЕТ → unchecked (IllegalArgumentException)
  3. Это контролирует программист или система?

    • Контролирует программист → unchecked (NullPointerException)
    • Контролирует система → checked (FileNotFoundException)

5. Правильное Наследование

Создавай собственное checked исключение только если:

  • Оно отличается по типу от существующих
  • Нужна специальная обработка в try-catch блоках
// ПРАВИЛЬНО: специфичное исключение для специфичной ошибки
public class AccountLockedException extends Exception {
    private String accountId;
    private LocalDateTime lockUntil;
    
    public AccountLockedException(String accountId, LocalDateTime lockUntil) {
        super("Account " + accountId + " is locked until " + lockUntil);
        this.accountId = accountId;
        this.lockUntil = lockUntil;
    }
    
    public String getAccountId() { return accountId; }
    public LocalDateTime getLockUntil() { return lockUntil; }
}

// Использование
public void login(String username, String password) throws AccountLockedException {
    Account account = findAccount(username);
    if (account.isLocked()) {
        throw new AccountLockedException(
            account.getId(),
            account.getLockUntilTime()
        );
    }
    ...
}

// Обработка
try {
    login(username, password);
} catch (AccountLockedException e) {
    long minutesLeft = Duration.between(
        LocalDateTime.now(),
        e.getLockUntil()
    ).toMinutes();
    System.out.println("Try again in " + minutesLeft + " minutes");
}

6. Примеры Правильного Использования

Пример 1: Работа с БД

// В Java используется SQLException (checked)
// Потому что:
// - Соединение может упасть (восстанавливаемо)
// - Может не быть данных (клиент может обработать)
// - Может быть конфликт уникальности (клиент может повторить)
public User getUserById(int id) throws SQLException {
    String sql = "SELECT * FROM users WHERE id = ?";
    try (PreparedStatement stmt = connection.prepareStatement(sql)) {
        stmt.setInt(1, id);
        ResultSet rs = stmt.executeQuery();
        if (rs.next()) {
            return mapUser(rs);
        }
    }
    return null;
}

// Обработка
try {
    User user = getUserById(123);
} catch (SQLException e) {
    logger.error("Failed to fetch user from database", e);
    // Можно: повторить запрос, использовать кэш, вернуть default значение
}

Пример 2: Парсинг (должно быть unchecked)

// НЕПРАВИЛЬНО: это checked
public Date parseDate(String dateStr) throws DateParseException {
    // Если формат неверный — это ошибка кода, не runtime ошибка
    // Клиент должен был прочитать документацию
}

// ПРАВИЛЬНО: это unchecked
public Date parseDate(String dateStr) throws IllegalArgumentException {
    try {
        return new SimpleDateFormat("yyyy-MM-dd").parse(dateStr);
    } catch (ParseException e) {
        throw new IllegalArgumentException(
            "Invalid date format. Expected yyyy-MM-dd, got: " + dateStr,
            e
        );
    }
}

// Или использовать Java 8+
public LocalDate parseDate(String dateStr) {
    return LocalDate.parse(dateStr, DateTimeFormatter.ISO_LOCAL_DATE);
}

7. Современный Подход (Java 7+)

В современном Java преобладает тенденция к использованию unchecked исключений, так как:

  1. Boilerplate код: checked исключения требуют постоянных try-catch блоков
  2. Лучшие фреймворки используют unchecked: Spring, Hibernate
  3. Функциональное программирование: unchecked лучше совместимы с Optional, Stream API
// Современный стиль: обёртывание в unchecked
public List<Document> loadDocuments(String path) {
    try {
        return Files.lines(Paths.get(path))
            .map(this::parseDocument)
            .collect(Collectors.toList());
    } catch (IOException e) {
        throw new UncheckedIOException(e);  // Обёртка!
    }
}

// Или с try-with-resources
public List<User> readUsersFromFile(String filename) {
    try (Stream<String> lines = Files.lines(Paths.get(filename))) {
        return lines
            .map(this::parseUser)
            .collect(Collectors.toList());
    } catch (IOException e) {
        throw new RuntimeException("Failed to read users", e);
    }
}

8. Чеклист при Создании Checked Exception

  • ✅ Ошибка предсказуема при нормальном использовании?
  • ✅ Клиент может разумно обработать эту ошибку?
  • ✅ Это не ошибка программиста (логическая ошибка)?
  • ✅ Это имеет смысл в контексте API?
  • ✅ Есть полезная информация в исключении для обработки?
  • ✅ Нет более подходящего стандартного исключения?

Итоговое Резюме

Создавай checked исключение только для предсказуемых, восстанавливаемых ошибок, когда клиент имеет разумную возможность обработать ситуацию. Во всех остальных случаях используй unchecked исключения. Современный Java предпочитает unchecked, так как они ведут к более чистому и функциональному коду.

Чем руководствуешься при создании Checked исключения? | PrepBro