Что я делаю не так

November 6, 2021

"Алексей - один из лучших разработчиков, с которыми я работал. Работой с ним я удовлетворён на 10 баллов из 10, потому что он практически всегда попадает в озвученные сроки, и в работе с ним не приходится накидывать 50% на всякий пожарный".

Денис Исаев, руководитель проектов

Привет! Алексей - это я. В этом посте я расскажу, что я делаю не так, как принято, чтобы получать такие отзывы.

Свой подход к разработке я называю Эргономичным и от общепринятого он отличается следующим:

  1. Я не пишу на Java, а пишу на Kotlin
  2. Я не использую Spring Data JPA, а использую Spring Data JDBC/R2DBC
  3. Я не игнорирую концепции состояния системы и эффектов операций, а тщательно их проектирую
  4. Я не структурирую программы исходя из собственного чувства прекрасного, а применяю принципы структурного дизайна
  5. Я не использую императивный стиль программирования, а предпочитаю декларативный стиль
  6. Я не декомпозирую систему на слои, а разделяю на концептуальные модули (пакеты)
  7. Я не отдаю конфигурацию системы на откуп Spring-овой автомагии, а описываю её руками
  8. Я не пишу тесты, которые проверяют детали реализации, а пишу тесты, которые проверяют поведение
  9. Я не использую микросервисную архитектуру для структурирования системы, а начинаю с модульных монолитов

Давайте разбираться, почему я всё это делаю не так, как принято и к чему это приводит для бизнеса.

Язык программирования

Как это ни странно, Java-команды для разработки обычно выбирают язык Java. Действительно, Java 17 - неплохой язык программирования, а эргономичный подход можно использовать практически с любым языком. Но некоторые языки делают разработку дешевле, а применение эргономичного подхода проще, чем другие.

Для себя я в качестве основного языка разработки выбрал Kotlin ещё в 2015 году.

Kotlin обладает рядом особенностей, которые положительно влияют и на качество продукта, и на стоимость его разработки:

  1. Поддержка nullable-значений на уровне системы типов. По данным Google [fc], в Google Play количество NullPointerException-ов в приложениях на Kotlin на 33% меньше, чем в приложениях на Java. Я думаю одна эта особенность языка на 1-2% снижает стоимость разработки и на столько же увеличивает лояльность пользователей.
  2. Корутины. В Java нет и не предвидится прямого аналога корутин. Базовая для Java модель конкурентного программирования - на потоках - и масштабируется для IO-интенсивных нагрузок намного хуже, чем корутины и намного более подвержена ошибкам [sc]. Библиотеки, реализующие акторный подход, обладают схожими характеристиками по масштабируемости и надёжности, но намного более громоздки в работе, чем корутины. Project Loom, не выпущен, и не даёт защиты от утечек асинхронных задач. Project Reactor, даёт схожую степень масштабируемости, но не даёт тот же уровень надёжности. Точных данных у меня нет, но я думаю корутины также дают 1-2% снижения стоимости разработки и увеличения стабильности работы приложения.
  3. Kotlin Multiplatform. У меня есть опыт успешного выделения самой сложной и дорогой части Android-приложения в общий модуль, с последующим его переиспользованием в iOS-приложении. Подобного коммерческого опыта с выделением общего кода между сервером и клиентами у меня нет, но я вижу у этой технологии потенциал для сокращения стоимости разработки систем на десятки процентов.
  4. По данным JetBrains программы на Kotlin содержат на 40% меньше строк исходного кода, чем аналогичные программы на Java [kf]. Это на 40% меньше усилий на написание кода, чтение кода, ревью кода и на экспоненциально меньше ошибок [cc]. Не думаю, что это даёт 40% сокращения стоимости разработки, но процентов 5-10 должно дать.
  5. Есть ещё целый ряд небольших особенностей - функции расширения, более богатая стандартная библиотека, инлайн функции, овеществлённые дженерики и т.д. - которые по моей субъективной оценке дают ещё 1-2% сокращения стоимости разработки.

Наконец, в контексте эргономичного подхода, лучшая поддержка декларативного программирования (предпочтение неизменяемых переменных и структур данных, функциональные типы и т.п.) Kotlin даёт ещё 1-2% процента снижения стоимости разработки.

Суммарно всё это даёт удешевление разработки на 10-15%.

Технология работы с БД

Обычно для работы с БД Java-команды без какого-либо критического анализа выбирают Spring Data JPA.

