JavaRush /Java блог /Random UA /Як влаштований рефакторинг у Java

Як влаштований рефакторинг у Java

Стаття з групи Random UA
Під час навчання програмуванню багато часу приділяється написанню коду. Більшість розробників-початківців вважають, що в цьому і полягає їхня майбутня діяльність. Частково це так, але до завдань програміста також входять підтримка та рефакторинг коду. Сьогодні поговоримо про рефакторинг. Як влаштований рефакторинг у Java - 1

Рефакторинг в курсі JavaRush

У курсі JavaRush тема рефакторингу торкається двічі: Завдяки великому завданню, є можливість познайомитися зі справжнім рефакторингом на практиці, а лекція про рефакторинг IDEA допоможе розібратися з автоматичними засобами, які неймовірно полегшують життя.

Що таке рефакторинг?

Це зміна структури коду без зміни його функціоналу. Наприклад, є метод, який порівнює 2 числа і повертає true , якщо перше більше, і false у протилежному випадку:
public boolean max(int a, int b) {
    if(a > b) {
        return true;
    } else if(a == b) {
        return false;
    } else {
        return false;
    }
}
Вийшов дуже громіздкий код. Навіть новачки рідко пишуть таке, проте такий ризик є. Здавалося б, навіщо тут блок if-else, якщо можна написати метод на 6 рядків коротше:
public boolean max(int a, int b) {
     return a>b;
}
Тепер цей метод виглядає просто та елегантно, хоча виконує ту ж дію, що й приклад вищий. Так і працює рефакторинг: змінює структуру коду, не торкаючись його суті. Існує безліч методів та технік рефакторингу, які розглянемо докладніше.

Навіщо потрібен рефакторинг?

Існує кілька причин. Наприклад, гонитва за простотою та лаконічністю коду. Прихильники цієї теорії вважають, що код має бути максимально коротким, навіть якщо для його розуміння потрібно кілька десятків рядків коментар. Інші розробники впевнені, що код повинен бути рефакторинговим настільки, щоб він був зрозумілий з мінімальною кількістю коментарів. Кожна команда обирає свою позицію, але треба пам'ятати, що рефакторинг – це не скорочення . Його головна мета – покращити структуру коду. У цю глобальну мету можна включити кілька завдань:
  1. Рефакторинг покращує розуміння коду, написаного іншим розробником;
  2. Допомагає шукати та усувати помилки;
  3. Дозволяє підвищити швидкість розробки;
  4. Загалом покращує композицію програмного забезпечення.
Якщо тривалий час не проводити рефакторинг, можуть виникнути труднощі у створенні до повної зупинки роботи.

"Запахи коду"

Коли код вимагає рефакторингу, кажуть, що він “пахне”. Звісно, ​​не буквально, але такий код справді виглядає не зовсім приємно. Нижче розглянемо основні техніки рефакторингу для початкового етапу.

Невиправдано великі елементи

Існують громіздкі класи та методи, з якими неможливо ефективно працювати саме через їхній величезний розмір.

Великий клас

Такий клас має величезну кількість рядків коду і багато різних методів. Зазвичай розробнику легше додати фічу до існуючого класу, а не створювати новий, через що він і росте. Як правило, функціонал такого класу перевантажено. І тут допомагає виділення частини функціоналу окремий клас. Про це поговоримо докладніше у розділі технік рефакторингу.

Великий метод

Цей “запах” з'являється, коли розробник додає у спосіб новий функціонал. "Навіщо мені виносити перевірку параметрів в окремий метод, якщо я можу написати її тут?", "Для чого необхідно виділяти метод пошуку максимального елемента в масиві, залишимо його тут. Так код ясніше”, — та інші помилки. Є два правила рефакторингу великого методу:
  1. Якщо при написанні методу хочеться додати коментар до коду, необхідно виділити цей функціонал окремим методом;
  2. Якщо метод займає більше 10-15 рядків коду, слід визначити завдання та підзавдання, які він виконує, та спробувати винести підзавдання в окремий метод.
