Неочевидный PlantUML
https://t.me/humane_analyst
Вступление
Использование визуальных средств способно повысить качество коммуникации между членами команды разработки. Особенно это актуально для аналитиков, чьей непосредственной задачей является построение различных моделей и донесение до других участников заложенных в них смыслов без искажений и разночтений. И среди инструментов, доступных аналитику для решения такого рода задач, особняком стоит PlantUML.
Этот инструмент позволяет создавать диаграммы без необходимости рисования и перетаскивания элементов по экрану, как это бывает в традиционных инструментах. Благодаря такому подходу и постоянному расширению доступных возможностей, как мне кажется, он и снискал такую популярность.
Но вместе с тем среди пользователей PlantUML есть расхожее мнение о том, что этот инструмент хорош только для создания диаграмм последовательности UML. Но почему так?
Думается, что это результат комбинации сразу нескольких факторов. Во-первых, диаграммы последовательности являются в принципе самыми популярными диаграммами (как следствие, при возникновении вопросов решение гуглится значительно быстрее). Во-вторых, код PlantUML рендерится по не всегда понятным правилам, результат может выглядеть неопрятно, а общеизвестных способов повлиять на результат мало. В-третьих, любая документация фокусируется на изложении возможностей, а практическое использование и возможные приёмы отходят на второй план.
И вдобавок ко всему есть шероховатости в самой документации. Некоторые аспекты приводятся непоследовательно: одна часть может быть описана в одном разделе, а другая часть — в другом; к тому же содержательно веб- и PDF-версии далеко не полностью «бьются» друг с другом, да и перевод с английского тоже стабильно запаздывает.
Поскольку проблема определена, можно попробовать с ней что-то сделать. Делать это предлагаю по пунктам, двигаясь от простого к сложному.
1. Доступные диаграммы
Начнём с того, что PlantUML официально поддерживает довольно широкий список видов диаграмм. С документацией можно ознакомиться на официальном сайте онлайн (на русском: https://plantuml.com/ru/, на английском: https://plantuml.com/en/), а также скачав себе PDF-версию документации https://plantuml.com/ru/guide. При прочих равных я советую обращаться к онлайн-версии, т.к. там содержится более полная и наиболее актуальная информация.
Также, как отмечал выше, в силу запаздывания перевода материалов на русский язык в ряде случаев англоязычная онлайн-версия оказывается более содержательной.
2. Управление линиями и стрелками
В диаграммах PlantUML линии и стрелки зачастую используют один и тот же синтаксис. Что означает, что различие между ними состоит лишь в наличии или отсутствии острия на конце. В остальном подход к управлению довольно универсальный, если не считать особенностей диаграмм последовательности. Эта разница и будет определять дальнейшее изложение.
В п. 2.1 будут рассмотрены вопросы, относящиеся непосредственно к диаграммам последовательности, а далее по тексту, если явно не указано иное, будут разбираться подходы к другим видам диаграмм.
2.1. Стрелки на диаграммах последовательности
Для диаграмм последовательности в PlantUML предусмотрено 2 основных вида стрелок (см. документацию):
- сообщение запроса/вызова:
-> - ответное сообщение:
-->
Продолжая традицию, положенную в документации по PlantUML, рассмотрим диаграмму с участием Элис и Боба.
@startuml Alice -> Bob: Запрос Bob --> Alice: Ответ @enduml

Этих стрелок оказывается достаточно для того, чтобы в большинстве случаев представить последовательность взаимодействий. Однако такое представление является явным упрощением и, вообще говоря, не соответствует спецификации языка UML.
Суть вот в чём. Во-первых, остриё стрелки для запроса должно быть заполненным (в виде треугольника). Во-вторых, остриё стрелки для ответного сообщения должно быть открытым и иметь тонкое начертание. И, в-третьих, отсутствует различимость с асинхронными запросами.
Первая проблема решается с помощью добавления в код инструкции skinparam style strictuml, а вторая и третья — модификацией способа задания стрелки. Вкупе получается следующее:
- синхронное сообщение:
-> - асинхронное сообщение:
->> - ответное сообщение:
-->>
Следующий пример иллюстрирует наиболее типичные способы представления сообщений, отвечающие всем требованиям UML.
@startuml skinparam style strictuml Alice -> Bob: Синхронное сообщение Bob -->> Alice: Ответное сообщение Alice ->> Bob: Асинхронное сообщение Alice -> Alice: Рефлексивное сообщение @enduml

Кому-то может показаться, что практической пользы от следования требованиям UML нет, но это не так. Следование общим соглашениям снимает барьеры в коммуникации. Также различия стрелок для синхронных и асинхронных взаимодействий становятся более заметными, а значит ни вы, ни потребители вашей документации не упустят эту важную деталь.
Напоследок необходимо проговорить ещё два момента.
- На самом деле, стандарт UML поддерживает ещё несколько видов стрелок и специфических условий их использования, но для решения подавляющего числа задач они не требуются.
- На некоторых диаграммах последовательности можно встретить стрелки с верхней половиной острия открытого типа. Так было принято изображать асинхронные сигналы в стародавнем стандарте UML 1.3 и более ранних версиях. Указание
-\\в PlantUML позволит добиться такого же начертания, но начиная с UML 1.4 (2000 год) такой подход считается устаревшим.
Следующие подразделы будут касаться иных типов диаграмм, если не указано иное.
2.2. Направление стрелок
Возможность задать направление стрелки определяется с помощью синтаксиса подсказок:
- вправо:
-r->,-right->или просто-> - вниз:
-d->,-down->или просто--> - влево:
-l->,-left-> - вверх:
-u->,-up->
Также существует ряд не самых широко известных модификаций.
Так, чем больше знаков дефиса в стрелке, тем она длиннее (т.е. вместо --> можно поставить --->, ---->, ------> и т.д.). Кроме того, вместо символов дефиса можно использовать точки (пример: ..>); в этом случае линия стрелки будет прерывистой.
Здесь сразу стоит сделать несколько замечаний.
- У любой стрелки 2 конца, поэтому остриё может быть направлено и в другую сторону (пример:
<--) и в обе стороны сразу (пример:<-->). Если же требуется получить простую линию (без острия стрелки), то для этого достаточно оставить только символы дефиса (пример:--). - Удлинение стрелки работает только вверх и вниз. Вправо и влево эта возможность по умолчанию не работает. Как говорится, это не баг, а фича.
- Направление стрелки прекрасно комбинируется со всеми доступными стилями самой стрелки (цветом, начертанием и длиной линии, формой острия). Соответственно, если нужна удлинённая сплошная стрелка с незакрашенным остриём, указывающая вверх, то получаем такое:
--up-|>; если нужна ещё более удлинённая пунктирная стрелка с незакрашенным отстриём синего цвета, указывающая вверх, то получаем такое:...[#blue]up.|>; а если нужна ещё-ещё более удлинённая пунктирная стрелка с незакрашенным остриём цвета фуксии, указывающая вверх, но ещё и имеющая с противоположной стороны остриё в форме закрашенного ромба, то получаем такое:*....[#fuchsia]up.|>. При желании можно использовать развёрнутый синтаксис, и, написав, к примеру, так-[#green,dotted,thickness=8]->, получим зелёную стрелку с широкой линией в форме точек, указывающую остриём вниз (с полным списком стилей можно ознакомиться в документации, например здесь). - PlantUML позволяет сделать стрелку/линию невидимой. Для этого нужно добавить к линии слово «hidden» в квадратных скобках. Несколько примеров:
-[hidden],-[hidden]-,-[hidden]>,-up[hidden]->. О том, зачем это может понадобиться, будет рассказано позднее, в 4 разделе. Главное сейчас — понять принцип. - Если вы указали инструкцию
left to right direction, рассматриваемую более детально в п. 4.1, то происходит следующее: смысл подсказок «верх» и «лево» меняется местами, «низ» и «право», в свою очередь, также меняются местами. Соответственно, все приведённые выше рассуждения надо как бы перевернуть (к примеру,-->станет указывать вправо, а значит удлинение стрелок станет возможным вправо и влево, а не вверх и вниз). Начинающему пользователю PlantUML в такой ситуации легко запутаться (см. пример ниже), но в 4 разделе будет дано тому объяснение. Пока же можно использовать следующую аналогию: ориентация листа формата A4 может быть книжной (короткая сторона в верхней части) или альбомной (длинная сторона в верхней части), и если лист повернуть на 90°, то ранее использованные направления на изображении изменяются.
@startuml left to right direction "Актор" -left-> (лево) "Актор" -right-> (право) "Актор" -up-> (верх) "Актор" -down-> (низ) @enduml

2.3. Типы линий
В большинстве видов диаграмм, поддерживаемых PlantUML, все линии по умолчанию изображаются в виде дуг разной степени изогнутости. И если для каких-то диаграмм это выглядит уместно (например, для небольших диаграмм вариантов использования), то в других случаях это может вызывать недоумение. Но и это можно поправить.
Для того, чтобы линии получали строго вертикальное и/или горизонтальное начертание, можно использовать инструкцию skinparam linetype ortho. В результате получатся так называемые ортогональные линии.
А если требуется, чтобы линии были прямые (без дуговых изгибов), хотя и не обязательно только горизонтальные и вертикальные (в том числе ломаные), можно задать skinparam linetype polyline.
В качестве иллюстрации можно посмотреть, как отображается следующий код:
@startuml
skin rose
skinparam linetype ortho
hide circle
class Client
abstract AbstractFactory {
+ CreateProductA()
+ CreateProductB()
}
Client --> AbstractFactory
Class ConcreteFactory1 #AntiqueWhite {
+ CreateProductA()
+ CreateProductB()
}
Class ConcreteFactory2 #PaleGreen {
+ CreateProductA()
+ CreateProductB()
}
AbstractFactory <|-- ConcreteFactory1
AbstractFactory <|-- ConcreteFactory2
abstract AbstractProductA
Client --> AbstractProductA
abstract AbstractProductB
Client --> AbstractProductB
Class ProductA1 #AntiqueWhite
Class ProductA2 #PaleGreen
AbstractProductA <|-- ProductA1
AbstractProductA <|-- ProductA2
Class ProductB1 #AntiqueWhite
Class ProductB2 #PaleGreen
AbstractProductB <|-- ProductB1
AbstractProductB <|-- ProductB2
ConcreteFactory1 ..> ProductA1
ConcreteFactory1 ..> ProductB1
ConcreteFactory2 ..> ProductA2
ConcreteFactory2 ..> ProductB2
@enduml

