Следить за обновлениями блога можно в моём канале: Эргономичный код

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

Это первый пост в серии, посвящённый диаграмме эффектов:

  1. "Спецификация": назначение диаграммы, основные концептуальные элементы и их визуальное представление.

  2. "Пример построения диаграммы, проект True Story Project (TSP)" - процесс построения диаграммы эффектов реального проекта.

  3. "Методика объектно-ориентированной декомпозиции" - рациональный подход к разбиению системы на модули с помощью диаграммы эффектов и его применение для декомпозиции проекта TSP.

  4. "Методика перевода диаграммы в код" - процесс трансляции диаграммы в исходный код на примере проекта TSP.

Введение

Идентичность информационной системы определяется тем, как она взаимодействует с внешним миром. Что, где и когда она сохраняет и запрашивает; что, куда и когда отправляет. Это я называю наблюдаемым поведением.

Можно переписать приложение с Java на Haskell, сменить слоёную архитектуру на шестиугольную, реляционную базу данных заменить документной, а пользовательский интерфейс перевести с серверной генерации HTML на React Native - если наблюдаемое поведение системы останется неизменным, то это будет просто очередная версия всё той же системы. Если же кардинально изменить её взаимодействие с внешним миром, то это будет уже другая система.

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

При всей значимости наблюдаемого поведения я не знаю ни одного общепринятого инструмента для его проектирования и визуализации. Поэтому изобрёл свой - диаграмму эффектов.

При чём здесь эффекты? При том, что наблюдаемое поведение состоит из эффектов. И так короче.

Концептуальная модель

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

Операция

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

Эффект

Если не вдаваться в философию программирования, то эффект - это ввод-вывод. Включая запись и чтение глобальных переменных (кучи).

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

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

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

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

Вообще "неэффектов" не существует - любое действие в программе выражается в изменении состояния транзисторов. В фон Нейманавской архитектуре, по крайней мере.

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

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

Высокоуровневый эффект может проходить через несколько этапов переноса, посредством чтения и записи. Например, эффект "Отправить пуш уведомление" пройдёт такой путь: сначала информация переносится через кэши из процессора в память программы, потом в память ОС, потом в память сетевой карты, потом через память нескольких роутеров и серверов в память сетевой карты другого компьютера (смартфона), там обратно в память программы, а оттуда, опять же через несколько слоёв, в память экрана, где состояние транзистора "аналогизиурется" в свечение пикселя. И где-то попутно этот эффект заодно осядет на транзисторах диска БД пуш-сервиса.

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

Ресурс

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

Событие

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


Чёткое понимание операций системы (когда они выполняются и к каким эффектам приводят) является критически важным на всех этапах жизненного цикла разработки системы.

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

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

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

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

Реализация концептуальной модели в коде

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

Тут есть небольшая шероховатость. События на самом деле транслируются в метод, передаваемый фреймворку (см. ниже), и вызов из него метода операции. По крайней мере, если следовать принципам единственности ответственности и/или разделения аспектов (separation of concerns). Но пока что я не вижу ни потребности, ни возможности отразить это в концептуальной модели. Возможно, я найду какую-то другую абстракцию для события, так как технически ничего не мешает передать метод операции напрямую фреймворку.

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

Ресурсы превращаются в структуру данных и коллекцию методов работы с ней - классы Spring Data агрегата и репозитория, классы события и ApplicationEventPublisher-а (или обёртки вокруг него), классы REST API модели и клиента и т.п. В контексте бэкэндов информационных систем, самыми распространёнными видами ресурсов являются:

  1. любые постоянные коллекции данных - таблицы в реляционной СУБД, коллекции в документной СУБД и т.д.;

  2. REST API внешних сервисов;

  3. любые очереди сообщений/шины событий;

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

События превращаются в методы, передаваемые фреймворку для последующего вызова - метод Spring-ового RestController-а, Swing-овый EventListener, реализация Runnable для таймера и т.д. Если говорить о бакэндах информационных систем, то самыми распространёнными видами событий являются:

  1. Получение запроса по сети (@RestController + @*Mapping в случае разработки на Spring). Сейчас популярностью пользуется протокол запросов в REST-стиле, но SOAP, gRPC, CORBA и т.п. так же попадают в эту категорию.

  2. Появление сообщения в очереди (@JmsListener).

  3. Доменное событие или событие приложения (@EventListener).

  4. Наступление определённого момента времени (@Scheduled). Два основных типа таких событий:

    1. наступление заранее известного момента времени (например, полуночи вторника).

    2. истечение определённого времени с момента в прошлом (например, истечение суток с момента создания предыдущего бэкапа).

