← Назад к вопросам

Сталкивался ли с legacy code?

2.2 Middle🔥 231 комментариев
#Другое

Комментарии (1)

🐱
deepseek-v3.2PrepBro AI6 апр. 2026 г.(ред.)

Ответ сгенерирован нейросетью и может содержать ошибки

Работа с Legacy Code в C# Backend: опыт и практики

Да, я сталкивался с legacy code (унаследованным кодом) на протяжении всей карьеры в C# Backend разработке. Практически каждый разработчик, работающий с корпоративными системами или долгоживущими проектами, неизбежно встречается с такими системами. Под legacy code я понимаю код, который трудно изменять и тестировать из-за устаревших подходов, отсутствия документации, высокого сцепления (coupling) и низкой связности (cohesion).

Пример типичного legacy кода на C#

// Класс с высокой ответственностью, нарушающий SRP
public class OrderProcessor
{
    public void ProcessOrder(Order order)
    {
        // Валидация
        if (order == null) throw new ArgumentNullException();
        if (order.Items.Count == 0) throw new InvalidOperationException("No items");
        
        // Логика расчета
        decimal total = 0;
        foreach (var item in order.Items)
        {
            total += item.Price * item.Quantity;
            // Прямой вызов БД - сильная связь
            var db = new SqlConnection("ConnectionString");
            db.Open();
            // ... запросы к БД
        }
        
        // Логика скидок (захардкоженная)
        if (total > 1000) total *= 0.9m;
        
        // Отправка уведомлений
        var smtp = new SmtpClient("smtp.server");
        smtp.Send("admin@company.com", "New order", $"Total: {total}");
        
        // Логирование в файл (еще одна ответственность)
        File.AppendAllText("log.txt", $"Order processed: {DateTime.Now}");
    }
}

Ключевые проблемы такого кода:

  • Нарушение принципа единственной ответственности (SRP)
  • Прямые зависимости от инфраструктуры
  • Отсутствие абстракций
  • Сложность тестирования
  • Жестко закодированные строки подключения

Мой подход к работе с legacy code

  1. Анализ и понимание:

    • Сначала изучаю бизнес-логику, даже если документация отсутствует
    • Использую инструменты вроде NDepend или SonarQube для анализа кодовой базы
    • Составляю карту зависимостей между компонентами
  2. Покрытие тестами перед рефакторингом:

    // Постепенное добавление модульных тестов
    [Test]
    public void ProcessOrder_ValidOrder_CalculatesTotalCorrectly()
    {
        // Arrange
        var legacyProcessor = new OrderProcessor();
        var order = CreateTestOrder();
        
        // Act & Assert
        // Может потребоваться использовать техники вроде Extract Method
        // или внедрение шунтов для тестирования
    }
    
  3. Постепенный рефакторинг:

    • Применяю шаблон "шаг за шагом" (Strangler Fig Pattern)
    • Сначала изолирую наиболее проблемные участки
    • Внедряю зависимости через конструктор (Dependency Injection)
    • Создаю абстракции для инфраструктурных вызовов
  4. Модернизация архитектуры:

    • Разделяю монолитные классы на меньшие с четкой ответственностью
    • Внедряю паттерны проектирования (Repository, Factory, Strategy)
    • Постепенно мигрирую с устаревших библиотек (например, с WebForms на ASP.NET Core)

Конкретные техники для C# legacy систем

  • Обертывание статического кода для обеспечения тестируемости:
public interface IDateTimeProvider
{
    DateTime Now { get; }
}

public class DateTimeWrapper : IDateTimeProvider
{
    public DateTime Now => DateTime.Now;
}
  • Медленная миграция с устаревших API:
// Старый код
public class LegacyService
{
    public DataSet GetData() { /* использует DataSet */ }
}

// Новый интерфейс
public interface IDataService
{
    Task<IEnumerable<DataDto>> GetDataAsync();
}

// Адаптер для обратной совместимости
public class DataServiceAdapter : IDataService
{
    private readonly LegacyService _legacyService;
    
    public async Task<IEnumerable<DataDto>> GetDataAsync()
    {
        var dataSet = _legacyService.GetData();
        // Преобразование DataSet в DTO
        return await TransformDataSetAsync(dataSet);
    }
}

Вызовы и решения

Основные сложности:

  • Отсутствие модульных тестов (часто 0% покрытия)
  • Использование устаревших технологий (Remoting, WebForms, .NET Framework 2.0-4.x)
  • Прямые вызовы БД из UI слоя
  • Глобальные состояния и статические классы

Мои стратегии преодоления:

  1. Всегда начинаю с добавления интеграционных тестов вокруг критической функциональности
  2. Использую Feature Toggles для постепенного внедрения новых реализаций
  3. Применяю анализ покрытия кода для определения наиболее рискованных участков
  4. Работаю в тесном контакте с бизнес-аналитиками для понимания реальных требований

Заключение

Работа с legacy code требует терпения, системного подхода и понимания, что мгновенный полный рефакторинг часто невозможен в production системах. Ключевой принцип — "не сломать существующую функциональность". За 10+ лет работы я выработал методологию постепенного улучшения, которая позволяет модернизировать системы без простоев бизнес-процессов. Legacy код — это не всегда плохо, часто он содержит ценную бизнес-логику, прошедшую проверку временем, и задача разработчика — аккуратно выделить её из устаревших технических решений.

Сталкивался ли с legacy code? | PrepBro