十年匠心定制 · 商业建站与技术教学双线并行 咨询热线:400-886-1026 service@lmnt.cn
ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

PlantUML 实战指南:用例图、类图、活动图与时序图一次搞定

PlantUML 实战指南:用例图、类图、活动图与时序图一次搞定 做技术文档最折磨人的不是写代码而是画图。方案评审要用例图详细设计要类图流程梳理要活动图联调排障要时序图。我见过太多团队用 Visio 画完第一版就再也没更新过也见过不少同事把 StarUML 里的箭头拖来拖去拖到崩溃。后来我彻底切换到 PlantUML所有图都用纯文本描述代码进 Git改需求就改几行文字再重新渲染一次图永远和代码同步。这篇文章就把我平时用得最多的四类图形完整过一遍用例图、类图、活动图、时序图从语法拆解到实战示例最后附上常见问题速查表希望能帮你少踩些坑。PlantUML 适合谁只要你的工作里需要画软件设计图的都合适。准备软考软件设计师的人可以用它刷用例图、类图的真题场景做嵌入式开发的人可以用它画 I2C、SPI 通信时序图写后端服务的人可以用它描述订单、会员这类业务模型甚至做产品需求分析的人也能用它把用户角色和功能边界画得清清楚楚。它是一个免费开源工具语法简单渲染方式灵活本地装插件、命令行跑 jar、网页版都能用。1. 为什么文档里的图总是过期以及我为什么选择 PlantUML先说一个我自己的观察大部分项目的设计图不是一开始画错了而是画完之后没人维护。需求一变代码改了图还在原地待着。原因很简单——用鼠标拖出来的图维护成本太高了。改一个类名你得找到那个矩形框右键编辑重新对齐连线换一个角色名整张图的布局可能都要调整。时间一长图自然就废了。PlantUML 解决的是图即代码的问题。它有一套接近自然语言的 DSL你用纯文本描述参与者和关系工具负责自动排版。比如我要画一个最简单的类只需要写class User {}剩下的事情交给渲染引擎。这个思路和写代码一样文本永远是最容易维护的diff 看得见注释写得清版本管理也顺手。代码评审的时候直接看 diff 就知道哪条关系线被改了这在团队协作里太重要了。1.1 PlantUML 的核心优势和适用边界很多人问我说UML 工具有那么多StarUML、Visio、Draw.io 都免费或常用为什么偏要费劲学一套 DSL我的回答是工具的选择取决于使用场景。如果你的需求是“一次性画一张精美的架构图拿去汇报”那 PlantUML 确实不是最优解它的默认样式偏朴素排版自由度也受限。但如果你要的是“设计文档里的一套图会随着版本迭代持续更新”PlantUML 的文本化优势就完全体现出来了。它可以把图直接嵌入 Markdown、Confluence、GitLab改几个字母就能重建整张图还能用脚本批量校验语法错误。这几年我用它画过的图少说也有几百张除了少数特别复杂的架构图我用 Draw.io 补充外其余全部是 PlantUML 搞定。我再给个对比感受一下能力PlantUMLStarUMLVisio / Draw.io学习成本低30 分钟上手中需要熟悉建模概念低靠拖拽版本管理天然支持文本可 diff差二进制文件差文件难以对比维护成本低改文字即可中拖拽改模型高布局重新调整自动化生成支持可嵌入 CI弱弱复杂布局一般依赖自动排版强手动微调强自由绘制用一句话来概括如果你想长期维护一套跟代码同步的图PlantUML 是性价比最高的选择。如果你只是为了应付一次性的汇报图其他工具更顺手。1.2 环境准备三种常用的跑图方式PlantUML 本身是一个 Java 程序运行方式非常灵活我常用的有三种第一种是 VS Code 装插件。在扩展商店搜 PlantUML装好后写代码按AltD就能预览修改实时刷新日常画图效率最高。要导出 PNG 或 SVG右键选导出即可。第二种是命令行运行适合批量处理和 CI 集成。官方 jar 包下载后可以用类似java -jar plantuml.jar -tsvg diagram.puml的命令直接渲染。第三种是官网的在线服务临时画一张图不求人直接把代码贴进网页就能出图但源码如果不脱敏就别往上贴。我日常的主力方式是 VS Code 加插件配合 PlantUML 的语法高亮和实时预览写起来很像在写代码。命令行方式我也会定期用尤其是做文档自动化的时候——把仓库里所有.puml文件批量导出出图结果直接发布到内部文档站。这里提醒一句PlantUML 依赖 Graphviz 做布局计算Windows 上如果出图报错多半是 Graphviz 没装或者没加入 PATH这是最常见的环境问题。2. 用例图把需求边界画清楚用例图是 UML 里业务味道最重的图它不需要描述类和方法的细节只需要表达“谁能用系统做什么”。我一般只在两个阶段用它项目启动期做需求分析和软考备考期做案例分析题。这里的“谁”叫参与者Actor“能做什么”叫用例Use Case两者之间用线条连接就可以快速确认一个系统的边界。很多人觉得用例图简单无非就是画个火柴人加椭圆。但真到动手画的时候最容易犯的错是把用例拆得太细或者把系统内部的步骤画到用例图上。记住一个判断标准用例必须给参与者带来可观察的结果。举一个图书管理系统的例子如果画一个“读者借书”用例那“验证读者身份”就不是一个独立的用例它是借书流程中的内部步骤应该用include关系挂在主用例上面。2.1 用例图基础语法与图书管理系统示例先看我画图书管理系统时最常写的模板这段代码包含了参与者、用例、系统边界框和关系线startuml left to right direction skinparam packageStyle rectangle actor 读者 as reader actor 管理员 as admin rectangle 图书管理系统 { reader -- (查询图书) reader -- (借阅图书) reader -- (归还图书) (查询图书) . (登录) : include (借阅图书) . (登录) : include (借阅图书) . (检查库存) : include (归还图书) . (处理逾期) : extend admin -- (管理图书) admin -- (管理读者) (管理图书) -- (添加图书) (管理图书) -- (删除图书) } enduml这段代码渲染出来的效果是读者在系统边界框左侧右侧是查询、借阅、归还三个用例管理员在下方对应管理图书和管理读者两个用例。关系线箭头分别表达依赖、包含、扩展。实际绘图时left to right direction能把参与者放到左边让整张图更紧凑。需要特别留意的是include和extend的区别。include表示基础用例一定包含被包含用例的执行比如借阅图书一定包含登录所以箭头从“借阅图书”指向“登录”虚线箭头写上 include。extend表示基础用例在某些条件下才触发扩展用例比如归还图书时如果需要缴纳逾期费才会执行“处理逾期”这个扩展用例所以箭头方向是从“处理逾期”指向“归还图书”表示扩展点在扣款发生时才介入。方向反了图的意思就完全变了。2.2 用例之间的关系与软考真题场景软考软件设计师里用例图的考察频率很高而且最喜欢问的就是 include、extend、泛化这三种关系的判定。泛化关系用带空心三角的实线表示通常用在参与者继承或者用例继承上。比如系统里可以有“普通读者”和“VIP 读者”两者都有借书的权限但 VIP 有额外额度这时候就可以让“VIP 读者”泛化“普通读者”。有一种快速判定关系的方法我一直用如果基础用例每次执行都必须执行另一个用例那就是 include如果基础用例执行到某个分支时才可能执行另一个用例那就是 extend如果两个用例有共同的行为但一个是另一个的特殊化那就是泛化。把这个标准套到软考真题里基本不会选错。我刷题时发现很多同学在用例图上纠结的不是关系而是“这个功能该不该画成用例”。比如图书管理系统的“库存管理”如果你只画了一个“管理库存”椭圆它其实不是一个好用例因为没有参与者会直接对着一个抽象的管理系统喊“帮我管理库存”。更合理的画法是拆成“添加图书”“下架图书”“盘点库存”这样的具体操作它们才对参与者有明确价值。3. 类图设计评审阶段最常用的关系图如果说用例图是给业务方看的那类图就是给程序员自己看的。类图描述系统的静态结构包括类、接口、属性、方法以及它们之间的关联、聚合、组合、依赖、继承、实现关系。每次做技术方案评审我几乎都是先甩一张类图出来大家对着图讨论字段归属和调用关系比对着几百行代码高效得多。类图画得好不好关键在两点一是类的职责划分是否清晰二是关系箭头是否用对。箭头用错非常容易被资深评审专家一眼抓出来因为它在语义上直接反映你的设计意图。下面我把六种常见关系全部用 PlantUML 写一遍配上实际业务场景保证你下次画法不会再混淆。3.1 类、接口、属性的 PlantUML 表达先看一个订单域的类图示例startuml class Customer { -id: Long -name: String -email: String getOrders(): ListOrder addOrder(order: Order): void } class Order { -orderId: String -createTime: Date -status: String calcTotal(): BigDecimal } class OrderItem { -productName: String -quantity: int -price: BigDecimal getSubtotal(): BigDecimal } class Payment { -method: String -amount: BigDecimal pay(): boolean } interface Discountable { getDiscountRate(): double } Customer 1 -- * Order : creates Order 1 *-- * OrderItem : contains Order -- 1 Payment : has Order .. Discountable : implements enduml这个例子涵盖了类、接口、属性和方法。属性前的符号和代码里的可见性对应-是 private是 public#是 protected~是包内可见。类型写法和 Java 语法一致冒号后跟类型方法写括号和返回类型。这些语法细节不复杂但对应到代码里就是实实在在的字段声明画的时候顺便能帮自己核对一遍设计是否合理。我说一个实用的习惯画类图的时候先只画出对外暴露的 public 方法private 方法一律不写。因为类图是给别人看设计意图的不是给 IDE 做代码索引的。你写十几个 private 方法进去读者根本分不清哪些是核心能力哪些只是内部辅助逻辑。想了解完整方法列表看代码仓库就行。3.2 类图箭头关联、聚合、组合、依赖、继承、实现这是整个类图里最容易出错的环节也是热词里“类图箭头”被搜爆的原因。我直接用 PlantUML 语法逐一组装关联A -- B实线普通箭头。比如 Customer 创建 Order两者各自独立存在互不控制生命周期。聚合A o-- B空心菱形加实线。比如部门和员工部门没了员工还能流转到其他部门这是弱拥有关系。组合A *-- B实心菱形加实线。比如 Order 和 OrderItem订单没了订单明细就没有存在的意义这是强拥有关系。依赖A .. B虚线箭头。比如 Order 需要调用 PaymentService只是方法级依赖不持有对方引用。继承A --| B空心三角加实线。子类继承父类。实现A ..| B空心三角加虚线。类实现接口。用 PlantUML 画的时候箭头方向一定要写成“从子指向父”“从依赖者指向被依赖者”。很多人画继承时箭头方向写反渲染出来的图语义完全错了。我自己的记忆方式很简单箭头的尖端指向被依赖的一方。继承时子类依赖父类尖端指向父类实现时实现类依赖接口尖端指向接口关联时订单依赖客户尖端指向 Customer。另外顺便提一句网上经常有人拿“如何用 StarUML 画类图”做教程核心思路和 PlantUML 是一样的只是操作方式不同。你如果已经理解了语义模型换任何工具都只是背按钮位置的事。但 StarUML 的类图文件很难做文本 diff所以团队协作我依然首选 PlantUML。IDE 自带的类图生成功能比如 IDEA 里右键 Diagram适合快速查看已有代码结构不适合一开始做设计因为它生成的是“代码现状”而不是“目标设计”。4. 活动图梳理流程和泳道活动图在 UML 里的定位是描述业务流程或算法逻辑比流程图更规范支持并发、分支、泳道这些高级表达。我通常在两种场景下使用一是分析业务流程比如审批流、订单状态流转二是描述用例图中的某个复杂用例的内部过程。软考面试也很喜欢考活动图里判断节点和并发叉的语义稍不注意就会在分支合并处丢分。很多人把活动图和时序图混在一起其实两者视角完全不同。活动图关注“做什么、按什么顺序做、哪些可以并行”不关心具体谁调用谁。时序图关注“谁发的消息、消息的先后顺序、返回结果”严格强调交互对象。一句话记忆活动图是流程视角时序图是通信视角。4.1 活动图基础元素和借阅流程示例下面是一个带泳道的图书借阅活动图泳道用|角色|来声明startuml |读者| start :查询图书; :选择图书; if (库存充足?) then (是) :提交借阅申请; else (否) :登记需求; stop endif |系统| :校验读者身份; :扣减库存; :生成借阅记录; |管理员| :审核申请; :办理借出; stop enduml这里的if (条件) then (分支名)是活动图里最常用的判断结构。注意每个判断分支都必须给标签比如“是”和“否”否则渲染出来线上没有说明文字读者要猜半天。start表示开始节点stop表示结束节点。泳道把不同角色负责的活动划分开在合作流程里非常直观。我见过不少新手写活动图从start到stop只有一条直线没有任何判断这种图本质上就是步骤列表价值不大。活动图的核心价值在于表达分支和并发所以设计的时候先问自己这条流程有没有条件分流有没有可以并行处理的任务有的话放心用判断节点和 Fork/Split 来表达。4.2 分支合并与并发分叉的进阶写法当流程出现并发时我用fork和split来拆两条平行路径startuml start :接收订单; fork :扣减库存; fork again :创建发货单; end fork :通知用户; stop endumlfork和fork again之间的节点会并行执行end fork表示汇合点。真实业务里“扣减库存”和“创建发货单”没有严格的先后关系可以并行用并发分支表达就非常合适。需要注意的一个细节是并行分支汇合后后续活动必须等待所有分支完成。如果业务上只需要其中一个分支完成就能继续那就要重新设计流程而不是硬用一个end fork收口。循环场景我也简单提一下。PlantUML 没有专门的循环关键字一般用判断节点自己指向自己来实现比如“检查库存不足则补货直到库存充足再继续”。写法是判断条件的“否”分支直接回到某个活动节点上面。这种图的渲染效果一开始可能有点不直观但读习惯了就会发现它跟标准流程图里的循环表达完全对应。用活动图做软考真题时还要特别注意合并多个判断的条件。比如“读者借阅上限为 5 本且无逾期未还”才能借书这其实是两个判断条件叠加。有些参考书把它画成一条判断线上写库存充足 无逾期我建议拆成两个判断节点分支标签分别写“是/否”这样语义更清晰阅卷也更容易理解你的思路。5. 时序图把交互过程落到实处时序图是日常开发中出场率最高的一种图。联调接口时后端同学发我一张时序图我立刻就能知道该在哪个环节返回数据、哪条消息需要阻塞等待、异常分支从哪里触发。底层协议分析也离不开它比如 I2C 和 SPI 的通信过程你要是用文字描述 START、STOP、ACK、数据位读者脑壳疼但画成时序图就一目了然。PlantUML 的时序图语法非常直观核心就三件事声明参与者、画消息箭头、标注返回。先用actor表示外部用户用participant或直接用类名表示系统内部组件然后用箭头表示调用关系用虚线表示返回结果。我强烈建议你在关键消息上加上注释因为时序图是给人做交互分析的信息足够完整才有价值。5.1 时序图基础语法和订单交互示例看一个用户下单的时序图startuml actor 用户 participant 订单服务 as orderService participant 库存服务 as stockService participant 支付服务 as payService 用户 - orderService : 提交订单 activate orderService orderService - stockService : 扣减库存 activate stockService stockService -- orderService : 返回扣减结果 deactivate stockService alt 扣减成功 orderService - payService : 发起支付 activate payService payService -- orderService : 支付结果 deactivate payService else 扣减失败 orderService -- 用户 : 提示库存不足 end orderService -- 用户 : 返回下单结果 deactivate orderService enduml这个例子里有几个容易忽略的点activate和deactivate表示生命线的激活和释放对应代码里的方法调用栈激活块的宽度能直观看出哪个服务处于占用状态alt和else表达条件分支渲染时会在消息区域画出分隔框非常清晰--是返回消息通常画成虚线。如果消息少、交互简单不写 activate 也能出图但交互一复杂激活块能帮你快速定位嵌套调用关系。PlantUML 还支持loop、opt、break这些组合片段。loop表示循环比如重试三次的场景opt表示可选分支比如“如果用户是会员则执行折扣计算”break表示中止场景。组合片段可以嵌套用法类似代码块语法也很简单只要注意每个片段必须有一个合法的结束标签就行。5.2 用 PlantUML 表达 I2C 和 SPI 通信时序做嵌入式开发时常有一类需求在串口屏、传感器、MCU 之间联调要画 I2C 或 SPI 的总线时序。传统做法是用 Wavedrom 画波形图但 Wavedrom 画的是信号级波形用来表达“谁在什么时刻拉高拉低”而 PlantUML 更适合表达协议层的交互流程比如主机发地址、从机回 ACK 这种消息级时序。我拿 I2C 的一个简单读写流程举例startuml actor MCU as master participant I2C Slave as slave master - slave : START (SDA low while SCL high) master - slave : Send Address R/W bit slave -- master : ACK master - slave : Send Register Address slave -- master : ACK master - slave : Read Data Byte slave -- master : Data ACK master - slave : STOP (SDA high while SCL high) enduml这里用参与者和消息文本把 I2C 的每个阶段按时间顺序排布非常适合汇报和调试。SPI 也是类似思路角色换成 Master 和 Slave消息换成 CS 拉低、发命令、发地址、读数据。有些同学喜欢在消息里描述电平变化比如SCL high、SDA low那就要注意表达粒度如果面向驱动开发建议信号级波形用 Wavedrom如果面向协议交互理解PlantUML 时序图反而更清爽。需要特别提醒的是PlantUML 里不要把所有内容都塞进一张巨长时序图图一旦超过一屏阅读体验会很差。我的做法是一个完整链路拆成多张小图比如“初始化时序”“单次读写时序”“异常处理时序”然后在文档里用目录组织。这样每张图信息集中也方便复用。6. 常见问题快查与几个我用着很顺手的小技巧写 PlantUML 时间长了总会遇到一些重复出现的坑。我整理了一个速查表专治各种渲染异常和语义误解。问题现象可能原因解决方案中文显示为方块字体配置缺失加!pragma layout smetana或设置skinparam defaultFontName Microsoft YaHei渲染提示语法错误多数是箭头数量不对检查-、--、..、--类图太长放不下布局方向不优加left to right direction或拆分多个包用package组织时序图消息序号乱没有开启自动编号加autonumber自动生成消息序号活动图分支标签缺失条件分支必须写标签if (库存充足?) then (是)里的(是)不能省略组合片段不闭合alt缺end检查每个组合片段是否有对应end导出图片过大尺寸参数不合理用scale 2放大或直接导出 SVG 避免模糊这个表格里的前两条我基本每周都会遇到一次。中文乱码的坑从 Windows 切换到 Mac 时尤其明显电脑上没有对应中文字体时HTML 预览正常但导出 PNG 就变成方块。解决方案就是在文件顶部加一行 font 设置或者统一用!include引入一个主题文件把字体信息集中配置。箭头数量的问题则更隐蔽-是消息箭头--是返回箭头..是依赖箭头这三者混用时很容易多打一个或少打一个横线渲染器就会报错。我写长图时习惯写完一段就预览一次把错误定位在最小范围内再继续。6.1 几个让图更专业的小习惯除了解决报错我还想分享三个让出图效果更专业的小习惯。第一给每张图加头部注释。PlantUML 支持双单引号注释我通常在startuml下面写清楚这张图的用途、维护人、修改日期。别小看这几行字过三个月你自己回来看这张图就能迅速想起来当时为什么这么画。团队协作时这个注释更是减少沟通成本的利器。第二用!include拆公共子图。如果多个 .puml 文件都包含同一个参与者定义或同一个类定义可以抽到公共文件里用!include引入。比如整个项目的角色列表我定义在actors.puml里每个图的文件只写业务逻辑。这样全局改一个角色名只需要改一行所有图同步更新。这个思路跟代码里的抽公共模块完全一致。第三为每个重要图配一段文字说明。图是给人快速理解的但复杂的业务约束光靠图形表达不了。我写时序图的时候会在分支片段附近用note right或者note over加注释说明这个分支的业务触发条件、异常处理策略。一个简短的 note 比画十根箭头更有解释力。6.2 如何把 PlantUML 集成进团队的文档流程如果你已经尝到了甜头建议再往前走一步把 PlantUML 接入团队的文档流水线。目前 GitLab、GitHub、Confluence、飞书文档都有对应的 PlantUML 插件只要把.puml代码块放进 Markdown 或文档页面里渲染和更新都能自动完成。我最常用的方式是在代码仓库里建一个docs/diagrams目录所有图源文件存 Git然后用 CI 脚本在每次合并请求时自动导出 PNG再上传到内部文档站。这套流程解决了团队协作里最头疼的“文档图过期”问题。因为图的源文件跟着代码走代码变更的时候顺手把图也改了评审的时候一眼就能看出改了什么。我甚至见过一个项目组用 Python 脚本扫描仓库中所有的.puml文件凡是解析失败的就在 CI 里直接给红灯从机制上保证图永远能渲染。在这里我想强调一句不要追求把所有图都画成一张巨型总图。真实项目里我更愿意用多张小图组合按模块、按功能拆开然后用目录组织。每张小图控制在 20 个元素以内阅读负担小也更容易维护。我个人的体会是PlantUML 真正改变我的不是画图速度而是画图的元认知——我开始把图当作代码一样认真对待。变量名起得好不好关系设计得对不对依赖方向有没有搞反这些问题在写文本的时候会被自然放大。画图的过程其实就是在做一次低成本的设计评审。如果你还在用鼠标拖框画图我真心建议你花一个下午试试 PlantUML先画一张图书管理系统的用例图再画一张订单类图等你感受到“改一行文字图就更新了”的快感你可能就回不去了。
返回列表