Когато се научите да програмирате, прекарвате много време в писане на code. Повечето начинаещи разработчици вярват, че това е, което ще правят в бъдеще. Това е отчасти вярно, но работата на програмиста включва също поддържане и преработване на codeа. Днес ще говорим за рефакторинг.
Рефакторинг на CodeGym
Рефакторингът се разглежда два пъти в курса CodeGym:- Голяма задача на ниво 5 от мисията Multithreading
- Урок за рефакторинг в IntelliJ IDEA на ниво 9 от мисията Java Collections.
Какво е рефакторинг?
Той променя структурата на codeа, без да променя неговата функционалност. Да предположим например, че имаме метод, който сравнява 2 числа и връща true , ако първото е по-голямо, и false в противен случай:
public boolean max(int a, int b) {
if(a > b) {
return true;
} else if (a == b) {
return false;
} else {
return false;
}
}
Това е доста тромав code. Дори начинаещите рядко биха написали нещо подобно, но има шанс. Защо да използвате if-else
блок, ако можете да напишете 6-редовия метод по-сбито?
public boolean max(int a, int b) {
return a > b;
}
Сега имаме прост и елегантен метод, който изпълнява същата операция като горния пример. Ето How работи рефакторингът: вие променяте структурата на codeа, без да засягате същността му. Има много методи и техники за рефакторинг, които ще разгледаме по-отблизо.
Защо се нуждаете от рефакторинг?
Има няколко причини. Например, за постигане на простота и краткост в codeа. Привържениците на тази теория смятат, че codeът трябва да бъде възможно най-кратък, дори ако са необходими няколко десетки реда коментари, за да се разбере. Други разработчици са убедени, че codeът трябва да бъде преработен, за да стане разбираем с минимален брой коментари. Всеки екип приема своя собствена позиция, но не забравяйте, че рефакторингът не означава намаляване . Основната му цел е да подобри структурата на codeа. Няколко задачи могат да бъдат включени в тази обща цел:- Рефакторингът подобрява разбирането на codeа, написан от други разработчици.
- Помага за намиране и коригиране на грешки.
- Може да ускори скоростта на разработка на софтуер.
- Като цяло подобрява дизайна на софтуера.
"Код мирише"
Когато codeът изисква рефакторинг, се казва, че има "мирис". Разбира се, не буквално, но такъв code наистина не изглежда много привлекателен. По-долу ще разгледаме основните техники за рефакторинг за началния етап.Неоправдано големи класове и методи
Класовете и методите могат да бъдат тромави, невъзможни за ефективна работа точно поради огромния им размер.Голям клас
Такъв клас има огромен брой редове code и много различни методи. Обикновено за разработчика е по-лесно да добави функция към съществуващ клас, отколкото да създаде нов, поради което класът расте. По правило в такъв клас е натъпкана твърде много функционалност. В този случай помага преместването на част от функционалността в отделен клас. Ще говорим за това по-подробно в раздела за техниките за рефакторинг.Дълъг метод
Тази „миризма“ възниква, когато разработчикът добави нова функционалност към метод: „Защо трябва да поставя проверка на параметър в отделен метод, ако мога да напиша codeа тук?“, „Защо имам нужда от отделен метод за търсене, за да намеря максимума елемент в масив? Нека го запазим тук. Кодът ще бъде по-ясен по този начин", и други подобни погрешни схващания.Има две правила за рефакторинг на дълъг метод:
- Ако искате да добавите коментар, когато пишете метод, трябва да поставите функционалността в отделен метод.
- Ако даден метод отнема повече от 10-15 реда code, трябва да идентифицирате задачите и подзадачите, които изпълнява, и да се опитате да поставите подзадачите в отделен метод.
Има няколко начина за премахване на дълъг метод:
- Преместете част от функционалността на метода в отделен метод
- Ако локалните променливи ви пречат да преместите част от функционалността, можете да преместите целия обект към друг метод.
Използване на много примитивни типове данни
Този проблем обикновено възниква, когато броят на полетата в клас нараства с времето. Например, ако съхранявате всичко (валута, дата, телефонни номера и т.н.) в примитивни типове or константи instead of малки обекти. В този случай добра практика би била преместването на логическо групиране на полета в отделен клас (екстракт клас). Можете също да добавите методи към класа за обработка на данните.Твърде много параметри
Това е доста често срещана грешка, особено в комбинация с дълъг метод. Обикновено това се случва, ако даден метод има твърде много функционалност or ако методът прилага множество алгоритми. Дългите списъци с параметри са много трудни за разбиране и използването на методи с такива списъци е неудобно. В резултат на това е по-добре да преминете цял обект. Ако даден обект няма достатъчно данни, трябва да използвате по-общ обект or да разделите функционалността на метода, така че всеки метод да обработва логически свързани данни.Групи от данни
Групи от логически свързани данни често се появяват в codeа. Например параметри за връзка с база данни (URL, потребителско име, парола, име на схема и т.н.). Ако нито едно поле не може да бъде премахнато от списък с полета, тогава тези полета трябва да бъдат преместени в отделен клас (екстракт клас).Решения, които нарушават принципите на ООП
Тези "миризми" се появяват, когато разработчик наруши правилния OOP дизайн. Това се случва, когато той or тя не разбира напълно възможностите на OOP и не успява да ги използва напълно or правилно.Неизползване на наследяване
Ако даден подклас използва само малко подмножество от функциите на родителския клас, тогава мирише на грешна йерархия. Когато това се случи, обикновено излишните методи просто не се отменят or хвърлят изключения. Един клас, който наследява друг, предполага, че дъщерният клас използва почти цялата функционалност на родителския клас. Пример за правилна йерархия: Пример за неправилна йерархия:Изявление за превключване
Какво може да не е наред с едноswitch
изявление? Лошо е, когато стане много сложно. Свързан проблем е голям брой вложени if
изрази.
Алтернативни класове с различни интерфейси
Няколко класа правят едно и също нещо, но техните методи имат различни имена.Временно поле
Ако даден клас има временно поле, от което обектът се нуждае само от време на време, когато стойността му е зададена, и то е празно or, не дай си Боже,null
през останалото време, тогава codeът мирише. Това е съмнително дизайнерско решение.
Миризми, които затрудняват модификацията
Тези миризми са по-сериозни. Другите миризми основно затрудняват разбирането на codeа, но те ви пречат да го модифицирате. Когато се опитате да въведете нови функции, половината от разработчиците напускат, а половината полудяват.Паралелни йерархии на наследяване
Този проблем се проявява, когато подкласирането на клас изисква да създадете друг подклас за различен клас.Равномерно разпределени зависимости
Всички модификации изискват да потърсите всички употреби на класа (зависимости) и да направите много малки промени. Една промяна — редакции в много класове.Сложно дърво на модификациите
Тази миризма е противоположна на предишната: промените засягат голям брой методи в един клас. По правило такъв code има каскадна зависимост: промяната на един метод изисква да коригирате нещо в друг, а след това в трети и т.н. Един клас — много промени."Боклукът мирише"
Доста неприятна категория миризми, която причинява главоболие. Безполезен, ненужен, стар code. За щастие съвременните IDE и линтери са се научor да предупреждават за такива миризми.Голям брой коментари в метод
Методът има много обяснителни коментари на почти всеки ред. Това обикновено се дължи на сложен алгоритъм, така че е по-добре да разделите codeа на няколко по-малки метода и да им дадете обяснителни имена.Дублиран code
Различните класове or методи използват едни и същи блокове code.Мързелив клас
Един клас поема много малко функционалност, въпреки че е планирано да бъде голям.Неизползван code
Клас, метод or променлива не се използва в codeа и е мъртва тежест.Прекомерна свързаност
Тази категория миризми се характеризира с голям брой неоправдани връзки в codeа.Външни методи
Един метод използва данни от друг обект много по-често, отколкото собствените си данни.Неподходяща интимност
Един клас зависи от детайлите на изпълнението на друг клас.Дълги разговори в клас
Един клас извиква друг, който изисква данни от трети, който получава данни от четвърти и т.н. Такава дълга верига от повиквания означава голяма зависимост от текущата структура на класа.Клас дилър на задачи
Клас е необходим само за изпращане на задача към друг клас. Може би трябва да се премахне?Техники за рефакторинг
По-долу ще обсъдим основни техники за рефакторинг, които могат да помогнат за премахване на описаните миризми на 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");
}
Кодът на всеки метод е станал много по-кратък и по-лесен за разбиране.
Преминаване на цял обект
Когато метод се извиква с параметри, понякога може да видите code като този:
public void employeeMethod(Employee employee) {
// Some actions
double yearlySalary = employee.getYearlySalary();
double awards = employee.getAwards();
double monthlySalary = getMonthlySalary(yearlySalary, awards);
// Continue processing
}
public double getMonthlySalary(double yearlySalary, double awards) {
return (yearlySalary + awards)/12;
}
Има employeeMethod
цели 2 реда, посветени на получаването на стойности и съхраняването им в примитивни променливи. Понякога такива конструкции могат да заемат до 10 реда. Много по-лесно е да прехвърлите самия обект и да го използвате за извличане на необходимите данни:
public void employeeMethod(Employee employee) {
// Some actions
double monthlySalary = getMonthlySalary(employee);
// Continue processing
}
public double getMonthlySalary(Employee employee) {
return (employee.getYearlySalary() + employee.getAwards())/12;
}
Просто, кратко и стегнато.
Логичното групиране на полета и преместването им в отделниclassDespite
факта, че примерите по-горе са много прости и когато ги погледнете, много от вас може да попитат: „Кой прави това?“, много разработчици правят такива структурни грешки поради небрежност, нежелание за преработване на codeа or просто отношение „това е достатъчно добро“.
Защо рефакторингът е ефективен
В резултат на добър рефакторинг програмата има лесен за четене code, перспективата за промяна на логиката й не е плашеща и въвеждането на нови функции не се превръща в ада за анализ на codeа, а instead of това е приятно изживяване за няколко дни . Не трябва да преработвате, ако ще бъде по-лесно да напишете програма от нулата. Например, да предположим, че вашият екип прецени, че трудът, необходим за разбиране, анализиране и преработване на codeа, ще бъде по-голям от прилагането на същата функционалност от нулата. Или ако codeът за преработване има много проблеми, които са трудни за отстраняване на грешки. Знанието How да се подобри структурата на codeа е от съществено meaning в работата на програмиста. А обучението за програмиране на Java се прави най-добре в CodeGym, онлайн курсът, който набляга на практиката. 1200+ задачи с незабавна проверка, около 20 мини-проекта, игрови задачи — всичко това ще ви помогне да се чувствате уверени в codeирането. Най-доброто време да започнете е сега :)Ресурси за по-нататъшно потапяне в преработването
Най-известната книга за рефакторинг е "Рефакторинг. Подобряване на дизайна на съществуващ code" от Мартин Фаулър. Има и интересна публикация за рефакторинг, базирана на предишна книга: „Рефакторинг с помощта на шаблони“ от Джошуа Кериевски. Говорейки за шаблони... Когато преработвате, винаги е много полезно да знаете основните дизайнерски модели. Тези отлични книги ще ви помогнат с това: Говорейки за шаблони... Когато преработвате, винаги е много полезно да знаете основните шаблони на проектиране. Тези страхотни книги ще помогнат за това:- „Design Patterns“ от Ерик Фрийман, Елизабет Робсън, Кати Сиера и Бърт Бейтс от поредицата Head First
- „Изкуството на четливия code“ от Дъстин Босуел и Тревър Фуше
- „Code Complete“ от Стив Макконъл, който излага принципите за красив и елегантен code.
GO TO FULL VERSION