本文介绍一个适合于软件工程师所使用的绘图工具:PlantUML。
前言
软件工程师常常需要绘制UML图来描述软件结构。市面上有非常多的UML工具可以选用。并且其中有一些价格不菲。
这里给大家介绍一个好用的免费工具:PlantUML。
PlantUML介绍
PlantUML是一个开源项目,其官网地址是:https://plantuml.com/。软件行业有很多工具都和它有关系,你可以在这里看到一个很长的列表:There are (too) many way of using PlantUML。这其中包括大名鼎鼎的:Eclipse,Microsoft Word,Google Docs,VS Code等。
PlantUML发表于2009年,是Java语言实现。其源码在github上:plantuml。
虽然PlantUML本身是GPL License,但是它生成的图像不受GPL/LGPL/ASL/EPL/MIT许可保护:由PlantUML的“执行”生成的图像(无论何种格式)均归其作者所有。
为什么是PlantUML?
之所以选择推荐这个工具,是因为我觉得它有如下好处:
- 免费:这个应该不需要解释了。
- 跨平台:我始终觉得,软件从业者选择的每一个工具都应该考虑其跨平台的能力。因为在你的职业生涯中,你很可能会在不同时期使用不同的操作系统。如果你所使用的所有工具都是特定操作系统的,当你换一个新的环境时,你可能就非常痛苦了(例如:从Mac环境切换到Windows)。
- 文本形式编辑:这个好处下面展开说。
- 支持多种导出格式:例如,你可以导出图片在文档中使用,也可以导出ASCII码格式在代码中使用,下文会讲到。
- 支持多种图形:既然是绘图软件,那当然是支持的图形类型越多越好。
使用文本的形式而不是图形拖拽的形式来编辑有什么好处?我觉得包括以下这些:
- 随时可以编辑:你通常在自己电脑上编辑文件,但你很可能会将文件传输到公司或团队的其他电脑上。如果是纯文本格式的,无论这个文件传到哪台电脑上,只要有文本编辑器,你就可以继续修改它,而不用安装几百M甚至几个G的特定软件才能编辑它(想想Office格式的文件)。
- 方便版本管理:二进制格式的文件放到git之类的版本管理工具中之后,最多只能查看版本历史,但没法针对每个版本进行diff。文本类型就不一样的,你可以diff,可以merge(方便多人协同)。
- 编辑更精准:如果你编辑过包含了几百个方框的架构图你就能体会到我所说的了:在那种情况下,每移动一个小方框,你都要小心谨慎。
- 内容与样式解耦:下文中我们将看到,在编辑图的时候,我们只需要考虑内容,不需要考虑样式。样式可以通过外部的主题文件单独指定。这样做的好处是:你可以编辑好一份图之后,不用对其做任何修改就可以指定不同的配色。
使用方式
你至少可以通过下面三种方式使用PlantUML:
- 直接访问online server:PlantUML Web Server
- 通过jar包的形式使用,PlantUML的jar包可以到这里下载:PlantUML compiled Jar。
- 在自己的机器上部署Web服务端(这种方式本文不涉及)。
这其中,第2,3种方式需要你额外安装两个依赖软件:
- 【必须】Java : 是运行PlantUML的必需条件。不过绝大部分软件开发者都应该已经有这个环境了。
- 【可选】graphviz-dot,如果想绘制除 时序图和活动图以外的图, 就需要安装Graphviz软件。请点击链接根据你所使用的操作系统选择安装方式。在Mac上,通过
brew install graphviz
即可完成安装。
第2种方式,使用jar包的好处是简单,你甚至可以在脚本文件中集成,这样可以批量生成最终图片。但坏处是性能较差,每次运行jar包都要创建一个新的进程,然后使用完再退出。
而第3种部署Web服务端的方式更合适团队内部多人使用。
使用PlantUML画UML图
接下来我们终于可以真正开始画图了。
PlantUML支持的UML图包括:
- 时序图
- 用例图
- 类图
- 对象图
- 活动图
- 组件图
- 部署图
- 状态图
- 定时图
类图
首先,以我们最常见的类图为例。
这个类图描述的内容是设计模式中 模板方法模式(Template Method)。原图可以在设计模式:可复用面向对象软件的基础(典藏版)中找到。
打开任何你喜爱的文本编辑器,然后输入下面这段内容即可:
@startuml
class Document {
+Save()
+Open()
+Close()
{abstract} +DoRead()
}
class MyDocument {
+DoRead()
}
class Apllication {
+AddDocument()
+OpenDocument()
{abstract} +DoCreateDocument()
{abstract} +CanOpenDocument()
{abstract} +AbountToOpenDocument()
}
class MyApplication {
+DoCreateDocument()
+CanOpenDocument()
+AbountToOpenDocument()
}
Document <|-- MyDocument
Apllication <|-- MyApplication
MyDocument <.. MyApplication
Document <--o "docs" Apllication
@enduml
对于聪明如你来说,这段内容应当很容易理解。@startuml
和@enduml
是图形开始和结束的标记。图形的正文语法也非常明确,前面大部分是类的定义。从这个定义中你可以看到,它与我们的代码语法是如此的接近。稍微需要说明一点的是几个特殊标记,例如:如何描述可见性,如何描述元素的特殊类型。
PlantUML的字符标记与可见性的关系如下表所示:
字符标记 | 可见性 |
---|---|
- | private |
+ | public |
~ | package |
# | protected |
另外,元素的特殊类型通过一些关键字描述,如下:
@startuml
abstract abstract
abstract class "abstract class"
annotation annotation
circle circle
() circle_short_form
class class
diamond diamond
<> diamond_short_form
entity entity
enum enum
interface interface
@enduml
定义完类型之后,下面几行是对类与类之间关系的描述:
Document <|-- MyDocument
Apllication <|-- MyApplication
MyDocument <.. MyApplication
Document <--o "docs" Apllication
描述关系的符号也很直观,*
代表实心菱形,o
代表空心菱形,:
符号 | 含义 |
---|---|
<|– | 扩展 |
*– | 组合 |
o– | 聚合 |
关系还可以通过双引号文字描述,例如:
@startuml
Class01 "1" *-- "many" Class02 : contains
@enduml
将前面的代码示例保存成文件class_diagram.uml
,然后通过执行下面这条命令
path_to/java -jar path_to/plantuml.jar path_to/class_diagram.uml
便可以得到下面这个图:
你可以对比一下这幅图和前面的uml文件,以理解每行代码的含义。
更多类图的语法请参见这里:PlantUML:类图。
时序图
下面我们再看一个示例。仍然是软件开发中很常见的,这次是时序图。这里我们仅用知道三个简单的语法,就可以绘制出时序图。
- 首先是
A -> B :Event
。这里的A和B是调用的发起者和被调用者。箭头表示调用的方向,如果是向左的箭头则表示回复。冒号后面是调用的名称。如果不强调调用者,那么A可以省略。 - 第二个是
B <-- A : Response
。类似的,这里表示A向B回复。两个--
表示这条线是虚线,即:异步回复。当然了,Response是回复的方法。 - 最后是
activate
和deactivate
描述事件开始和结束的范围,后面跟着相应的角色。
有了上面的基础我们就可以理解下面代码了,这个时序图描述了用户收发邮件的过程。
@startuml
-> Computer : checkEmail
Computer -> Server : sendUnsentEmail
activate Server
deactivate Server
Computer -> Server : newEmail
activate Server
Computer <-- Server : response
deactivate Server
Computer -> Server : downLoadEmail
activate Server
deactivate Server
Computer -> Server : deleteOldEmail
activate Server
deactivate Server
@enduml
整个过程分成5个事件,所以有5个小段。有了上面的语法介绍,我想这段代码已经不用多做说明了。如果你还有不理解的地方,直接对比下图,应该就容易明白了。
更多时序图的语法请参见这里:PlantUML:时序图。
非UML图
除了UML以外,PlantUML还支持以下类型的图:
- JSON data
- YAML data
- Network diagram (nwdiag)
- 线框图形界面
- 架构图
- 规范和描述语言 (SDL)
- Ditaa diagram
- 甘特图
- 思维导图
- Work Breakdown Structure diagram
- 以 AsciiMath 或 JLaTeXMath 符号的数学公式
- Entity Relationship diagram
由于篇幅所限,这里不会介绍所有图形的语法。思维导图是我们很常用的一种描述图形,所以这里仅以它为例进行一些介绍。
思维导图
思维导图以@startmindmap
和@endmindmap
作为开始和结束的标记。嵌套关系以*
的数量为层级。
下面的示例描述了2021年的24节气:
@startmindmap
* 2021年24节气
** 一月
*** 1月6日 小寒
*** 1月20日 大寒
** 二月
*** 2月3日 立春
*** 2月18日 雨水
** 三月
*** 3月5日 惊蛰
*** 3月30日 春分
** 四月
*** 4月4日 清明
*** 4月20日 谷雨
** 五月
*** 5月5日 立夏
*** 5月21日 小满
** 六月
*** 6月5日 芒种
*** 6月21日 夏至
** 七月
*** 7月7日 小暑
*** 7月22日 大暑
** 八月
*** 8月7日 立秋
*** 8月23日 处暑
** 九月
*** 9月7日 白露
*** 9月23日 秋分
** 十月
*** 10月8日 寒露
*** 10月23日 霜降
** 十一月
*** 11月7日 立冬
*** 11月22日 小雪
** 十二月
*** 12月7日 大雪
*** 12月21日 冬至
@endmindmap
最后我们得到的图形如下所示:
更多语法
本文当然不打算把官方用户指南照抄一遍,所以上文仅仅是给出几个最简单的示例让读者看到PlantUML的基本使用方法。更多的内容请阅读官方的文档手册,其中有详细的各种图的编写语法。
如果觉得读官方手册太枯燥的话,也可以通过一些实际的示例来进行学习:Real World PlantUML。这个网站包含了很多具体的示例以及对应的结果展示,找到和你类似的场景,然后拷贝源码进行修改即可。
样式定制
PlantUML的默认样式未必符合你的胃口。好消息是它提供了非常多的参数来定制化外观。具体可以看这里:All Skin Parameters。
但对于软件工程师来说,自己配一整套样式可能并非我们所擅长。这个时候你自然会想到,有没有别人已经配置好的主题我可以直接使用。答案是肯定的:puml-themes。点击链接可以看到这个几个主题的示例。在github上下载源文件之后通过-I
参数指定主题文件便可以使用(注:这个参数需要指定主题文件的完全路径,不支持~
)。例如:
java -jar plantuml.jar -I/Users/paul/puml-themes/themes/bluegray/puml-theme-bluegray.puml sequence.uml
我们便得到了下面这样的图:
对于样式,应当能找到很多别人配置好的主题(例如:Sublime就有很多界面和代码高亮的不同配色)。但可惜的是,我没能在网上找到更多好的选择(还有些主题只支持部分类型的图)。如果你知道,请告诉我。
为了使自己不陷入选择困难症,所以通常我会在UML图中加上这样一句,以使用纯黑白的配色:
skinparam monochrome true
这种配色虽然不如上面的靓丽,但看起来更正式,适用的场景会更广泛些。
另外,PlantUML默认生成的图片分辨率太低,稍微放大之后就会变得模糊。所以通常我会在文件开头添加一行scale 2
表示生成图像需要放大两倍。为了获得高分辨率,本文贴出的图片都是放大了的。
与Sublime集成
前面说了,PlantUML可以和很多软件集成在一起。但是为了方便很多像我一样使用Sublime的开发者,这里专门提一下如何将PlantUML与Sublime集成在一起。
之所以选择Sublime作为我的主力编辑器,自然也是因为它的跨平台特性。除此之外我觉得它还有其他几个优势:
- 启动速度极快,不用等待慢腾腾的进度条(对比一下VS Code,Eclipse,Android Studio,Atom就知道了)
- 插件丰富,这个很主观,但至少我需要的插件都能找到
- 代码扫描机制自动且效率高(不用像Scitool Understand那样每次代码变动都要全盘扫描所有代码),鼠标悬停就可以跳转到函数定义和调用处
- 定制功能强大,例如,很简单就可以与PlantUML集成
- 没有太多多余的,我不需要的东西
Sublime有一个专门插件支持PlantUML:PlantUmlDiagrams。但我个人并不太喜欢这个插件。原因是它的配置方式过于死板,只支持配置PlantUML的jar包路径或者服务端地址。这样做的后果是:如果你想传递不同的参数给plantuml.jar
(例如:通过-I
使用定制的主题)那就没法做到了。
除了这个插件,还有一种更简洁的与Sublime集成的方式:Sublime支持自定义编译方式,你可以添加一个新的Build System来运行plantuml.jar
生成UML图。在Sublime中操作步骤如下:
Tools -> Build System -> New Build System...
然后输入:
{
"shell_cmd": "/usr/bin/java -jar /Users/paul/Developer/plantuml.jar \"$file\""
}
这段配置应该很容易理解,就是运行指定路径的plantuml.jar
。java
命令和jar包路径请根据你机器上的实际路径修改。$file
变量代表了当前编辑的文件。这样配置好之后,可以为其命名为“PlantUML”保存。
在这之后,直接通过Command + B
(Linux和Windows上是Ctrl + B
)命令便可以调用我们自定义的编译命令了,你会在修改文件的同目录下找到生成的图片文件。
导出
除了PNG图片格式之外,PlantUML还支持很多其他类型的格式导出。其参数和格式如下:
-tpng To generate images using PNG format (default)
-tsvg To generate images using SVG format
-teps To generate images using EPS format
-tpdf To generate images using PDF format
-tvdx To generate images using VDX format
-txmi To generate XMI file for class diagram
-tscxml To generate SCXML file for state diagram
-thtml To generate HTML file for class diagram
-ttxt To generate images with ASCII art
-tutxt To generate images with ASCII art using Unicode characters
-tlatex To generate images using LaTeX/Tikz format
-tlatex:nopreamble To generate images using LaTeX/Tikz format without preamble
这对于要编码的软件开发者来说就太方便了,因为我们可以直接将画的UML图通过ASCII的形式导出,然后贴到代码中。例如,通过java -jar ~/Developer/plantuml.jar -ttxt sequence.uml
命令将得到下面的结果(删除了多余的部分):
,--------. ,------.
|Computer| |Server|
`---+----' `--+---'
checkEmail| |
----------> |
| |
| sendUnsentEmail ,-.
| --------------->|X|
| `-'
| |
| |
| |
| newEmail ,-.
| --------------->|X|
| `-'
| response |
| <- - - - - - - - -
| |
| downLoadEmail ,-.
| --------------->|X|
| `-'
| |
| |
| |
| deleteoldEmail ,-.
| --------------->|X|
| `-'
| |
| |
,---+----. ,--+---.
|Computer| |Server|
`--------' `------'
通过git管理UML图,通过ASCII码的形式描述复杂算法的时序。做到这些之后,是不是又为软件开发流程提升了一些些效率。所谓高效人士,只不过是平时一点点的积累了这些好工具,好习惯罢了。
古人云:
不积跬步,无以至千里;
不积小流,无以成江海。