JavaRush /Java Blog /Random-TL /Paano gumagana ang refactoring sa Java

Paano gumagana ang refactoring sa Java

Nai-publish sa grupo
Kapag natututong magprogram, maraming oras ang ginugugol sa pagsusulat ng code. Karamihan sa mga nagsisimulang developer ay naniniwala na ito ang kanilang aktibidad sa hinaharap. Ito ay bahagyang totoo, ngunit kasama rin sa mga gawain ng programmer ang pagpapanatili at pag-refactor ng code. Ngayon ay pag-uusapan natin ang tungkol sa refactoring. Как устроен рефакторинг в Java - 1

Refactoring sa kursong JavaRush

Sinasaklaw ng kursong JavaRush ang paksa ng refactoring nang dalawang beses: Salamat sa malaking gawain, mayroong isang pagkakataon upang maging pamilyar sa tunay na refactoring sa pagsasanay, at ang isang panayam sa refactoring sa IDEA ay makakatulong sa iyo na maunawaan ang mga awtomatikong tool na nagpapadali sa buhay.

Ano ang refactoring?

Ito ay isang pagbabago sa istraktura ng code nang hindi binabago ang pag-andar nito. Halimbawa, mayroong isang paraan na naghahambing ng 2 numero at nagbabalik ng true kung ang una ay mas malaki, at false kung hindi:
public boolean max(int a, int b) {
    if(a > b) {
        return true;
    } else if(a == b) {
        return false;
    } else {
        return false;
    }
}
Ang resulta ay napakahirap na code. Kahit na ang mga nagsisimula ay bihirang magsulat ng isang bagay na tulad nito, ngunit may ganoong panganib. Tila, bakit mayroong isang bloke dito if-elsekung maaari kang magsulat ng isang pamamaraan na 6 na linya na mas maikli:
public boolean max(int a, int b) {
     return a>b;
}
Ngayon ang pamamaraang ito ay mukhang simple at eleganteng, bagama't ginagawa nito ang parehong bagay tulad ng halimbawa sa itaas. Ito ay kung paano gumagana ang refactoring: binabago nito ang istraktura ng code nang hindi naaapektuhan ang kakanyahan nito. Mayroong maraming mga pamamaraan at pamamaraan ng refactoring, na isasaalang-alang namin nang mas detalyado.

Bakit kailangan ang refactoring?

Mayroong ilang mga dahilan. Halimbawa, ang pagtugis ng pagiging simple at pagiging maikli ng code. Ang mga tagapagtaguyod ng teoryang ito ay naniniwala na ang code ay dapat na maigsi hangga't maaari, kahit na nangangailangan ito ng dose-dosenang linya ng komentaryo upang maunawaan ito. Ang ibang mga developer ay naniniwala na ang code ay dapat na refactored upang ito ay maunawaan sa isang minimum na bilang ng mga komento. Ang bawat koponan ay pumipili ng sarili nitong posisyon, ngunit dapat nating tandaan na ang refactoring ay hindi isang pagbawas . Ang pangunahing layunin nito ay upang mapabuti ang istraktura ng code. Maaaring isama ang ilang layunin sa pandaigdigang layuning ito:
  1. Pinapabuti ng refactoring ang pag-unawa sa code na isinulat ng isa pang developer;
  2. Tumutulong na mahanap at ayusin ang mga error;
  3. Binibigyang-daan kang pataasin ang bilis ng pagbuo ng software;
  4. Sa pangkalahatan, pinapabuti ang komposisyon ng software.
Kung ang refactoring ay hindi isinasagawa sa loob ng mahabang panahon, ang mga paghihirap sa pag-unlad ay maaaring lumitaw, hanggang sa isang kumpletong paghinto ng trabaho.

"Ang amoy ng code"

Kapag ang code ay nangangailangan ng refactoring, sinasabi nilang "amoy." Siyempre, hindi literal, ngunit ang naturang code ay talagang hindi maganda ang hitsura. Sa ibaba ay isasaalang-alang natin ang mga pangunahing pamamaraan ng refactoring para sa paunang yugto.

Hindi kinakailangang malalaking elemento

May mga masalimuot na klase at pamamaraan na imposibleng gumana nang epektibo nang tumpak dahil sa kanilang malaking sukat.

Malaking klase

