Рефакторинг legacy кода на C#: Практическое руководство для .NET разработчика

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

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

Начать курс

Рефакторинг legacy кода на C#: Как превратить устаревший проект в современный .NET-продукт

Каждый разработчик .NET рано или поздно сталкивается с legacy-кодом — монолитными классами на 3000 строк, спагетти-логикой в файлах .aspx.cs и магическими строками, разбросанными по всему проекту. Рефакторинг такого кода — это не просто "причесывание" синтаксиса, а стратегическая задача: улучшить архитектуру, не сломав бизнес-логику. В этой статье мы разберем практические подходы к рефакторингу C# кода, которые помогут вам безопасно и эффективно модернизировать унаследованные проекты.

1. Подготовка: Почему рефакторинг legacy кода требует особого подхода

Главная ошибка новичков — сразу бросаться переписывать код. В C# .NET legacy-проекты часто содержат скрытые зависимости: статические классы, глобальные состояния, жесткие связи с базой данных через ADO.NET. Любое изменение может вызвать каскад ошибок.

Основные признаки legacy кода в C#:

  • Отсутствие модульных тестов — код не покрыт юнит-тестами, что делает рефакторинг рискованным.
  • Божественные объекты (God Objects) — классы, которые делают всё: от работы с БД до рендеринга UI.
  • Жесткие зависимости — прямое создание экземпляров через new SqlConnection() внутри методов.
  • Магические строки и числа — конфигурации, вшитые в код, а не в app.config или appsettings.json.
  • Использование устаревших API — например, WebClient вместо HttpClient или ArrayList вместо List<T>.

Перед началом рефакторинга обязательно: заморозьте требования у заказчика, настройте систему контроля версий (Git) и создайте "страховочную сетку" в виде интеграционных тестов.

2. Безопасный рефакторинг: Стратегия "Передвижение стены"

Классическая техника для C# — Strangler Fig Pattern (Паттерн "Удушающий плющ"). Вместо того чтобы переписывать всё сразу, вы постепенно заменяете старые компоненты новыми, пока старый код не перестанет использоваться.

2.1. Выделение интерфейсов и внедрение зависимостей

Самый частый антипаттерн в legacy C# — статические методы-помощники. Рассмотрим пример:

// Устаревший код
public class OrderService
{
    public void ProcessOrder(int orderId)
    {
        var conn = new SqlConnection("Server=myServer;Database=myDB;...");
        var cmd = new SqlCommand("SELECT * FROM Orders WHERE Id = " + orderId, conn);
        // ... тонны логики
    }
}

Шаг 1: Выделяем интерфейс для доступа к данным.

public interface IOrderRepository
{
    Order GetById(int orderId);
}

public class OrderRepository : IOrderRepository
{
    private readonly string _connectionString;

    public OrderRepository(string connectionString)
    {
        _connectionString = connectionString;
    }

    public Order GetById(int orderId)
    {
        using var conn = new SqlConnection(_connectionString);
        // ... безопасный запрос
    }
}

Шаг 2: Внедряем зависимость через конструктор. Теперь вы можете подменить репозиторий в тестах, а сам OrderService перестал зависеть от конкретной реализации БД.

public class OrderService
{
    private readonly IOrderRepository _orderRepository;

    public OrderService(IOrderRepository orderRepository)
    {
        _orderRepository = orderRepository;
    }

    public void ProcessOrder(int orderId)
    {
        var order = _orderRepository.GetById(orderId);
        // ... бизнес-логика
    }
}

2.2. Замена устаревших типов на современные

В .NET Framework часто использовались ArrayList, Hashtable и StringBuilder (хотя последний всё ещё актуален). При рефакторинге на .NET 6+ обязательно замените их на типизированные коллекции:

// Было (устаревшее)
ArrayList items = new ArrayList();
items.Add("text");
string first = (string)items[0]; // Грязный кастинг

// Стало (современное)
List<string> items = new List<string>();
items.Add("text");
string first = items[0]; // Безопасно и читаемо

3. Рефакторинг бизнес-логики: Избавляемся от "спагетти"

Часто legacy код — это один гигантский метод, который делает всё. Наша задача — разбить его на маленькие, тестируемые куски.

3.1. Техника "Извлечение метода" (Extract Method)

Представьте метод, который одновременно проверяет права пользователя, вычисляет скидку и отправляет email:

public void Checkout(int userId, int productId)
{
    var user = GetUser(userId);
    if (user.IsBlocked) throw new Exception("User blocked");

    var product = GetProduct(productId);
    decimal discount = 0;
    if (user.IsPremium) discount = 0.1m;

    var total = product.Price * (1 - discount);
    // ... ещё 50 строк кода
}

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

public void Checkout(int userId, int productId)
{
    var user = GetUser(userId);
    ValidateUser(user);

    var product = GetProduct(productId);
    var discount = CalculateDiscount(user);
    var total = CalculateTotal(product, discount);

    SendConfirmationEmail(user.Email, total);
}

private void ValidateUser(User user)
{
    if (user.IsBlocked) throw new UserBlockedException(user.Id);
}

private decimal CalculateDiscount(User user)
{
    return user.IsPremium ? 0.1m : 0m;
}

private decimal CalculateTotal(Product product, decimal discount)
{
    return product.Price * (1 - discount);
}

Теперь каждый метод можно протестировать изолированно, а сам код стал самодокументируемым.

3.2. Убираем магические строки и числа

Строки вроде "SELECT * FROM Orders WHERE Status = 'Active'" или числа 0.1m должны быть кон

Блоги

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