Проблеми mobile-розробки, які вже всім набридли
Вітаю! Мене звати Володимир, і я сьогодні багато говоритиму про проблеми, з якими стикається розробник.
Якщо ви не працюєте в одній компанії дуже довго, вам може не повезти долучитися до багатьох проєктів на різних стадіях, провести їх з нуля до проду, врятувати від неминучої смерті чи просто добити, щоб не мучився. Такий досвід дає змогу подивитися, які складнощі зустрічаються як в маленьких пет-проєктах, так і у великому ентерпрайзі.
Багато з них часто ігноруються, що може призвести до неочевидних проблем чи багів, деякі ж дуже просто виправити й ваше життя на проєкті стане трішки легше. Тут переважно будуть рекомендації, що стосуються розробки Android-застосунків, але більшість з них стосується проєктів будь-якої технології. Більшість з цього — опис власного досвіду та збірка факапів, про які цікаво почитати. Та й на чужих помилках веселіше вчитися, еге ж?
Структура проєкту
Коли IDE створює новий проєкт з нуля, там можуть згенеруватися багато файлів та тек, що будуть непотрібні чи заважатимуть у проєкті. Тут варто розуміти базову структуру та компоненти, з якими треба працювати: модулі, конфіги, тести, які ніхто не буде писати.
Якщо зазирнути в .gitignore
, там буде десь local.properties
, хоча вони й лежать в ігнорі, навіть IDE може на це плюватися, але для чого вірити якомусь софту?
Хоча завжди може бути «краще». В одній компанії першого робочого дня я стягував репозиторії Android (1,5 Гб) та iOS (4,5 Гб). Косяк в тому, що попередники комітили всі версії застосунків, які заливалися в Google Play, а це спричинило неадекватний набір ваги репозиторію. А тепер уявіть, як було CI/CD клонувати цей проєкт щоразу, коли потрібно було його зібрати та відтворити півтора тесту.
Якщо не помиляюся, то кожен білд там займав по 40 хвилин. Найцікавіше, що це можна пофіксити, якщо підчистити git-історію — спробував це зробити і проєкт важив вже ~100 Мб, а не 1,5 Гб, але чи це до цього часу виправили — мені невідомо.
З останньою розробленою фічею я викинув з проєкту більше тисячі файлів. Гарний клікбейтовий заголовок, але все набагато простіше — попередник просто клонував TS-проєкт до Android-проєкту, не забувши при цьому закомітити всю теку node_modules (аналог .gradle
чи build-теки).
Для чого? Щоб просто мати змогу раз в місяць-два генерувати спеціальний файл, який потім використовується у проєкті. Чому б не відкрити його в окремому IDE та робити там свої кодогенераційні темні справи? От і мені не відомо.
Поки що найкращий пул-реквест виглядає так:
З хороших підходів тут можна згадати про шаблони. Завдяки цій функції можна один раз зробити проєкт та використовувати готову структуру, що буде максимально відповідати вашим потребам. Особливо це актуально у компаніях, що мають багато мікросервісів.
Відсутність README
Напишіть в коментарях вашу історію, коли під час відкриття проєкту він у вас не збирався. Чи навпаки — коли витрачали години на те, щоб просто цей мотлох змусити працювати.
Вам можуть допомогти один раз налаштувати середовище розробки, а потім, як прийде час змінити ноут чи до вас прийде новий колега, історія повторюється знову.
Звісно, не варто робити README заради того, щоб він просто був. Тут часто можна побачити описи модулів, які за нормального іменування і так зрозумілі, або ж ці описи були актуальні роки два тому.
Якщо ваш mobile-проєкт відкриває не mobile-розробник і навпаки, він перш за все піде дивитися README, якщо не вийде нічого зібрати. Особливо часто можна це побачити в iOS-проєктах, якщо у вас там ще використовуються поди, або ж в інших, де треба танці з бубном/докером/чимось ще.
Доволі хорошою звичкою може стати опис процесів та всіх тих скриптів, що роблять «магію під капотом», яку ви цілий тиждень намагалися автоматизувати, хоча там роботи на годину.
Відсутність конфігурації проєкту
Чи часто ви бачили коміти з кривими відступами? Ваші колеги форматують код кому як хочеться, а потім у вас все злітає і це +100500 рядків до наступного код-ревʼю? Чи коли імпорти стрибають у кожному зміненому файлі і ви маєте «руками» вирішувати конфлікти?
Це все просто можна вирішити завдяки Code style, EditorConfig та Reformat code у зв’язці з static analysis
.
В чому суть: ваша команда (або ви самі, якщо маєте достатньо влади) вирішує, що ті чи інші правила є однаковими для всіх і ви їх дотримуєтесь. Ці конфіги кладуться в проєкт, що дозволяє IDE виставити для всіх однакові налаштування, а завдяки форматуванню коду більше не буде проблем з імпортами, відступами, форматуванням та іншими речами, які зазвичай бісять на код-ревʼю.
Оновлення компонентів
Вам може бути байдужою ця нудна задача, а замовник ще буде кричати, що йому треба стабільність на проді. Можна зробити собі якийсь технічний борг і пояснити колегам/менеджменту/замовнику важливість даного кроку. На памʼяті маю кілька проєктів, які мали з цим проблеми, і один приклад, де мав проблеми я.
Google просто відхилив оновлення, допоки нема піднятого targetSDK (найбільш наболіла проблема в Android-розробників), а ця підтримка потребувала значного часу, і тоді клієнти не отримали оновлення вчасно, бо всі сили пішли на підготовку нової версії застосунку з новими правилами для версії OS.
Також зустрічалися креші у певних користувачів, бо баг був у версії Google-бібліотеки. Вона була дуже рідкісна, але була, при чому вона залишала за собою такий стектрейс, що до кінця не було зрозуміло, у чому була причина. Після оновлення бібліотеки креші перестали відбуватися.
Одного разу я забув, що на проєкті була увімкнена обфускація, і оновив AGP без оновлення GSON, бо дуже треба було випустити оновлення, а оновлення бібліотеки мало бути з певною міграцією. Помилкою було те, що тестувальники мали трішки іншу версію застосунку, яка потім полетіла в Google Play, а бібліотека була не готова до нових правил AGP. В результаті обфускація вирізала поля network-моделей, крешів не сталося, але перестали працювати головні функції застосунків і зникла половина даних як таких.
Загалом, повідомлення IDE про те, що існує нова версія бібліотеки — дуже хороша нагода глянути, що там оновили, можливо саме той баг пофіксили чи додали круту фічу. Не варто забувати читати документацію оновлення. Через цю помилку колись дуже багато mobile-розробників оновили версію Firebase RemoteConfig та отримали величезний відсоток крешів під час надто частого запиту конфігів.
Відсутність архітектури / Присутність «архітектури»
Буває так, що відкриваєш проєкт якогось парттайму або ще гірше — основної роботи, а там неможливо знайти потрібні файли чи усвідомити функції проєкту загалом. Багатомодульність, абстракція, чистий код? А навіщо, все одно це ніхто не буде потім читати та підтримувати, а через рік викинуть та перепишуть з нуля. І було б добре, якби так, але після вас хтось точно ще буде щось дописувати нове. І проклинатиме він саме вас.
Одного разу замовник попросив виправити з десяток речей, які до того часу писало шестеро різних людей десь за пів року часу. Сказати, що це було складно — не дуже, оскільки проєкт був сам технічно слабким. Його писали люди, які не мали комерційного досвіду в KMM та і сам застосунок був на десяток екранів. Більша складність була в бізнес-логіці, як завжди, а так переважно апка плану покажи-редагуй-відправ-покажи все знову.
Найбільшою проблемою цього проєкту став неймовірний фетиш на use cases. Буквально на кожну просту й очевидну роботу, яка адекватно лягала у потребу використання вхідного параметра функції, робився use case і тулився в потрібні місця.
Найгіршим було навіть не їхня кількість, а каскадне використання кількох use case, тоді щось продебажити було нереально складно і кількість брейкпойнтів просто клала IDE. Про те, що це були навіть не асинхронні функції, я вже мовчу, старий-добрий callback hell.
Коментарі
Розумні люди кажуть, що треба писати код так, щоб все було зрозуміло без коментарів. Вартісна порада, допоки не приходить бізнес і не просить вставити величезний костиль у ваш ідеальний код. Ви наразі памʼятаєте про цю задачу, але через місяць успішно забудете.
Якщо вам здається, що щось варто задокументувати, навіть якщо воно трохи очевидне — мабуть, це і треба зробити. А у звʼязці з правильно гіт-стратегією вам пізніше буде простіше відтворити всі вимоги й усвідомити причину того костиля, що тримає ваш код в робочому стані.
Static Analysis
Але не всі ж колеги будуть натискати дві кнопки, щоб код відформатувався під лінієчку, так? Саме тому варто налаштувати лінтери на проєкті. Вони були придумані з хорошою метою, а не для того, щоб їх вимикали на релізі.
Завдяки статичному аналізу коду ви можете захистити себе від різного роду багів, які можуть бути неявними для людського ока чи просто зроблені з необережності. Наприклад, якби хтось не ігнорував попередження про те, що якісь рядки на проєкті не використовуються, такої кількості мемів в інтернеті недавній реліз би не назбирав.
В робочих проєктах можна використовувати звичайний Android-лінтер, який доволі хороший в питаннях ресурсів, а також будь-який додатковий лінтер для коду. Мій вибір наразі зупинився на ktlint
, оскільки маю власний KTS-плагін, що кочує він проєкту до проєкту разом із .editorconfig
, який він використовує для аналізу коду.
Особливим мотиватором за чистоту коду може виступати імейл на всіх колег в команді, які скажуть, що не будуть робити код-ревʼю, поки всі білди не будуть зелененькими.
Використання сміття в проєкті
От уявіть, ви пройшли кілька кіл пекла, де вас задовбували питаннями про найневідоміші патерни, якусь Барбару, профайлинг ассемблерного коду, а потім ви ще на онлайн-кодуванні програмували алгоритм посадки частини ракети для Ілона Маска. Ви відкриваєте проєкт, а там якась крива ліба невідомого розробника з Китаю чи Індії, яку додав вже давно колишній колега, і от тепер ваша черга читати той мануал англійською, якщо повезе. Якщо ви на таке не потрапляли — ви щасливці! Мені ж не так щастило, трохи розповім про останні яскраві приклади із памʼяті.
На поточному проєкті нема обфускації і застосунок варто було б посадити на дієту. Якось заради цікавості подивився, що там стільки важить, і помітив незрозумілу теку на п’ять-шість мегабайтів (розмірі самого APK — 52). Після перевірки залежностей і швидкого пошуку в інтернеті дізнався, що це була бібліотека для показу PDF-файлів, яка за собою тягнула ще одну залежність, що мала також кілька мегабайтів розміру.
Можна було б зрозуміти, якщо це був би якийсь унікальний проєкт чи була потреба в максимально гнучкому використанні, але ж ні, просто показати PDF, готова фіча, який вже існує в Android SDK. Загалом, після читання документації і годинки програмування все запрацювало. Згодом ще знайшов якусь компоненту, яку було викинуто, і загалом застосунок схуд на 12 Мб.
Попередні півтора року я працював над гібридними застосунками, які будувалися за принципом White Label — маючи певні конфігурації ми могли змінювати та розширювати їхню функціональність для різних клієнтів. Складнощів і так вистачало на рівному місці, але однією з них була бібліотека, яка використовувала свій костиль-driven-підхід для навігації між екранами за допомогою Compose.
Сказати, що користуватися цим рішенням було просто — не дуже. От тільки недавно дізнався, що команда планує викинути його з проєкту. А якщо ви працювали з навігацією в Android, то розумієте, що це не покроковий процес, який можна робити етапами, це треба сісти й зробити всю міграцію за раз. Як бачите, невдалий вибір основи для проєкту з часом може заподіяти йому лише шкоду.
Відсутність обфускації / resource shrinking
Дуже багато розробників не люблять сюди лізти та вимикають ці налаштування, оскільки вони можуть спричинити помилки у застосунку, і ти ніколи не знатимеш, де саме баг, поки не почнеш тестувати. Також це дозволяє викинути різні невикористані ресурси, що доволі добре впливає на розмір застосунку, особливо якщо використовується Compose.
Відсутність git strategy
В курсі Distributed Systems Design Fundamentals від Particular Software вартістю кілька тисяч доларів засновник компанії розповідає людям з 15+ річним стажем у розробці, що дуже важливо правильно підписувати коміти. Ні, це не жарт. Ця проблема не виноситься взагалі на загал, але є дуже критичною. Навіщо щось вдумливо писати, якщо можна просто запушити коміт з текстом fixes? Розглянемо це детальніше.
Перше за все — іменування гілок. Програмісти часто працюють з великою кількістю ревізій коду одночасно і важливо точно розуміти, яку версією коду ти наразі дивишся. Пошук правильної гілки, потім вибір однієї із кількох зі схожими назвами, або взагалі, якщо у вас велика організація, то навіть пошук за потрібним типом дуже дратує і може забрати час.
Якщо в команді є домовленість щодо певного типу структури — це все спрощує. В цьому випадку іменування типу cicd/sonar-cube-check-implementation, bugfix/superuser-right-are-not-properly-assigned чи feature/property-detail-screen полегшують логічне усвідомлення, що є в тій чи іншій гілці.
Також варто поговорити про те, що наразі потужно працює звʼязка issue tracking-систем з VCS, будь це GitHub, JIRA чи якийсь інший інструмент. Якщо ви назвете свою гілку cicd/123-sonar-cube-check-implementation, де 123 — це номер задачі чи тікету, система автоматично їх звʼяже і це допоможе вам/вашим колегам бачити, які зміни були зроблені в скоупі даної задачі і який статус поточної гілки.
Це не завжди рятує від конфузів. Одного разу один EM сказав клієнту, що все було змержено, бо він подивився, що на відповідну позначку в тікеті (але те, що вона була змержена не куди треба, то він не бачив, оскільки на проєкті була костиль-driven гіт-стратегія). Але загалом цей підхід допомагає швидко розібратися в ситуації.
З повідомленнями в комітах — та ж сама історія. Доволі часто там пишуть одне-два слова, а змінених файлів — 50. А тепер уявіть, що у вас в гілці 10 комітів, вам треба знайти відповідь на питання, а у всіх 10 комітах написано fixes :)
З чисто практичного погляду та емпіричним шляхом тестування на індійцях було визначено, що доволі непогано працює звʼязка такого плану:
Branch name: BranchType/TicketID-Brief-Ticket-Purpose
Commit Message: #TicketID: TicketID-Brief-Ticket-Purpose
- change #1
- change #2
- ...
- change #n
Які переваги такого підходу:
- Не губиться сама гілка, яка репрезентує виконання задачі Х. Менеджер завжди може зазирнути в таску і побачити, що робота йде чи завершилась, навіть якщо розробник забув оновити статус задачі.
- В самій задачі Х ти можеш через пів року знайти всі змінені файли, що потребували її виконання; якщо прийде бізнес і скаже «А памʼятаєш цю задачу? Нам тепер треба переробити там все», не потрібно буде заново витрачати час на пошук логіки, що була змінена. Навіть якщо цю задачу будуть виконувати зовсім інші люди, в них є доступ до історії і їм буде простіше простежити плин розробки цієї фічі.
- Всі коміти добре описані та привʼязані до задачі. Може бути такий варіант, що задачу було зроблено одним комітом, але опісля розробник побачив, що дещо потрібно змінити чи додати, і це також є частиною задачі Х, тоді ми матимемо в історії два коміта, що стосуються задачі Х.
- Простіша навігація серед комітів. Уявіть, що у вас велика фіча, 10 комітів і вам треба щось знайти серед того важливе чи перевірити, на якому місці сталась помилка роботи. Особливо це актуально у великих фічах чи через великий проміжок часу, коли хочеш щось знайти і набагато простіше орієнтуватися в структурованих повідомленнях, аніж в однослівних.
Звісно, цей підхід може не спрацювати для вас та вашої команди, але його наявність в будь-якій варіації дуже спрощує роботу команді.
Відсутність чи криве CI/CD
Одного разу на співбесіді розробник казав, що CI/CD не треба, і що в нього є такий скрипт, який допомагає білдити застосунок без проблем на його ноуті. Чисто практично, він мав рацію, оскільки сам цей процес можна було б оптимізувати: закритися в кімнаті з самим собою та робити ідеальні білди. Але якщо говорити про більш-менш серйозні проєкти, то CI/CD потрібен і допомагає економити нам час.
Для прикладу можу пригадати одну компанію, яка займається White-Label-продуктом, що має просто багато конфігів, і процес ручного релізу був дуже повільним. От уявіть, що ви робите застосунок, а потім його заливаєте, куди треба. Це в середньому займає хвилин 10 роботи.
А якщо у вас таких застосунків 20? 50? 100+? А якщо розробник у відпустці, а треба зарелізити щось, що вже було протестоване і готове до релізу? Завдяки CI/CD ми цей процес автоматизували і зробили максимально швидким, а завдяки паралелізму всі клієнти отримують релізи за 15 хвилин. Бізнесу це зручно — все автоматично, розробнику це зручно — він не робить монотонну роботу.
Варто також згадати, що є такі люди, як Mobile DevOps, що часто налаштовують такі процеси. В крутих компаніях це розумні люди, які шарять, як працює мобайл, і роблять все для того потрібне. Але не всім може так добре.
За каденції Android Architect-позиції в одному американському стартапі я помітив проблему, яка дуже впливала на час збірки білдів. Якщо памʼятаєте, в нас є такий gradlew-таск, як assemble
. Вона за замовчуванням збирає всі версії застосунків. Якщо у вас один білд 15 хвилин, а застосунок має 2 конфіга, то ця задача буде годину збирати чотири конфіги, з яких вам тільки один буде потрібним. З погляду CI/CD-пайплайнів все було чудово зроблено, але тільки мобільний розробник розуміє цю потребу і зможе зробити коректне налаштування.
На цьому поки все. Список не повний, рандомний і підлягає доповненню. Якщо у вас був такий проєкт, де розробник кидав APK імейлами чи відправляв кожен раз у Slack, напишіть про це в коментарях, а також діліться проблемами, які ви часто зустрічаєте на різних проєктах.
8 коментарів
Додати коментар Підписатись на коментаріВідписатись від коментарів