Одним этим решением команда обрекает бизнес на дорогой в разработке продукт с большим количеством ошибок и регрессий и постоянным ростом стоимости и сроков реализации требований. Технология JPA - крайне неудачна и её драматическое влияние на качество всего продукта в целом я рассмотрю ниже. Если же говорить только о технической стороне, то выбор JPA ведёт к:

  1. Низкой отзывчивости приложения, из-за:
    1. Чрезмерной сложности технологии и, как следствие, большой разницей между поведением программы в голове программиста и реальным поведением [jpas]
    2. Лаконичности и "красоты" медленного кода (повсеместно использующего ленивую загрузку)
    3. Кажущейся тяжеловесности быстрого кода (выполняющего необходимый минимум оптимальных запросов, извлекающих из БД только нужные данные)
  2. Высокой стоимости оборудования, необходимого для обеспечения хоть сколько-нибудь приемлемого уровня производительности
  3. Большому количеству ошибок из-за:
    1. Всё той же разницей между поведением программы в голове программиста и реальным поведением
    2. Возможности неявно и случайно (через мутацию объекта, доступного через несколько ссылок внутри транзакции) внести лишние изменения в хранимые данные
  4. Большому количеству регрессий. JPA подталкивает программистов к тому, чтобы создавать высокую степень связанности (coupling) между различными модулями, через полносвязный двунаправленный граф всех сущностей системы [jag]. Как следствие, повышается вероятность регрессий в одних модулях, при внесении изменений в другие модули.
  5. Непредсказуемому времени исправления ошибок. Из-за высокой сложности ("автомагичности") JPA, зачастую программисты сталкиваются с проблемами, которые проявляются только в промышленной эксплуатации. Такие проблемы совершенно непонятно чем вызваны, в какой момент что-то пошло не так и как всё это отлаживать и исправлять. Как следствие, в проектах с JPA зачастую бывают "нормальные" исключения в логах - какие-то ошибки, с которыми не понятно что делать, но в целом можно жить.

Подробнее о проблемах свойственных JPA можно почитать в моём посте, посвящённом этому вопросу.

Для решения описанных проблем я изучил рынок существующих решений для работы с БД, опробовал наиболее перспективные из них на практике и на данный момент остановился на Spring Data JDBC и R2DBC. У этих технологий два ключевых преимущества перед технологиями, основывающимся на JPA:

  1. В них минимум "автомагии" и из кода работы с БД очевидно какие запросы будут действительно выполнены. Это положительно сказывается практически на всех аспектах приложения:
    1. Скорости работы приложения
    2. Требованиях к ресурсам
    3. Количеству ошибок
    4. Времени исправления ошибок
  2. Использование Spring Data JDBC/R2DBC подталкивает программистов к разделению состояния системы на изолированные агрегаты. Это уменьшает связанность (coupling) системы и как следствие удешевляет её развитие и уменьшает количество регрессий.

В итоге использование Spring Data JDBC/R2DBC означает для бизнеса, что он быстрее и дешевле получит более стабильный и отзывчивый продукт, темп развития которого практически не будет падать со временем.

Управление состоянием и эффектами

Обычно для разработчиков термин "Состояние" ассоциируется только с одноимённым шаблоном проектирования, а "Эффект" вообще не имеет ассоциаций в контексте разработки информационных систем.

Между тем хранение и обработка состояния, является главной целью информационной системы. Конечный пользователь воспринимает систему через эффекты отображения её текущего состояния, а корректность работы системы определяет по набору эффектов, осуществлённых в ответ на его действия. Подробнее о состоянии, эффектах и их значимости можно почитать в моём посте, посвящённом этому вопросу.

Непонимание этих фундаментальных понятий приводит к тому, что команды формируют структуру состояния информационной системы хаотично, без какой-либо стратегии. Это порождает запутанный граф состояния системы, зачастую с циклами и нестабильными зависимостями. Такой граф сложно и понять, и модифицировать.

А пренебрежение эффектами ведёт к тому, что они разбросаны по коду в произвольных местах. Это усложняет понимание того, к каким эффектам приведёт та или иная операция и наоборот, какие операции могут привести к тому или иному эффекту. Также это существенно усложняет автоматизацию тестирования - тестирование кода с эффектами требует подготовки окружающей среды, с последующей проверкой изменений в ней.

