Мы все бывали в такой ситуации — смотрели на два почти одинаковых блока кода, как растерянные близнецы на семейном воссоединении: «Разве ты не Билли?» «Нет, я Боб!» «Но у вас обоих одинаковый нос!» Такое космическое дублирование кода — именно то, что пытается предотвратить принцип 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;
Решение? Выделить общий паттерн:
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))
Зона Златовласки повторного использования кода
- Обнаружение дублирования через распознавание паттернов
- Один и тот же код с разными литералами? Параметризуйте его.
- Аналогичная логика в нескольких сервисах? Создайте общую библиотеку.
- Применяйте правило трёх
- Первое вхождение: пусть будет.
- Второе вхождение: сделайте заметку.
- Третье вхождение: время для абстракции.
- Учитывайте изменчивость
- Будут ли будущие изменения влиять на все экземпляры одинаково?
- Является ли абстракция специфичной для домена или универсальной?
- Пробуйте
// До 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 как к соусу чили — достаточно, чтобы усилить вкус, но не настолько, чтобы сжечь всю систему. Ваше будущее «я» (и ваши товарищи по команде) будут благодарить вас, когда придёт следующее изменение требований.
А теперь прошу прощения, мне нужно проверить, не создал ли я случайно круговую зависимость в своей метафоре о качестве кода…