Введение: Почему 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 код часто содержит статические