Назад
Назад к заметкам
· 4 мин чтения

DRY в программировании: как избежать дублирования кода

Принцип "Don't Repeat Yourself" помогает избавиться от повторяющихся фрагментов, упростить поддержку проекта и снизить количество багов.

programmingbest-practicesrefactoringdry-principle

Если в проекте каждый второй компонент копирует один и тот же кусок логики, возникает вопрос: “Почему я все время пишу одно и то же?”.

Дублирование приводит к росту багов, усложненному тестированию и бесконечным правкам. Разберем, что такое DRY в программировании, почему он важен и как внедрить его без лишних сложностей.

Почему DRY важен для качества кода

Принцип Don’t Repeat Yourself не просто красивая фраза, а фундамент устойчивой архитектуры. Когда одна бизнес-логика реализуется в нескольких местах, ее изменение требует правки в каждом дублированном фрагменте. Шанс пропустить один из них и получить скрытый баг резко возрастает. На практике DRY сокращает объем поддержки и упрощает рефакторинг: меняем только один “источник правды”. Подробнее о влиянии повторения на качество кода можно почитать в статье о рефакторинге.

Как выявить дублирование в проекте

Самый простой способ поиск по репозиторию. Более системный подход включает:

  • Анализ git-history: частые коммиты с похожими изменениями указывают на скрытое дублирование.
  • Статический анализ: инструменты вроде ESLint или SonarQube отмечают похожие функции.
  • Код-ревью: задавайте вопрос “Есть ли уже готовый кусок, который можно переиспользовать?”.

Таблица типичных мест дублирования

ОбластьПризнак дублированияКак решить
UI-компонентыПохожие шаблоны разметкиВыделить в общий компонент
Сетевые запросыПовторяющиеся URL и обработка ошибокСоздать apiClient-модуль
Валидация формОдни и те же правила в разных формахВыделить в отдельный schema
КонстантыОдни и те же строки/числа в разных файлахПоместить в constants.js
Логика бизнес-правилДублирование условных ветокИнкапсулировать в сервисный слой

Пример без дублирования: сервис авторизации

До (дублирование в двух местах):

// auth.js
export function login(email, password) {
  return fetch("/api/login", {
    method: "POST",
    body: JSON.stringify({ email, password }),
  })
    .then((r) => r.json())
    .then((data) => {
      if (data.error) throw new Error(data.error);
      localStorage.setItem("token", data.token);
      return data;
    });
}

// profile.js
export function updateProfile(data) {
  return fetch("/api/profile", {
    method: "PUT",
    headers: { Authorization: `Bearer ${localStorage.getItem("token")}` },
    body: JSON.stringify(data),
  })
    .then((r) => r.json())
    .then((res) => {
      if (res.error) throw new Error(res.error);
      return res;
    });
}

Логика получения токена, обработки ошибок и парсинга JSON повторяется.

После (DRY-решение):

// apiClient.js единый клиент
export async function apiClient(url, options = {}) {
  const defaults = {
    headers: {
      "Content-Type": "application/json",
      ...(options.auth && {
        Authorization: `Bearer ${localStorage.getItem("token")}`,
      }),
    },
    body: options.body ? JSON.stringify(options.body) : undefined,
  };
  const response = await fetch(url, { ...defaults, ...options });
  const data = await response.json();
  if (data.error) throw new Error(data.error);
  return data;
}

// auth.js
import { apiClient } from "./apiClient";
export function login(email, password) {
  return apiClient("/api/login", { method: "POST", body: { email, password } });
}

// profile.js
import { apiClient } from "./apiClient";
export function updateProfile(data) {
  return apiClient("/api/profile", { method: "PUT", auth: true, body: data });
}

Весь повторяющийся код сосредоточен в apiClient. Добавлять новые запросы теперь проще достаточно указать путь и параметры. Похожий подход раскрывается в руководстве по модульному подход.

Как внедрять принцип постепенно

  1. Идентифицировать “скользкие” места: список функций или компонентов, которые часто меняются одновременно.
  2. Создать общую абстракцию: отдельный модуль или хук (React) для переноса повторяющегося кода.
  3. Писать тесты: убедитесь, что после рефакторинга поведение осталось тем же. Тесты ваш страховочный полис, когда меняете “источник правды”.
  4. Не переусердствовать: если абстракция слишком общая, ее будет трудно понять. Лучше несколько небольших и понятных функций, чем одна огромная.

Ловушки при применении принципа

  • Преждевременная оптимизация: создание общих модулей до того, как обнаружено реальное дублирование, приводит к избыточному коду.
  • Слишком абстрактные названия: функции вроде doIt() ничего не объясняют и только усложняют чтение.
  • Забытый контекст: одинаковый код в разных частях проекта может зависеть от разных окружений; вынесение в общий модуль может скрыть важные отличия.
  • Отсутствие документации: без пояснений новые разработчики могут копировать код заново.
  • Игнорирование тестов: без покрывающих тестов изменения в “общем” месте легко сломают несколько функций одновременно.

FAQ / чеклист

  • Как проверить, что DRY-принцип соблюден? Прогоните статический анализатор и посмотрите количество дублирующих блоков.
  • Можно ли полностью избавиться от дублирования? Нет. Иногда дублирование оправдано, если оно повышает читаемость.
  • Что делать, если абстракция стала слишком громоздкой? Разбейте ее на более мелкие части, следуя принципу Single Responsibility.
  • Как убедиться, что рефакторинг не сломал бизнес-логику? Пишите короткие юнит-тесты до и после изменений.
  • Какие типичные ошибки при вынесении кода в сервисы? Ошибки с контекстом (например, this в классах) и забытые зависимости в import-ах.

Если ваш проект уже сталкивается с постоянными багами из-за повторяющихся функций, применение DRY поможет собрать все “источники правды” в одном месте и снизить нагрузку на поддержку. Именно в такие моменты принцип экономит время и делает код более предсказуемым.

Готов начать?

Выбери удобный способ связи — напиши напрямую или оставь заявку

Написать в Telegram

Отвечу в течение пары часов