Из-за этого разработчикам сложно понять все последствия вносимых изменений, и у них нет надёжной сетки безопасности в виде автоматических тестов. В итоге, особенно под давлением, разработчики вносят вслепую изменения, которые решают поставленную задачу. А выявление побочных эффектов этих изменений отдают на откуп QA и пользователям. Порождая при этом дополнительные циклы разработчик-QA - дополнительные рабочие и календарные часы.

Отдельный вклад в хаос вносит применение JPA, которая:

  1. Подталкивает программистов к созданию высоко связанной структуры состояний с двунаправленными связями [jag].
  2. Подталкивает программистов к разбросу эффектов (ленивой загрузки и изменения полей сущностей, с последующим автоматическим сохранением) по всему коду бизнес-логики [hbp]

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

Я же осознанно и целенаправлено формирую структуру состояния системы таким образом, чтобы минимизировать стоимость внесения изменений в неё. Руководствуюсь я при этом давно известными принципами дизайна:

  1. В структуре состояния системы не должно быть циклов [asdppp]
  2. Зависимости должны быть направлены в сторону более стабильных элементов [asdppp]
  3. Детали устройства элементов состояния с высокой вероятностью изменений должны быть инкапсулированы в границах соответствующих модулей [ih]
  4. Наращивание функциональности системы должно выполняться за счёт расширения структуры состояния, а не модификации существующих элементов [asdppp]

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

  1. Структурный дизайн [sd]
  2. Архитектуру функциональное ядро/императивная оболочка, которую я считаю развитием структурного дизайна [fcis]

Применение этих техник даёт:

  1. Сокращение трудоёмкости реализации требований за счёт того, что:
    1. Изменения в структуре состояний инкапсулируются в одном модуле, либо затрагивают небольшое количество модулей
    2. Последствия изменений в эффектах операции инкапсулируются в одном модуле, либо затрагивают небольшое количество модулей, список которых можно получить автоматически
  2. Минимизацию количества ошибок за счёт упрощения автоматизации тестирования бизнес-логики системы
  3. Минимизацию количества регрессий, за счёт:
    1. Минимизации трудоёмкости изменений
    2. Повышения видимости последствий изменений
    3. Повышения покрытия автоматическими тестами

То есть мы снова приходим к тому, что бизнес получает более качественный продукт за меньшие деньги.

Стиль программирования

Я не сталкивался с информационными системами, спроектированными другими людьми и не использующие JPA, поэтому не могу сказать является ли связь между JPA и процедурным стилем программирования причинно-следственной или корреляционной. Тем не менее JPA не оставляет выбора и вынуждает использовать устаревший процедурный стиль кодирования, когда пакеты процедур (Сервисы) императивно модифицируют структуры данных (Сущности) [auap].

JPA настолько сложна и непредсказуема, что программисты боятся применять принципы объектно-ориентированного программирования и помещать какой-либо код кроме сеттеров и геттеров в сущности. Кроме того, спецификация JPA накладывает ряд ограничений на классы сущностей (обязательная открытость для наследования, обязательный конструктор по умолчанию, наличие геттеров и сеттеров), которые непосредственно противоречат принципам объектно-ориентированного дизайна. Подробнее можно почитать в моём посте, посвящённом JPA.

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

Остаётся только процедурное программирование. Притом, как правило, хаотичное процедурное программирование, руководимое интерпретациями программистов и ревьюверов нескольких популярных книг вроде Чистого Кода Роберта Мартина. Вместо рациональных методик из классических книг [sp], [sd].

Всё это ведёт к разработке сложной системы, с высокой связанностью (coupling) и низкой связностью (cohesion), что для бизнеса выливается в высокую стоимость разработки и поддержки, большое количество ошибок, долгий онбординг новых разработчиков [sd].

От объектно-ориентированной парадигмы я тоже отказался, т.к. несмотря на заявления её сторонников, по моему опыту она плохо подходит для моделирования нашего мира (уступая в этом акторной модели [pe]), а следование принципам ООД на уровне кода (Информационный Эксперт из GRASP, в частности) ведёт к созданию божественных объектов [auap], [dsdp]. В этом выборе я не одинок - анкл Боб, одна из ключевых фигур в ООП/ООД, примерно с 2010 года [ot] предпочитает программировать на функциональном Clojure [wc] (не отказавшись при этом от объектно-ориентированного подхода в дизайне).

