Сложность программного проекта является комбинацией его системной (essential, имманентной) и случайной (accidental, ненужной) сложности.

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

Для управления случайной сложностью хорошо подходят практики «предметно-ориентированного проектирования»(DDD)[1]: соотнесение предметной области с реализуемой моделью, разграничение предметных областей (т.е. выделение «ограниченных контекстов»[2]), формирование «единого языка»[3] описания системы.

Если модель не сопоставлена предметной области, то изменение в предметной области вызывает непропорциональные изменения в модели. Например, если в модели финансового приложения не определено понятие «транзакции» из предметной области, то перечисление средств может происходить путем прямого изменения баланса пользователей. При изменении в предметной области, например, при появлении требования отката перевода, придется еще больше усложнять модель.

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

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

Использование практик DDD не бесплатно и требует принятия компромисса, в зависимости от задач, стоящих перед проектом.

Какая альтернатива лучше:

1. Большее (но константное) время на разработку новой функции в начале проекта; при этом в дальнейшем оно не сильно меняется.
2. Быстрая выдача новой функции в начале проекта, с последующей деградацией при росте кодовой базы.

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

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

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

1. Формирование единого языка для выявления непротиворечивой модели
2. Реализация модели с использованием единого языка для синхронизации модели системы с моделью предметной области
3. Разделение контекстов и модульная реализация для сокрытия рисков и сложности
4. Выделение вспомогательных слоев для отделения технических задач от бизнес-задач
5. Соблюдение «принципа открытости-закрытости»[4] и наличие тестов для внесения изменений без опасения деградации качества или «регрессии ПО»[5]

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


По сложившейся традиции в разработке Java-приложений можно ожидать примерно следующего подхода:

1. Выявление наборов данных для хранения в нормализованном виде (что хранить -> таблица БД -> Java-класс). Получили Equipment.java, представляющий структуру для хранения.
2. Описание операций над данными в виде сервиса. Получили EquipmentService.java, для операций с Equipment.java
3. Создание вспомогательных сервисов, таких как ДАО и прочее

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

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

Что произойдет, при добавлении-изменении:

1. Представлений (экранов)?
2. Полей (сущностей)?
3. Сценариев (процессов)?

Чаще всего, это приводит к непрогнозируемым по объему изменениям системы, которые очень трудно объяснить и обосновать заказчику.

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

 

Из чего появляется такой набор модулей:

 

Со следующими моделями предметных областей:

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

«Законченность» компонента подразумевает наличие в нем не только программной бизнес-модели, но и необходимых технических средств обработки запросов (обычно реализуемых в виде дополнительного слоя).

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

В заключение хотелось бы также привести перечень следующих подходов, применение которых показывает хорошие результаты в борьбе со сложностью и за константое время доработок:

1. Дешёвая модуляризация за счет выделения в отдельные пакеты ограниченных контекстов.
2. Для объединения данных с различных контекстов реализуется модуль шлюза.
3. Взаимодействие с контекстом только через продуманный интерфейс.
4. Только ссылочные связи между классами доменной модели разных модулей (например, UserId)
5. Разделение каждого контекста на слои, где каждый слой скрывает свою часть сложности (адаптеры, сценарии, модель)
6. Инкапсуляция бизнес-функций в слое модели. При этом технический слой обеспечивают только вспомогательные функции, такие как биндинг входных параметров, передачу управления и представление вернувшегося результата.
7. Семантизация операций доступа к данным в рамках развития единого языка. Достигается применением паттерна «Репозиторий» вместо неформализованного ДАО.
8. Для разработки UI не должен требоваться бэкенд. Если для внесения даже минимальных изменений в UI требуется запуск бэкенда, это будет служить постоянным источником неэффективных временных затрат. Для разделения можно использовать json-server / in-memory-web-api

 

Ссылки:

1. Предметно-ориентированное проектирование (Domain Driven Design; DDD)
2. Ограниченный контекст (Bounded Context)
3. Единый язык (Ubiquitous Language)
4. Принцип открытости-закрытости (Open-Closed Prinicple; OCP)
5. Регрессия ПО (Software Regression)