Рубріки: Теорія

Як провести рефакторинг та не створити нові баги: повний розбір підходу

Ольга Змерзла

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

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

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

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

Рефакторинг — це дуже важлива практика в процесі розробки програмного забезпечення, тому що вона гарантує, що код залишиться зручним для супроводу, масштабованим і гнучким навіть із плином часу.

Ця практика стосується практично всіх мов програмування та середовищ розробки, включаючи Java, Python і Ruby on Rails.

Мета рефакторингу  зробити код зрозумілішим, модифікувати та підтримувати його з плином часу.

Ця мета досягається за рахунок усунення надскладності коду, поліпшення його загального дизайну, підвищення зручності читання та виразності. Рефакторинг також допомагає виявити надлишковий або повторюваний код. Видалення такого коду підвищує продуктивність та зручність супроводу програмного забезпечення.

Рефакторинг можна робити різними способами. Ось кілька базових прикладів:

  • розбивати великі методи та класи на дрібніші;
  • перейменовувати змінні, функції та класи;
  • додавати коментарі з поясненнями;
  • видаляти шматки коду, які дублюються.

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

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

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

Правила рефакторингу

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

Основні правила рефакторингу перераховані нижче:

  1. Рефакторинг не повинен змінювати поведінку коду з погляду користувача. Наприклад, дані, які виводить код, після рефакторингу повинні залишитися колишніми.
  2. До та після рефакторингу потрібно ретельно протестувати код. Після — щоб переконатися, що ви не створили нові баги чи помилки. До — щоб достеменно знати всі особливості оригінального коду.
  3. Рефакторинг слід виконувати поетапно. Кожен крок має бути невеликим і включати лише одну-дві дії. Це зведе до мінімуму ризик появи нових помилок чи проблем технічного характеру.
  4. Перш ніж переходити до наступного кроку, потрібно перевірити, як вплинув на код попередній.
  5. Код повинен мати чіткі, осмислені імена для змінних, функцій та класів. Це потрібно, щоб іншим розробникам було легко його зрозуміти.
  6. Шматки коду, які дублюються, видаляють, щоб зменшити складність і полегшити підтримку коду. Виняток: якщо немає можливості інакше написати функціональність.
  7. Код має бути максимально простим та зрозумілим. Складний код важко підтримувати з часом.
  8. Код має бути добре організованим та зручним для навігації. Це спрощує пошук та виправлення помилок або проблем у міру їх виникнення.

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

Принципи рефакторингу на прикладах

Щоб краще зрозуміти, як застосовувати вищеописані правила, розглянемо їх на прикладах.

Поліпшення коду без зміни його поведінки з погляду користувача

Припустимо, у вашому коді є функція, яка обчислює суму двох чисел. Після її рефакторингу результат обчислення (тобто те, що код виведе користувачеві) повинен залишитися тим самим:

// до рефакторингу
function sum(a, b) {
  return a + b;
}

// після рефакторингу
function sum(a, b) {
  const result = a + b;
  return result;
}

Тестування

Тестування допомагає перевірити, що код, як і раніше, працює так, як замислено, навіть після внесення змін. Але найголовніше, що тестування має бути ретельним та широким.

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

// до рефакторингу
function sortArray(array) {
  return array.sort();
}

// після рефакторингу
function sortArray(array) {
  const sortedArray = [...array].sort((a, b) => a - b);
  return sortedArray;
}

Рефакторинг слід проводити поетапно

Припустимо, вам потрібно реорганізувати складну функцію, яка виконує кілька операцій. Ви можете розбити її на дрібніші і керованіші функції. При цьому створення кожної нової функції вважатеметься новим кроком рефакторингу.

// до рефакторингу
function complexFunction(a, b, c, d) {
  // perform operation 1
  // perform operation 2
  // perform operation 3
  // ...
  return result;
}

// після рефакторингу
function operation1(a, b) {
  // perform operation 1
  return result1;
}

function operation2(c, d) {
  // perform operation 2
  return result2;
}

function operation3(result1, result2) {
  // perform operation 3
  return result3;
}

function complexFunction(a, b, c, d) {
  const result1 = operation1(a, b);
  const result2 = operation2(c, d);
  const result3 = operation3(result1, result2);
  return result3;
}

Чіткі та осмислені імена змінних, класів та методів

Наприклад, у вас є функція, яка обчислює площу прямокутника. Вона включає:

  • Назву самої функції. Наприклад, calculateRectangleArea.
  • Назви трьох її змінних: ширини та висоти, а також обчислюваної площі. Їх можна так і назвати: width, height і area.
// до рефакторингу
function calculateArea(x, y) {
  return x * y;
}

// після рефакторингу
function calculateRectangleArea(width, height) {
  const area = width * height;
  return area;
}

Дублі коду видаляються

