Будь суворішим до себе: як обмеження допомагають зробити код кращим

Якщо вам доводилося замислюватися про побудову ефективної екосистеми проекту і визначення ролей тимліду і розробника - стаття Артема Прозорова з ZeBrains для вас.


Пропоную вам задуматися над одним питанням. Але не поспішайте з відповіддю, тому що він не такий очевидний, як може здатися:

Яка з команд може реалізувати більш технічно стабільний продукт?

Команда № 1: Проектний менеджер, аналітик, тестувальник і кілька розробників, у кожного з яких за плечима мінімум три роки досвіду. Всі працюють в одному офісі, присвячуючи свій час одному проекту в режимі fulltime.

Команда № 2: Один сильний розробник. Йому допомагають безліч не знайомих між собою людей з різних часових поясів. У кожного - свій набір компетенцій і рівень досвіду. Роботою над проектом учасники займаються у вільному режимі, по кілька годин на тиждень.

* * *

Відповідь на це запитання ми отримаємо до кінця статті, а зараз - трохи нудної, але важливої теорії.

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

Парадигми програмування

У 1968 році Едсгер Вібе Дейкстра показав, що нестримне використання переходів (інструкцій goto) шкідливо для структури програми. Він запропонував замінити переходи більш зрозумілими конструкціями if/then/else і do/while/until. Це дало основу парадигмі структурного програмування.

Можна сказати, що структурне програмування накладає обмеження на пряму передачу управління.

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

ОВП встановлює обмеження на непряму передачу управління.

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

Функціональне програмування накладає обмеження на присвоювання значень.

Виходить, кожна парадигма привносить свої обмеження, при цьому жодна не додає нові сутності.

Принципи проектування та шаблони

Естафету парадигм підхоплюють принципи проектування, які додають свої обмеження:

  • SOLID - на побудову абстракції.
  • DRY - на повторюваність коду.
  • KISS - складність логіки.

Не відстають і шаблони проектування. Наприклад, MVC обмежує поділ логіки. А різні лінтери і стандарти визначають правила оформлення коду, що теж є обмеженням.

Неформальне визначення якості коду

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

Код настільки якісний, наскільки розробник дотримується встановлених обмежень.

Приклад зі світу вільного ПЗ

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

Не кидаючи каміння в бік комерційної розробки, все ж треба відзначити, що досить часто ПЗ з відкритим вихідним кодом забирає собі левову частку ринку, залишаючи свої комерційні аналоги далеко позаду. Досить поглянути на ОС Linux, ОС Android, веб-сервери Apache і Nginx, СУБД PostgreSQL, MySQL. Всі вони є стандартами де-факто у своїй галузі.

Більш того, часто ПЗ, розроблене командою, що нагадує другу з прикладу на початку статті, написано набагато більш якісно, ніж ПЗ, розроблене InHouse.

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

Успіх вільного ПО

Команда успішного проекту з відкритим вихідним кодом часто складається з ключового розробника або ініціативної групи і спільноти контриб'юторів. Роль ключового розробника полягає в тому, щоб сформувати ідею і закласти таку архітектуру, в якій величезна кількість розробників з абсолютно різними компетенціями буде ефективно працювати. Під архітектурою тут мається на увазі набір інтерфейсів (контрактів, протоколів), що оперують описаними структурами даних. Як правило, архітектура супроводжується специфікацією - вона описує, як саме система повинна функціонувати. Реалізація ж цих інтерфейсів лежить на плечах всієї спільноти. При цьому кожен контриб'ютор може бути занурений в проект рівно настільки, наскільки це дозволяють його компетенції, бажання або можливості.

Головна якість ключового розробника - це здатність розбити функціонал програми на мало пов'язані один з одним компоненти, реалізація кожного з яких не вимагає глибокого знання про систему поза межами цього компонента.

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

Ця тема перегукується з принципом предметно-орієнтованого проектування (Domain Driven Design, DDD). Серед розробників існує думка, що основне призначення DDD - забезпечення легкого перемикання між фреймворками. Це не так. Головне завдання DDD - це відокремлення логіки програми від логіки фреймворку. Це дає можливість працювати з високорівневою логікою програми, не залізаючи в дебрі фреймворку, і навпаки. Але це тема для окремої статті.

Обмеження, накладені на вільне ПЗ

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

Для проекту з відкритим вихідним кодом суворе дотримання обмежень - це запорука виживання проекту в цілому.

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

Друга відмінність розробки проектів з відкритим вихідним кодом від комерційних полягає в обмеженнях на комунікацію. Оскільки команда проекту може постійно змінюватися, структурування і збереження інформації для таких проектів - це не тільки вимушений захід, але і єдиний спосіб вижити і розвиватися. Тому вся комунікація тісно пов'язана з кодом і фіксується в обговореннях всередині Pull Request-ів, в todo-шках і коментарях прямо в коді, в issues, сторінках з документацією і так далі.

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

Секрет якісного коду - в управлінні обмеженнями

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

Для ефективної роботи команди на проекті намагайтеся накладати на свою команду якомога більше обмежень.

Обмежте роль тимліду

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

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

Обмежте роль розробника

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

Використовуйте лінтери та статичні аналізатори коду

І чим більше, тим краще.

Використовуйте код рев'ю і крос рев'ю

Код рев'ю - потужний інструмент підвищення якості коду. Але не забувайте і про крос рев'ю - взаємне рев'ю розробників, які працюють над різними частинами системи або навіть на різних проектах.

Пишіть тести

Причому - модульні (юніт), тому що написання саме таких тестів накладає на розробників архітектурні обмеження: ви не зможете написати модульний тест без використання таких набоїв, як Dependency Injection, DI Container, тощо.

Замість резюме

Слідкуйте за світом вільного ПЗ, вивчайте архітектуру проектів з відкритим вихідним кодом. Переймайте їх практики. Встановіть обмеження на робочу комунікацію всередині команди. Дотримуйтесь правил роботи з системою контролю версій, правил гілки та створення Pull Request-ів.

Обмеження сприяють підвищенню якості коду, що, в свою чергу, призводить до створення більш життєздатного продукту.

Постскриптум: Формалізація комунікацій і «портування» InHouse правил розробки, властивих OpenSource проектам, ні в якому разі не скасовує необхідність живого спілкування, вирощування командної культури і здорової атмосфери в колективі. В іншому випадку - будь-який, навіть найбільш налагоджений процес зведуть нанівець холівари і бодання в Pull Request-ах.

COM_SPPAGEBUILDER_NO_ITEMS_FOUND