Рефакторинг legacy кода на Java: пошаговое руководство от А до Я

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

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

Начать курс

Введение: что такое legacy код и почему его нужно рефакторить

Каждый Java-разработчик рано или поздно сталкивается с legacy кодом — устаревшим, плохо структурированным и слабо документированным программным обеспечением. Такой код часто написан без unit-тестов, с нарушением принципов SOLID, и его поддержка превращается в кошмар. Рефакторинг legacy кода на Java — это не просто прихоть, а необходимость для снижения технического долга и ускорения разработки новых фич.

В этой статье мы разберем рефакторинг legacy кода на Java от А до Я: от выявления проблемных участков до безопасного внедрения изменений с помощью современных инструментов.

Этап 1: Анализ и оценка legacy кода

Прежде чем менять код, его нужно понять. Вот ключевые шаги анализа:

1.1. Сбор метрик и поиск "запахов" кода

Используйте статические анализаторы:

  • SonarQube — выявляет дублирование, сложные методы, нарушения code style
  • PMD — находит потенциальные баги и dead code
  • Checkstyle — проверяет соответствие стандартам оформления

Пример типичного "запаха" — гигантский метод:

public class OrderProcessor {
    public void process(Order order) {
        // 200 строк кода: валидация, расчет скидок, отправка email, логирование...
        // Разделите это на маленькие методы!
    }
}

1.2. Построение карты зависимостей

Legacy код часто содержит циклические зависимости и God-классы. Используйте инструменты визуализации:

  • IntelliJ IDEA Dependency Viewer — встроенный инструмент
  • Structure101 — профессиональное решение для анализа архитектуры

Этап 2: Создание защитной сети из тестов

Без тестов рефакторинг — это игра в русскую рулетку. Ваша цель — покрыть ключевые сценарии Characterization Tests (тесты, которые фиксируют текущее поведение).

2.1. Техника "Золотая запись" (Golden Master)

Для больших блоков кода без тестов:

  1. Запустите систему с реальными данными
  2. Сохраните вывод (логи, ответы API) как эталон
  3. После рефакторинга сравните результат с эталоном

2.2. Пример теста для legacy класса

import org.junit.jupiter.api.Test;
import static org.junit.jupiter.api.Assertions.*;

public class LegacyCalculatorTest {
    
    @Test
    public void testLegacyCalculation() {
        LegacyCalculator calc = new LegacyCalculator();
        // Фиксируем текущее поведение
        int result = calc.calculate(10, 5);
        assertEquals(15, result); // Если баг — тест сохраняет баг!
    }
}

Важно: сначала закоммитьте тесты, затем рефакторите. Зеленый тест — ваша страховка.

Этап 3: Пошаговый рефакторинг с паттернами

Рефакторинг legacy кода на Java — это не "все переписать", а серия маленьких безопасных трансформаций.

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

Самый безопасный рефакторинг. IDE делает это автоматически.

До:

public void printInvoice(Invoice invoice) {
    double total = 0;
    for (Item item : invoice.getItems()) {
        total += item.getPrice() * item.getQuantity();
    }
    double discount = total > 100 ? total * 0.1 : 0;
    double finalTotal = total - discount;
    System.out.println("Total: " + finalTotal);
}

После (с извлечением методов):

public void printInvoice(Invoice invoice) {
    double total = calculateTotal(invoice);
    double discount = calculateDiscount(total);
    double finalTotal = total - discount;
    printTotal(finalTotal);
}

private double calculateTotal(Invoice invoice) {
    return invoice.getItems().stream()
        .mapToDouble(i -> i.getPrice() * i.getQuantity())
        .sum();
}

private double calculateDiscount(double total) {
    return total > 100 ? total * 0.1 : 0;
}

private void printTotal(double total) {
    System.out.println("Total: " + total);
}

3.2. Замена условных операторов полиморфизмом

Типичный legacy-антипаттерн — цепочка if-else или switch:

public double calculateShipping(Order order) {
    switch (order.getType()) {
        case "STANDARD": return 5.0;
        case "EXPRESS": return 15.0;
        case "INTERNATIONAL": return 25.0;
        default: throw new IllegalArgumentException();
    }
}

Рефакторинг с паттерном Strategy:

// Интерфейс стратегии
public interface ShippingStrategy {
    double calculate(Order order);
}

// Конкретные стратегии
public class StandardShipping implements ShippingStrategy {
    @Override
    public double calculate(Order order) { return 5.0; }
}

public class ExpressShipping implements ShippingStrategy {
    @Override
    public double calculate(Order order) { return 15.0; }
}

// Использование
public class ShippingCalculator {
    private final Map<String, ShippingStrategy> strategies;
    
    public ShippingCalculator() {
        strategies = new HashMap<>();
        strategies.put("STANDARD", new StandardShipping());
        strategies.put("EXPRESS", new ExpressShipping());
        strategies.put("INTERNATIONAL", new InternationalShipping());
    }
    
    public double calculate(Order order) {
        ShippingStrategy strategy = strategies.get(order.getType());
        if (strategy == null) throw new IllegalArgumentException();
        return strategy.calculate(order);
    }
}

3.3. Разделение God-класса (God Class)

Классы с 3000+ строк — типичный признак legacy. Используйте Extract Class и Facade:

  1. Создайте новый класс для логики (например, EmailService из OrderProcessor)
  2. Перенесите методы по одному, перекомпилируя после каждого
  3. В исходном классе оставьте делегирование (Facade)
// После рефакторинга
public class OrderProcessor {
    private final EmailService emailService;
    private final PaymentService paymentService;
    
    public void process(Order                

Блоги

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