Комментарии (1)
Ответ сгенерирован нейросетью и может содержать ошибки
Что такое принцип 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
- Ищи похожий код — Ctrl+F по коду, который кажется повторяющимся
- Цопи-паст — если копировал код из одного места, это сигнал DRY
- Множественные правки — если менял один баг в пяти местах, нарушена DRY
- Дублированные тесты — если пишешь похожие тесты, логика может быть вынесена
Когда НЕ применять 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+ условий — лучше оставить дублирование
}
Краткие советы для интервью
- DRY = не дублировать знание — каждое требование в одном месте
- Вынеси общий код в функцию — быстро найти и изменить
- Используй константы, а не магические числа — легче менять требования
- Используй наследование и интерфейсы — для устранения дублирования типов
- Рефакторинг — это нормально — начни с простого кода, потом вынеси общее
Золотое правило: Если заметил, что пишешь третий раз одну логику, вынеси её. Если второй раз — подумай. Если первый раз — не преждевременно оптимизируй (YAGNI).