← Назад к вопросам

Когда не стоит использовать дублирующий код?

2.0 Middle🔥 92 комментариев
#Архитектура и паттерны

Комментарии (2)

🐱
claude-haiku-4.5PrepBro AI29 мар. 2026 г.(ред.)

Ответ сгенерирован нейросетью и может содержать ошибки

Дублирующий код: когда его использовать нельзя (DRY принцип)

Суть принципа DRY (Don't Repeat Yourself)

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

Правило: Если код повторяется 3+ раза — пора рефакторить.

Когда НЕЛЬЗЯ использовать дублирующий код

1. Одинаковая бизнес-логика

// ❌ ПЛОХО — дублирование валидации пользователя
async function registerUser(email: string, password: string) {
  if (!email || !email.includes('@')) {
    throw new Error('Invalid email');
  }
  if (password.length < 8) {
    throw new Error('Password too short');
  }
  // ... регистрация
}

async function updateUserProfile(email: string, password: string) {
  if (!email || !email.includes('@')) {
    throw new Error('Invalid email');
  }
  if (password.length < 8) {
    throw new Error('Password too short');
  }
  // ... обновление
}

// ✅ ХОРОШО — выносим валидацию в функцию
function validateUserInput(email: string, password: string) {
  if (!email || !email.includes('@')) {
    throw new Error('Invalid email');
  }
  if (password.length < 8) {
    throw new Error('Password too short');
  }
}

async function registerUser(email: string, password: string) {
  validateUserInput(email, password);
  // ... регистрация
}

async function updateUserProfile(email: string, password: string) {
  validateUserInput(email, password);
  // ... обновление
}

2. Повторяющаяся обработка ошибок

// ❌ ПЛОХО — дублирование обработки ошибок
async function getUser(id: string) {
  try {
    const response = await fetch(`/api/users/${id}`);
    if (!response.ok) {
      logger.error(`HTTP ${response.status}:`, response.statusText);
      throw new Error('Failed to fetch user');
    }
    return await response.json();
  } catch (error) {
    logger.error('Network error:', error);
    throw error;
  }
}

async function getPost(id: string) {
  try {
    const response = await fetch(`/api/posts/${id}`);
    if (!response.ok) {
      logger.error(`HTTP ${response.status}:`, response.statusText);
      throw new Error('Failed to fetch post');
    }
    return await response.json();
  } catch (error) {
    logger.error('Network error:', error);
    throw error;
  }
}

// ✅ ХОРОШО — выносим HTTP логику в обёртку
async function fetchJson<T>(url: string): Promise<T> {
  try {
    const response = await fetch(url);
    if (!response.ok) {
      logger.error(`HTTP ${response.status}:`, response.statusText);
      throw new Error(`HTTP ${response.status}`);
    }
    return await response.json();
  } catch (error) {
    logger.error('Network error:', error);
    throw error;
  }
}

async function getUser(id: string) {
  return fetchJson(`/api/users/${id}`);
}

async function getPost(id: string) {
  return fetchJson(`/api/posts/${id}`);
}

3. Дублирование структуры объектов

// ❌ ПЛОХО — повторяющийся DTO маппинг
async function getUserDTO(id: string) {
  const user = await db.users.findById(id);
  return {
    id: user.id,
    name: user.name,
    email: user.email,
    createdAt: user.createdAt,
  };
}

async function getUsersListDTO(ids: string[]) {
  const users = await db.users.findByIds(ids);
  return users.map(user => ({
    id: user.id,
    name: user.name,
    email: user.email,
    createdAt: user.createdAt,
  }));
}

// ✅ ХОРОШО — выносим трансформацию
function toUserDTO(user: User): UserDTO {
  return {
    id: user.id,
    name: user.name,
    email: user.email,
    createdAt: user.createdAt,
  };
}

async function getUserDTO(id: string) {
  const user = await db.users.findById(id);
  return toUserDTO(user);
}

async function getUsersListDTO(ids: string[]) {
  const users = await db.users.findByIds(ids);
  return users.map(toUserDTO);
}

4. Дублирование SQL запросов

// ❌ ПЛОХО — один и тот же WHERE clause повторяется
async function getActiveUsers() {
  return db.query(`
    SELECT * FROM users 
    WHERE deleted_at IS NULL 
    AND status = 'active'
  `);
}

async function countActiveUsers() {
  return db.query(`
    SELECT COUNT(*) FROM users 
    WHERE deleted_at IS NULL 
    AND status = 'active'
  `);
}

async function deleteActiveUsers() {
  return db.query(`
    DELETE FROM users 
    WHERE deleted_at IS NULL 
    AND status = 'active'
  `);
}

// ✅ ХОРОШО — использовать query builder или параметризованные запросы
const ACTIVE_USER_FILTER = {
  where: {
    deletedAt: null,
    status: 'active'
  }
};

async function getActiveUsers() {
  return db.users.find(ACTIVE_USER_FILTER);
}

async function countActiveUsers() {
  return db.users.count(ACTIVE_USER_FILTER);
}

async function deleteActiveUsers() {
  return db.users.deleteMany(ACTIVE_USER_FILTER);
}

5. Дублирование констант

// ❌ ПЛОХО — магические числа повторяются
function validatePassword(pwd: string): boolean {
  return pwd.length >= 8 && pwd.length <= 128;
}

function generatePasswordResetToken(): string {
  return randomString(128); // Откуда 128?
}

async function storePassword(hash: string): Promise<void> {
  if (hash.length > 128) {
    throw new Error('Hash too long');
  }
}

// ✅ ХОРОШО — выносим константы
const PASSWORD_CONFIG = {
  MIN_LENGTH: 8,
  MAX_LENGTH: 128,
  RESET_TOKEN_LENGTH: 128,
} as const;

function validatePassword(pwd: string): boolean {
  return pwd.length >= PASSWORD_CONFIG.MIN_LENGTH && 
         pwd.length <= PASSWORD_CONFIG.MAX_LENGTH;
}

function generatePasswordResetToken(): string {
  return randomString(PASSWORD_CONFIG.RESET_TOKEN_LENGTH);
}

async function storePassword(hash: string): Promise<void> {
  if (hash.length > PASSWORD_CONFIG.MAX_LENGTH) {
    throw new Error('Hash too long');
  }
}

Исключения из DRY (когда дублирование допустимо)

1. Случайное совпадение кода (False DRY)

// Это НЕ одна логика, они независимые!
// ❌ НЕПРАВИЛЬНО объединять:

function calculateUserScore(purchases: number, reviews: number): number {
  return purchases * 10 + reviews * 5;
}

function calculateProductRecommendationScore(
  salesCount: number, 
  ratingCount: number
): number {
  return salesCount * 10 + ratingCount * 5; // Выглядит как дубль!
}

// ✅ ПРАВИЛЬНО — оставить отдельно:
// Это разные метрики с разным смыслом!
// Если в одной нужно изменить логику — нельзя менять другую

2. Разные уровни абстракции

// ✅ Допустимо дублирование на разных уровнях:

// Service level (бизнес-логика)
class UserService {
  async createUser(data: CreateUserDTO) {
    if (!data.email.includes('@')) {
      throw new Error('Invalid email');
    }
    return await this.repository.save(data);
  }
}

// API level (валидация входа)
@Post('/users')
async createUserEndpoint(@Body() body: any) {
  if (!body.email || !body.email.includes('@')) {
    return { error: 'Invalid email' };
  }
  return await this.userService.createUser(body);
}

// Это НЕ дублирование! Разные слои, разные ответственности.
// API проверяет формат данных, Service проверяет бизнес-правила.

3. Производительность и читаемость

// ✅ Иногда дублирование УЛУЧШАЕТ читаемость:

// Отдельные функции явно показывают намерение
export function validateEmail(email: string): boolean {
  return /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(email);
}

export function validatePhoneNumber(phone: string): boolean {
  return /^\+?[\d\s-]{10,}$/.test(phone);
}

// Хотя regex похожи, их объединение усложнит код:
// ❌ validateContact(value, 'email') — менее понятно

Как рефакторить дублирование

Шаги:

  1. Найти повторяющийся паттерн
  2. Выделить в отдельную функцию/класс
  3. Параметризировать различия
  4. Использовать везде
// Было:
function processUserData(user: User) {
  const name = user.firstName + ' ' + user.lastName;
  const email = user.contactEmail || user.primaryEmail;
  console.log(`User: ${name}, ${email}`);
}

function processAdminData(admin: Admin) {
  const name = admin.firstName + ' ' + admin.lastName;
  const email = admin.contactEmail || admin.primaryEmail;
  console.log(`Admin: ${name}, ${email}`);
}

// Стало:
interface Person {
  firstName: string;
  lastName: string;
  contactEmail?: string;
  primaryEmail: string;
}

function getDisplayName(person: Person): string {
  return `${person.firstName} ${person.lastName}`;
}

function getEmail(person: Person): string {
  return person.contactEmail || person.primaryEmail;
}

function displayPerson(person: Person, role: string) {
  console.log(`${role}: ${getDisplayName(person)}, ${getEmail(person)}`);
}

Заключение

Правила:

  • DRY — главный принцип, соблюдай его
  • Три раза — уже много — рефакторь после третьего повтора
  • False DRY — не объединяй случайно совпадающий код
  • Читаемость выше DRY — если объединение усложнит код, лучше дублировать
  • Разные слои — нормально, если одна логика на API и другая на Service

Правильное применение DRY — это баланс между DRY и других принципов (KISS, SOLID).

Когда не стоит использовать дублирующий код? | PrepBro