Дублі коду зазвичай з’являються, якщо одна й та ж дія виконується кілька разів. Іноді можна переписати код так, щоб дія виконувалася лише один раз. Але не завжди.

У всіх інших випадках ви можете просто винести код, який повторюється, в окрему функцію:

// до рефакторингу
function calculateTotalPrice(quantity, price) {
  const totalPrice = quantity * price;
  const discount = 0.1;
  const discountedPrice = (totalPrice * discount) + totalPrice;
  console.log(discountedPrice);
}
// після рефакторингу
function calculateDiscountedPrice(totalPrice, discount) {
  return totalPrice * (1 - discount);
}

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

Зазвичай рефакторинг проводять по одній (або кількох одразу) з наступних причин:

  1. Поліпшення якості коду. Рефакторинг допомагає виявити та усунути недоліки коду або дизайну, які можуть призвести до помилок та зниження продуктивності.
  2. Простота обслуговування. Роблячи код легшим для читання та розуміння, рефакторинг допомагає спростити його обслуговування та оновлення з часом. Це особливо важливо для великих та складних програмних систем, якими важко керувати без регулярного рефакторингу.
  3. Продуктивність. Рефакторинг може підвищити продуктивність коду за рахунок усунення непотрібних кроків, зменшення складності коду та оптимізації алгоритмів.
  4. Скорочення технічного боргу. Рефакторинг допомагає скоротити накопичення сміття, дрібних проблем та погано виконаних завдань при проєктуванні та розробці програмного забезпечення. Часто саме вони призводять до погіршення якості коду та ускладнюють підтримку та оновлення коду у майбутньому.
  5. Спільна робота. Рефакторинг допомагає зробити код більш читабельним та зрозумілим для інших розробників. Це полегшує спільну роботу над проєктами та дозволяє уникнути непорозумінь та конфліктів.

Коли потрібно проводити рефакторинг?

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

Ознаки, що рефакторинг потрібен:

  • Ви додали чи збираєтеся додавати нові функції. Якщо ви плануєте розширити свій код, рекомендується спочатку провести рефакторинг того, що є.
  • Ви часто знаходите помилки. Якщо ви витрачаєте багато часу на виправлення помилок у коді, це може бути ознакою того, що вам варто його реорганізувати. Іноді рефакторинг однієї функції вирішує кілька проблем.
  • Ви незадоволені продуктивністю. Якщо код працює повільно, рефакторинг з акцентом на оптимізацію алгоритмів може підвищити його продуктивність.
  • Ви готуєтеся до майбутніх змін. Якщо ви знаєте, що вам потрібно буде вносити зміни до свого коду в майбутньому, спочатку виконайте рефакторинг, щоб спростити реалізацію цих змін.
  • Код має ознаки проблем у системі («код із запашком»). «Запахи коду» — це ознаки того, що у вашому коді можуть бути недоліки дизайну або інші проблеми, які можуть призвести до помилок або зниження продуктивності. Рефакторинг може допомогти усунути системні проблеми та покращити якість всього ПЗ.

Чому рефакторинг дає результати

Рефакторинг приносить результати, тому що покращує якість, зручність супроводу та продуктивність вашого коду без зміни його зовнішньої поведінки.

Це як редагування готового тексту: ви не пишете нічого з нуля, а думаєте, як покращити матеріал.

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

Це своєрідна самоперевірка — чи зможете ви коротко в коментарях пояснити, чому потрібен саме цей шматок коду?

Приклад ефективного рефакторингу

Припустимо, ви працюєте над вебзастосунком з формою авторизації. Форма має одну-єдину функцію перевірки. Функція стежить за тим, щоб поля імені користувача та пароля не були порожніми.

def validate_login(username, password):
    if not username:
        return "Username is required"
    if not password:
        return "Password is required"
    return None

Але застосунок розвивається, і вимог до нього стає дедалі більше. Так, тепер ім’я користувача має містити лише дійсну адресу електронної пошти, а пароль містити рівно вісім символів.

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

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

Логічне рішення — винести кожну з перевірок в окрему функцію з відповідною назвою та метою:

def validate_username(username):
    if not username:
        return "Username is required"
    if not re.match(r"[^@]+@[^@]+\.[^@]+", username):
        return "Username must be a valid email address"
    return None

def validate_password(password):
    if not password:
        return "Password is required"
    if len(password) < 8:
        return "Password must be at least 8 characters long"
    if not any(c.isupper() for c in password):
        return "Password must contain at least one uppercase letter"
    if not any(c.isdigit() for c in password):
        return "Password must contain at least one digit"
    return None

def validate_login(username, password):
    errors = []
    username_error = validate_username(username)
    if username_error:
        errors.append(username_error)
    password_error = validate_password(password)
    if password_error:
        errors.append(password_error)
    return errors or None