Этот код позволяет описать паттерн проектирования «банды четырёх» «Абстрактная фабрика» (Abstract factory). Получилось довольно наглядно и стилистически привычно, но если удалить инструкцию skinparam linetype ortho, то будет уже не столь красиво.
2.4. Группировка стрелок
Также существует инструкция, которая позволяет группировать стрелки, отражающие отношение наследования между классами.
Инструкция следующая: skinparam groupInheritance X, где Х — минимальное количество стрелок, ведущих к одному и тому же элементу, по достижении которого выполняется группировка. Так, если указано 3, то при наличии только двух стрелок, ведущих к одному элементу (другими словами, у родительского класса 2 класса-потомка), группировка выполняться не будет. А если задать 1, то группировки выполняться не будет ни при каких условиях (это умолчательное значение).
@startuml skin rose hide circle hide empty members skinparam groupInheritance 2 Base <|-- Derived1 Base <|-- Derived2 Base <|-- Derived3 @enduml

Надо отметить, что рассмотренная опция не очень хорошо сочетается со skinparam linetype. Возможно, это баг, поэтому в сложных ситуациях лучше поэкспериментировать.
3. Управление оформлением диаграмм
PlantUML предоставляет возможность управления стилями как отдельных объектов, так и диаграмм в целом.
3.1. Скины и темы
Если стандартная цветовая схема PlantUML не очень нравится, то можно воспользоваться одной из следующих возможностей:
- использовать инструкцию
skin rose, чтобы установить цветовую гамму в стиле Rational Rose (в примерах выше я её уже использовал); - установить одну из поддерживаемых тем с помощью инструкции
!theme X, где Х — название темы (примеры:!theme spacelabи!theme sketchy). С полным перечнем тем можно ознакомиться на странице; - устанавливать стили отдельных элементов через CSS-подобный синтаксис (подробнее здесь) или с помощью команды
skinparam(подробнее здесь и здесь); - также можно точечно задавать стили отдельным элементам, указывая после символа решётки название цвета или его шестнадцатиричный код в формате RGB или RGBA (примеры:
participant Bob #blueиparticipant Rob #0000FF). В документации приводится достаточно примеров подобной техники.
Вот пара незатейливых примеров.
@startuml !theme sketchy hide circle hide empty members Base <|-- Derived1 Base <|-- Derived2 Base <|-- Derived3 @enduml

@startuml
skinparam handwritten true
skinparam actor {
BorderColor black
FontName Courier
BackgroundColor<< Human >> Gold
}
skinparam usecase {
BackgroundColor DarkSeaGreen
BorderColor DarkSlateGray
BackgroundColor<< Main >> YellowGreen
BorderColor<< Main >> YellowGreen
ArrowColor Olive
}
User << Human >>
:Main Database: as MySql << Application >>
(Start) << One Shot >>
(Use the application) as (Use) << Main >>
User -> (Start)
User --> (Use)
MySql --> (Use)
@end

Напоследок отмечу, что все указанные способы управления цветом можно сочетать. Далеко не всегда получится что-то удачное, но поэкспериментировать вполне можно.
3.2. Работа с цветом
Продуманное использование цвета в диаграммах способно решать многие задачи без введения дополнительных символов или иного усложнения диаграмм (подробнее об этой идее можно прочитать в моей статье).
Но даже если вы планируете использовать цвета просто для эстетического удовольствия, всё равно рано или поздно может возникнуть вопрос их выбора и сочетания. И в этом случае можно пойти разными путями: можно подбирать цвета самостоятельно, варьируя состав красного, зелёного и синего компонентов, либо можно воспользоваться имеющейся палитрой. В последнем случае доступно 2 варианта:
- если воспользоваться командой
colorsбез параметров, то PlantUML выдаст полную палитру с названиями цветов; - если команду
colorsпараметризовать названием цвета или его шестнадцатиричным кодом, то PlantUML выдаст в форме пчелиных сот палитру из 18 именованных цветов, наиболее близких к указанному вами.
Ниже проиллюстрирован последний упомянутый способ: вызов команды colors для поиска цветов, наиболее близких к цвету #DDFF00, и полученный результат.
@startuml colors #DDFF00 @enduml

Более подробно про работу с цветом в PlantUML можно прочитать в документации.
Но это ещё не всё. PlantUML предоставляет ряд встроенных функций, которые могут оказаться полезными для расстановки акцентов в уже расцвеченной диаграмме. Функции следующие.
-
%lighten(color, ratio)— возвращает более светлый оттенок заданного цвета. Степень осветления определяется значением 2-го параметра, который принимает значение от 0 (ничего не меняется) до 100 (максимально высветлен, т.е. получается белый цвет). -
%darken(color, ratio)— возвращает более тёмный оттенок заданного цвета. Степень затемнения определяется значением 2-го параметра, который принимает значение от 0 (ничего не меняется) до 100 (максимально затемнён, т.е. получается чёрный цвет). -
%reverse_color(color)— возвращает цвет, обратный заданному. Обращение цвета выполняется по каждому из компонентов, поэтому обратным цветом для #EE7733 будет #1188CC (в шестнадцатеричной системе: FF-EE = 11, FF-77 = 88, FF-33 = CC).
Ниже представлен пример использования всех названных функций для изменения цвета стрелок в диаграмме состояний. Изменяемый цвет задавался HEX-кодом, хотя можно было использовать и имя.
@startuml
skinparam state {
backgroundColor lemonChiffon
arrowColor #FF7700
}
state "Состояние 1" as st1
state comp as "Составное состояние" {
state check <<choice>>
state "Состояние 2" as st2
state "Состояние 3" as st3
[*] --> check
check -[%lighten("#FF7700", 50)]-> st2 : [Успешно]
check -[%darken("#FF7700", 50)]-> st3 : [Ошибка]
st2 --> [*]
st3 --> [*]
}
[*] -> st1
st1 -> comp
comp -[%reverse_color("#FF7700")]> [*]
@enduml

3.3. Стандартные иконки
PlantUML поддерживает набор иконок, которыми порой можно удачно украсить свои диаграммы. Но, поскольку описание этих иконок размещено в главе документации, посвящённой разработке каркасов пользовательского интерфейса (wireframe) Salt (ссылка), то может возникнуть иллюзия, что их использование ограничивается каркасами. Но это не так.
В п. 7.1 будет проиллюстрировано использование иконки с ключом, а в п. 7.2 будут активно использоваться иконки стрелок различных направлений.
С перечнем доступных иконок также можно ознакомиться, если вызвать специальную команду.
@startuml listopeniconic @enduml