Рефакторинг legacy кода на C#: от А до Я

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

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

Начать курс

Введение: Почему legacy код требует рефакторинга?

Каждый разработчик, работающий с платформой .NET, рано или поздно сталкивается с legacy кодом. Это программы, написанные на старых версиях C# (например, .NET Framework 4.x), использующие устаревшие паттерны вроде DataTable в ASP.NET Web Forms или монолитные классы на тысячи строк. Такой код — источник технического долга: он сложен в поддержке, содержит скрытые баги и тормозит внедрение новых фич.

Рефакторинг — это процесс изменения внутренней структуры кода без изменения его внешнего поведения. В этой статье мы разберём пошаговый план рефакторинга legacy кода на C#, от диагностики до внедрения современных практик. Вы узнаете, как безопасно улучшить старый проект, используя инструменты .NET и проверенные техники.

1. Диагностика и подготовка: с чего начать?

Перед тем как менять код, необходимо понять его текущее состояние. Без анализа вы рискуете сломать функциональность.

1.1. Оценка технического долга

Используйте анализаторы кода, встроенные в Visual Studio или доступные через NuGet:

  • Roslyn analyzers — находят потенциальные ошибки и устаревшие конструкции.
  • SonarQube (или SonarCloud) — оценивает качество кода, дублирование, сложность.
  • NDepend — визуализирует зависимости между классами и модулями.

1.2. Написание тестов как защитная сетка

Рефакторинг без тестов — это игра в русскую рулетку. Для legacy кода часто нет unit-тестов. Начните с написания характеристических тестов (characterization tests), которые фиксируют текущее поведение:

// Пример теста для старого метода
[Test]
public void CalculateDiscount_ShouldReturn10Percent_WhenOrderTotalIsGreaterThan100()
{
    // Arrange
    var legacyClass = new OrderProcessor();
    var order = new Order { Total = 150 };
    
    // Act
    var result = legacyClass.CalculateDiscount(order);
    
    // Assert
    Assert.AreEqual(15, result); // 10% от 150
}

Создайте тесты для критических путей. Это позволит вам безопасно рефакторить, зная, что поведение не изменится.

1.3. Выбор стратегии: "Стратегия огорода"

Не пытайтесь рефакторить весь проект сразу. Выберите один модуль или класс, который приносит больше всего проблем (высокая сложность, частые баги). Работайте итеративно: маленькие шаги, частые коммиты.

2. Основные техники рефакторинга legacy кода на C#

Рассмотрим ключевые приёмы, которые помогут превратить "спагетти-код" в чистую архитектуру.

2.1. Избавление от God-объектов (большие классы)

Часто legacy код содержит классы, которые делают всё: работают с БД, валидируют данные, отправляют email. Используйте принцип Single Responsibility (SRP).

До рефакторинга:

public class UserService
{
    public void RegisterUser(string name, string email)
    {
        // Валидация
        if (string.IsNullOrEmpty(name)) throw new Exception();
        
        // Работа с БД
        using (var conn = new SqlConnection("..."))
        {
            conn.Execute("INSERT INTO Users ...");
        }
        
        // Отправка email
        SmtpClient client = new SmtpClient();
        client.Send(new MailMessage(...));
    }
}

После рефакторинга:

public class UserService
{
    private readonly IUserRepository _userRepository;
    private readonly IEmailService _emailService;
    private readonly IValidator<UserRegistration> _validator;

    public UserService(IUserRepository repo, IEmailService email, IValidator<UserRegistration> validator)
    {
        _userRepository = repo;
        _emailService = email;
        _validator = validator;
    }

    public void RegisterUser(string name, string email)
    {
        var registration = new UserRegistration(name, email);
        _validator.Validate(registration);
        
        var user = new User(name, email);
        _userRepository.Save(user);
        
        _emailService.SendWelcomeEmail(user);
    }
}

2.2. Замена магических чисел и строк константами

Legacy код часто содержит "магические" значения, которые сложно поддерживать:

// Плохо
if (status == 3) { ... }

// Хорошо
private const int StatusActive = 3;
if (status == StatusActive) { ... }

// Лучше — enum
public enum UserStatus { Inactive = 1, Active = 3, Blocked = 5 }
if (user.Status == UserStatus.Active) { ... }

2.3. Устранение дублирования кода

Используйте Resharper или встроенный рефакторинг Visual Studio (Ctrl+R, Ctrl+M) для выделения повторяющихся блоков в методы. Например, если в 10 местах повторяется проверка прав доступа:

private bool HasPermission(User user, string permission)
{
    return user.Roles.Any(r => r.Permissions.Contains(permission));
}

После этого вызовите метод везде, где была эта логика.

3. Безопасный рефакторинг: работа с устаревшими технологиями .NET

Особая боль — миграция с .NET Framework на .NET Core / .NET 8+. Здесь требуется аккуратность.

3.1. Замена Web Forms на ASP.NET Core MVC

Если проект использует Web Forms, не пытайтесь переписать всё сразу. Используйте Strangler Fig Pattern: новый функционал пишите на MVC, а старые страницы постепенно заменяйте, перенаправляя маршруты.

3.2. Отказ от DataTable в пользу Entity Framework Core

Старый код часто использует DataTable и прямые SQL-запросы. Замените их на ORM:

// Старый стиль
DataTable dt = new DataTable();
using (var cmd = new SqlCommand("SELECT * FROM Orders WHERE CustomerId = @id", conn))
{
    cmd.Parameters.AddWithValue("@id", customerId);
    dt.Load(cmd.ExecuteReader());
}

// Новый стиль с EF Core
var orders = await _context.Orders
    .Where(o => o.CustomerId == customerId)
    .ToListAsync();

3.3. Внедрение Dependency Injection

Legacy код часто содержит статические

Блоги

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