Тепер у кожного правила перевірки є своя функція: читати такий код набагато зручніше. Крім того, ми додали функцію validate_login, яка викликає кожну функцію перевірки та повертає список помилок.

Якщо подивитися на ситуацію з боку користувача, нічого не змінилося. Як би не було записано функцію перевірки, на виході вона дасть помилку, якщо ви як користувач введете недійсну адресу електронної пошти або пароль у чотири символи. Але ви як програміст (і ваші колеги) більше не плутатиметеся в тому, де яка перевірка.

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

Коли рефакторинг не потрібний?

За всіх переваг рефакторингу бувають ситуації, коли трудовитрати на нього не мають сенсу. Ось кілька прикладів, коли рефакторинг не потрібний:

  1. Код має обмежений термін служби. Якщо код призначений лише для короткострокового чи одноразового використання, немає сенсу оптимізувати його — його розвитку та розширення точно не буде.
  2. Код стабільний і добре працює. Якщо код працює без суттєвих проблем чи помилок, рефакторингу просто нічого буде поліпшити.
  3. Вартість рефакторингу надто висока. Це не завжди проблема, але при обмеженому бюджеті та інших пріоритетах краще витратити ці гроші на завдання, які мають більший вплив на проєкт в цілому.
  4. Код уже добре спроєктовано. Наприклад, рефакторинг вже проводився, або з нуля було написано добре. У поліпшеннях заради поліпшень ніколи немає сенсу.

Найгірше, що ви можете зробити — це почати робити рефакторинг, не розуміючи навіщо. Так ви просто витратите гроші та час дарма.

Рефакторинг та проєктування

Рефакторинг та проєктування (дизайн-код) — тісно пов’язані поняття у розробці програмного забезпечення, але з деякими ключовими відмінностями.

Рефакторинг — це процес покращення дизайну існуючого коду без зміни його зовнішньої поведінки .

Проєктування — це процес створення структури та організації коду з нуля.

Через цю основну відмінність, рефакторинг проводять після проєктування. Іншими словами, результат проєктування — це код, який використовується для подальшого рефакторингу. Але це не означає, що на цьому все закінчується.

Рефакторинг — це безперервний процес, який має виконуватися постійно протягом усього циклу розробки, а не лише як разовий захід.

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

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

Таким чином проєктування та рефакторинг — два найважливіші аспекти розробки ПЗ. Дизайн закладає основу програмної системи, а рефакторинг допомагає покращити її код.

Рефакторинг та продуктивність

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

Тим не менш, бувають випадки, коли рефакторинг взагалі не впливає на продуктивність. Давайте розберемося, як це працює:

  • Спрощення коду = покращення продуктивності. Якщо код був настільки складний, що комп’ютеру було важко його виконувати, рефакторинг виправить цю проблему. В окремих випадках оптимізація буде помітною не на всіх пристроях.
  • Видалення надлишкового коду = покращення продуктивності. Аналогічно, надлишковий код може уповільнити виконання програми, оскільки вимагає від комп’ютера виконання непотрібних обчислень.
  • Оптимізація алгоритмів = покращення продуктивності. Погано написані алгоритми можуть бути повільними та неефективними. Оптимізуючи їх, рефакторинг може прискорити роботу програми.

АЛЕ:

  • Нові проблеми та помилки = зниження продуктивності. Рефакторинг може спричинити нові проблеми, які можуть вплинути на продуктивність. Наприклад, алгоритм, який здасться більш ефективним, насправді вимагатиме більше обчислювальної потужності.

    Ось чому важливо ретельно протестувати код після рефакторингу, щоб переконатися, що він, як і раніше, працює коректно.

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

Розробка тестів

Ми вже часто згадували це у матеріалі, щоб зробити однозначний висновок: тестування — невід’ємна частина процесу рефакторингу. Це гарантія того, що рефакторинг не внесе до коду нових помилок і що після цього процесу код, як і раніше, буде працювати так, як потрібно.

Розберемо докладніше, коли та як проводять тести.

Перед рефакторингом

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

Приклад:

Припустимо, у вас є функція на Python, яка змінює розмір зображення за допомогою бібліотеки Pillow:

from PIL import Image

def resize_image(input_path, output_path, max_size):
    with Image.open(input_path) as img:
        width, height = img.size
        if width > max_size or height > max_size:
            ratio = min(max_size / width, max_size / height)
            new_size = (int(width * ratio), int(height * ratio))
            img = img.resize(new_size)
        img.save(output_path)

Щоб протестувати цю функцію, ви можете написати простий скрипт, який (1) створює файл зображення потрібного розміру, (2) викликає функцію resize_image для зменшення розміру зображення, а потім (3) перевіряє правильність розмірів вихідного файла:

import os
import tempfile

