(Не) культ чистого коду: як знайти баланс між швидкістю та якістю у продуктовій команді

Усі статті, обговорення, новини про Mobile — в одному місці. Підписуйтеся на телеграм-канал!

Привіт! Мене звати Катерина Павлюкова, я — iOS Engineer в компанії Universe. Ми працюємо у двох напрямах — створення додатків утиліт та розвиваємо власний R&D-центр. Сьогодні ми займаємо 3 місце у світі за доходом як паблішер функціональних бізнес-додатків у світі. Моя команда саме займається напрямом розробки утиліт — Scan Guru, Translator Guru, Cleaner Guru.

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

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

Що таке чистий код

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

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

Для кожної команди оцінка якості коду істотно відрізнятиметься. Навіть у двох iOS-командах можуть існувати різні правила щодо чистоти коду. Тому оцінка якості — радше суб’єктивна. Але пропоную умовний чекліст, як оцінити код:

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

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

Стандарти чистого коду в продуктовій команді

Наші утиліти в Universe стартували в статусі R&D-проєктів чотири роки тому, а зараз у нас вже 40 млн юзерів зі всього світу. Тоді у нас в команді було два iOS-розробники, а сьогодні — пʼятеро.

Спершу продуктова команда прагнула придивитися до ринку, перевірити гіпотези, спробувати його «на дотик». У цей час, звичайно ж, пріоритет віддається швидкості розробки. Головне завдання розробника — знайти баланс між швидкістю та якістю, подальшою масштабованістю коду та гнучкістю рішень. Інша особливість продуктових команд — проведення великої кількості A/B тестів, які з часом захаращують проєкт.

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

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

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

  • Дублювання коду — коли у двох або більше модулях пишеться ідентичний фрагмент коду. Надалі розробник буде змушений вносити зміни не в одній функції, а шукати всі продубльовані фрагменти. Крім збільшення розміру кодової бази, це спричиняє проблеми в тестуванні, читабельності тощо. У великому проєкті такі речі вважаються критичними, адже витрачається дуже багато часу на рефакторинг і тестування. Це знає кожен розробник, проте це правило часто забувають.
  • Реалізація простого функціонала дуже складними способами. Вважаю, що рівень розробника характеризує вміння описати щось складне дуже просто. Так, щоби через пів року це було зрозумілим і йому, і менш досвідченому розробнику, що прийде на проєкт. Це важливо — писати не під себе, а для інших, ставитися до проєкту, як до командного продукту.
  • Експерименти з підходами, архітектурами. На ці граблі ми наступили в Universe. Наші проєкти ми пишемо на RxSwift, використовуємо архітектуру MVVM+Coordinator. Але в якийсь момент нам захотілося спробувати інший підхід — Redux. Спробували — непогано, але великого ефекту не побачили. Зрештою Redux залишився, але він не приніс жодних плюсів. Натомість лише ускладнив проєкт, додав ще одну сходинку для входу нового розробника, і проблеми з подальшою підтримкою. Водночас додавання Redux в один проєкт призвело до його міграції в інші, адже створюючи наступний сканер, ми беремо за основу модулі попереднього. І видалити цей фрагмент — уже дуже дорого й болісно. Тому краще, щоб подібні експерименти проводилися на невеликих проєктах. І якщо переваги будуть суттєвими, далі запроваджувати їх у нових продуктах.
  • Тимчасові рішення. Часто розробника просять зробити якусь фунціональність нашвидкуруч, адже скоро реліз. Тому він пише тимчасове рішення. Далі воно проходить перевірку A/B тестом. І часто в цей момент усі забувають, що це було тимчасово, лишають цей технічний борг і йдуть далі. Розробник має нагадати менеджеру, що йому потрібен час на допрацювання та тестування, щоби цей фрагмент коду став стабільним та зайняв повноцінне місце в проєкті. Такі речі не можна залишати на потім.

Пʼять порад, що полегшать життя розробникам

1. Вкластися у MVP

На старті проєкту розробнику краще бути налаштованим, що цей проєкт житиме довго, команда — збільшуватиметься, і код також буде змінюватися, розвиватися, зростати. Розуміючи цю відповідальність, розробник закладатиме хороший фундамент. Часто зустрічається зворотний настрій — «хто його знає, чи зайде чи ні, зараз MVP швидко випустимо й далі подивимось». Але коли проєкт вистрілює, стає дуже прикро, що MVP написано неякісно.