А вот использование адекватных технологий (Spring Data JDBC/R2DBC) позволяет мне работать в рамках декларативной парадигмы. Академическое функциональное программирование с высокоуровневыми абстракциями, понятными только хорошо тренированным математикам, я тоже не считаю подходящим средством для разработки информационных систем. Поэтому из функционального программирования я взял только две ключевые на мой взгляд техники - функции без побочных эффектов (чистые функции) и неизменяемые структуры данных. Это позволяет мне получить все выгоды декларативного программирования, оставляя код доступным для понимания всем программистам.

Декларативный подход подразумевает разделение кода описывающего желаемые эффекты (бизнес-логики) и их осуществляющего (ввода-вывода). Однако операции системы в реальном мире подразумевают одновременно и эффекты (в противном случае, операция просто нагреет воздух и не изменит состояние мира) и бизнес-логику (в противном случае, эффекты оставят мир в текущем состоянии). В отсутствие сложных функциональных техник (вроде интерпретаторов свободных монад), естественным способом объединения бизнес-логики и ввода-вывода являются контролирующие модули из структурного дизайна [sd]. Контролирующие модули являются второй отличительной чертой моего стиля кодирования.

Всё вместе это превращается в уже упомянутый мной подход функциональное ядро/императивная оболочка [fcis]. Такой подход кардинально повышает характеристики разработки и продукта, существенные для бизнеса:

  1. Выделение бизнес-логики в чистые функции упрощает покрытие их автоматическими тестами, включая продвинутые тесты свойств (property testing) [qc]. Это, в свою очередь, снижает количество регрессий и ошибок, снижает стоимость QA, снижает накладные расходы на дополнительные итерации исправления ошибок, повышает качество пользовательского опыта
  2. Разделение бизнес-логики, ввода/вывода и модулей управления, повышает переиспользуемость отдельных элементов, снижая стоимость разработки
  3. Декларативный код более прост для понимания, чем императивный и сокращает время онбординга новых разработчиков [wfpm], [tcem], [ddd], [rstc]
  4. Декларативный код менее подвержен ошибкам при первичной реализации и последующих модификациях и рефакторинге, давая все те же преимущества [wfpm], [them], [ddd], [rstc]
  5. Чистые функции проще оптимизировать и людям (их выполнение можно без ограничений распараллеливать и свободно кэшировать результат) и компиляторам [tcem], что существенно упрощает оптимизацию "горячих точек" приложения

Декомпозиция системы на пакеты

Обычно дизайн пакетов сводится к разбиению классов на функциональные категории - сервисы, ДТО, исключения и т.п. Однако нет ни одного авторитетного источника, который бы рекомендовал такой подход и существует множество авторитетных источников, критикующих такой подход [ih], [asdppp], [sd], [auap], [ddd], [ca].

Критика сводится к четырём тезисам:

  1. Механическое пакетирование по функциональным категориям не отражает архитектуру приложения и структуру предметной области и тем самым затрудняет понимание системы [ddd], [iddd], [ca]. Из своего опыта я могу сказать, что есть 100% корреляция между таким пакетированием и архитектурой "Большой Ком Грязи" [bbom], т.е. фактическим отсутствием архитектуры
  2. Такое пакетирование исключает возможности для сокрытия информации и инкапсуляции [ih], [asdppp], [sd], [auap]. Если говорить о Java, то в этом языке самым удобным является модификатор доступа package private - который используется, если модификатор доступа не был указан явно. То есть дизайнеры языка планировали, что именно package private будет наиболее распространённым [jgoj]. Пакетирование же по функциональности, вынуждает делать все классы и большинство методов публичными. Тотальная публичность элементов кода лишает разработчиков одного из ключевых средств локализации изменений и ведёт к чрезмерной трудоёмкости реализации изменений в требованиях.
  3. Декомпозиция по функциональности ведёт к низкой связности (cohesion) и высокой связанности (coupling) пакетов [sd], [asdppp], [ddd]. Ещё в 60ых года Ларри Константин доказал, что такие программы сложны в понимании и поддержке и как следствие подвержены большому количеству ошибок и регрессий.
  4. Механическое разбиение превращает разработчиков в Эллочку-людоедку со словарным запасом из ~8 слов - контроллер, сервис, репозиторий, сущность, ДТО, исключение, фабрика, перечисление. Тем самым искусственно ограничивая разработчика в инструментарии для корректного моделирования предметной области [ddd], [iddd].