У такого класса есть огромное количество строк codeа и много различных методов. Обычно разработчику легче добавить фичу в существующий класс, а не создавать новый, из-за чего он и растет. Как правило, функционал такого класса перегружен. В этом случае помогает выделение части функционала в отдельный класс. Об этом поговорим подробнее в разделе техник рефакторинга.

Большой метод

Этот “запах” возникает, когда разработчик добавляет в метод новый функционал. “Зачем мне выносить проверку параметров в отдельный метод, если я могу написать ее тут?”, “Для чего необходимо выделять метод поиска максимального element в массиве, оставим его тут. Так code яснее”, — и прочие заблуждения. Есть два правила рефакторинга большого метода:
  1. Если при написании метода хочется добавить комментарий в code, необходимо выделить этот функционал в отдельный метод;
  2. Если метод занимает более 10-15 строк codeа, следует определить задачи и подзадачи, которые он выполняет, и попробовать вынести подзадачи в отдельный метод.
Несколько способов устранить большой метод:
  • Выделить часть функционала метода в отдельный метод;
  • Если локальные переменные не дают вынести часть функционала, можно передать весь an object в другой метод.

Использование множества примитивных типов данных

Обычно такая проблема возникает, когда с течением времени в классе растет количество полей для хранения данных. Например, если использовать примитивные типы instead of маленьких an objectов для хранения данных (валюта, дата, телефонные номера и т.д.) or константы для codeирования Howой-либо информации. Хорошей практикой в этом случае будет логическая группировка полей и вынос в отдельный класс (выделение класса). Также в класс можно включить методы для обработки этих данных.

Длинный список параметров

Достаточно распространенная ошибка, особенно в совокупности с большим методом. Обычно она возникает, если функционал метода перегружен, or метод объединяет несколько алгоритмов в себе. В длинных списках параметров очень трудно разбираться, и использовать такие методы неудобно. Поэтому лучше передать an object целиком. Если у an object нет достаточно данных, стоит использовать более общий an object or разделить функционал метода, чтобы он обрабатывал логически связанные данные.

Группы данных

Часто в codeе появляются логически связанные группы данных. Например, параметры подключения в БД (URL, Name пользователя, пароль, Name схемы и тд). Если из перечня элементов нельзя удалить ни одно поле, значит перечень — это группа данных, которую необходимо вынести в отдельный класс (выделение класса).

Решения, которые портят концепцию ООП

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

Отказ от наследования

Если подкласс использует минимальную часть функций родительского класса, тут пахнет неправильной иерархией. Обычно в таком случае ненужные методы просто не переопределяются or выбрасывают исключения. Если класс унаследован от другого, это подразумевает под собой практически полное использование его функционала. Пример правильной иерархии: Как устроен рефакторинг в Java - 2 Пример неправильной иерархии: Как устроен рефакторинг в Java - 3

Оператор switch

What плохого может быть в операторе switch? Он плох, когда его конструкция очень сложная. Также сюда относятся и множество вложенных блоков if.

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

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

Временное поле

Если в классе заложено временное поле, которое нужно an objectу лишь изредка, когда он заполняется значениями, а в остальное время — пустое or, не дай бог, null, значит code “попахивает”, а такой дизайн — сомнительное решение.

Запахи, которые затрудняют модификацию

Эти “запахи” более серьезные. Остальные в основном ухудшают понимание codeа, тогда How эти не дают возможность его модифицировать. При внедрении Howих-либо фич половина разработчиков уволится, а половина сойдет с ума.

Параллельные иерархии наследования

При создании подкласса Howого-либо класса необходимо создавать еще один подкласс для другого класса.

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

При выполнении любых модификаций приходится искать все зависимости (использования) этого класса и вносить множество мелких правок. Одно изменение — правки во множестве классов.

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

Этот запах противоположен предыдущему: изменения затрагивают большое количество методов одного класса. Как правило, зависимость в таком codeе каскадная: изменив один метод, нужно поправить что-то в другом, а затем в третьем и так далее. Один класс — множество изменений.

“Мусорные запахи”

Достаточно неприятная категория запахов, которая вызывает головную боль. Бесполезный, ненужный, старый code. К счастью, современные IDE и линтеры научorсь предупреждать о таких запахах.

Большое количество комментариев в методе