def test_resize_image():
    input_file = tempfile.NamedTemporaryFile(suffix='.jpg')
    output_file = tempfile.NamedTemporaryFile(suffix='.jpg')
    img = Image.new('RGB', (1000, 1000))
    img.save(input_file.name)
    resize_image(input_file.name, output_file.name, 500)
    assert os.path.exists(output_file.name)
    with Image.open(output_file.name) as img:
        width, height = img.size
        assert width <= 500 and height <= 500

У цьому прикладі функція test_resize_image створює тимчасовий вхідний файл з використанням модуля tempfile розміром 1000×1000 пікселів. Потім він викликає функцію resize_image, щоб змінити розмір зображення до максимального розміру 500 пікселів, і перевіряє, що вихідний файл існує і має розміри менші або рівні 500×500 пікселів.

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

Під час рефакторингу

Регулярні перевірки в процесі рефакторингу допомагають виявити будь-які проблеми та помилки на ранній стадії та усунути їх.

Після рефакторингу

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

Якщо регулярне тестування проводилося досить ретельно, перевірка після рефакторингу не повинна виявити серйозних проблем.

Автоматизоване тестування може бути особливо корисним під час рефакторингу, тому що воно допомагає проводити часте та всебічне тестування з мінімальними зусиллями. Його включають на всіх етапах перевірок: до, після та в процесі рефакторингу.

Проблеми рефакторингу

Хоча рефакторинг має багато переваг, він також може призвести до виникнення певних проблем:

  • порушення існуючої функціональності;
  • надмірна оптимізація;
  • необхідність внесення змін до тих частин коду, де це не передбачалося спочатку;
  • відсутність документації;
  • опір із боку членів команди, яким комфортно працювати з існуючою кодовою базою.

Щоб проблем не було, потрібно підходити до рефакторингу обережно та методично, маючи чіткий план та цілі.

Тестування та документування мають бути головним пріоритетом протягом усього процесу, а зміни потрібно вносити поступово і з прицілом на зручність супроводу та зручність читання.

Методи рефакторингу

Насамкінець поділимося з вами деякими ефективними методами рефакторингу:

  1. Метод вилучення. Включає вилучення розділу коду в окрему функцію. Код стає більш читабельним та зручним.
  2. Вбудований метод. Включає видалення функції та розміщення її коду безпосередньо в функції, що викликається. Код стає простішим на ефективнішим.
  3. Вилучення змінної. Включає створення нової змінної для зберігання значення, яке використовується кілька разів у розділі коду. Спрощується читання та обслуговування коду.
  4. Вбудована змінна. Включає заміну змінної її значенням у коді. Спрощує код та підвищує його продуктивність.
  5. Метод переміщення. Включає переміщення функції з одного класу до іншого. Покращує організацію та структуру коду.
  6. Метод перейменування. Включає перейменування функції або змінної, щоб краще відобразити її призначення та функціональність. Це може зробити код більш зрозумілим та зручним для роботи.
  7. Введення параметра об’єкта. Включає створення нового об’єкта для зберігання групи пов’язаних параметрів, які передаються методом. Робить код більш гнучким та простим в обслуговуванні.

Це лише деякі з багатьох методів, які можна використовувати для рефакторингу коду. Конкретні методи будуть залежати від конкретних цілей процесу рефакторингу і характеристик коду.

Останні статті

Вакансій і наймів більше, а зарплати — менше: що відбувалося на ринку праці у квітні

В квітні на ринку праці збільшилася кількість вакансій для IT-фахівців. На DOU та Djinni спостерігались…

07.05.2024

І всього лише $300. Китайці представили ноутбук на базі RISC-V для ШІ-девелоперів

Китайський стартап SpacemiT представив MuseBook — ноутбук на базі восьмиядерного процесора K1 RISC-V, орієнтований на…

06.05.2024

Учасники Brave1 створили ШІ-платформу HARVESTER для органів держбезпеки

Учасники Brave1, українська команда MATHESIS, розробила для органів держбезпеки платформу HARVESTER на основі штучного інтелекту.…

06.05.2024

Програміст криптовалютного стартапу DeFi хотів виїхати з України за італійським паспортом

Волинський програміст криптовалютного стартапу DeFi намагався виїхати з України за італійським паспортом. Але спроба не…

06.05.2024

Міноборони створило онлайн-калькулятор грошового забезпечення військових

Міністерство оборони запустило онлайн-калькулятор грошового забезпечення військовослужбовців ЗСУ. Про це Міноборони повідомило в соціальній мережі…

06.05.2024

Айтівець Міноборони США понабирав кредитів і хотів продати рф секретну інформацію

32-річний розробник безпеки інформаційних систем Агентства національної безпеки Джарех Себастьян Далке отримав 22 роки в'язниці…

30.04.2024