Всё это ведёт к всё тем же проблемам для бизнеса: программы с таким принципом пакетирования стоят дороже, требуют больших усилий для реализации новой функциональности и особенно подвержены ошибкам и регрессиям.

Избежать всех этих проблем помогает разбиение классов по пакетам на основе агрегатов DDD и юз кейсов [ddd], [ca], [auap], [oose], с учётом основных принципов проектирования:

  1. Принцип ацикличного графа зависимостей [adpppp]
  2. Принцип сокрытия информации [ih]
  3. Принцип стабильных зависимостей [adpppp]
  4. Принципы высокой связности (cohesion) и низкой связанности (coupling) [sd], [auap], [ddd]
  5. Принцип единственности ответственности [adpppp]
  6. Принцип расширения поведения, за счёт нового кода

Всё это позволяет декомпозировать систему на набор изолированных пакетов, со стабильным ядром и минимальным количеством связей между ними. Кроме того, такой подход позволяет использовать Dependency Structure Matrix [dsm] не только для контроля зависимостей между отдельными классами, но и для контроля зависимостей между модулями.

Для бизнеса это значит, что изменения в требованиях будут требовать минимум усилий и порождать минимум регрессий, а добавление новой функциональности будет выполняться преимущественно за счёт нового кода (и состояния) и тем самым исключать регрессии в принципе. Это ведёт к сокращению стоимости и сроков разработки, в том числе за счёт сокращения количества ошибок и регрессий.

Использование Spring для связки бинов

Обычно разработчики для связки бинов приложения используют Spring Component Scan и, хоть и реже в последнее время, автоматическое связывание на полях.

Моя практика показывает, что такой подход ведёт к полной утере разработчиками контроля за зависимостями в приложении, что ведёт к моментальному появлению циклических зависимостей, зависимостей стабильных классов от нестабильных классов и классов с огромным (более 20) количеством зависимостей. В итоге система быстро скатывается к архитектурному стилю Big Ball of Mud [bbom].

Что влечёт для бизнеса все те же проблемы - постоянно увеличивающиеся стоимость и сроки разработки и количество ошибок и регрессий.

Для того чтобы обеспечить себя и команду инструментарием для контроля за зависимостями и вытащить зависимости на первый план, я применяю рад нестандартных техник конфигурирования Spring-приложений:

  1. Я отключаю Spring Component Scan
  2. Я использую инъекцию зависимостей только через конструкторы классов
  3. Я слежу за тем, чтобы у бинов было не более 3 зависимостей на бины из других пакетов (модулей)
  4. Я создаю по отдельной Spring Configuration для каждого верхнеуровневого пакета
  5. В конфигурациях я делаю публичными только бины, определяющие публичный интерфейс пакета, а бины отвечающие за детали его реализации скрываю
  6. Если какой-то пакет зависит от бина другого пакета, я импортирую не сам бин, а конфигурацию соответствующего пакета
  7. Каждый класс конфигурации явным образом импортирует все другие конфигурации (включая автоматические), от которых он зависит

Всё это вместе даёт ряд мне инструментов для мониторинга архитектуры и противодействия скатыванию к Big Ball of Mud:

  1. Архитектура (зависимости между пакетами, в частности) становится доступной в один клин на диаграмме контекстов Spring в IDEA и матрице структуры зависимостей (с гранулярностью на уровне пакетов)
  2. Добавление новой зависимости начинает требовать существенных усилий (добавление в конструктор класса, изменение класса конфигурации, добавление в конструктор конфигурации) и как следствие создаёт барьер, на котором у программиста появляется шанс остановиться и оценить последствия добавления зависимости
  3. Появляется несколько дополнительных элементов чек-листа ревью - количество зависимостей в бинах на другие модули, стабильность зависимостей, адекватность области видимости бинов, отсутствие циклов в зависимостях - которые позволяют следить за "здоровьем" архитектуры на регулярной основе

Кроме того, такой подход делает отдельные пакеты полноценными единицами переиспользования с точкой входа в виде Spring Configuration пакета. В частности, это существенно упрощает изолированное тестирование отдельных пакетов программы.

Всё это ведёт к созданию архитектуры с продуманной структурой зависимостей. Хорошо спроектированную структуру легче понять и она минимизирует количество модулей, требующих изменений при изменении в требованиях. Это даёт бизнесу всё те же преимущества - сокращение стоимости и сроков реализации и количества ошибок и регрессий.

Тестирование

