Что такое принцип DRY (Don’t Repeat Yourself)?

Ответ

DRY (Don't Repeat Yourself — «Не повторяйся») — это фундаментальный принцип разработки ПО, который гласит: «Каждое знание должно иметь единственное, непротиворечивое и авторитетное представление в системе».

На практике это означает устранение дублирования логики, а не просто текста кода. Два фрагмента кода считаются дублированием, если изменение одного из них по логической необходимости требует изменения другого.

Пример нарушения DRY:

public class OrderProcessor
{
    public void Process(Order order)
    {
        // Дублирующаяся проверка и логирование
        if (order.Items.Count == 0)
        {
            Logger.Log("Заказ пуст. Обработка отменена.");
            return;
        }
        // Логика обработки...
    }

    public void Cancel(Order order)
    {
        // Та же самая проверка и логирование снова
        if (order.Items.Count == 0)
        {
            Logger.Log("Заказ пуст. Отмена невозможна.");
            return;
        }
        // Логика отмены...
    }
}

Исправленный вариант (следование DRY):

public class OrderValidator
{
    // Знание о валидации непустого заказа представлено в одном месте.
    public static bool ValidateNotEmpty(Order order, string operationName)
    {
        if (order.Items.Count == 0)
        {
            Logger.Log($"Заказ пуст. Операция '{operationName}' отменена.");
            return false;
        }
        return true;
    }
}

public class OrderProcessor
{
    public void Process(Order order)
    {
        if (!OrderValidator.ValidateNotEmpty(order, nameof(Process))) return;
        // Логика обработки...
    }

    public void Cancel(Order order)
    {
        if (!OrderValidator.ValidateNotEmpty(order, nameof(Cancel))) return;
        // Логика отмены...
    }
}

Преимущества следования DRY:

  • Снижение количества ошибок: Исправление или улучшение логики требуется только в одном месте.
  • Упрощение поддержки: Код становится легче читать и изменять.
  • Повышение согласованности: Поведение системы становится предсказуемым, так как одинаковая логика не разбросана по кодовой базе.

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

Ответ 18+ 🔞

Ну вот смотри, этот ваш DRY, он же как в жизни — если одно и то же по десять раз на дню повторять, так и ебнуться недолго. Суть-то проще пареной репы: каждая фигня в твоём коде должна жить в одном-единственном месте, и точка.

Не в том дело, чтобы просто скопипастить строчки в функцию, а в том, чтобы знание — как эта штука работает — не было размазано по всей кодовой базе. Если меняешь логику в одном месте, а в другом забываешь — пиши пропало, будет тебе баг, как корове седло.

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

public class OrderProcessor
{
    public void Process(Order order)
    {
        // Вот эта проверка с логом...
        if (order.Items.Count == 0)
        {
            Logger.Log("Заказ пуст. Обработка отменена.");
            return;
        }
        // ...она же, блядь, через два метода...
    }

    public void Cancel(Order order)
    {
        // ...повторяется слово в слово! Чистой воды копипаста!
        if (order.Items.Count == 0)
        {
            Logger.Log("Заказ пуст. Отмена невозможна.");
            return;
        }
        // ...
    }
}

Представь, заказчику взбрело в голову, что "пустой заказ" — это не когда ноль items, а когда общая сумма нулевая. Иди теперь ищи все эти order.Items.Count == 0 по всей программе, их же, наверное, дохуя! Обосрёшься, пока всё поправишь.

А вот как надо, по-человечески:

public class OrderValidator
{
    // Всё! Знание о том, как проверить заказ на пустоту, живёт тут. В одном месте, блять.
    public static bool ValidateNotEmpty(Order order, string operationName)
    {
        if (order.Items.Count == 0)
        {
            Logger.Log($"Заказ пуст. Операция '{operationName}' отменена.");
            return false;
        }
        return true;
    }
}

public class OrderProcessor
{
    public void Process(Order order)
    {
        // Просто юзаем знание оттуда
        if (!OrderValidator.ValidateNotEmpty(order, nameof(Process))) return;
        // ...
    }

    public void Cancel(Order order)
    {
        // И тут юзаем. И везде оно одинаковое.
        if (!OrderValidator.ValidateNotEmpty(order, nameof(Cancel))) return;
        // ...
    }
}

Теперь если заказчик ебёт мозг, меняешь логику в одном месте — в OrderValidator.ValidateNotEmpty. И всё, пиздец. Во всём приложении проверка сразу станет правильной. Красота!

Что это даёт, кроме спокойного сна?

  • Меньше косяков: Поправил один раз — и везде применилось. Не будет ситуации "а тут исправил, а тут забыл".
  • Легче ковыряться: Понимаешь, где что искать. Вся логика не раскидана, а сгруппирована.
  • Всё стабильно: Система ведёт себя одинаково, потому что не может быть трёх разных реализаций одной и той же хуйни.

Но и тут есть подводный камень, ёпта! Не надо с этим принципом, как дурень с писаной торбой, носиться. Если видишь два похожих куска кода, но не уверен на все сто, что они всегда будут меняться вместе — не торопись их склеивать. Получится какая-нибудь монстрообразная функция с кучей if-ов, которую потом и сам не разберёшь. Иногда лучше пусть будет два простых, но понятных дубликата, чем одна "умная" хуйня, которая всех запутает. Сначала пойми, что к чему, а потом уже рефактори.