Мы все бывали в такой ситуации — смотрели на два почти одинаковых блока кода, как растерянные близнецы на семейном воссоединении: «Разве ты не Билли?» «Нет, я Боб!» «Но у вас обоих одинаковый нос!» Такое космическое дублирование кода — именно то, что пытается предотвратить принцип DRY (Don’t Repeat Yourself). Давайте разберёмся, как применять этот принцип, не превращая нашу кодовую базу в излишне усложнённую машину Рубе Голдберга.

Принцип DRY демистифицирован

DRY — это не просто предотвращение копирования и вставки, это управление знаниями. Как сказано в книге «Прагматичный программист»:

«Каждое знание должно иметь единственное, однозначное и авторитетное представление».

Представьте себе этот ужас с SQL:

-- Отчёт A
SELECT 
    user_id,
    COUNT(*) AS total_orders,
    SUM(CASE WHEN status = 'shipped' THEN 1 ELSE 0 END) AS shipped_orders
FROM orders
WHERE created_at > '2025-01-01'
GROUP BY user_id;
-- Отчёт B  
SELECT
    user_id,
    COUNT(*) AS total_orders,
    SUM(CASE WHEN status = 'returned' THEN 1 ELSE 0 END) AS returned_orders  
FROM orders
WHERE created_at > '2025-01-01'
GROUP BY user_id;
flowchart TD A[Дублирующий код] --> B{Необходимы изменения?} B -->|Да| C[Обновление в нескольких местах] B -->|Нет| D[Накопление технического долга] C --> E[Потенциальные ошибки] D --> F[Кошмар обслуживания]

Решение? Выделить общий паттерн:

CREATE MACRO order_summary(status_filter) AS (
    SELECT
        user_id,
        COUNT(*) AS total_orders,
        SUM(CASE WHEN status = status_filter THEN 1 ELSE 0 END) AS filtered_orders
    FROM orders
    WHERE created_at > '2025-01-01'
    GROUP BY user_id
);
-- Использование
SELECT * FROM order_summary('shipped');
SELECT * FROM order_summary('returned');

Теперь при изменении фильтра даты мы обновляем одно место, а не играем в «Ударь крота».

Когда DRY идёт не так

Я однажды столкнулся с «помощной» функцией, которая выглядела так, будто пыталась решить проблему голода в мире. Оригинальный разработчик настолько агрессивно использовал абстракции, что для навигации по коду требовались компас и шерпа. Помните: абстракции должны уменьшать сложность, а не создавать её.

# Преступление против читабельности
def process_data(data, format='json', validate=True, sanitize=False, 
                 log_level='DEBUG', callback=None, retries=3):
    # 200 строк условной лапши
    ...

против

# Просто и сфокусированно
def parse_json(data):
    ...
def validate_user_input(data):
    ...
# Составное и понятное
valid_data = validate_user_input(parse_json(raw_data))
flowchart LR A[Дублирующий код] --> B(Абстракция) B --> C{Хороший дизайн?} C -->|Да| D[Чистый поддерживаемый код] C -->|Нет| E[Чрезмерно усложнённый беспорядок] E --> F[Труднее отлаживать] F --> G[Повышенная когнитивная нагрузка]

Зона Златовласки повторного использования кода

  1. Обнаружение дублирования через распознавание паттернов
  • Один и тот же код с разными литералами? Параметризуйте его.
  • Аналогичная логика в нескольких сервисах? Создайте общую библиотеку.
  1. Применяйте правило трёх
  • Первое вхождение: пусть будет.
  • Второе вхождение: сделайте заметку.
  • Третье вхождение: время для абстракции.
  1. Учитывайте изменчивость
  • Будут ли будущие изменения влиять на все экземпляры одинаково?
  • Является ли абстракция специфичной для домена или универсальной?
  1. Пробуйте
// До DRY
function calculateTax(price) { return price * 0.2; }
function calculateVAT(amount) { return amount * 0.2; }
// После DRY
const TAX_RATE = 0.2;
const calculateTax = (value) => value * TAX_RATE;

Но будьте осторожны с ложными срабатываниями! Похоже выглядящий код не всегда означает одинаковое назначение. Я однажды «исправил» два похожих платёжных процессора только для того, чтобы узнать, что один обрабатывал йены, а другой — евро. Упс!

Советы для приверженцев DRY

  • Религиозно документируйте абстракции Тот умный универсальный парсер, который вы сделали? Через шесть месяцев он будет выглядеть как древние иероглифы без комментариев.
  • Мудро используйте генерацию кода Для шаблонов, которые утомительны, но стабильны:
# Генерация CRUD endpoints
./generate-endpoint.sh User name:string email:string
  • Используйте чит-код современных IDE Используйте структурный поиск/замену по проектам, чтобы найти кандидатов на дублирование.
  • Знайте, когда нарушать правила Код прототипа? Возможно, дублирование выполняется быстрее, чем правильная абстракция. Просто добавьте комментарий // TODO: Переделать перед отправкой (и действительно сделайте это!)

Парадокс обслуживания

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

Золотая середина? Относитесь к DRY как к соусу чили — достаточно, чтобы усилить вкус, но не настолько, чтобы сжечь всю систему. Ваше будущее «я» (и ваши товарищи по команде) будут благодарить вас, когда придёт следующее изменение требований.

А теперь прошу прощения, мне нужно проверить, не создал ли я случайно круговую зависимость в своей метафоре о качестве кода…