DRY (Don't Repeat Yourself)

DRY — принцип разработки, требующий, чтобы каждый фрагмент знания имел единственное, недвусмысленное, авторитетное представление в системе.

Зачем нужно

Дублирование кода приводит к багам: изменяя логику в одном месте, легко забыть обновить копию. DRY сокращает объём кода, упрощает поддержку и снижает вероятность ошибок.

Где используется

  • Рефакторинг повторяющегося кода
  • Вынесение общей логики в функции/модули
  • CSS-переменные вместо дублирования значений
  • HTML-шаблоны и компоненты
  • Конфигурации и константы

Предпосылки

DRY в JavaScript

До — код с дублированием

// ПЛОХО: логика валидации повторяется
function createUser(name, email) {
  if (!name || name.length < 2) {
    throw new Error('Имя должно быть не менее 2 символов');
  }
  if (!email || !email.includes('@')) {
    throw new Error('Некорректный email');
  }
  return { name, email, createdAt: new Date };
}

function updateUser(user, name, email) {
  if (!name || name.length < 2) {
    throw new Error('Имя должно быть не менее 2 символов');
  }
  if (!email || !email.includes('@')) {
    throw new Error('Некорректный email');
  }
  user.name = name;
  user.email = email;
  user.updatedAt = new Date();
  return user;
}

После — DRY

// ХОРОШО: валидация вынесена в отдельные функции
function validateName(name) {
  if (!name || name.length < 2) {
    throw new Error('Имя должно быть не менее 2 символов');
  }
}

function validateEmail(email) {
  if (!email || !email.includes('@')) {
    throw new Error('Некорректный email');
  }
}

function validateUser(name, email) {
  validateName(name);
  validateEmail(email);
}

function createUser(name, email) {
  validateUser(name, email);
  return { name, email, createdAt: new Date };
}

function updateUser(user, name, email) {
  validateUser(name, email);
  Object.assign(user, { name, email, updatedAt: new Date });
  return user;
}

DRY в HTML

До

<!-- ПЛОХО: повторяющаяся структура карточки -->
<div class="card">
  <img src="photo1.jpg" style="width:200px; border-radius:8px;">
  <h3 style="color:#333; font-size:18px;">Продукт 1</h3>
  <p style="color:#666; font-size:14px;">Описание продукта 1</p>
  <button style="background:#007bff; color:white; padding:8px 16px;">Купить</button>
</div>
<div class="card">
  <img src="photo2.jpg" style="width:200px; border-radius:8px;">
  <h3 style="color:#333; font-size:18px;">Продукт 2</h3>
  <p style="color:#666; font-size:14px;">Описание продукта 2</p>
  <button style="background:#007bff; color:white; padding:8px 16px;">Купить</button>
</div>

После — шаблонизация через JS

// ХОРОШО: одна функция генерирует карточки
function renderCard({ image, title, description }) {
  return `
    <div class="card">
      <img src="${image}" class="card__image">
      <h3 class="card__title">${title}</h3>
      <p class="card__description">${description}</p>
      <button class="card__button">Купить</button>
    </div>
  `;
}

const products = [
  { image: 'photo1.jpg', title: 'Продукт 1', description: 'Описание 1' },
  { image: 'photo2.jpg', title: 'Продукт 2', description: 'Описание 2' },
];

document.querySelector('#catalog').innerHTML =
  products.map(renderCard).join('');

DRY в CSS

/* ПЛОХО: повторяющиеся значения */
.header { background-color: #007bff; color: white; }
.button { background-color: #007bff; color: white; }
.link { color: #007bff; }
.footer { border-top: 2px solid #007bff; }

/* ХОРОШО: CSS-переменные */
:root {
  --color-primary: #007bff;
  --color-text-light: white;
}

.header { background-color: var(--color-primary); color: var(--color-text-light); }
.button { background-color: var(--color-primary); color: var(--color-text-light); }
.link { color: var(--color-primary); }
.footer { border-top: 2px solid var(--color-primary); }

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

// 1. Функции — самый простой способ
function formatPrice(amount, currency = 'RUB') {
  return new Intl.NumberFormat('ru-RU', {
    style: 'currency', currency
  }).format(amount);
}

// 2. Модули — экспорт/импорт общей логики
// utils/validation.js
export function isEmail(str) {
  return /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(str);
}

// 3. Классы — объединение связанных методов
class Validator {
  static isEmail(str) { /* ... */ }
  static isPhone(str) { /* ... */ }
  static isRequired(str) { return str != null && str !== ''; }
}

// 4. Замыкания — фабрики функций
function createValidator(rules) {
  return function validate(value) {
    return rules.every(rule => rule(value));
  };
}

const validatePassword = createValidator([
  val => val.length >= 8,
  val => /[A-Z]/.test(val),
  val => /[0-9]/.test(val),
]);

Когда НЕ применять DRY

DRY — не абсолютный закон. Слепое следование приводит к Wrong Abstraction (неправильной абстракции).

// Две функции ПОХОЖИ, но обслуживают разную логику
// ПЛОХО: насильно объединять их ради DRY
function handlePayment(type, amount, ...args) {
  if (type === 'credit') {
    // 20 строк логики кредитных карт
  } else if (type === 'crypto') {
    // 20 строк совершенно другой логики
  } else if (type === 'bank') {
    // ещё 20 строк
  }
}

// ЛУЧШЕ: отдельные функции, даже если есть небольшое повторение
function handleCreditPayment(amount, cardDetails) { /* ... */ }
function handleCryptoPayment(amount, walletAddress) { /* ... */ }
function handleBankPayment(amount, bankAccount) { /* ... */ }

Правило: дублирование дешевле неправильной абстракции. Объединяйте код только когда:

  • Повторение происходит 3+ раз (Rule of Three)
  • Причина повторения одна и та же
  • Изменения всегда применяются ко всем копиям одновременно

Частые ошибки

  1. DRY ради DRY. Объединение несвязанного кода создаёт хрупкие зависимости
  2. Преждевременная абстракция. Не абстрагируйте до 3 повторений
  3. Путать DRY кода и DRY знания. DRY — про знание (бизнес-правило), а не про совпадение строк кода
  4. Игнорировать DRY в конфигах. Magic numbers и захардкоженные строки — тоже дублирование

Практика

  1. Найдите повторяющийся код в своём проекте и вынесите в функцию
  2. Замените магические числа константами:
    // До
    if (age >= 18) { /* ... */ }
    // После
    const MINIMUM_AGE = 18;
    if (age >= MINIMUM_AGE) { /* ... */ }
    
  3. Создайте модуль utils.js с 3-5 переиспользуемыми функциями

Связанные темы

Ресурсы

  • The Pragmatic Programmer (Hunt, Thomas) — оригинальное определение DRY
  • Sandi Metz — The Wrong Abstraction (статья)
  • refactoring.guru — каталог рефакторингов