Clean Code на C#: принципы написания читаемого кода с примерами

Онлайн-тренажер Python для начинающих

Изучайте Python легко и без перегрузки теорией. Решайте практические задачи с автоматической проверкой, получайте подсказки на русском языке и пишите код прямо в браузере — без необходимости что-либо устанавливать.

Начать курс

Введение: Почему чистый код — залог успешного проекта?

В мире разработки программного обеспечения на платформе .NET написание рабочего кода — это лишь половина дела. Настоящее мастерство проявляется в умении создавать код, который легко читать, поддерживать и расширять. Концепция Clean Code (Чистый код), популяризированная Робертом Мартином, представляет собой набор принципов, которые превращают хаотичную программу в стройную, понятную систему. Для C#-разработчика следование этим принципам означает не только уважение к коллегам, но и значительное сокращение времени на отладку и внедрение новых фич.

В этой статье мы разберем ключевые принципы Clean Code применительно к языку C# и фреймворку .NET. Вы увидите конкретные примеры "плохого" и "хорошего" кода, которые помогут вам писать более качественные приложения.

Принцип 1: Говорящие имена и единый стиль

Правильное именование переменных и методов

Имя переменной, класса или метода должно отвечать на вопросы: "Что это?" или "Что оно делает?". Избегайте однобуквенных сокращений (кроме счетчиков циклов) и абстрактных названий вроде data, info, temp.

Плохой пример:

public int Calc(int a, int b) // Что такое a и b? Что делает метод?
{
    int d = a * b;
    return d;
}

Хороший пример (Clean Code):

/// <summary>
/// Вычисляет площадь прямоугольника.
/// </summary>
public int CalculateRectangleArea(int width, int height)
{
    int area = width * height;
    return area;
}

Единообразие в команде

Используйте camelCase для локальных переменных и параметров, PascalCase для методов и классов, _camelCase для приватных полей (согласно популярным гайдлайнам .NET). Это делает код предсказуемым.

public class OrderService
{
    private readonly IOrderRepository _orderRepository; // приватное поле

    public async Task<Order> GetOrderByIdAsync(int orderId) // метод
    {
        var order = await _orderRepository.FindAsync(orderId); // локальная переменная
        return order;
    }
}

Принцип 2: Маленькие функции и принцип единственной ответственности (SRP)

Функция должна делать только одну вещь

Это самый важный принцип Clean Code. Если метод выполняет несколько действий (валидация, логирование, бизнес-логика, сохранение в БД), его сложно тестировать и изменять. Разбивайте такие методы на более мелкие.

Плохой пример (метод делает всё сразу):

public void ProcessOrder(Order order)
{
    // 1. Валидация
    if (order == null || order.TotalAmount <= 0)
        throw new ArgumentException("Invalid order");

    // 2. Логирование
    Console.WriteLine($"Processing order {order.Id}");

    // 3. Бизнес-логика
    var discount = order.TotalAmount > 100 ? 0.1m : 0;
    order.FinalAmount = order.TotalAmount - (order.TotalAmount * discount);

    // 4. Сохранение
    _context.Orders.Add(order);
    _context.SaveChanges();
}

Хороший пример (разделение на методы):

public void ProcessOrder(Order order)
{
    ValidateOrder(order);
    LogOrderProcessing(order);
    ApplyDiscount(order);
    SaveOrder(order);
}

private void ValidateOrder(Order order)
{
    if (order == null || order.TotalAmount <= 0)
        throw new ArgumentException("Invalid order", nameof(order));
}

private void LogOrderProcessing(Order order)
{
    Console.WriteLine($"Processing order {order.Id}");
}

private void ApplyDiscount(Order order)
{
    const decimal discountThreshold = 100m;
    const decimal discountRate = 0.1m;

    if (order.TotalAmount > discountThreshold)
    {
        order.FinalAmount = order.TotalAmount - (order.TotalAmount * discountRate);
    }
    else
    {
        order.FinalAmount = order.TotalAmount;
    }
}

private void SaveOrder(Order order)
{
    _context.Orders.Add(order);
    _context.SaveChanges();
}

Принцип 3: Избегайте дублирования и магических чисел

DRY (Don't Repeat Yourself)

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

Магические числа и строки

Числа и строки, значения которых непонятны из контекста, называются "магическими". Их необходимо выносить в константы.

Плохой пример:

if (user.Age > 18) // Что значит 18? Почему именно 18?
{
    // ...
}

var connectionString = "Server=.;Database=MyDb;..."; // Строка подключения в коде

Хороший пример:

private const int LegalAdultAge = 18; // Константа с понятным именем

if (user.Age > LegalAdultAge)
{
    // ...
}

// Строка подключения берется из конфигурации (appsettings.json)
// IConfiguration configuration;
var connectionString = configuration.GetConnectionString("DefaultConnection");

Принцип 4: Работа с ошибками и null

Не возвращайте null без необходимости

Возврат null из метода часто приводит к NullReferenceException. Используйте паттерн Null Object или возвращайте null только в исключительных случаях, документируя это.

Начиная с C# 8.0, активно используйте nullable reference types для явного указания, может ли переменная быть null.

public class UserService
{
    // Плохо: возвращаем null, если пользователь не найден
    public User? FindUser(int id) // Знак ? означает, что может быть null
    {
        return _repository.GetById(id); // Может вернуть null
    }

    // Хорошо: используем паттерн Result или выбрасываем исключение
    public User GetUserOrThrow(int id)
    {
        var user = _repository.GetById(id);
        if (user == null)
        {
            throw new KeyNotFoundException($"User with id {id} not found.");
        }
        return user;
    }
}

// Использование
var user = userService.GetUserOrThrow(42); // Никогда не будет null
Console.WriteLine(user.Name); // Безопасно

Обработка исключений

Не используйте исключения для управления потоком выполнения

Блоги

Рекомендаций по книгам