2. Запобігати нестандартним рішенням

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

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

3. Повторне використання

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

4. Неймінг

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

5. Регулярна чистка

Коли в проєкті активно проводять A/B тести, вони накопичуються та збільшують кодову базу, а ресурси, що для них використовуються (картинки, відео, контент), нарощують вагу збірки. Тести також можуть перетинатися та впливати на результати одне одного. Тому ми завжди виділяємо час, щоби пройтися по ключах та перевірити, що актуально, а що ні, і видаляємо з коду та Remote Config.

Нещадно видаляйте мертвий код. Не варто зберігати його «про всяк випадок» — завжди є система управління версіями, за допомогою якої ви зможете відновити код, адаптувати його або використовувати повторно.

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

Шаблон неймінгу для компонентів модуля

Кожен екран, який бачить користувач, є окремим модулем. Для його створення потрібен набір компонентів — ViewModel, ViewController, Coordinator. Припустимо, у нас є модуль Onboarding та Settings. Для початку пропишемо протоколи для ViewModel.

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

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

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

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

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

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

Що не так із рефакторингом

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

  1. Рефакторинг без покриття тестами. Якщо функціональність не покрита тестами, рефакторинг може дати лише додаткові проблеми. Тому наводити лад перед релізом, не враховуючи час на тестування — це найбільша помилка. Правило «працює — не чіпай» ніхто не скасовував.
  2. Рефакторинг без погодження. Якщо нова функціональність суттєво залежить від старої і програміст стоїть перед вибором — продовжити «поганий код» або провести рефакторинг — варто узгодити з командою кількість часу/ресурсів, вигоди та доцільність його проведення.
  3. Рефакторинг поза планом. При плануванні релізу варто враховувати час на закриття технічного боргу. Розробник має попередити менеджера про ризики тимчасового рішення і про необхідність доопрацювання в наступному релізі.

Ми не рефакторимо всі наші проєкти повністю, адже це не рентабельно. Скоріше, ми робимо висновки та намагаємось не повторювати помилок. Не варто створювати культ ідеального коду й постійно переписувати його — це дорого і не має сенсу. Памʼятайте про це під час написання коду і вам не доведеться його виправляти. Адже, згідно з класичною роботою Роберта Мартіна на тему чистого коду, істина десь посередині:

«Програмісти стикаються з основним парадоксом базових цінностей. Кожен розробник, хто має значний досвід роботи, знає, що попередній безлад уповільнює його роботу. Але при цьому всі розробники під тиском створюють безлад у своєму коді для дотримання графіка. Отже, вони не мають часу, щоби працювати швидко! Справжні фахівці знають, що друга половина цього феномена неправильна. Неможливо витримати графік, влаштувавши безлад. Насправді цей безлад відразу ж уповільнить вашу роботу, і графік буде зірвано. Єдиний спосіб витримати графік і єдиний спосіб працювати швидко полягає в тому, щоби постійно підтримувати чистоту в коді».

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

  1. Онбординг нового співробітника. Нещодавно до нас в команду вийшов новий iOS-розробник. Перший тиждень він проходив процес адаптації, а вже з наступного — провів реліз з трьома тестами.
  2. Автономність розробників. Сеньйор розробник витрачає менше часу для навчання джуніорів.
  3. Шаблонний код пришвидшує випуск схожих релізів. Ми постійно тестуємо варіанти екранів продажів у застосунках. Тому не витрачаємо додатковий час на розробку нового рішення, адаптуємо попередні і запускаємо тест.
  4. Взаємозамінність розробників між продуктами.

Корисні ресурси для тих, хто працює зі Swift

  • SwiftLint — утиліта від розробників Realm для автоматичної перевірки Swift-коду. Утиліта містить набір правил, які прийняті в спільноті Swift. Ці правила добре описані в популярних посібниках за стилем, таких як Ray Wenderlich’s Swift Style Guide. Зрозуміло, можна додавати правила. SwiftLint підтримує інтеграцію з Xcode, Appcode, Atom.
  • SwiftGen — інструмент для автоматичної генерації коду Swift для ресурсів ваших проєктів (таких, як зображення, локалізовані рядки тощо), щоби зробити їх безпечними для використання.
  • Sourcery — генератор коду для мови Swift, побудований на власному SwiftSyntax від Apple. Дозволяє автоматично генерувати шаблонний код.