У метода очень много поясняющих комментариев практически на каждой строке. Обычно это связано со сложным алгоритмом, поэтому лучше разделить code на несколько методов поменьше и дать им говорящие названия.

Дублирование codeа

В разных классах or методах используются одинаковые блоки codeа.

Ленивый класс

Класс берет на себя очень малый функционал, хотя планировался большой.

Неиспользуемый code

Класс, метод or переменная не используется в codeе и являются “мертвым грузом”.

Излишняя связанность

Эта категория запахов характеризуется большим количеством неоправданных связей в codeе.

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

Метод использует данные другого an object гораздо чаще, чем собственные данные.

Неуместная близость

Класс использует служебные поля и методы другого класса.

Длинные вызовы классов

Один класс вызывает другой, тот запрашивает данные у третьего, тот у четвертого и так далее. Такая длинная цепь вызовов означает высокий уровень зависимости от текущей структуры классов.

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

Класс нужен только для того, чтобы передать задание другому классу. Может быть, его стоит удалить?

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

Ниже пойдет речь о начальных техниках рефакторинга, которые помогут устранить описанные “запахи” codeа.

Выделение класса

Класс выполняет слишком много функций, часть необходимо вынести в другой класс. Например, имеется класс Human, в котором также содержится address проживания и метод, предоставляющий полный address:
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();
   }
}
Хорошим тоном будет вынести информацию об addressе и метод (поведение обработки данных) в отдельный класс:
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();
   }
}

Выделение метода

Если в методе Howой-либо функционал можно сгруппировать, следует вынести его в отдельный метод. Например, метод, который вычисляет корни квадратного уравнения:
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");
}
Код каждого метода стал гораздо короче и понятнее.

Передача всего an object

При вызове метода с параметрами иногда можно встретить такой code:
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 строчек. Гораздо проще передать в метод сам an object, откуда можно извлечь необходимые данные:
public void employeeMethod(Employee employee) {
    // Некоторые действия
    double monthlySalary = getMonthlySalary(employee);
    // Продолжение обработки
}

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

Логическая группировка полей и вынос в отдельный класс

Несмотря на то, что вышеописанные примеры — очень простые и при взгляде на них многие могут задаться вопросом “Да кто вообще так делает?”, многие разработчики от невнимательности, нежелания проводить рефакторинг codeа or просто “И так сойдет” допускают подобные структурные ошибки.

Почему рефакторинг эффективен

Итог хорошего рефакторинга — программа, code которой легко читать, модификации логики программы не становятся угрозой, а внесение новых фич не превращается в ад разбора codeа, а приятным занятием на пару дней. Рефакторинг не стоит применять, если программу проще переписать с нуля. Например, команда оценивает трудозатраты на разбор, анализ и рефакторинг codeа выше, чем на реализацию такого же функционала с нуля. Или у codeа, который нужно отрефакторить, есть множество ошибок, сложных в отладке. Знание, How улучшить структуру codeа обязательно в работе программиста. Ну а изучать программирование на Java лучше на JavaRush — онлайн-курсе с акцентом на практику. 1200+ задач с мгновенной проверкой, около 20 минипроектов, задачи-игры — все это поможет почувствовать себя уверенно в codeинге. Лучшее время, чтобы начать — сейчас :) Как устроен рефакторинг в Java - 4

Ресурсы для дополнительного погружения в рефакторинг

Самая известная книга о рефакторинге — это “Рефакторинг. Улучшение проекта существующего codeа” Мартина Фаулера. Также есть интересное издание о рефакторинге, написанное на основе предыдущей книги — “Рефакторинг с использованием шаблонов” Джошуа Кириевски. Кстати о шаблонах. При рефакторинге всегда очень полезно знать основные паттерны проектирования приложений. В этом помогут эти отличные книги:
  1. “Паттерны проектирования” — авторства Эрика Фримена, Элизабет Фримен, Кэтти Сьерра, Берта Бейтса из серии Head First;
  2. “Читаемый code, or программирование How искусство” — Дастин Босуэлл, Тревор Фаучер.
  3. “Совершенный code” Стива Макконнелла, в которой изложены принципы красивого и элегантного codeа.
Ну и несколько статей о рефакторинге:
  1. Адская задачка: приступаем к рефакторингу унаследованного codeа;
  2. Рефакторинг;
  3. Refactoring for everyone.
    Mga komento
    TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
    GO TO FULL VERSION