Clean Code на C#: принципы и примеры на практике
Чистый код (Clean Code) — это не просто модный термин, а фундаментальный навык профессионального разработчика. В экосистеме .NET и C#, где проекты часто живут годами и поддерживаются десятками программистов, умение писать понятный и легко изменяемый код становится критически важным. В этой статье мы разберем основные принципы Clean Code применительно к C# и рассмотрим конкретные примеры их применения.
Почему чистый код важен в C#?
C# — строго типизированный, объектно-ориентированный язык с богатой экосистемой. Однако его мощь может обернуться хаосом, если не следовать дисциплине. Чистый код уменьшает количество багов, ускоряет онбординг новых разработчиков и снижает стоимость поддержки. Как говорил Роберт Мартин: «Единственный способ быстро писать код — это писать его хорошо».
Принцип 1: Говорящие имена
Имена переменных, методов и классов должны раскрывать намерение. Не экономьте символы — плохой код требует больше времени на понимание.
Плохой пример
int d; // время с момента изменения
List<int> lst = new List<int>();
void Proc(int a, int b) { ... }
Хороший пример (Clean Code)
int elapsedTimeInDays;
List<int> customerIds = new List<int>();
void CalculateDiscount(decimal basePrice, decimal taxRate) { ... }
Правило: Если имя требует комментария, значит оно плохое. Используйте camelCase для переменных и PascalCase для методов и классов.
Принцип 2: Маленькие функции (SRP на уровне методов)
Каждая функция должна делать ровно одну вещь и делать её хорошо. Если метод содержит более 20-30 строк или имеет вложенность более двух уровней — это сигнал к рефакторингу.
Плохой пример: функция-швейцарский нож
public void ProcessOrder(Order order)
{
// Валидация
if (order == null || order.Items.Count == 0)
throw new ArgumentException("Invalid order");
// Расчет скидки
decimal discount = 0;
if (order.Total > 1000) discount = 0.1m;
else if (order.Total > 500) discount = 0.05m;
// Сохранение в БД
using var db = new AppDbContext();
db.Orders.Add(order);
db.SaveChanges();
// Отправка email
SendEmail(order.CustomerEmail, "Order received");
}
Хороший пример: разделение ответственности
public void ProcessOrder(Order order)
{
Validate(order);
var discount = CalculateDiscount(order.Total);
ApplyDiscount(order, discount);
SaveOrder(order);
SendConfirmationEmail(order);
}
private void Validate(Order order)
{
if (order == null || order.Items.Count == 0)
throw new ArgumentException("Invalid order");
}
private decimal CalculateDiscount(decimal total)
{
if (total > 1000) return 0.1m;
if (total > 500) return 0.05m;
return 0;
}
private void SaveOrder(Order order)
{
using var db = new AppDbContext();
db.Orders.Add(order);
db.SaveChanges();
}
Принцип 3: Избегайте дублирования (DRY)
Повторяющийся код — корень зла. Если вы копируете блок кода более одного раза, вынесите его в метод или класс.
Пример: дублирование логики
// В контроллере пользователей
if (user.Role == "Admin" || user.Role == "Manager")
GrantAccess();
// В контроллере отчетов
if (user.Role == "Admin" || user.Role == "Manager")
ShowReport();
Рефакторинг: инкапсуляция проверки
public static class UserExtensions
{
public static bool HasElevatedPermissions(this User user)
{
return user.Role == "Admin" || user.Role == "Manager";
}
}
// Использование
if (user.HasElevatedPermissions())
GrantAccess();
Принцип 4: Комментарии — зло (кроме исключений)
Комментарии часто врут и устаревают. Лучший комментарий — хорошо написанный код. Используйте комментарии только для объяснения почему вы сделали что-то неочевидное, а не что делает код.
Плохой комментарий
// Увеличиваем счетчик на 1
counter++;
Хороший комментарий (объяснение причины)
// Используем int вместо decimal для производительности,
// так как точность до 2 знаков не требуется
int priceInCents = (int)(price * 100);
Принцип 5: SOLID для архитектуры C#
Принципы SOLID — основа чистого кода в объектно-ориентированных языках. Рассмотрим главный из них — Принцип открытости/закрытости (OCP).
Нарушение OCP: класс, требующий изменений
public class DiscountCalculator
{
public decimal Calculate(Order order, string customerType)
{
if (customerType == "Regular") return order.Total * 0.05m;
if (customerType == "VIP") return order.Total * 0.15m;
if (customerType == "Employee") return order.Total * 0.25m;
return 0;
}
}
Решение: стратегия (Strategy Pattern)
public interface IDiscountStrategy
{
decimal Calculate(Order order);
}
public class VipDiscount : IDiscountStrategy
{
public decimal Calculate(Order order) => order.Total * 0.15m;
}
public class DiscountCalculator
{
private readonly IDiscountStrategy _strategy;
public DiscountCalculator(IDiscountStrategy strategy)
{
_strategy = strategy;
}
public decimal Calculate(Order order) => _strategy.Calculate(order);
}
Теперь добавление нового типа скидки не требует изменения существующего кода — просто создайте новый класс, реализующий IDiscountStrategy.
Практические рекомендации для .NET разработчиков
- Используйте LINQ осмысленно: цепочки из 10 методов LINQ — плохая практика. Разбивайте на промежуточные переменные с понятными именами.
- Избегайте магических чисел: выносите константы в
private constили отдельные классы конфигурации. - Применяйте null-безопас