Рефакторинг Java кода: когда и как не наступить на грабли

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

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

Начать курс

Рефакторинг Java кода: искусство превращать «кашу» в конфетку

Вы когда-нибудь открывали свой старый Java-проект и чувствовали, что вот-вот расплачетесь? Классы размером с «Войну и мир», методы с пятью уровнями вложенности и переменные с именами вроде a1, temp2 и resultFinalReallyFinal. Знакомо? Добро пожаловать в мир «кода с душком» (code smell).

Но не спешите удалять всё и писать заново. Есть более элегантный путь — рефакторинг Java кода. Это не про «переписать по-новому», а про то, чтобы сделать код чище, понятнее и безопаснее для изменений, не ломая при этом его поведение. В этой статье мы разберём, когда стоит браться за рефакторинг, а когда лучше оставить всё как есть, и покажем на конкретных примерах, как это делается правильно.

Когда рефакторинг Java кода необходим? Признаки «плохого кода»

Рефакторинг — это не хобби и не развлечение на пятницу вечером. Это инвестиция в будущее проекта. Но как понять, что пора действовать? Вот несколько верных признаков:

  • Метод-монстр (Long Method). Если метод занимает больше 20–30 строк и вы не можете понять, что он делает, не прокрутив экран вверх-вниз — это тревожный звоночек.
  • Класс-бог (God Object). Один класс знает всё и делает всё: и с базой работает, и UI рисует, и логи пишет. Такой класс нарушает принцип единственной ответственности (SRP).
  • Дублирование кода (Duplicated Code). Вы скопировали один и тот же блок кода в трёх разных местах? Когда понадобится изменить логику, вы гарантированно забудете поправить хотя бы одно из них.
  • Сложные условные конструкции (Switch/if-else мания). Ветвление на 10+ условий, которое растёт с каждым новым требованием — верный кандидат на замену полиморфизмом.
  • Запах «Ленивого класса» (Lazy Class). Класс, который делает слишком мало, чтобы оправдать своё существование. Обычно он появляется после неудачного рефакторинга или «на вырост».

Если вы узнали свой код хотя бы в одном пункте — отлично! Значит, есть куда расти. Если во всех — не паникуйте, мы всё починим.

Когда рефакторинг Java кода делать НЕ стоит

Да, бывают ситуации, когда лучше держать руки подальше от IDE:

  • Код работает и не трогается. Если проект завтра сдают, а сегодня вы решили «навести красоту» — остановитесь. Рефакторинг без тестов — это русская рулетка.
  • Нет автоматических тестов. Вы не сможете доказать, что после изменений код ведёт себя так же. Без тестов рефакторинг превращается в «рефакторинг вслепую».
  • Планируется полная переработка. Если через месяц вы переходите на микросервисы, не нужно вылизывать монолит. Сфокусируйтесь на том, что важно сейчас.

Практические примеры рефакторинга Java кода

1. Избавляемся от Long Method: выделяем метод

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

public double calculatePrice(double basePrice, String clientType) {
    double discount = 0.0;
    if (clientType.equals("VIP")) {
        discount = 0.2;
        // логирование VIP-клиента
        System.out.println("VIP client detected!");
    } else if (clientType.equals("REGULAR")) {
        discount = 0.1;
    } else {
        discount = 0.05;
    }
    // ещё 20 строк кода с проверками...
    return basePrice * (1 - discount);
}

Это классический «метод-швейцарский нож». Выносим логику расчёта скидки в отдельный приватный метод:

public double calculatePrice(double basePrice, String clientType) {
    double discount = getDiscountForClientType(clientType);
    logClientIfVip(clientType);
    return basePrice * (1 - discount);
}

private double getDiscountForClientType(String clientType) {
    switch (clientType) {
        case "VIP": return 0.2;
        case "REGULAR": return 0.1;
        default: return 0.05;
    }
}

private void logClientIfVip(String clientType) {
    if ("VIP".equals(clientType)) {
        System.out.println("VIP client detected!");
    }
}

Теперь каждый метод делает ровно одну вещь. Читаемость повысилась в разы, а рефакторинг Java кода прошёл безболезненно.

2. Замена магических чисел на константы

Магические числа (и строки) — зло. Что такое 0.2? 20% скидка для VIP? Или налог? Замените их на именованные константы:

public class DiscountCalculator {
    private static final double VIP_DISCOUNT = 0.2;
    private static final double REGULAR_DISCOUNT = 0.1;
    private static final double DEFAULT_DISCOUNT = 0.05;

    public double calculate(double basePrice, String clientType) {
        double discount = getDiscount(clientType);
        return basePrice * (1 - discount);
    }

    private double getDiscount(String clientType) {
        return switch (clientType) {
            case "VIP" -> VIP_DISCOUNT;
            case "REGULAR" -> REGULAR_DISCOUNT;
            default -> DEFAULT_DISCOUNT;
        };
    }
}

Теперь, если скидка изменится, вы поменяете её в одном месте. И да, мы использовали современный switch из Java 14+ — это тоже часть рефакторинга.

3. Упрощение сложных условий (Replace Conditional with Polymorphism)

Допустим, у вас есть класс OrderService, который считает стоимость доставки в зависимости от типа:

public double calculateShippingCost(Order order) {
    if (order.getType().equals("STANDARD")) {
        return 5.0;
    } else if (order.getType().equals("EXPRESS")) {
        return 15.0;
    } else if (order.getType().equals("OVERSEAS")) {
        return 25.0 + order.getWeight() * 0.5;
    }
    throw new IllegalArgumentException("Unknown type");
}

Каждый раз, когда добавляется новый тип доставки, мы лезем в этот метод и добавляем ещё один else if. Правильный путь — вынести логику в отдельные классы, используя интерфейс или абстрактный класс:

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 OverseasShipping implements ShippingStrategy {
    @Override
    public double calculate(Order order) {
        return 25.0 + order.getWeight() * 0.5;
    }
}

Теперь в OrderService вы используете

Блоги

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