Что такое контрактное программирование (Design by Contract) в Java?

Ответ

Контрактное программирование (DbC) — это парадигма, где взаимодействие между компонентами (например, клиентом и методом) формализуется в виде контракта, состоящего из условий.

Три основных типа условий:

  1. Предусловия (Preconditions): Условия, которые должны быть истинными перед вызовом метода. Отвечают за корректность входных данных. Ответственность клиента.
    // Пример с явной проверкой (рекомендуется)
    public void transfer(Account from, Account to, BigDecimal amount) {
        Objects.requireNonNull(from, "Source account cannot be null");
        Objects.requireNonNull(to, "Target account cannot be null");
        if (amount.compareTo(BigDecimal.ZERO) <= 0) {
            throw new IllegalArgumentException("Amount must be positive");
        }
        // Логика перевода
    }
  2. Постусловия (Postconditions): Условия, гарантированные после выполнения метода. Отвечают за корректность результата и состояния. Ответственность метода.
    public BigDecimal withdraw(BigDecimal amount) {
        BigDecimal oldBalance = this.balance;
        // ... логика снятия ...
        // Постусловие: новый баланс должен быть меньше или равен старому на сумму снятия
        assert this.balance.compareTo(oldBalance.subtract(amount)) == 0 : "Balance incorrect after withdrawal";
        return amount;
    }
  3. Инварианты (Invariants): Условия, истинные на протяжении всего времени жизни объекта (и до, и после вызова любого публичного метода).
    class Account {
        private BigDecimal balance;
        // Инвариант: баланс никогда не должен быть отрицательным
        private void checkInvariant() {
            assert balance.compareTo(BigDecimal.ZERO) >= 0 : "Balance invariant violated";
        }
    }

Инструменты в Java:

  • Ключевое слово assert: Для проверки постусловий и инвариантов (требует флага -ea при запуске JVM).
  • Библиотеки: java.util.Objects.requireNonNull(), Guava Preconditions, Spring Framework Assert — для проверки предусловий с явными исключениями.

Почему это важно: DbC делает код более надежным, самодокументируемым и помогает выявлять ошибки на самой ранней стадии — при нарушении контракта.

Ответ 18+ 🔞

О, слушай, а вот есть такая штука — контрактное программирование. Это когда ты с кодом не просто так, а как с бухгалтером на зарплате: всё по пунктам, всё по договору, блядь. Никаких «ой, я думал, ты передашь нормальное число».

Представь, что метод — это такой занюханный клуб, а ты — вышибала. Контракт — это твои правила на входе.

Три кита, на которых всё держится, пока не проебалось:

  1. Предусловия (Preусловия, Preconditions): Это что должен сделать клиент, прежде чем сунуться в метод. Твоя ответственность — проверить, не пришёл ли какой-нибудь мудак с пустыми руками.

    public void transferMoney(Account from, Account to, BigDecimal summa) {
        // Вот тут ты стоишь на входе и проверяешь документы
        Objects.requireNonNull(from, "Откуда переводим, пидор? С неба что ли?");
        Objects.requireNonNull(to, "Кому переводим, вникуда?");
        if (summa.compareTo(BigDecimal.ZERO) <= 0) {
            throw new IllegalArgumentException("Ты мне ноль или минус принёс? Иди нахуй с таким переводом!");
        }
        // Если все проверки прошли — проходи, дорогой, внутри уже разберёмся.
    }

    Если клиент нарушил — это его проблема, его исключение и его позор. Ты предупредил.

  2. Постусловия (Postconditions): Это что метод обязан сделать для клиента после того, как отработает. Его ответственность. Как будто ты заказал пиццу, а тебе привезли коробку с крошками и запиской «извините, кот сожрал». Непорядок.

    public BigDecimal snatDengi(BigDecimal summa) {
        BigDecimal stariyBalance = this.balance; // Запоминаем, сколько было до всей движухи
        // ... тут магия снятия, списывания, возможно, с комиссией...
        // А теперь постусловие: проверяем, не наебал ли я клиента?
        // Новый баланс должен быть РОВНО старый минус сумма снятия.
        assert this.balance.compareTo(stariyBalance.subtract(summa)) == 0 : "Ёпта, баланс посчитался криво! Где-то хуйня!";
        return summa;
    }

    Если метод нарушил своё постусловие — это его косяк, его внутренний пиздец. assert тут как внутренний аудитор, который орёт, когда видит хуйню.

  3. Инварианты (Invariants): Это священные коровы, которые должны быть истиной ВСЕГДА, в любой момент жизни объекта. До метода, после метода, во время метода (хотя во время могут и нарушаться, главное — к концу восстановиться).

    class Account {
        private BigDecimal balance;
        // Священный инвариант: баланс НИКОГДА не должен уходить в минус.
        // Не «иногда», не «по пятницам», а НИКОГДА.
        private void proverkaInvarianta() {
            assert balance.compareTo(BigDecimal.ZERO) >= 0 : "Баланс ушёл в минус! Всё, пиздец, система посыпалась!";
        }
        // И эту проверку надо вызывать в критичных местах.
    }

    Инвариант — это как здоровье печени. Пока оно есть — ты его не замечаешь. Как только нарушилось — всё, ёперный театр, цирроз и смерть объекта.

Чем это всё, блядь, полезно? А тем, что когда что-то ломается, ты сразу понимаешь, кто накосячил: клиент (предусловие), метод (постусловие) или объект вообще поехал кукухой (инвариант). Вместо трёх дней дебага в попытках понять, где же эта сука ошибка зарылась. Всё по контракту, всё честно. И код сам себе документация, ебать его в сраку.