Декілька способів усунути великий метод:
  • Виділити частину функціоналу методу окремий метод;
  • Якщо локальні змінні не дають винести частину функціоналу, можна передати весь об'єкт інший метод.

Використання безлічі примітивних типів даних

Зазвичай така проблема виникає, коли з часом у класі зростає кількість полів для зберігання даних. Наприклад, якщо використовувати примітивні типи замість маленьких об'єктів для зберігання даних (валюта, дата, номери телефонів тощо) або константи для кодування будь-якої інформації. Хорошою практикою в цьому випадку буде логічне угруповання полів та винесення в окремий клас (виділення класу). Також у клас можна включити методи обробки цих даних.

Довгий список параметрів

Досить поширена помилка, особливо в сукупності з великим методом. Зазвичай вона виникає, якщо функціонал методу перевантажений, чи метод поєднує кілька алгоритмів у собі. У довгих списках параметрів важко розбиратися, і використовувати такі методи незручно. Тому краще передати об'єкт повністю. Якщо об'єкт не має достатньо даних, варто використовувати більш загальний об'єкт або розділити функціонал методу, щоб він обробляв логічно пов'язані дані.

Групи даних

Часто у коді з'являються логічно пов'язані групи даних. Наприклад, параметри підключення до БД (URL, ім'я користувача, пароль, ім'я схеми тощо). Якщо з переліку елементів не можна видалити жодне поле, це означає, що перелік — це група даних, яку необхідно винести в окремий клас (виділення класу).

Рішення, що псують концепцію ОВП

“Запахи” цього виникають, коли розробник порушує дизайн ООП. Таке відбувається, якщо він не до кінця розуміє можливості цієї парадигми, використовує їх не до кінця чи неправильно.

Відмова від наслідування

Якщо підклас використовує мінімальну частину функцій батьківського класу, пахне неправильною ієрархією. Зазвичай у разі непотрібні методи просто не перевизначаються чи викидають винятки. Якщо клас успадкований від іншого, це передбачає практично повне використання його функціоналу. Приклад правильної ієрархії: Як влаштований рефакторинг у Java - 2 Приклад неправильної ієрархії: Як влаштований рефакторинг у Java - 3

Оператор switch

Що поганого може бути в операторі switch? Він поганий, коли його конструкція дуже складна. Також сюди належать і безліч вкладених блоків if.

Альтернативні класи з різними інтерфейсами

Декілька класів фактично виконують те саме, але їх методи називаються по-різному.

Тимчасове поле

Якщо в класі закладено тимчасове поле, яке потрібно об'єкту лише зрідка, коли він заповнюється значеннями, а в решту часу — порожнім або, не дай Боже, значить nullкод «пахає», а такий дизайн — сумнівне рішення.

Запахи, які ускладнюють модифікацію

Ці “запахи” серйозніші. Інші переважно погіршують розуміння коду, тоді як ці не дають можливість його модифікувати. При впровадженні яких-небудь фіч половина розробників звільниться, а половина збожеволіє.

Паралельні ієрархії успадкування

При створенні підкласу будь-якого класу необхідно створювати ще один підклас іншого класу.

Рівномірний розподіл залежності

За виконання будь-яких модифікацій доводиться шукати всі залежності (використання) цього і вносити безліч дрібних правок. Одна зміна — редагування в багатьох класах.

Складне дерево модифікацій

Цей запах протилежний попередньому: зміни торкаються великої кількості методів одного класу. Як правило, залежність у такому коді каскадна: змінивши один метод, потрібно виправити щось в іншому, а потім третьому і так далі. Один клас – безліч змін.

"Сміттєві запахи"

Досить неприємна категорія запахів, що спричиняє головний біль. Марний, непотрібний, старий код. На щастя, сучасні IDE та лінтери навчабося попереджати про такі запахи.

