Прощавай, else: покращення якості коду за допомогою лінійних конструкцій
Програмування — це коли ти розв’язуєш одну проблему і створюєш дві нові
Привіт, я Руслан Дмитракович — розробник програмного забезпечення та підприємець. Кар’єру в ІТ розпочав у 1994 році. Спеціалізуюсь на керуванні складністю в розробці та працюю архітектором.
Кожен розробник стикався з легасі-кодом — заплутаним, складним для розуміння та модифікації. Іронія в тому, що майже кожен з нас у якийсь момент створює такий код сам. Чому так відбувається?
Одна з головних причин — поступове ускладнення логіки програми. Простий на початку код з часом обростає новою логікою, модифікується наступними розробниками і через деякий час стає достатньо бридким, щоб почати використовувати для його опису нелітературні епітети.
Існує багато статей і книжок, присвячених покращенню якості коду. Нема сумнівів, що більшість розробників їх читала. Однак, попри це, далеко не всі з них запам’ятовують і застосовують запропоновані рекомендації.
Причина проста — когнітивні можливості людини обмежені, і досить важко як запам’ятати, так і на практиці використовувати велику кількість різних рекомендацій, особливо в умовах постійного тиску дедлайнів, на які, як правило, посилаються автори нечитабельного коду.
У програмуванні поганим тоном вважається використання конструкцію GOTO. Попри її наявність у багатьох мовах програмування, покоління розробників фактично «скасували» GOTO, і в коді цей оператор зустрічається дуже рідко. Сьогодні я запропоную вам зробити те ж саме зі словом ELSE, тобто відмовитися від його використання, і показати, як при мінімальних зусиллях отримати значний прогрес у боротьбі за якість коду.
Принцип коду без else
Ключова ідея полягає у відмові від використання конструкції if-else на користь більш лінійної структури коду. Нижче наведені різні варіанти логіки, у яких можливе використання else, але ми застосуємо альтернативні варіанти.
Послідовність інструкцій та перевірок
Замість вкладених умовних конструкцій код організовується як послідовність окремих блоків. Кожен блок або виконує певну дію, або перевіряє умову і виходить з функції, якщо умова виконується.
Приклад:
def process_order(order): if not order.is_valid(): return "Invalid order" if order.is_paid(): return "Order already paid" payment_result = process_payment(order) if not payment_result.is_successful(): return f"Payment failed: {payment_result.error_message}" send_confirmation_email(order) return "Order processed successfully"
Використання раннього виходу
Замість else-блоків використовуються return, throw exception або break для виходу з функції чи блоку при певних умовах.
Приклад:
function saveOrderToDB($order, $db) { try { $db->beginTransaction(); // Збереження інформації про замовлення $orderId = $order->save($db); if (!$orderId) { throw new Exception("Failed to save order"); } // Оновлення кількості товару на складі if (!$order->updateInventory($db)) { throw new Exception("Failed to update inventory"); } // Створення запису про оплату if (!$order->createPaymentRecord($db)) { throw new Exception("Failed to create payment record"); } $db->commit(); return $orderId; } catch (Exception $e) { $db->rollBack(); return false; } }
Тернарний оператор
Для простих умовних присвоєнь використовується тернарний оператор замість if-else.
const status = user.isLoggedIn() ? "active" : "inactive";
Спробую пояснити, чому я вважаю тернарний оператор простішою конструкцією. Нижче наведена альтернатива цьому коду, написана з використанням if-else.
let status; if (user.isLoggedIn()) { status = "active"; } else { status = "inactive"; }
Тепер порівняємо метрики двох фрагментів і зробимо висновки:
Метрика |
Тернарний оператор |
Конструкція if-else |
Кількість знаків |
57 |
93 |
Кількість знаків без пробілів |
50 |
73 |
Кількість слів |
7 |
15 |
Кількість рядків |
3 |
6 |
Якщо хтось вважає, що лаконічність без втрати змісту не є вагомим аргументом, сперечатись не буду. Найголовніше — у випадку тернарного оператора менше можливостей ускладнити логіку. У першому випадку ви не зможете легко додати будь-який код, на відміну від конструкції if.
Додатково зверну увагу на форматування коду тернарного оператора. Вираз можна написати в один рядок, і тоді його сприйняття значно погіршиться. До речі, те саме можна зробити і з конструкцією if-else. Але автоформатер вашого середовища розробки виправить вас і розіб’є такий код на декілька рядків. Відповідно бажано налаштувати його і на форматування тернарного оператора для покращення читабельності.
Тим, хто пише на Go, з тернарним оператором не пощастило. Розробники мови дотримувались іншої думки і не створили тернарний оператор (але, мабуть, оскільки в операторі присутнє Go, реалізували GoTO :-) ). Тому інші розробники на Go, яким подобається тернарний оператор, пропонують робити так:
func Ternary(statement bool, a, b interface{}) interface{} { if statement { return a } return b } var res = Ternary(val > 0, "positive", "negative")
Виділення логіки в окремі функції
При необхідності складної логіки замість вкладених if та розгалуження if-else створюється окрема функція, що має зрозумілу описову назву.
public double getDiscountPercentage(User user, Product product) { if (user.isPremium() && product.isOnSale()) { return 0.2; } if (user.isPremium() || product.isOnSale()) { return 0.1; } return 0; } // Використання: double discount = 0.0; if (isBlackFriday) { discount = getDiscountPercentage(currentUser, selectedProduct); } System.out.println("Discount percentage: " + discount);
Весь ваш код отримує однотипну структуру і обмежений рівень вкладеності. Якщо ви будете намагатись не використовувати else, то, швидше за все, вам досить часто доведеться «перевертати» умови. Наприклад, замість «==» використовувати «!=», а також створювати більше функцій і таким чином думати над їх назвами. Обидві ці операції будуть спонукати вас краще міркувати над кодом.
Когнітивне навантаження та лінійність
Запропонований підхід — це реалізація правила «слона треба їсти по частинах». Люди легко сприймають лінійну послідовність команд, на відміну від коду, який містить багато розгалужень. Це пов’язано з обмеженою оперативною пам’яттю. Що більше треба одночасно тримати в голові, то більше потрібно розумових зусиль і переміщень кодом.
Блок коду сприймається програмістом як щось суцільне, і для його розуміння необхідно розібратися з усіма його частинами. У книзі Фелін Хермане «Розум програміста: як зрозуміти та осмислити будь-який код» авторка розповідає про те, що програмісти сприймають код окремими частинами — «чанками». І що простіше розбити код на чанки, то легше він сприймається.
За цією технікою ви свідомо виділяєте чанки, які буде легше зрозуміти іншим програмістам, що працюватимуть з кодом у майбутньому.
Коли розробник натрапляє на логіку, побудовану за принципом послідовних перевірок та дій, він може легко простежити потік виконання програми. Максимум, що треба тримати в голові — одну умову та одну операцію, яка має виконуватись.
Вимоги до пам’яті розробника знижуються до рівня акваріумної рибки — все, що потрібно в цю секунду, у нього перед очима.
Є ще один неочевидний, але дуже важливий наслідок використання else. Більшість програмістів використовують правило: «працює — не чіпай». Тому коли розробник стикається з кодом, у який потрібно внести зміни, він, швидше за все, ще більше його ускладнить. Додасть нову логіку замість того, щоб робити покращення, змінюючи наявний код.
Отже, код з else з більшою ймовірністю буде «розбухати» та створювати конструкцію, у якій важко розібратися. Прибираючи else, ви зменшуєте потенційну можливість ускладнювати код.
Наслідки застосування цього методу:
- Підвищення читабельності коду. Лінійна структура коду без складних розгалужень робить його більш зрозумілим з першого погляду.
- Спрощення модифікації. Впровадження нової логіки не вимагає втручання в наявні умовні конструкції, а просто додає новий блок перевірки чи дії.
- Зменшення ймовірності помилок. Простіша структура коду зменшує шанси допустити логічну помилку при його написанні чи модифікації.
- Покращення тестованості. Код з чіткими точками виходу легше покрити unit-тестами.
- Усунення спокуси ускладнювати поточний код.
Висновки
Підхід до написання коду без використання else є простим для запам’ятовування та застосування, але при цьому дає значний ефект.
Відмова від складних умовних конструкцій на користь лінійної послідовності дій та перевірок дає змогу створювати програму, яку легше читати, розуміти та змінювати. Це особливо важливо в довгострокових проєктах, де код має залишатися зрозумілим та підтримуваним протягом тривалого часу.
Застосування цього методу вимагає певної дисципліни та зміни звичного способу мислення. Однак описані вище результати варті цих зусиль.
Пам’ятайте, що наявність else у коді може бути «лакмусовим папірцем» зайвої складності. Кожного разу, коли ви відчуваєте спокусу використати else, подумайте, чи не можна переписати цю частину коду більш лінійно.
Простий принцип — уникнення «шкідливого слова» else — може стати потужним інструментом у вашому арсеналі для боротьби зі складністю. І на відміну від довгого списку правил, його легко запам’ятати і застосовувати щодня.
Найкращі коментарі пропустити