Simplifying the Complex: Effective Management of Large-Scale PHP Projects [ukr]
Talk presentation
Developing and managing large projects can become a challenge for developers. This presentation will show how to overcome these challenges, using the example of one of the largest open-source PHP projects. We will discuss approaches to code organization, coding standards, effective tools, as well as the role of code review and testing. We won't forget about design patterns either.
Talk transcription
Окей, всім привіт. Вітаю вас на останій доповіді сьогоднішньої конференції, де ми логічно продовжимо тему, яку почав Владислав. Це про ведення проєктів. Ми подивилися, які є паттерни, як це застосовувати і як ви можете це спростити. Сьогодні ми подивимося трошки далі в моїй доповіді, що з цим робити.Для кого ми взагалі пишемо код? Це, я вважаю, найважливіше питання. Якщо ви пишете код для замовника, то вам і не потрібна попередня доповідь. За паттерни вам ніхто не заплатить. Якщо воно працює в продакшн, все, на цьому можна завершити насправді. Якщо вам з цим кодом доведеться працювати через рік, пора задуматись про документацію якусь там, чи про те, щоб цей код якось організувати, щоб ви самі могли з цим розібратись. Якщо у вас команда, тоді попередня доповідь гарно розкриває, які є підходи, і як ви можете організувати свій код.
Моя доповідь трошки про інше, це про те, як розробляти проекти, які будуть далі використовувати інші розробники. Ми пишемо проекти, на базі яких будуть розроблятися інші. Тобто ми пишемо фреймворк, або будуть робитися розширення для нашого проєкту. Якщо коротко, я працюю в компанії Oro. Перший реліз Oro продуктів був у 2012 році. У 2013 році ми почали з PHP 5.3, Symfony 2.1, ще були якісь доволі стандартні технології. І пройшло 10 років, наразі ми оновилися до PHP 8.2, це було не за один раз, як ви розумієте, ми оновлювалися постійно, і Symfony ми також зараз використовуємо останню LTS-версію.
Звучить скучно, не гонимося за новими фічами, стек доволі стандартний. Але чому ця доповідь має бути вам цікавою? По-перше, тому що в нас моноліт. Багато тут використовує моноліт, підніміть руки, будь ласка. Окей. А в кого на мікросервісах є один, який називається моноліт? Так, ну насправді, чим цікавий наш проєкт, тим, що в нас більше трьох мільйонів строк коду, це open source. Тут є, мабуть, не дуже гарно видно, графік порівняння по логічним рядкам коду з популярним. Це не те, що часто порівнюють, немає якогось стандартного рейтингу, де ви подивитеся, де найбільші проєкти є в open source. Але в нас коду трошки більше, ніж в Magento, і якщо порівнювати з тим, що ви знаєте, то це Drupal, Symfony, Wordpress, і от там є Silius, який також пише e-commerce.
І це доволі важливо, тому що ми маємо моноліт з великою кількістю кодів, з великою історією. Одночасно на цьому проєкті працювало трошки більше 80 розробників. І ми справились, в нас в принципі зберіглася архітектура, стек ми не змінили за всі ці 10 років, масштабованість все ще достатня, і що важливо, ціна розробки фічів не зростала. Як ми знаємо, якщо ми пишемо дуже багато паттернів, використовуємо, проєкт росте, росте, росте, а потім ми кнопку передвігаємо 2 тижні, а це значить, що треба переходити на мікросервіси. От ми не перейшли. Так. І, як я вже казав, наш проєкт реалізований на Symfony. Чому Symfony ідеально підходить для вашого проєкту? Якщо він великий чи малий, це неважливо. Symfony — це ідеальний фреймворк.
Перше, це те, що там є backward compatibility promise. Місяць назад я читав доповідь про штучний інтелект, і розробник, який працює з JavaScript, мене запитав, а що мені робити, якщо я йому запитую щось, а він не знає про нові технології, бо він обучався там 2 роки назад. В PHP такої проблеми немає. Не те, що в нас немає нових технологій, але якщо ви подивитесь, це правда. У нас є нова версія PHP, але там додали тили. Ми не робимо ці самі речі новим способом. І це дуже важливо. Якщо ви використовуєте JS, там треба кожен рік вчити новий фреймворк, все переписувати, і це, мабуть, прикольно, але мені подобається те, що вже написано і працює, воно працює далі.
Ми там ректором додали типів, і все. Воно з новою бібліотекою працює. Доволі така гарна документація. У ком'юніті це все і так зрозуміло. Що найважливіше для великого проєкта? Чому ми вибравши Symfony 10 років назад? В попередньому докладі був такий use case. А що, якщо прийдеться міняти фреймворк? PHP теж колись помре, але я вважаю, що Symfony – це архітектура, яка покриє будь-який великий проєкт в першу чергу. І це дуже важливо, тому що її списали з Spring. Як ми знаємо, в PHP нічого нового не придумали. Архітектура Symfony взята з Java Spring, який використовується для дуже великих проєктів. І там всі ці самі підходи на нашому улюбленому PHP. І що найменш важливо – це те, що Symfony підтримує Україну. Якщо ви заходите на сайт Symfony, то ви бачите цей банер. І пряма підтримка – це завжди приємно в такі важкі часи.
Наш проєкт. Як це виглядає? Три мільйони строк коду, як я сказав. Шістсот тисяч логічних строк коду – це те, що щось робить, а не оці всі класи, PHP-доки і так далі. І це доволі багато. Це моноліт, це один проєкт. Ми розділяємо фічі в двох дименшинах основних. Синенькі – це компоненти. Компоненти розмазані по всьому проєкту. Тобто Entity, наприклад. У мене Entity може бути в одному модулі, Entity є в іншому модулі. І є фічі. Це там репорти. Щось там ще є. Можна не читати. User management і так далі. Тобто будь-які фічі у вашому продукті є. Це те, що ми розділяємо по модулям.
І є фічі ядра або фічі фреймворку, які ми використовуємо для різних фіч нашої системи, для фіч бізнесу. Розділення доволі таке просте, але воно дозволяє нам зрозуміти, що в принципі, коли ви працюєте з монолітом, у вас немає ніякої модульності. Про це мови йти не може. Коли у вас є мікросервіси, то у вас є інші модулі. У вас є оця папка, ви в неї поклали код, і щоб спілкуватися з другою папкою, вам треба було там дуже багато всього. Треба було там конвертнути в JSON чи gRPC. У нас одна папка, друга папка, я з тієї папки визиваю код з другої папки, і все роботає. У мене 200 папок, вони всі друг про друга знають. Це називається модульність на моноліті. Ви ж розумієте, так?
Тому, в принципі, коли мова йде про організацію коду в моноліті, ми маємо думати в першу чергу про те, як це логічно організувати і як не думати про те, в яку ж папку мені покласти цей клас, коли воно стосується двох-трьох фіч і так далі. Тому ми розділили по основним ідеям, є базові компоненти і є фічі. Весь код нашого проекту зберігається в одному репозиторі. Це доволі стандартний підхід, якщо ви працювали з Symfony, в них теж є монореп, в якому розробка ведеться всіх компонентів. Ми ж ведемо розробку трошки більшого продукта.
В одному репозиторі у нас знаходиться 13 застосунків. Є десятки composer-пакеджів, в яких є декілька сотень бандлів. В цьому ж репозиторії лежить вся ICD-конфігурація, тому якщо щось зламалося на Continuous Integration, це може подивитися розробник і він розуміє, як з цим працювати. Якщо у вас декларативний пайплайн або він не дуже складний, там також знаходяться всі DevTools, тому якщо щось оновлюється, якісь інструменти налаштування, це все також легко налаштовується. І документація. Як було зазначити, в попередньому докладі дуже важливо тримати разом з кодом, але ми подивимося, як зробити так, щоб їй все-таки не забували цю папочку оновлювати.
Як працювати з 13 застосунками в одному репозиторії? Доволі просто. Ось тут є приклад коду. Це DevJSON. Звучить страшно. Є ComposerJSON. Всі знають, коли ви працюєте з композером. Але насправді за допомогою змінної оточення композер дорівнює. Можна сказати, що замість ComposerJSON використовує інший файл. І у нас виходить, коли ми працюємо з Monolith, у нас використовується один Configuration файл для композера в цій папці, а коли ми деплоємо, то ми вже використовуємо інший композер файл, який ComposerJSON.
І коли ми в Monolithі знаходимось, то у нас замість реальних пакунків застосовується, ми кажемо, що у нас є репозиторій, це як Packagist, тільки тепер замість Packagist у нас буде ще один репозиторій, в якому TypePath. І шукай пакунки в цій папці. Вийде на два рівня вище, там лежить папка Packagist з пакунками. Коли ми працюємо в Monolithі, ми працюємо з DevJSON, і ми не думаємо про те, як ці аплікейшни будуть потім далі застосовуватись. Коли ми далі це розгортаємо за допомогою GitSub3 в багато різних репозиторіїв, там кожний композер пакунок буде в своєму окремому репозиторії, якщо в цьому є необхідність, кожний аплікейшн в своєму композер-репозиторії, ми вже користуємося ComposerJSON і вже працюємо з версіями, задиплоєваними на Packagist.
Кожний пакунок складається з декількох бандлів. Якщо ви працювали з Symfony, а хто працює з Symfony? Підніміть руки, тут три доповіді з чотирьох про Symfony. Окей, це Symfony Camp. Ви, мабуть, чули, що бандли за Deprecated Feature, і взагалі бандли не використовуєте. Насправді це так. Якщо ви пишете Hello World, і якщо у вас аплікейшн знаходиться в одній папці SRC, але коли ви пишете розширяємий застосунок, коли ви навіть ставите якісь екстеншн з Symfony, там завжди є бандл. Бандли нікуди не ділись, вони просто скриті для того, щоб Symfony було так же просто користуватися, як Laravel. Але насправді це все ще доволі корисна фіча. Якщо взяти стандартний якийсь пакунок, у нас в одному Composer Package лежить декілька бандлів Symfony. В яких ми організовуємо код. Тобто Package — це якась більша, набір фіч. І далі кожен бандл відповідає за свою фічу. І в бандлі можуть використовуватись компоненти.
Як з цим працювати? Я не буду зациклюватись на інструментах. Ви можете знайти по ним доволі розлогу документацію. Перше, для того, щоб моноліт, це ще називається монореп, це один репозиторій з багатьма пакеджами, розбити на декілька репозиторіїв. В нашому випадку це там більше п'ятидесяти. То використовується GitSub3. Почитаєте, тобто є один репозиторій, вийшло багато, і виходить, коли деплоється воно на продакшн, то там використовується тільки один маленький репозиторій, в якому є тільки таймкод, який відноситься до цього проєкту. Депенденції, Composer dependency чи NPM dependency цього репозиторія, вони також могли б девелопитись в одному моноліті, де ми направимо папочку вендор, хоча в нас через 7 лімків пакеджів наших підкинуті всі наші залежності, але воно потім в продакшн підтягується через Composer і використовується якби ми не маємо там нічого зайвого. Нам не потрібно деплоїти дуже великий проєкт.
Друге питання, яке потрібно вирішити з монолітним репозиторієм, це те, що він дуже великий. Для того, щоб зачекавити наш проєкт, у нас не дуже великий проєкт, у Google великий проєкт, у нас 700 МБ, ну треба там пів годинки почекати, поки воно скачає його з Гітхаба, а якщо мені потрібно працювати з декілька гілками, от у мене є проєкт 700 МБ, а от другу гілку я хочу розгорнути поряд, для того, щоб зробити рев'ю коду і так далі, і це все займає час, і це можна дуже просто зекономити, насправді це не тільки для монолітів працює, є така фіча в Git, вбудована це Gitwork 3, де ви кажете, що оцю гілку розгорни, будь ласка, не тут само, а поряд, от у мене є проєкт з моїм кодом, а іншу гілку розгорни поряд, і там не буде дублюватися вся історія, там будуть тільки ті зміни, які ви використовуєте, це дуже зручно, у вас виходить 10 гілок займає там 1 ГБ, якщо проєкт важить 700, це якщо там велика історія, а не великі якісь сесети.
Також ми використовуємо GitHub Code Owners, це також, як я сказав, стандартна фіча, яка дозволяє нам отримувати ці нотифікації, коли є розробники, які пишуть код і отримують зарплату, тому що в PHP долар це доволі таки очевидно, є розробники, яким подобається, щоб цей код працював правильно і робив якісь правильні речі, вони переживають за той код, що написаний, і їм важливо, щоб цей код ніхто не знаймав, але коли у вас на команді 80 розробників, хтось допоміняє у вашій папочці код, і тоді ви розстроїтесь, для того, щоб цього не було, можна просто в Code Owners файлі дописати свій код, і вам будуть приходити нотіфікації, як тільки хтось там щось робить, тоді ви можете прийти і ай-яй-яй сказати. І що дуже важливо, оскільки в Monolith модулі це окремі папки, то важливо тестувати, що це дійсно, хоч намагається бути модулями, що це не просто папки, в яких лежать файли, чому вони там лежать не зрозуміло, тому шари, які у вас є між залежностями системи, вони мають мати якісь односторонні залежності.
Це доволі таки просто, коли ви працюєте з модулями, у вас не має бути, наприклад, ордер-модуль і продукт-модуль, і ордер знає про продукт, і продукт знає про ордер, і приходить новий розробник, мені потрібно створити новий файл з формою, і мені треба покласти її кудись в продукт-модуль, чи в ордер-модуль, ну там і продукти є, і ордер є, і коли залежності односторонні, у вас немає таких питань, коли є прості правила, ви просто почитали їх і працюєте.
У нас, до речі, онбордінг розробника на проект проходить за 6 годин, 6 днів перегляду відео про те, як це все працює, і потім він працює над задачами, це стандартний тренінг по нашому проекту. Тобто проект великий, 3 мільйони строк коду, чи знаю я, що там відбувається? Ні, я архітектор. Є речі, ви маєте знати по тому, як воно влаштоване. Якщо мені потрібно знайти фічу, я знаходжу, де вона є, тому що я знаю принципи, по яких побудований проект. Якщо мені потрібно знати на проєкті, які, в принципі, фічі, як мають працювати, я можу звернутися до продуктовнара, чи до quality assurance інженіра, або подивитися тести, які, в принципі, описують те, як раніше воно працювало і т.д.
В принципі, для того, щоб зробити фічу, я маю проєкт. І це важливо. Паттерни — це дуже прикольно, коли ми пишемо код, але, як ви, мабуть, вже бачили, на будь-яку проблему можна, якщо взяти банду чотирьох, ні, не 20% паттернів, з моєго досвіду, одного паттерну достатньо для того, щоб закрити весь проєкт. Це chain of responsibility. Ви можете просто весь проєкт через нього зробити, і воно буде працювати, і той, хто це написав, буде розбиратися. Це дуже суб'єктивно. Це дуже залежить від досвіду ваших розробників. У нас всі сіньори, і у кожного є своя думка. Ми всі читали банду чотирьох і інші важливі книжки, які розказують про те, як писати правильно код, але все одно це дуже-дуже суб'єктивно.
І в результаті ми можемо отримати систему, частини якої написані по-різному, і ми їх не можемо розуміти. Для того, щоб цього не відбувалося, ми використовуємо деякі з підходів. Але є речі, які, в принципі, складні, і з цим треба щось робити. І перше, що ми можемо зробити, це абстрагуватися від цього. Ми використовуємо Symfony, і якщо в нас є якийсь складний функціонал, який для того, щоб розібратися, потрібно сидіти з X-дебагом і ходити по кроку в день, то перше, що ми робимо, це ми дивимося, як це можна спростити. Що робить розробник, коли він ходить з X-дебагом по коду? Він будує в голові дерево, що воно там викликає, що за чим, для того, щоб зрозуміти, як працює бізнес-логіка, ми можемо пофіксити цей баг. Навіщо це робити, якщо ваша фіча пронизує весь проєкт? Наприклад, є якісь там Symfony-форми, які ви використовуєте, ви заходите в Symfony Toolbar, і ви бачите, що в формі вже відрендерились такі-то поля. Якщо ви підете в код, то це зазвичай є формтип, який використовує другі формтипи, на це все є екстеншн, і це доволі складно. Там ще є якісь датамапери і дуже багато всього, дата-трансформери.
Заходимо в екстеншн, бачимо, що побудувалося, знаходимо те, що нам треба поміняти, відкриваємо файл, міняємо. В Symfony, якщо хто не знав, є дуже крута навігація з Symfony Toolbar в вашу IDE. Якщо ви не щасливий користувач Linux, то воно просто працює. На Linux це треба додатково налаштовувати, але ти нажав, воно відкрило шторм, поправив, працює, далі мені нічого не потрібно знати. Я не знаю навіть, де той файл лежав, тому що в мене є екстеншн, який розказав, як працюється форма. У нас є там з десяток додаткових екстеншнів для наших фіч саме компонентів, які використовуються в різних фічах системи, для того, щоб без Xdebug можна було зайти і побачити, як воно працює. Є там складна система лояутів, workflow і так далі, там API, в якому дуже багато всього викликається. Мені не потрібно знати, як все це облаштовано, тому що там 20 паттернів використовується для того, щоб одну фічу зробити. І це дуже складно, тому що, ну, я це можу читати. Тулбари дуже спрощують. Виглядає просто, насправді їх дуже просто писати. Наступний поєнт, це коли нам потрібно якусь агрегацію отримати, наприклад, в нас в проєкті використовується екстендед метадата для доктрини. У доктрині є метаданні, які ви пишете в анотаціях, от в нас там десь в 50 разів більше записано в цій метаданні, тому що такі є вимоги до деяких фіч-проєктів.
І для того, щоб подивитися ці метаданні, які зберігаються десь в php, спочатку, вони уходять в базу, потім вони лежать в кеші, і щось використовується. Ми просто написали консольну команду, яка виводить в зручному форматі ці метаданні, і девелопер зразу це бачить. Фіча надзвичайно складна, її реально розуміє в проєкті декілька розробників, і це нормально, коли є складна фіча, вона реалізована складно. Але якщо вам потрібно, щоб з цією фічою працювала велика кількість розробників, ми можемо інкапсулюватися від цього і отримати фасад.
На попередній доповіді був цікавий приклад php-кода, де було декілька методів, які працювали з різними речами. Мене це не тригерить, мені такі речі подобаються. Він простий, там три методи, може на рівні php-кода це виглядає складно. Коли я працюю з Symfony, я відкриваю Toolbar, там 20 вкладок, я нажав ту, що треба, вона відкриває те, що я хочу бачити, і все, і я з цим працюю. Воно складно під капотом, складні речі не можуть бути зроблені просто, тому що e-commerce це доволі таки складна робота.
Наступне, що нам дозволяє відійти від складності, якщо я працюю з фічою, яка не покрита цими Toolbar-ами і вона все одно залишилась складна, або мені треба просто працювати з такими детальними речами, які все ще складні, то я можу поправити щось, що я не знаю, чи буде воно працювати, чи ні. Знайшов, вроді би тут, поміняв, true на false, наче працює. Якщо є тести, якщо ці тести запускаються не локально розробниками, а вони запускаються на site, то це буде працювати. І в цьому списку є Blackfire, як не дивно, це не про перфоманс. Якщо ви використовуєте Blackfire на складному проєкті, то він нам дозволяє побачити дерево викликів, якщо працює. Іноді це слугує як заміну xDebug, щоб побачити всю складну цепочку того, що відбувається. Там можна взяти більш складні end-пойнти того, що ви саме хочете бачити.
Є книжка «21 день з Blackfire», не знаю, чи вона все ще так називається, я дуже рекомендую, якщо ви часто працюєте з xDebug, якщо там умовно 70% часу на вашому проєкті — це xDebug, то Blackfire дозволяє вам значно скоротити час, просто показавши зразу дерево викликів того, з чим ви працюєте. В нашому Monorepo ми також зберігаємо документацію. І ми поклали її доволі таки скоро в ту саму папку, де є код. І в нас є документація для розробників, де є приклади коду, є документація для юзерів, яку підтримують TechWriter, і там взагалі все чудово. От з розробниками є проблема. Він править код, а документацію забуває правити. Потім приходить інший розробник, він йде по документації щось робити, там вже цих інтерфейсів немає і нічого не працює.
Ми вирішили це питання тим, що в нас є тести на документацію. Ми використовуємо Sphinx RST. Приклад доволі таки абстрактний, але його в принципі можна перенести на будь-який проект. Це RST файл. RST це, хто не знає, альтернатива Markdown формату для написання документації. Тут в мене є якийсь текст. Далі йде include кода. Я код, виходить, пишу не інлайном в моїй документації, а він звідкись інклудується. Є папка code examples, commerce demo, і там є якийсь bundle PHP. Я інклужу код, і далі є отакий додатковий тег, Oro Integrity Check, який по суті реалізований дуже просто.
Це надбудова, це екстеншн для Sphinx, який перевіряє, що хеш по коду, який інклудується, він співпадає з цим хешом, що тут написано. Що це нам дає? Виходить, коли розробник править PHP файл якийсь в проекті, проект працює, і все добре. Якщо у вас є тести на оці кодеки, а в нас вони обов'язково покриваються 100% тестами, то у розробника впав CI, тому що він каже, отам кодек зламались, він йде править кодек а тепер падає тест документації, тому що якщо файл кодек помінявся, то тепер і в документації потрібно поміняти хеш.
Він йде і міняє хеш, але якщо це відповідальний розробник, то він тепер ще й перечитає, чи там не здвинулись строки, чи не потрібно оновити текст, і так далі. І як мінімум, якщо розробники все ще ліниві і не дуже працюють з текстом, то давайте на чистоту, хто читає ту документацію. Коли ви заходите на документацію, там будь-яку, все, що ви робите, ви копіюєте ці куски кода. Ніхто не читає, що між ними написано. Тому це в принципі дуже сильно нам допомагає. Доволі проста техніка, її просто екстраполювати на будь-який фреймворк, який у вас білдить документацію, але якщо у вас документація поряд з кодом, це не коментарі, а саме файли, тому що це дуже складно, то це дуже нас виручає.
Наступна річ, яка доволі добре допомагає, я не знаю, тут, мабуть, не буде видно цих прикладів, це логування. Підніміть руки, хто пише логи, взагалі. Окей, ну це я не очікував, якщо чесно. Зазвичай логи пишеться доволі багато, для онлайн-трансляцій, якщо ще хтось буде дивитись, доволі багато людей підняло руки, і це говорить про те, що в нас доволі такі серйозні ком'юніті тут зібралися. Тому що зазвичай розробники не пишуть логи, тому що кому вони потрібні, я ось роблю фічу, вона працює. Якщо доходить до продакшена, там не можна поставити xdebug, там не можна поставити echo і vardump, і важко розібратися. Але мова йде не тільки про якийсь продакшен.
Наприклад, коли ми працюємо з Continuous Integration, в нас є багато тестів, вони запускаються, вони падають, і нам потрібно зрозуміти, що відбувається, мені потім треба це відтворити локально, там бла-бла-бла. Найпростіша річ, яку можна зробити, це в налаштуваннях логера Monolog-Handler додати includeStackTrace на CI, і у вас виходить в логах,якщо exception вивалюється, то зразу і ви хоча б бачите, до чого він відноситься. В складному проєкті це дуже важливо.
Другий приклад документації буде незрозумілий. Це trait, який ми додаємо до, я думаю, тут нічого не видно, там метод runTest в traitі який додатково додав логи зверху і знизу, і на наступному слайді, я надіюсь, буде видно добре, отак виглядають логи у нас в функціональних тестах. Є зверху така дужка, яка каже, що от нижче будуть йти логи з функціонального теста, і знизу дужка, яка каже, що ці логи закінчились. І тоді, коли в мене в одному білді на CI піднімається там 40 тисяч тестів по черзі йдуть, і якийсь там посередині падає, там же є ще негативні сценарії, які в принципі очікують, що в логах будуть ексепшн і так далі. Ви приходите, вам раніше потрібно було що робити?
Порівнювати дату можливо, чи просто локально репродюсити. Зараз я зразу можу зайти в логи на CI і побачити ті, що відносяться до мого впавшого теста,проаналізувати їх і зробити якісь висновки. Тобто ми прибрали просто ручну роботу. Розробники можуть запам'ятовувати багато всього, але всі цього не запам'ятають. Їх буде 5. Якщо у вас велика команда, то більшість поліниться робити всякі складні речі. Банальний приклад. Я приходжу до розробника, питаю, тебе все нормально? Ти все робиш гарно, так? Окей. А чого в тебе ця задача робиться довго? Це не проблема? Ні, це не проблема. Я ставлю, воно білдиться пів години. Я йду, поки граю в ігри, приходжу, воно збілдилось, я далі собі працюю. Для розробника не проблема, що щось відбувається довго.
Дуже часто. Це нормально, тому що розробник отримує часто гроші за те, що він робить роботу. Іноді йому не важлива якість коду. І знайти тих, кому дійсно важливо, коли неправильно використовуються патерни, доволі складно. Якщо ви зараз подивитесь на ринок, там дуже багато розробників, і найняти того, хто дійсно цікавиться, є складність. Тому ми робимо так, щоб за це можна було не перейматися. Ми спрощуємо життя розробників. Найскладніша і найбільш болюча тема, яка у нас є, - це код-рев'ю. У нас на кожні зміни, які є, обов'язково проводиться код-рев'ю. Що важливо, код-рев'ю робить інший розробник. У нас немає одного мегамозга, який може перевірити весь код. Є команда, є джуніор-розробник, є тімлід. Джуніор може перевіряти тімліда.
Це нормально, тому що всі важливі речі ми вже покрили тестами. Те, що стосується якості коду, вам покаже СІА, якщо там є проблеми. Код-рев'ю це не про те, що ти там запятував пропуск, або ось тут ти використав не той підхід, тому що якщо ви доходите до код-рев'ю, це значить, що задача колись там планувалася. Ви, в принципі, разом погодилися на якісь підходи, які будуть використані для рішення цієї задачі. І код-рев'ю це вже про те, щоб перевірити, що, в принципі, зроблено те, що потрібно. У нас для код-рев'ю є чек-ліст. Колись ми його додавали в пул-реквест. У нас було код-рев'ю з галочками, так називаємо. Ось пул-реквест, а тут не видно, але ці маленькі штучки - це елементи, які мав перевірити розробник, коли читає пул-реквест. І тут він мав поставити галочку, що, наприклад, пройшло. П'ять галочок. Ми їх відмічали якийсь час, і ми зрозуміли, що це не дуже ефективно, тому що розробники це забувають, все одно.
Хоча ці речі потрібно переписувати, і в нас цей документ є, ми до нього посилаємося, коли розробники не погоджуються. Але більшу частину речей, які важливі для вас, їх можна автоматизувати. Зараз є AST, є PHP-стан, і можна писати додаткові правила на все, що важливо для вашого проєкту. Те, що важливо в CodeReview у нас, оскільки розробник перевіряє розробника, розробник може написати тест, який буде зеленим для його фічі, другий розробник може погодитися за шоколадку, то у нас розробники пишуть тести, але їх перевіряють QA, тобто люди, які розуміють, як має працювати фіча, і вони обов'язково тебе заставлять дописати якісь сценарії використання, які важливі. Ми пишемо тести, вони, якщо хтось не знає, використовують Gherkin Syntax, це синтаксис, де ти можеш просто читати тест звичайним текстом, там кожен крок в сценарії - це просто рядок якогось текста.
І QA дуже просто читають ці специфікації, вони використовують їх для того, щоб навіть давати задачу, коли приходить задача, може відразу описано сценарій, який вона має покривати і так далі. І коли задача виконується, то всі сценарії використання мають бути 100% покриті QA. На попередній доповіді досить добре розповіли про кодекси стилю, їх, як багато хто відзначив, це все було 10 років тому. Екосистема нам наштовхує дуже хороші правила для CS, і вони працюють як для малих проєктів, так і для великих, тут немає нічого нового. Те, що в нас відрізняється? Те, що в нас, наприклад, є PHP mess detector, і якщо ви користуєтеся ним, то він вам каже, наприклад, що в вашому класі дуже багато залежностей, або цей клас дуже великий.
І є проєкти, на яких mess detector має бути зеленим. От наш проєкт такий же, але якщо клас великий, тобто тобі каже mess detector, ти можеш не робити його маленьким, ти можеш сказати, я знаю, що він великий, він такий і має бути, тому що це складна фіча, я знаю, що в нього багато залежностей. Якщо я розношу його на три класи, і там буде single responsibility і це все, то іноді це не спростить код. Тому mess detector це нам як радник, який каже, що ось тут зверни увагу, але це не обов'язково виконувати. Ми не дуже часто відхиляємося від mess detector, але є речі, які ми дозволяємо собі робити складними, тому що вони такими мають бути.
Також до стандартів коду, як це може здатися дивно, відноситься у нас і PHPUnit. У нас є обов'язкове правило, що 100% нового коду має бути покрито PHPUnit. Там стоїть дірочка, оскільки у нас є просто виключення, існують речі, які не має сенсу покривати юніт-тестами, тому ми їх не пишемо. Проте, юніт-тести не стосуються просто того, чи працює ваш проект, це також про архітектуру. Якщо ви написали складний клас, то його юніт-тест також буде складним. Досить просте правило, яке допомагає тримати речі читабельними. Якщо клас складний, вам доведеться мучитися, щоб написати його юніт-тест. Також, щоб уникнути накопичення технічного боргу в проекті, ми маємо правило, що в коді не повинно бути залишених TODO-завдань, або закоментованого коду, який не може пройти. Якщо вам потрібно щось виправити, ви можете це зробити одразу. Якщо цього не робити, це може стати завданням у системі управління проектами на поліпшення в майбутньому, але мати неробочий код у репозиторії — це неефективно.
Останнє правило: якщо тест не забороняється, це означає, що воно дозволено. Це дуже важливо, оскільки кожен розробник має свої власні уподобання і розуміння того, як має виглядати код. Якщо це не стосується функціоналу, не торкається тестів, і тести пройшли, а також забудована система CI показує зелений результат, то це може бути прийнято. Це аргумент. Якщо ми не ввели яке-небудь правило для continuous integration, це означає, що воно не є обов'язковим. Ми PHP-розробники. Тут немає строгої типізації, і ми можемо обходити кутки. Це може хтось вважати поганою практикою через наявність у PHP таких систем як WordPress, але насправді це є особливістю мови, яку слід прийняти. Ви можете писати 100% типізований код і використовувати його так, як вам потрібно. Проте, обираючи PHP, ми вважаємо, що це надає деяку свободу. Баланс полягає в тому, що код може бути легко підтримуваним великим проектом.
Назви класів мають бути коректними. Було сказано на попередньому доповіді, що це можна розглядати як інвестицію у читабельність. Ми докладаємо зусиль, щоб назви були красивими. Кожен розробник має своє розуміння, але ми намагаємося зробити їх гарними. Розробляючи це, ми вивчили, що найбільшою проблемою стає кешування і іменування. Щоб вирішити проблему іменування класів, ми використовуємо інспекцію PHP CS, яка додає коментар до класу, щоб описати його. Це допомагає розробникам швидше розуміти, що робить клас, не заглядаючи в його імплементацію. Це важливо, особливо на великих проектах. Безпека — ще один важливий аспект, без якого не може обійтися жоден проект. Symfony має вбудовану екосистему, але ми також написали правила для притягання до проекту при використанні Doctrine.
Це PHP-стандарти, які виявляють DQL і SQL-ін'єкції в вашому коді. Якщо ви, я думаю, більшість розробників знають про SQL-ін'єкції, то DQL-ін'єкція може виглядати якось інакше. В доктрині можна було виконати SQL-ін'єкцію навіть без використання Simple Query? Так, можна було. Можна робити ін'єкції як в SQL, так і в DQL, якщо ви конкатенуєте речі і т. д. Бібліотека дозволяє виявляти ці помилки і, в принципі, показує їх відразу. Також у нас є власний інструмент для аналізу XSS-ін'єкцій, який використовує наші BHA-тести, запускає їх з різними пейлодами, які можна використовувати для XSS. Це не тільки скрипт-техніка, але також можуть бути атаки, наприклад, в атрибутах JavaScript. Це також стосується XSS і так далі. І якщо розробник все-таки щось пропустив, у нас є Web Application Firewall, який спочатку перевіряє на CI, що це працює, а потім на продакшені. Мабуть, я пропустив цей аспект.
У великому проєкті тестування — це важлива частина. Ми використовуємо BHA для покриття всіх функцій. Ось невелика статистика: у нас є 1800 BHA-функцій, більше 10000 юніт-тестів і так далі. Це дуже велика кількість всього. Щоб запустити один pull-request, і в pull-request ви не можете замерзнути без тестів, потрібно 60 годин. Звісно, розробник не чекає 60 годин. Якщо у вас є сотня машин, на яких це виконується паралельно, ви отримуєте фідбек набагато швидше. Все це паралелюється, і фідбек отримується швидко. Тестування має свої проблеми, і ми виявляємо ці проблеми за допомогою моніторингу. Так само, як і в продакшні, якщо у вас виникають проблеми, у вас є моніторинг, так само моніторинг можна використовувати для тестів і відстежувати проблеми, коли ви бачите статистику, що цей тест часто ламається, цей часто падає, їх можна покращувати. Мабуть, я пропустив цей розділ про проектування, оскільки він добре висвітлено в попередній доповіді.
Основні проблеми, які я бачу на проектах, полягають в тому, що бібліотека використовується не за призначенням. Ми взяли Symfony форму і використовуємо її для API. Це додало нам складностей, оскільки в якийсь момент розробники вирішили, що форми не для API, вони для HTTP. І є різниця. Інша проблема полягає в тому, що багато хто, приходячи, думає, що dependency injection — це просто отримання об'єкта, який ти вже інжектуєш із залежностей. Це все ще дає вам багато можливостей порушити оці залежності між слоями. Ви можете інжектувати те, що не повинно використовуватися в інших частинах коду. Тому це в принципі не досить. Важливо використовувати стандартні підходи, рекомендовані великими товстими книгами. І найголовніше — це SOLID і DRY. Вони конфліктують між собою, оскільки SOLID говорить: роби багато маленьких класів, а DRY каже: ні, напиши все в контроллері і буде зрозуміло. У нас на великому проекті теж є DRY, і ми не завжди пишемо все маленькими розширюваними класами. Є речі, які повинні бути простішими.
Якщо говорити про паттерни проектування, їх багато, і вони мають свої особливості. Є такий сайт Refactoring Guru, який, на мою думку, ви вже добре знаєте, якщо ви працювали з паттернами. Там, внизу, завжди є пояснення, чому обраний саме цей паттерн і як він співвідноситься до інших. Усі ці паттерни підходять для створення розширюваних проектів, які можна розглядати як фреймворк. Ми розробляємо фреймворк, який можна легко розширювати в рамках власного додатка. Кожен паттерн може бути використаний для вирішення певних задач. У нашому проекті ми використовуємо не лише один паттерн, але кожен з них має свої властивості. Це суб'єктивна оцінка, але розуміння різниці між подібними паттернами полегшує роботу, як капітан говорить.
Останній аспект, про який я б хотів розповісти, це оновлення і зміни архітектури. Ми використовуємо Symfony, і зараз остання версія — 6.2, яка, наскільки я пам'ятаю, актуальна на даний момент. Ми залишаємося на версії 5.4 Symfony, оскільки це LTS-версія (Long-Term Support), що означає довгострокову підтримку. Ми плануємо оновитися до версії 6.4, коли вона стане доступною. Оновлення буде простим, зі стабільними функціями та без проблем. Ми не робимо це занадто часто, оскільки не виправдовуємо заради нестабільних функцій, що є нормальним підходом для великого проекту. Ми також використовуємо Rector для рефакторингу коду та його оновлення. Є правила для оновлення Symfony, але, як вже знають ті, хто користувався Rector, він не оновлює, наприклад, юніт-тести, тому що це вимагає ручного оновлення. Rector спрямований на оновлення PHP-коду, а не конфігураційних файлів та інших неявних аспектів, таких як робота з контейнером, Twig, JS. Все це потребує ручного оновлення, тому потрібно мати автоматизацію. Зараз легко отримати допомогу від ChatGPT, якщо ви подаєте приклад коду до та після, йому вдасться вам створити регулярний вираз для впровадження необхідних змін, навіть якщо це стосується не PHP, а якоїсь іншої мови чи шаблону.
Якщо ми розглядаємо зміст доповіді, можна побачити, що вона взаємодіє з попередньою, де люди створюють кастомізації на основі фреймворку, а ми розробляємо фреймворк. Ми бачимо, що в екосистемі Symfony існують рішення, які були придумані ще 10 років тому, а PHP взяв їх. Це нормальний підхід — узяти краще з існуючого та імплементувати його. Ми використовуємо ці рішення від малого до великого. Є підходи, які допомагають краще працювати з великими проектами, спрощують життя розробникам, роблять їхню роботу більш спрощеною, і з цим можна жити. Наприкінці може бути два розробники, які глибоко розуміють певну функціональність, і це дозволяє нам абстрагуватися від цього фрагменту. Ось у нас все. Дякую fwdays за організацію. Під час вступу хотілося б пролити пару сльоз. Велике спасибі. Це було неймовірно, що на мить можна було забути про те, що відбувається, і повернутися до часів, коли ми організовували конференції. Ми також організовуємо маленькі мітапи в Черкасах. Якщо ви з Черкас, приєднуйтеся до нашої телеграм-групи. Дякую.