Нотация

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

Основу визуального языка диаграммы эффектов я позаимствовал в модели C4. Во-первых, мне нравится сам язык модели C4. А во-вторых, диаграмму эффектов можно встроить в модель C4 на четвёртом уровне - вместо кода. Кроме того, диаграмму третьего уровня (компонентов) я строю как раз на базе диаграммы эффектов.

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

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

Краткая нотация

В краткой нотации диаграмма выглядит следующим образом:

Пример краткой нотации
Пример краткой нотации

Теперь рассмотрим отдельные элементы.

Операции

Операции обозначаются прямоугольником с именем операции:

Пример элемента операции
Пример элемента операции

Ресурсы

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

Пример элемента ресурса
Пример элемента ресурса

Эффекты

Эффект модификации ресурса обозначается "сильной" (более заметной) стрелкой от операции к ресурсу, с кратким описанием эффекта:

Пример элемента эффекта записи
Пример элемента эффекта записи

Эффект чтения ресурса обозначается стрелкой от ресурса к операции, с кратким описанием считываемых данных:

Пример элемента эффекта чтения
Пример элемента эффекта чтения

Эффекты вызова операций

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

Пример элемента эффекта вызова операции
Пример элемента эффекта вызова операции

Примечания

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

Пример элемента примечания
Пример элемента примечания

Это все элементы, составляющие ядро диаграммы эффектов.

Полная нотация

Теперь рассмотрим ту же функциональность, описанную в полной нотации:

Пример полной нотации
Пример полной нотации

В полной нотации появляются:

  1. события;

  2. описание операций и ресурсов в формате модели C4;

  3. границы контейнера из C4. Обозначает границы процесса - всё, что находится внутри этих границ выполняется в памяти визуализируемого приложения;

  4. внешние системы, базы данных и компоненты из C4. Внешние системы могут быть как источником события, так и средством реализации ресурса.

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

События

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

Пример элемента события
Пример элемента события

Описания

Затем блоки операций и ресурсов можно дополнить типом, способом реализации и описанием:

Пример элементов с подробным описанием
Пример элементов с подробным описанием

Внешние системы

Элементы, обозначающие границы системы и внешние системы полностью соответствуют нотации C4:

  1. Границы системы отображаются прерывистым прямоугольником приглушённого цвета и подписью с именем контейнера.

  2. Управляемые внешние системы и базы данных обозначаются прямоугольником и символом "База Данных".

  3. Неуправляемые внешние системы и компоненты обозначаются приглушёнными прямоугольниками.

  4. Неуправляемые базы данных обозначаются приглушённым символом "База Данных".

Внешние системы связываются с операциями посредством событий:

А ресурсы связываются с внешними системами посредством стрелок с описанием:

Ресурс может быть связан со сторонним компонентом, работающем в том же процессе:

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


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

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

Я сам обычно начинаю с промежуточной нотации - краткой с событиями, и дополняю её по мере необходимости.

Инструментарий

Одним из плюсов базирования на визуальном языке модели C4 является то, что для диаграммы эффектов можно использовать любой инструмент с поддержкой C4. А в силу простоты C4, таким инструментом может быть хоть графический редактор. Тем не менее поддержка привязки элементов сильно помогает, поэтому я сам сейчас использую десктопную версию draw.io.

Заключение

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

Самым удобным способом отразить суть поведения системы является связка События-Эффекты - какими эффектами на какие ресурсы в ответ на какие события система реагирует.

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

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

Комментарии (5)


  1. Maxh
    24.05.2022 09:44
    +1

    Спасибо, как раз занялся вопросом создания простого описания всего что накопилось на данный момент. Из-за того, что в основном уделялось время только руководству пользователя и "нехватало" времени сделать внутрянку для быстрой адаптации сотрудников. Полез в сеть для поиска примеров простых и лёгких алгоритмов работы систем и продуктов и ничего толком не нашёл))))))) Кстати, может кто подскажет по поводу документациий ТОП-примеры


    1. jdev Автор
      24.05.2022 10:57

      Вам спасибо за комментарий - я узнать рад, что вы нашли пост полезным:)


      1. jdev Автор
        24.05.2022 11:37

        мастер йода деткед????‍♂️


  1. blood_develop
    24.05.2022 10:46
    +1

    Затем блоки операций и ресурсов можно дополнить типом, способом реализации и описанием:

    Так ведь в начале Вы пишите:

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

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

    Противоречие получается


    1. jdev Автор
      24.05.2022 10:56

      Спасибо за замечание

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

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

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

      В любом случае, думаю вы правы, в том, что надо разделить краткую и полную нотацию. У них разные контексты и цели.