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

Что такое принцип DRY?

1.2 Junior🔥 181 комментариев
#ООП и паттерны проектирования

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

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

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

Что такое принцип DRY (Don't Repeat Yourself)

Определение

DRY (Don't Repeat Yourself) — это фундаментальный принцип разработки, который гласит: "Каждый кусок знания должен находиться в одном месте и в одной форме".

Проще говоря: не копируй-пасти код. Если ты пишешь одну и ту же логику дважды, значит, нужно её вынести в отдельную функцию или класс.

Основная идея

Проблема: нарушение DRY

// ❌ НЕПРАВИЛЬНО: одна и та же валидация повторяется
public class UserService
{
    public void CreateUser(string email, string password)
    {
        // Валидация
        if (string.IsNullOrWhiteSpace(email))
            throw new ArgumentException("Email required");
        if (email.Length < 3)
            throw new ArgumentException("Email too short");
        if (!email.Contains("@"))
            throw new ArgumentException("Invalid email");
        if (string.IsNullOrWhiteSpace(password))
            throw new ArgumentException("Password required");
        if (password.Length < 8)
            throw new ArgumentException("Password too short");
        
        // Создание пользователя...
    }
    
    public void UpdateUser(string email, string password)
    {
        // ОДНА И ТА ЖЕ ВАЛИДАЦИЯ!
        if (string.IsNullOrWhiteSpace(email))
            throw new ArgumentException("Email required");
        if (email.Length < 3)
            throw new ArgumentException("Email too short");
        if (!email.Contains("@"))
            throw new ArgumentException("Invalid email");
        if (string.IsNullOrWhiteSpace(password))
            throw new ArgumentException("Password required");
        if (password.Length < 8)
            throw new ArgumentException("Password too short");
        
        // Обновление пользователя...
    }
}

Решение: следование DRY

// ✅ ПРАВИЛЬНО: логика вынесена в отдельный метод
public class UserService
{
    private void ValidateEmail(string email)
    {
        if (string.IsNullOrWhiteSpace(email))
            throw new ArgumentException("Email required");
        if (email.Length < 3)
            throw new ArgumentException("Email too short");
        if (!email.Contains("@"))
            throw new ArgumentException("Invalid email");
    }
    
    private void ValidatePassword(string password)
    {
        if (string.IsNullOrWhiteSpace(password))
            throw new ArgumentException("Password required");
        if (password.Length < 8)
            throw new ArgumentException("Password too short");
    }
    
    public void CreateUser(string email, string password)
    {
        ValidateEmail(email);
        ValidatePassword(password);
        // Создание пользователя...
    }
    
    public void UpdateUser(string email, string password)
    {
        ValidateEmail(email);      // Используем вынесенный метод
        ValidatePassword(password); // Используем вынесенный метод
        // Обновление пользователя...
    }
}

Преимущества DRY

1. Проще поддерживать код

Если нужно изменить логику валидации, меняешь её в одном месте, а не в пяти методах:

// Если требование изменилось: пароль теперь должен содержать цифру
private void ValidatePassword(string password)
{
    if (string.IsNullOrWhiteSpace(password))
        throw new ArgumentException("Password required");
    if (password.Length < 8)
        throw new ArgumentException("Password too short");
    if (!password.Any(char.IsDigit))
        throw new ArgumentException("Password must contain digit");
}

// Все методы, которые используют ValidatePassword(), автоматически получают новую логику

2. Меньше ошибок

Если копировать-пастить код, легко допустить ошибку в одном месте:

// ❌ НЕПРАВИЛЬНО: скопировали, но забыли обновить условие
public void CreateOrder(decimal amount)
{
    if (amount <= 0)
        throw new ArgumentException("Amount must be positive");
    // ...
}

public void UpdateOrder(decimal amount)
{
    if (amount < 0)  // ОШИБКА! Должно быть <= 0, как в CreateOrder
        throw new ArgumentException("Amount must be positive");
    // ...
}

3. Легче тестировать

Общая функция тестируется один раз, а не много раз:

// Тестируем валидацию один раз в одном тесте
[Test]
public void ValidateEmail_InvalidEmail_ThrowsException()
{
    // Arrange
    var service = new UserService();
    
    // Act & Assert
    Assert.Throws<ArgumentException>(() => service.ValidateEmail("invalid"));
}

// Остальные методы автоматически получают корректную валидацию

Практические примеры

Пример 1: Дублированные SQL запросы

// ❌ НЕПРАВИЛЬНО: SQL повторяется
public class UserRepository
{
    public User GetUserById(int id)
    {
        using (SqlConnection conn = new SqlConnection(_connectionString))
        {
            SqlCommand cmd = new SqlCommand("SELECT * FROM Users WHERE Id = @id", conn);
            cmd.Parameters.AddWithValue("@id", id);
            conn.Open();
            // Выполнение...
        }
    }
    
    public User GetUserByEmail(string email)
    {
        using (SqlConnection conn = new SqlConnection(_connectionString))
        {
            SqlCommand cmd = new SqlCommand("SELECT * FROM Users WHERE Email = @email", conn);
            cmd.Parameters.AddWithValue("@email", email);
            conn.Open();
            // Выполнение... (ДУБЛИРОВАНИЕ!)
        }
    }
}

// ✅ ПРАВИЛЬНО: используем ORM (Entity Framework)
public class UserRepository
{
    private readonly DbContext _context;
    
    public User GetUserById(int id) 
        => _context.Users.FirstOrDefault(u => u.Id == id);
    
    public User GetUserByEmail(string email) 
        => _context.Users.FirstOrDefault(u => u.Email == email);
}

Пример 2: Дублированная бизнес-логика

// ❌ НЕПРАВИЛЬНО: расчёт скидки повторяется
public class OrderService
{
    public decimal CalculatePrice(List<Item> items, bool isVip)
    {
        decimal total = items.Sum(i => i.Price);
        
        if (isVip)
            total = total * 0.8m;  // 20% скидка
        
        return total;
    }
    
    public decimal CalculatePriceWithTax(List<Item> items, bool isVip)
    {
        decimal total = items.Sum(i => i.Price);
        
        if (isVip)
            total = total * 0.8m;  // ДУБЛИРОВАНИЕ!
        
        decimal tax = total * 0.1m;
        return total + tax;
    }
}

// ✅ ПРАВИЛЬНО: логика в отдельном методе
public class OrderService
{
    private decimal ApplyDiscount(decimal total, bool isVip)
    {
        return isVip ? total * 0.8m : total;
    }
    
    public decimal CalculatePrice(List<Item> items, bool isVip)
    {
        decimal total = items.Sum(i => i.Price);
        return ApplyDiscount(total, isVip);
    }
    
    public decimal CalculatePriceWithTax(List<Item> items, bool isVip)
    {
        decimal total = items.Sum(i => i.Price);
        total = ApplyDiscount(total, isVip);
        return total + (total * 0.1m);
    }
}

Пример 3: Дублированные конфигурации

// ❌ НЕПРАВИЛЬНО: магические числа повторяются
public class ValidationService
{
    public bool ValidatePassword(string password)
        => password.Length >= 8;
    
    public bool ValidateUsername(string username)
        => username.Length >= 8;  // ДУБЛИРОВАНИЕ минимальной длины
}

// ✅ ПРАВИЛЬНО: константа в одном месте
public class ValidationService
{
    private const int MinLength = 8;
    
    public bool ValidatePassword(string password)
        => password.Length >= MinLength;
    
    public bool ValidateUsername(string username)
        => username.Length >= MinLength;
}

DRY vs WET

WET = Write Everything Twice — противоположность DRY, антипаттерн.

// ❌ WET: копируем код
for (int i = 0; i < count; i++) { /* ... */ }
// ... и где-то ещё:
for (int i = 0; i < count; i++) { /* ... */ }

// ✅ DRY: вынесли в метод
void ProcessCount(int count) { for (int i = 0; i < count; i++) { /* ... */ } }
ProcessCount(count);
ProcessCount(count);

Как найти нарушения DRY

  1. Ищи похожий код — Ctrl+F по коду, который кажется повторяющимся
  2. Цопи-паст — если копировал код из одного места, это сигнал DRY
  3. Множественные правки — если менял один баг в пяти местах, нарушена DRY
  4. Дублированные тесты — если пишешь похожие тесты, логика может быть вынесена

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

Иногда дублирование приемлемо:

1. Если логика отличается в деталях

// Две валидации ПОХОЖИ, но не совпадают:
// CreateUser требует email, UpdateUser может быть без email
public void CreateUser(string email) { /* ... */ }
public void UpdateUser(string email = null) { /* ... */ }
// Не стоит вынимать, если будут сложные условия

2. Если вынесение усложняет код

// Если общая функция получится очень параметризованной:
private bool Validate(string value, int minLength, int maxLength, bool nullable, bool requireDigit)
{
    // 10+ условий — лучше оставить дублирование
}

Краткие советы для интервью

  1. DRY = не дублировать знание — каждое требование в одном месте
  2. Вынеси общий код в функцию — быстро найти и изменить
  3. Используй константы, а не магические числа — легче менять требования
  4. Используй наследование и интерфейсы — для устранения дублирования типов
  5. Рефакторинг — это нормально — начни с простого кода, потом вынеси общее

Золотое правило: Если заметил, что пишешь третий раз одну логику, вынеси её. Если второй раз — подумай. Если первый раз — не преждевременно оптимизируй (YAGNI).

Что такое принцип DRY? | PrepBro