Обычно разработчики делают акцент на юнит-тестировании, подразумевая под юнитом отдельный метод. А для изоляции кода от коллабораторов активно используют моки.

Такой подход популяризировала Лондонская школа тестирования [ls], но основатели движения TDD - Кент Бек и Роберт Мартин придерживаются других практик [tdd], [wtm], [tab], [wtddgw], [tddid].

Потому что Лондонскому подходу свойственен ряд существенных недостатков:

  1. Такие тесты ничего не говорят о работоспособности системы [wmuaw], [ls], [tim]
  2. Такие тесты проверяют реализацию, а не поведение и как следствие препятствуют рефакторингу [wtddgw], [ls], [tim]. Что ведёт к его удорожанию, как следствие, отказу от него, как следствие - постепенному сгниванию дизайна.
  3. Такие тесты оторваны от потребностей заказчика [wtddgw], [ls]. Заказчика не интересует, выбрасывает ли метод Foo.bar исключение при определённых значениях входных параметров. Заказчика интересует реализовано ли требование запрета на перевод денег, в случае нехватки средств на исходном счету.
  4. Ценность, как правило, возникает именно в интеграциях. Уникальный алгоритм торговли на бирже с доходностью 150% совершенно бесполезен, без интеграции с этой биржей. А Лондонская школа тестирования держит интеграции вне своего фокуса [ls], [tim].

Для бизнеса это значит, что такие тесты лишь увеличивают стоимость первоначальной разработки и последующей поддержки, при этом практически никак не влияют на стоимость QA, количество фич возвращённых разработчикам на исправление ошибок и регрессий и количество ошибок и регрессий, дошедших до конечных пользователей.

Поэтому я пишу тесты, которые проверяют, что система удовлетворяет требованиям и мокирую только внешние системы, которые слишком дорого использовать в тестах, или которые работают нестабильно.

Такие тесты позволяют мне:

  1. Сократить на порядок количество задач возвращённых QA на исправление ошибок и регрессий и количество ошибок и регрессий, дошедших до конечного пользователя
  2. Выполнять рефакторинг (изменение структуры кода, без изменения поведения) без изменений в тестах
  3. Быть уверенным в том, что если все тесты прошли, то система удовлетворяет всем согласованным требованиям

Для бизнеса это, как обычно, значит сокращение стоимости и сроков разработки, и меньшее количество ошибок и регрессий дошедших до конечного пользователя.

Подробнее о моём подходе к тестированию и результатам, которые он даёт можно почитать в посте Тесты которым можно доверять.

Архитектура

С точки зрения архитектуры сейчас наиболее распространены три варианта, усложняющие жизнь разработчикам и бизнесу.

Исторически самым распространённым архитектурным стилем был слоёный монолит, который ввиду описанных выше обычаев разработки уже через три-четыре месяца разработки превращался в Layered Big Ball of Mud.

После начала хайпа микросервисов многие команды начали делать системы по микросервисной архитектуре и получается у них сильно по-разному.

Если команда не была в состоянии спроектировать модульный монолит, а переход на микросервисную архитектуру не послужил триггером к качественному скачку в дизайнерской квалификации, то команда получала наихудший из возможных вариантов - Distributed Big Ball of Mud [mm], [mom].

Если команде удалось хорошо спроектировать микросервисы, но декомпозиция системы была единственной причиной их применения, то команда зря заплатила огромные накладные расходы на старте разработке и при последующей поддержке [mm], [mom].

Применение микросервисной архитектуры со старта проекта уместно в двух случаях [mm], [mom]:

  1. Проект разрабатывается для 1% компаний, которым действительно важны операционные свойства микросервисов - независимые масштабируемость, развёртывание, конфигурируемость и т.п.
  2. Архитектура разрабатывается для крупной корпорации, которая планирует львиную долю работ отдать на аутсорс и аутстаф разным командам

Наконец, в последнее время набирает хайп Чистая Архитектура Роберта Мартина [ca]. Этому хайпу поддался и я и сделал три коммерческих проекта по Чистой Архитектуре. В результате пришёл к выводу, что она практически ничего не даёт относительно от остальных моих практик, с точки зрения борьбы с Big Ball of Mud, зато несёт с собой существенные накладные расходы, удорожая разработку.

