Рефакторинг 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 и
Blogs
Book Recommendations
Read also