Читабельність коду. Способи, кращі практики та помилки
«Чистий код читається як добре написана проза», — писав Граді Буч, автор Object Oriented Analysis and Design with Applications. Звичайно, я його не читав, але цитата дуже подобається.
Привіт, я Олександр, COO у VT Labs. Маю близько десяти років досвіду в Software Development, працював як у великих закордонних компаніях, так і сам на себе. За цей час я мав нагоду подивитись на код з різних сторін: як розробник, як менеджер, та як замовник, тож маю певне уявлення, як ті чи інші практики та стандарти коду впливають на життя девелопера, його оточення та продукт. Тож ця стаття може бути цікава не тільки розробникам, але і людям з бізнесу.
Перш за все, пропоную домовитись про те, навіщо і кому взагалі потрібна читабельність коду. Те, що ми робимо, зазвичай називається software, і це є антиподом hardware. Це те, що можна легко змінити. Якщо розробник не може зрозуміти код, він не зможе виправити баг чи запилити нову фічу.
Є відомий факт про те, що швидкість друку не впливає на швидкість розробки, бо більшість часу девелопер читає код, а не пише. Не думаю, що когось питали на інтерв’ю про число символів у секунду.
Одне з моїх найулюбленіших питань на співбесідах, це «Який код краще, той що працює, але не зрозумілий, чи несправний, але його легко прочитати?». Багато хто відповідає, що справний краще. Але баги можна виправити, а незрозумілий код вже не змінити, його можна тільки викинути. Він вже не є software.
Так кому ж потрібен зрозумілий код? В першу чергу тому, хто його буде змінювати. Парадокс в тому, що найчастіше — це той самий девелопер, що його писав. Коли ти пишеш читабельний код, ти допомагаєш майбутньому собі.
Часто під тиском замовника або менеджера виникає ілюзія того, що можна зараз зробити швиденько, а потім відрефакторити. Але такий підхід уповільнить розробку вже поточної таски, навіть не наступної, тому що майбутнє наступає швидше, ніж хочеться — вже наступного дня, а також на code review, тестуванні, та на стадії «це дуже круто, але треба дещо інакше».
Спочатку скажи «ніт»
Час — це завжди гроші. Для бізнесу час — це кількість оплачених годин девелоперів та втрати від того, що фіча ще не у продакшені. Сукупно це можуть бути величезні кошти, тому бізнес завжди бажатиме всього, бажатиме на вчора, та обов’язково не так, як було раніше узгоджено, тому що ситуація на ринку вже змінилась. Це природньо. Якщо замовник цього не хоче, то мертвий або він, або його компанія.
«Скільки часу вам потрібно, щоб запилити ці фічі? Тільки майте на увазі, що це потрібно зробити цього тижня! Ви ж фахівець, придумайте як!», — хто не був учасником такої розмови? «Так, я фахівець і тому знаю, що це неможливо. Зараз я піду і подумаю, що я можу зробити цього тижня, а що потім і коли», — це єдина відповідь, після якої ваш код може залишитись читабельним.
«Єдиний спосіб робити швидко — робити добре», — писав Роберт Мартін. А щоб робити добре, потрібен час. Девелопмент можна порівняти з приготуванням їжі — якщо збільшити вогонь, то вона згорить замість того, щоб приготуватися швидше. Те саме відбувається з кодом, коли розробник погоджується на нереальні терміни.
Срібна куля
Чи можна мати якусь автоматичну метрику читабельності коду? Звичайно, наприклад в SonarQube є метрика cognitive complexity, тобто складність сприйняття. Її можна побачити в розділі Measures. Зрозуміло, що гарні показники не обов’язково означають зрозумілий код, але погані точно скажуть, що щось пішло не так.
How to find complexity metrics in SonarQube
Дуже важливо змінити дефолтне значення у правилі «Cognitive complexity of functions should not be too high» в розділі Quality Profiles. Стандартне «15» пропускає взагалі все, можна поставити хоча б «5».
Те саме варто зробити й з правилом «Cyclomatic complexity of functions should not be too high». Ця метрика показує, скільки юніт-тестів потрібно написати, щоб покрити ними весь код.
Знову тести :(
Яке взагалі відношення мають тести до читабельности коду? Якщо подивитись на графіки Cognitive Complexity і Cyclomatic Complexity, то вони виявляються дуже схожими, і це не просто так. Якщо функція є нагромадженням if/else/case, сайд-ефектів, додаткових аргументів та return стейтментів, то буде складно і прочитати її й написати тести.
Функції з більшою Cyclomatic Complexity(7 vs 2) складніше читати
Cognitive Complexity vs Cyclomatic Complexity in SonarQube
Дуже просте правило: якщо ти не в змозі швидко написати простий тест, то код треба рефакторити до тих пір, поки тест не буде значно простіший за код. Більшість проблем, пов’язаних з юніт-тестами, — це проблеми поганого коду.
Складності також виникають, коли розробник спочатку пише код, а потім намагається покрити його тестами. Такий код вже не проєктувався таким чином, щоб можна будо писати тести легко. Потрібно витратити час на ручне тестування, потім на те, щоб покрити тестами код, який може бути дуже складний для цього, а потім на рефакторинг. Тому що з першого разу написати тест не вийшло. Що трапиться в більшості випадків? Правильно: «Зараз важливіше закрити таску, ніж ті тести». І поганий код йде в продакшн.
Як не витрачати на це час? Дуже просто — тестувати свій код тестами, а не руками. Скільки разів на хвилину розробник може вивести console.log? А скільки тисяч юнітів можна виконати? Приємним бонусом буде і те, що автоматичний тест не помиляється, що скоротить час на фікс багів.
Достатньо відомим є експеримент, коли девелопер виконував одну й ту саму невелику задачу кілька разів поспіль з TDD і без. Рішення з TDD завжди були швидше, хоча і відчувались суб’єктивно довше.
Але як такий підхід впливає на реальні проєкти? Є багато наукових досліджень з різними висновками, але тільки одне вивчає вплив TDD на maintainability. Це, на мій погляд, висвітлює відірваність академічних праць від індустрії в реальному світі. Дослідження ж maintainability, попри зниження продуктивності, показує суттєве зниження середнього часу на change request з 80+ до 60- часів. Причиною цьому цілком може бути зниження cyclomatic complexity з 6-7к до 4.5к. Код, дизайн якого базується на тестах — простіший, і тому його легше читати та змінювати.
Зниження складності з TDD. Credit: T. Dogsa, D. Batic
Слід зазначити, що розробники в дослідженні не використовували TDD раніше, що може бути причиною нижчої продуктивности на першому етапі. Але у світі сучасної Agile-розробки, яка базується на зворотному зв’язку від юзера, більш ніж на upfront design, проєкти перетворюються на потік change request. Тож ми можемо стверджувати про вищу продуктивність в перспективі.
Фідбек девелоперів про TDD. Credit: T. Dogsa, D. Batic
Звичайно, винятково TDD мало хто витримає, проте це чудовий приклад того, що правильне використання тестів пришвидшує розробку, а не сповільнює. Навіть якщо просто почати з заміни ручного тестування юнітами, то результат приємно вразить.
FUBAR foobar
Назви — це найголовніше з того, що не виправити автоматично чи тестами. Ми не читаємо оператори та дужки, ми читаємо назви. Вони надають коду сенс, це зміст прози. Хтось читав би Толкієна, якщо замість Арагорна був би prsn_2931? Там і без того достатньо речей, які відрізняються лише одним символом.
Перш за все, назва повинна максимально точно і зрозуміло описувати сутність. Вона не має дезінформувати.
- Назва має бути змістовна.
- Змінні, константи та класи це завжди іменники.
- Методи та функції — дієслова.
- Не треба скорочувати — це унеможливлює пошук.
- Не треба використовувати синоніми для однієї сутності. Наприклад user і customer.
- Не треба робити «magic numbers». Числа повинні бути зрозумілими.
Чудовою ідеєю буде встановити Code Spell Checker в IDE, це не тільки позбавить від несподіванок при пошуці, але й від сорому, якщо код буде читати нейтив.
Погані приклади:
- const p
- const prise
- const price = () => ‘$2000’
- getUserData()
getCustomerInfo() - Product.productName
- if (width < 768) {...}
Як краще:
- const price
- const price (use Code Spell Checker)
- const getPriсe = () => ‘$2000’
- getUser()
- Product.name
- const MOBILE_BREAKPOINT = 768
if (width < MOBILE_BREAKPOINT) {...}
Tab vs Space
Форматування дуже важливе, бо не можна читати книгу без полів, абзаців, заголовків, розділів, та у якої текст не влазить на сторінку. Ще важливіше, щоб воно було завжди однаковим. Можна користуватись табами, можна пробілами, але їх не треба змішувати.
На щастя, про більшість проблем форматування можна забути, якщо користуватись Prettier або чимось подібним.
Але є дещо, на що варто звернути увагу:
- Довжина рядка — не повинно бути горизонтального скролу, бо так не зручно читати. Зазвичай, це навколо 120 символів.
- Якщо немає вертикального скролу, то взагалі добре, але не завжди можливо.
- Імпорти з бібліотек варто відокремити порожнім рядком.
- Варто видаляти старий код — чим менше коду, тим легше читати.
No comments
Не треба писати коментарі, вони не зроблять незрозумілий код краще. Напевно, кожен читав якусь стару товсту книгу, в якій відбувається щось дивне, і виносками пояснюється, що насправді мав на увазі автор в контексті його часу та положення. Це і є коментарі, які пояснюють код. Цю книгу треба було б відрефакторити, але її девелопер вже давно вимер, як і розробники на Cobol.
Ще один підводний камінь коментарів полягає в тому, що їх забувають змінити або видалити, коли змінюють код. Як наслідок, код з часом наповнюється дезінформацією.
Якщо код не зрозуміти без коментаря, то треба рефакторити код, а не писати коментар.
Але є винятки:
- Legal коментарі. Наприклад, ліцензія.
- Пояснення неочевидної бізнес-вимоги. Наприклад, «треба обрізати три останні символи строки, тому що третя сторона передає неправильні дані». Такій коментар не пояснює, що відбувається, він пояснює навіщо.
- TODO — наші рішення не завжди оптимальні, тому варто залишати собі нагадування щось виправити, коли таке трапляється.
TL;DR
Читабельний код — це тільки основа, але без неї складно обговорювати інші аспекти програмування.
- «Єдиний спосіб робити швидко — робити добре», — Роберт Мартін.
- Static code analysis дуже сильно покращує життя.
- Юніт-тести допомагають писати читабельний код швидше.
- Назва завжди повинна мати сенс.
- Жити без Prettier не дуже добре.
- Коментарі — це зло, але іноді виправдане.
121 коментар
Додати коментар Підписатись на коментаріВідписатись від коментарів