Какие невалидные состояния объекта вы знаете?

Ответ

Невалидное состояние объекта возникает, когда его внутренние данные противоречат бизнес-правилам или инвариантам класса, делая объект непригодным для использования. Это ключевая проблема, ведущая к ошибкам времени выполнения.

Распространенные примеры невалидных состояний:

  1. Нарушение обязательности полей (Null в обязательных полях):

    public class User
    {
        // Имя пользователя обязательно, но свойство может быть null
        public string Name { get; set; }
    }
    // Невалидное состояние: user.Name == null
  2. Нарушение допустимых диапазонов значений:

    public class OrderItem
    {
        // Количество не может быть отрицательным или нулевым
        public int Quantity { get; set; }
    }
    // Невалидное состояние: item.Quantity == -5
  3. Нарушение бизнес-инвариантов:

    public class BankAccount
    {
        // Баланс не должен быть отрицательным, если не предусмотрен овердрафт
        public decimal Balance { get; set; }
    }
    // Невалидное состояние: account.Balance == -100.00m
  4. Внутренняя противоречивость данных:

    public class DateRange
    {
        public DateTime Start { get; set; }
        public DateTime End { get; set; }
        // Инвариант: End всегда >= Start
    }
    // Невалидное состояние: range.End < range.Start
  5. Неполная или некорректная инициализация:

    public class Product
    {
        public int Id { get; }
        // Конструктор забыл присвоить значение Id
        public Product(int id) { /* Id = id; // ОШИБКА: пропущено */ }
    }
    // Невалидное состояние: product.Id == 0 (значение по умолчанию)

Стратегии защиты от невалидных состояний:

  • Валидация в конструкторах и сеттерах: Гарантирует, что объект не может быть создан или изменен в невалидном состоянии.
  • Принцип Guard Clauses: Проверка входящих аргументов в начале метода.
    public void SetQuantity(int quantity)
    {
        if (quantity <= 0) throw new ArgumentOutOfRangeException(nameof(quantity));
        _quantity = quantity;
    }
  • Использование неизменяемых (Immutable) объектов: Состояние устанавливается один раз при создании и после этого не меняется, что упрощает валидацию.
  • Введение Value Objects: Замена примитивных типов (int, string, decimal) на специализированные типы, которые сами обеспечивают свою валидность (например, EmailAddress, PositiveInteger, Money).

Ответ 18+ 🔞

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

Представь себе, создаёшь ты объект, а он уже с рождения кривой, как говно в проруби. И дальше вся твоя система на этом говне держится, пока не рухнет в самый неподходящий момент. Вот в чём, сука, проблема!

Смотри, какие бывают пиздецовые состояния, прямо как в цирке уродцев:

  1. Обязательное поле — пустое, как твоя голова после пятницы.
    Типа, пользователь есть, а имени нет. Кто ты такой, призрак, блядь?

    public class User
    {
        // Имя обязательно, но может быть null — вот и приехали
        public string Name { get; set; }
    }
    // И тут бац: user.Name == null. И кто этот анонимный мудак?
  2. Значения за пределами адеквата.
    Заказано минус пять штук товара. Это как, блядь? Ты мне ещё должен привезти, что ли?

    public class OrderItem
    {
        // Количество не может быть нулём или отрицательным, это же очевидно, ёпта!
        public int Quantity { get; set; }
    }
    // А у нас: item.Quantity == -5. Магазин из другого измерения, блядь.
  3. Нарушение самых простых бизнес-правил.
    Баланс на счету ушёл в минус, а овердрафта не было. Откуда деньги, сука? Из жопы?

    public class BankAccount
    {
        // Баланс не должен быть отрицательным, если не договорились иначе
        public decimal Balance { get; set; }
    }
    // Невалидное состояние: account.Balance == -100.00m. Ёб твою мать, грабитель!
  4. Внутренняя логика поехала, как крыша у психопата.
    Диапазон дат, где конец раньше начала. Это путешествие во времени, что ли? Нахуй надо?

    public class DateRange
    {
        public DateTime Start { get; set; }
        public DateTime End { get; set; }
        // По логике вещей, End всегда >= Start
    }
    // Невалидное состояние: range.End < range.Start. Обратный ход времени, блядь!
  5. Кривая инициализация, забыл самое главное.
    Создал продукт, а ID не присвоил. Ну и кто ты после этого, беспаспортный бомж?

    public class Product
    {
        public int Id { get; }
        // Конструктор забыл присвоить значение Id — гений, блядь!
        public Product(int id) { /* Id = id; // ОШИБКА: пропущено, руки-крюки */ }
    }
    // Невалидное состояние: product.Id == 0. Привет, аноним!

Как со всем этим бороться, чтобы не охуеть?

  • Валидируй на входе, как охранник в клубе.
    В конструкторах и сеттерах сразу отсекай хуйню. Не дай объекту родиться уродцем.
  • Guard Clauses — твои верные друзья.
    Первым делом в методе проверяй аргументы, а потом уже делай дела.
    public void SetQuantity(int quantity)
    {
        if (quantity <= 0) throw new ArgumentOutOfRangeException(nameof(quantity));
        _quantity = quantity; // Только так, блядь!
    }
  • Делай объекты неизменяемыми, как скала.
    Задал состояние один раз при рождении — и всё, пиписьки нет, менять нечего. Проще и надёжнее.
  • Заворачивай примитивы в Value Objects.
    Вместо голого int — создай тип PositiveInteger, который сам себя валидирует.
    Вместо строки для email — тип EmailAddress, который не даст вписать любую хуйню.
    Это, блядь, как дать ребёнку не просто деньги, а кошелёк с замком, чтобы он не проёб всё сразу.

Короче, смысл в том, чтобы объект вообще не мог существовать в ебучем невалидном состоянии. Сделал проверки один раз — и спи спокойно, а не бегай потом с отладчиком, как угорелый, ища, где же опять вылезла эта манда с ушами.