Велика кількість коментарів у методі

У методу дуже багато коментарів, що пояснюють, практично на кожному рядку. Зазвичай це пов'язано зі складним алгоритмом, тому краще розділити код на кілька методів поменше і дати їм назви, що говорять.

Дублювання коду

У різних класах чи методах використовуються однакові блоки коду.

Лінивий клас

Клас перебирає дуже малий функціонал, хоча планувався великий.

Код, що не використовується

Клас, метод чи змінна не використовується у коді і є “мертвим вантажем”.

Зайва пов'язаність

Ця категорія запахів характеризується великою кількістю невиправданих зв'язків у коді.

Сторонні методи

Метод використовує дані іншого об'єкта набагато частіше, ніж власні дані.

Недоречна близькість

Клас використовує службові поля та методи іншого класу.

Довгі виклики класів

Один клас викликає інший, той запитує дані у третього, той у четвертого тощо. Таке довге коло викликів означає високий рівень залежності від поточної структури класів.

Клас-таск-дилер

Клас потрібен лише у тому, щоб передати завдання іншому класу. Може, його варто видалити?

Техніки рефакторингу

Нижче йтиметься про початкові техніки рефакторингу, які допоможуть усунути описані "запахи" коду.

Виділення класу

Клас виконує занадто багато функцій, частину необхідно винести до іншого класу. Наприклад, є клас Human, в якому також міститься адресаа проживання та метод, що надає повну адресау:
class Human {
   private String name;
   private String age;
   private String country;
   private String city;
   private String street;
   private String house;
   private String quarter;

   public String getFullAddress() {
       StringBuilder result = new StringBuilder();
       return result
                       .append(country)
                       .append(", ")
                       .append(city)
                       .append(", ")
                       .append(street)
                       .append(", ")
                       .append(house)
                       .append(" ")
                       .append(quarter).toString();
   }
}
Хорошим тоном буде винести інформацію про адресау та метод (поведінка обробки даних) в окремий клас:
class Human {
   private String name;
   private String age;
   private Address address;

   private String getFullAddress() {
       return address.getFullAddress();
   }
}
class Address {
   private String country;
   private String city;
   private String street;
   private String house;
   private String quarter;

   public String getFullAddress() {
       StringBuilder result = new StringBuilder();
       return result
                       .append(country)
                       .append(", ")
                       .append(city)
                       .append(", ")
                       .append(street)
                       .append(", ")
                       .append(house)
                       .append(" ")
                       .append(quarter).toString();
   }
}

Виділення методу

Якщо у методі якийсь функціонал можна згрупувати, слід винести його на окремий метод. Наприклад, метод, який обчислює коріння квадратного рівняння:
public void calcQuadraticEq(double a, double b, double c) {
    double D = b * b - 4 * a * c;
    if (D > 0) {
        double x1, x2;
        x1 = (-b - Math.sqrt(D)) / (2 * a);
        x2 = (-b + Math.sqrt(D)) / (2 * a);
        System.out.println("x1 = " + x1 + ", x2 = " + x2);
    }
    else if (D == 0) {
        double x;
        x = -b / (2 * a);
        System.out.println("x = " + x);
    }
    else {
        System.out.println("Equation has no roots");
    }
}
Винесемо обчислення всіх трьох можливих варіантів в окремі методи:
public void calcQuadraticEq(double a, double b, double c) {
    double D = b * b - 4 * a * c;
    if (D > 0) {
        dGreaterThanZero(a, b, D);
    }
    else if (D == 0) {
        dEqualsZero(a, b);
    }
    else {
        dLessThanZero();
    }
}

public void dGreaterThanZero(double a, double b, double D) {
    double x1, x2;
    x1 = (-b - Math.sqrt(D)) / (2 * a);
    x2 = (-b + Math.sqrt(D)) / (2 * a);
    System.out.println("x1 = " + x1 + ", x2 = " + x2);
}

