Если вам приходилось задумываться о построении эффективной экосистемы проекта и определении ролей тимлида и разработчика — статья Артема Прозорова из 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-ах.