👍ПодобаєтьсяСподобалось5
До обраногоВ обраному4
LinkedIn
Дозволені теги: blockquote, a, pre, code, ul, ol, li, b, i, del.
Ctrl + Enter
Дозволені теги: blockquote, a, pre, code, ul, ol, li, b, i, del.
Ctrl + Enter

Швидко та якісно може бути лише у варіації максимально просто

«хто його знає, чи зайде чи ні, зараз MVP швидко випустимо й далі подивимось». Але коли проєкт вистрілює, стає дуже прикро, що MVP написано неякісно.

Краще гуано що вистрілило ніж якісний проект який ні)

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

Так в них продукт, в них все відбувається за кошти клієнта.
Та й в аутсорсі/аутстафі з оплатою за тушко-години клієнт платить за все)

Та то я таблетку забув випити. Не звертайте уваги.

Ось дивиться, інформація старіє. Треба її амортизувати.
Підтримка нового коду лехка і приємна. Дешева.
Підтримка коду написаного хто зна коли — коштує скажених грошей.
Які б можна було узяти із амортизаційного фонду. Як би він був.
Чи може він є?
Мабуть, проблема COBOL\а була б вирішена, якби були амортизаційні відчислення на відшкодування старіння коду.

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

Як на мене то розробник повинен вміти працювати в спектрі «гівно й пали але зараз» до «якісно, але дорого та довго» і виходячи із ситуації пропонувати підхід. Пропонуючи гівно та палки коли горять строки треба доносити до менеджменту що то я не знайшов спосіб зробити модуль которий коштує 10к за 6к. Це означає що 6к треба буде витратити зараз, та ще 4..6к потім, в рамках технічної сторьки, бажано її прям зараз заводити, прилінковувати до старої, оцінювати та планувати на майбутні спринти.

Як на мене, клієнт платить за весь обсяг робіт, включаючи розробку документації та тех. підтримку. Але, документаторам та техпідтримці платять не стільки щоб вони виконували належним чином амортизаційні заходи. Чому так? Мабуть це така хитра економія на амортизації.
Це є болісна проблема всього економічного сьогодення — тобто капіталістичного устрою. Продукт продається, а ось відходи виробництв то є такі собі хитрі доходи капіталістів. Це може бути якось повязане із бичо-медвежими циклами у економиці.
З іншого боку можна сказати так: програмні продукти дешеві настільки що їх закупівля та використання дуже великі вигоди приносять. Беремо продукт — якщо документація або відсутна або застаріла це маркер.
Дрова+Вогонь=Тепло+Зола
Із реалізацієй тепла все гаразд, а ось на золу усі 0 уваги. Наче її немає, але тоді у нас
perpetuum mobile у економиці. Бо якісь махінації із золою це прибуток капіталіста. Саме із за цього ростуть гори garbage. Невигідно амортизувати програмні продукти.
Але необхідно.

Q: Як знайти баланс між швидкістю та якістю у продуктовій команді?
A: Ніяк. Або якісний продукт, або лайно (але дешеве). Обидва виграють на відповідному ринку.

Золотої середини в маркетингу не існує. Дороге напівлайно нікому не треба.

Покажи хоть один качественный продукт, выпущенный за последние 10 лет?

Телеграм устроит? Львиная доля его апдейтов — это новые фичи, а совсем даже не багфиксы.

Нет. Ему еще нет 10 лет.

Покажи хоть один качественный продукт, выпущенный за последние 10 лет?
Нет. Ему еще нет 10 лет.

facepalm.bmp

Телеграм был хорош. Но после апдейта когда премиум добавили — всё пошло по п...

У меня не работает видеосообщение, просто не запускается. Зашел в гугл отзывы, там у многих посыпалось всё...

не зовсім програма, хоча...
minecraft — 10 років вже, останній великий реліз, десь місяць тому

Підписатись на коментарі