public void dEqualsZero(double a, double b) {
    double x;
    x = -b / (2 * a);
    System.out.println("x = " + x);
}

public void dLessThanZero() {
    System.out.println("Equation has no roots");
}
Код кожного методу став набагато коротшим і зрозумілішим.

Передача всього об'єкту

При викликі методу з параметрами іноді можна зустріти такий код:
public void employeeMethod(Employee employee) {
    // Некоторые действия
    double yearlySalary = employee.getYearlySalary();
    double awards = employee.getAwards();
    double monthlySalary = getMonthlySalary(yearlySalary, awards);
    // Продолжение обработки
}

public double getMonthlySalary(double yearlySalary, double awards) {
     return (yearlySalary + awards)/12;
}
У методі employeeMethodцілих 2 рядки відводиться отримання значень і збереження в примітивних змінних. Іноді такі конструкції займають до 10 рядків. Набагато простіше передати в метод сам об'єкт, звідки можна отримати необхідні дані:
public void employeeMethod(Employee employee) {
    // Некоторые действия
    double monthlySalary = getMonthlySalary(employee);
    // Продолжение обработки
}

public double getMonthlySalary(Employee employee) {
    return (employee.getYearlySalary() + employee.getAwards())/12;
}
Просто, коротко та лаконічно.

Логічне угруповання полів та винесення в окремий клас

Незважаючи на те, що вищеописані приклади — дуже прості і при погляді на них багато хто може поставити питання «Та хто взагалі так робить?», багато розробників від неуважності, небажання проводити рефакторинг коду або просто «І так зійде» припускаються подібних структурних помилок.

Чому рефакторинг ефективний

Підсумок хорошого рефакторингу - програма, код якої легко читати, модифікації логіки програми не стають загрозою, а внесення нових фіч не перетворюється на пекло розбору коду, а приємним заняттям на пару днів. Рефакторинг не варто використовувати, якщо програму простіше переписати з нуля. Наприклад, команда оцінює трудовитрати на розбір, аналіз та рефакторинг коду вище, ніж на реалізацію такого ж функціоналу з нуля. Або код, який потрібно відрефакторити, має безліч помилок, складних у налагодженні. Знання, як поліпшити структуру коду, обов'язково в роботі програміста. Ну а вивчати програмування на Java краще на JavaRush - онлайн-курсі з акцентом на практику. 1200+ завдань із миттєвою перевіркою, близько 20 мініпроектів, завдання-ігри – все це допоможе відчути себе впевнено у кодингу. Найкращий час, щоб почати - зараз :) Як влаштований рефакторинг у Java - 4

Ресурси для додаткового занурення у рефакторинг

Найвідоміша книга про рефакторинг – це “Рефакторинг. Поліпшення проекту існуючого коду Мартіна Фаулера. Також є цікаве видання про рефакторинг, написане на основі попередньої книги – “Рефакторинг із використанням шаблонів” Джошуа Кірієвскі. До речі про шаблони. При рефакторинг завжди дуже корисно знати основні патерни проектування додатків. У цьому допоможуть ці чудові книги:
  1. "Паттерни проектування" - авторства Еріка Фрімена, Елізабет Фрімен, Кетті Сьєрра, Берта Бейтса із серії Head First;
  2. "Читуваний код, або програмування як мистецтво" - Дастін Босуелл, Тревор Фаучер.
  3. "Довершений код" Стіва Макконнелла, в якій викладено принципи красивого та елегантного коду.
Ну і кілька статей про рефакторинг:
  1. Пекельне завдання: приступаємо до рефакторингу успадкованого коду ;
  2. Рефакторинг ;
  3. Refactoring for everyone .
    Коментарі
    ЩОБ ПОДИВИТИСЯ ВСІ КОМЕНТАРІ АБО ЗАЛИШИТИ КОМЕНТАР,
    ПЕРЕЙДІТЬ В ПОВНУ ВЕРСІЮ