К счастью или несчастью, я никогда не разрабатывал архитектуру для проектов, в которых микросервисы были бы уместны со старта. Поэтому я (снова, после отхода от чистой архитектуры) начинаю проекты с самой простой и дешёвой архитектуры - слоёного монолита. А для того чтобы он не скатился к Big Ball of Mud, я пользуюсь описанными выше практиками и делаю модульный слоёный монолит. В отдельных модулях, где я вижу что применение Чистой Архитектуры окупит свою стоимость - я на уровне модуля применяю чистую архитектуру.

Пока что ни один из моих проектов, выполненных по эргономичному подходу, не дорос до потребностей к операционной работе, которые могут обеспечить только микросервисы, поэтому все они ещё монолитные. Но я держу в голове потенциальную необходимость выделять из монолита микросервисы и слежу за тем, чтобы модули могли быть выделены в самостоятельные приложения в течение нескольких часов.

Такой подход совмещает в себе лучшее двух миров - отсутствие лишних расходов на старте, и возможность быстро выделить функциональность, для которой появились особые операционные требования, в отдельный микросервис [mm], [mom].

Для бизнеса это значит существенное сокращение сроков и стоимости разработки и поддержки продукта.

Что получается, когда делаешь не так

Эргономичный подход - это согласованный набор принципов и техник, которые я по крупицам собирал и проверял на практике последние десять лет. Эти принципы и техники я взял из признанных и проверенных источников от авторов с мировом именем в ИТ - эргономичный подход несёт с собой ноль рисков относительно общепринятого подхода. Все они служат глобальной цели - минимизации усилий, необходимых для реализации требований, в том числе за счёт сокращения количества ошибок и регрессий. Игнорирование практик эргономичного подхода, помимо технических проблем, влечёт ещё и проблемы с командой.

Мотивация разработчиков

Для разработчиков обычный подход к разработке создаёт две проблемы:

  1. Реализация изменений в требованиях выливается в неадекватно большие усилия
  2. Реализация изменений в требованиях приводит к большому количеству регрессий

По моему опыту, в таких условиях разработчики сначала прокрастинируют, а потом увольнялся в поисках лучших условий. Тем самым ещё больше увеличивая стоимость и сроки реализации продукта для бизнеса.

Я тоже так делал. Но после третьей смены работы, я понял, что подход к работе и условия везде одинаковые. И мне необходимо самому создать для себя комфортные условия. Вот почему я начал делать эргономичный подход. Вот почему с рождением ребёнка я не забросил эргономичный подход, а начал вставать в 5 утра чтобы урвать час для работы над ним.

И думаю я на верном пути. К текущему моменту я сделал в эргономичном подходе четыре проекта, командами в два-четыре разработчика. В этих проектах у меня ни разу не возникало потребности мотивировать кого-то. Ушёл с проекта только один человек.

Высоко замотивированная команда с низкой ротацией даёт бизнесу всё те же выгоды - сокращение стоимости и сроков реализации продукта.

Разница в деньгах

Я утверждаю - для бизнеса, эргономичный подход выливается в более качественный продукт за меньшие деньги.

У меня нет результатов "рандомизированных двойных слепых плацебо контролируемых" исследований, на выборке статистически достоверного размера, для подтверждения этого тезиса. Однако ряд аргументов в его подкрепление я могу привести:

  1. Ларри Константин, первооткрыватель морфологии ориентированной на трансформации, одной из основных техник эргономичного подхода, проводил исследования на более чем ста системах. И в результате пришёл к выводу, что соответствие этой морфологии является надёжным классификатором для разделения систем на дешёвые (в случае соответствия) и дорогие
  2. Субъективно я чувствую себя более эффективным при работе над системами, сделанными по эргономичному подходу. Мне реже приходят ошибки и регрессии от QA, я реже испытываю давление по срокам, я реже раздражаюсь на чрезмерно трудоёмкий рефакторинг
  3. В проектах, сделанных по эргономичному подходу, я трижды проводил крупные рефакторинги и ни разу не внёс регрессий
  4. По статистике, ошибки я в среднем допускаю одну за спринт
  5. По статистике, регрессии я в среднем допускаю одну в квартал
  6. Руководитель проекта, сделанного мной по последней версии эргономичного подхода, дал мне такую рекомендацию: "Алексей - один из лучших разработчиков, с которыми я работал. Работой с ним я удовлетворён на 10 баллов из 10, потому что он практически всегда попадает в озвученные сроки, и в работе с ним не приходится накидывать 50% на всякий пожарный"

Вот к чему ведёт то, что я делаю не так, как это принято.

Ссылки