ARTICLE DETAIL

资讯详情

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

软件工程师该关注的UML图:从类图到时序图的全景指南

软件工程师该关注的UML图:从类图到时序图的全景指南 软件工程师应该关注的几种 UML 图我见过不少软件工程师面试时能把九种UML图背得滚瓜烂熟进了项目组之后画图却只用来应付文档评审。问起来理由出奇一致画了也没人看代码里全都有何必多此一举。这话对了一半。确实如果你画的图只是把代码里的类名、方法名换了个形式誊抄一遍那它毫无价值。但如果你画的图能回答代码里看不出来的问题——比如这套系统对外承诺了什么行为、一个请求跨了哪些服务、订单状态在什么条件下才能流转——那它就是能救命的东西。这篇文章我想聊的就是从一个实用主义软件工程师的角度真正值得你投入时间去掌握的几种UML图以及怎么画才能不白画。1. 先回答一个尴尬问题为什么你画的 UML 没人看1.1 从考试必考到项目里吃灰的落差UMLUnified Modeling Language统一建模语言在国内的普及路径很奇特它首先是系统架构师考试的重点科目然后才是工程实践工具。这就导致一个现象——很多工程师对UML的认知停留在背图例、背关系箭头的阶段考完即忘。进了公司以后所谓的UML实践通常长这样项目启动时画几张用例图、类图评审会上投影出来讲一遍散会之后进入Git仓库吃灰再也没人打开过。我早期也犯过这个毛病。有一年做电商中台重构我花了整整两天画了一张极其详细的类图把十几个模块的几十个类、所有方法签名都塞了进去。结果图出来以后团队里没人愿意看——信息量太大找不着重点而且代码里明明什么都有为什么还要看你的图那次经历让我意识到一个问题UML图的价值不在画这个动作上而在用这个场景里。如果你的图不能辅助思考、不能驱动沟通、不能暴露设计风险那它就是一张昂贵的壁纸。1.2 判断一张图值不值得画的四个标准那什么样的图才值得画我总结了一套判断标准画之前先拿这四个问题过一遍它能不能暴露问题一张好图最核心的价值是让设计缺陷显形。比如类图画到一半你发现A模块居然反向依赖了B模块的底层实现这就是画图的意义。它能不能在十分钟内讲清楚如果一个图需要你逐行讲解超过十分钟那听众大概率已经走神了。UML图是沟通语言不是学术论文。一张图只承载一个核心信息。它对应的是不是一个真实存在的变化点如果你的图描述的是一个永远不会变的静态结构比如用户表有id、name、age那确实没必要画。值得画的是那些未来可能要扩展或者当前设计上有争议的部分。它有没有人需要依据它做决策画图之前问一句谁会看需求评审时产品和测试会看用例图接口联调时会看时序图新人上手时会看类图。有受众图才有生命力。凡是没有通过这四关的图我的建议是别画省下来的时间去读代码。2. 类图软件工程师最该练熟的一张图2.1 类图到底在表达什么类图是所有UML图里和代码关系最近的一种也是软件工程师最绕不开的。我甚至觉得你哪怕其他图一概不画只要把类图画明白日常开发就已经受益无穷。类图表达三层信息。第一层是静态结构有哪些类、哪些接口、它们的属性和方法的可见性。第二层是关系依赖、关联、聚合、组合、继承、实现。第三层是设计味道循环依赖、过度深度的继承链、双向耦合这些坏味道在代码里往往藏得很深但画在图上会刺眼得让你无法忽视。我自己用得最多的是第三层。做代码评审的时候与其在一百行的diff里人肉找耦合不如先把改动相关的类图画出来。图上一看改一个公共接口居然有八个模块直接依赖它拆分的冲动立刻就有了。这种一图顶千行的体验画过的人自然懂。2.2 六种关系怎么记才不混淆类图的关系是最容易记混的部分尤其聚合和组合很多干了几年的人还在靠空心菱形还是实心菱形临时翻书。我提供一个更不容易忘的记忆锚点——不要死记箭头符号而是记生命周期和所有权的强弱。关系箭头表达语意在代码里的体现生命周期特征依赖Dependency虚线箭头指向被依赖方我只是用了一下你方法参数、局部变量、静态方法调用完全没有持有关系调用结束即断关联Association实线箭头指向目标类我长期认识你成员变量中直接引用另一个对象随持有者共存亡但彼此互不约束对方生命周期聚合Aggregation空心菱形 实线菱形在整体侧你是我的部分但你可以独立存在构造函数传入外部对象并保存引用整体销毁部分依然活着组合Composition实心菱形 实线菱形在整体侧你是我的零件我负责你的生死构造函数内部直接 new 出来整体销毁部分也跟着销毁继承Generalization空心三角形 实线三角形指向父类我是你的一种extends 关键字类层次关系实现Realization空心三角形 虚线三角形指向接口我遵守你的约定implements 关键字契约关系记的时候抓住一条主线依赖最弱组合最强聚合居中。依赖就是临时借个火聚合是你住在我家但你有自己钥匙组合是你就是我身体的一部分。这么一想空心菱形和实心菱形的区别就再也不会忘了。2.3 用类图做代码地图的实操方法画类图最常见的错误是追求全恨不得把整个项目的类都铺上去。我的做法恰恰相反类图是挑着画的只画核心链路和核心模型。实操时可以分三步走。第一步从业务上最重要的聚合根类开始比如订单、用户、商品、支付单把它们的字段和行为画出来。第二步只画有行为交互的关系忽略那些纯粹的POJO数据对象因为数据对象的依赖关系在数据库表结构里一目了然不需要在类图里重复。第三步用颜色或边框标注稳定性——核心稳定的部分用一种颜色频繁变化的部分用另一种颜色。这样图一眼看过去哪些地方是地基、哪些地方是装修清清楚楚。需要提醒的是类图一定不要手画现状然后不管了。我的习惯是借助IDE插件从代码反推类图再在反推结果上标注问题。以IDEA为例PlantUML插件和JaCoCo等工具都可以直接生成代码的依赖关系图你只需要在那个基础上圈出重点、标出问题。代码变了重新生成一次去比对标注才不会让图变成陈年旧货。3. 用例图别让产品和开发鸡同鸭讲3.1 用例图的三件套参与者、用例、边界如果说类图是面向代码内部的那用例图就是面向系统外部的。它回答的问题是这个系统到底对外提供哪些价值谁在用这些价值。用例图只有三个元素。参与者Actor指与系统交互的外部角色可以是一个人、一个外部系统甚至是一个定时器用例Use Case指系统对外提供的一个可观测的完整价值比如提交订单查询物流系统边界System Boundary用一个大方框把用例框起来左边是参与者右边是系统内部。很多工程师画用例图时最容易忽略的就是系统边界。这恰恰是我觉得最重要的一笔。边界画清楚了职责边界就清楚了。比如在用优惠券下单这个用例时如果发现计算优惠价格到底是用户端系统的职责还是营销系统的职责说不清楚那图上这条虚线就是架构评审时最该吵架的地方。3.2 用例图粒度怎么把握从用户登录到扫码登录粒度是画用例图最容易翻车的点。初学者容易画成两类一类是细到像流程图把点击按钮弹出提示都列成用例另一类是粗到没信息量整个系统就三个用例管理用户处理订单生成报表。我给一个自己的把握标准一个用例必须能对应到一个用户感知得到的完整业务目标。用户登录算一个用例吗算因为用户能感知我登录进来了。但用户输入密码算吗不算因为这只是登录用例里的一个步骤。用户登录下面要不要细分成扫码登录短信验证码登录微信授权登录我建议是当一个登录方式有独立的业务规则时才拆分比如扫码登录涉及绑定确认、授权回调短信登录涉及验证码错误次数限制它们的行为差异足够大拆开反而更清楚。我画用例图时有一个习惯先把参与者列全。画之前拿一张纸从谁会受益出发列参与者清单——用户、客服、运营、第三方支付、短信网关、消息队列、定时任务。这一步非常容易暴露需求边界问题。有一次做风控系统PRD里只写了用户这个参与者但一画用例图就发现真正高频使用风控系统的是运营人员他们要配置规则、查看拦截列表。这个参与者要是漏了整个管理后台的需求就全丢了。3.3 用例图在需求评审中的真实用法我特别推荐在需求评审会上把用例图拿给产品经理和测试一起看而不是只丢出PRD让各人自己扫描。原因很简单PRD是线性的文字流用例图是结构化的关系网。一张图摆出来谁是什么角色、要做什么事、边界在哪里一眼就能扫完。用例图还有一个隐藏价值就是它天然适合做测试用例的蓝图。用例图的每个用例对应一组正常流程测试用例而参与者与系统的交互边界是异常测试的重点。我们在实际项目中测试同学经常直接用用例图反推用例覆盖矩阵比从PRD里硬抠需求条款高效得多。这也再次证明让图有受众、有用处它才不会吃灰。4. 时序图接口对接、故障排查的时间轴4.1 为什么时序图是排查线上问题的最佳拍档如果只能选一种UML图来做思考工具我投时序图一票。原因是代码里最难读的维度不是逻辑复杂度而是时间顺序。尤其是微服务架构之下一个用户请求可能跨四五个服务、产生七八次RPC调用有同步、有异步、有回调、有重试。这些事情叠加在一起读代码的体验就像在看一本被撕碎重排的小说。时序图把谁在什么时候调了谁的什么方法、返回了什么、失败了怎么办按时间轴展开这是人脑最容易理解的叙事结构。排查线上问题时我几乎不直接打开IDE而是先把调用链路上的几张时序图在白板上画出来把正常路径画完再拿放大镜找异常路径——哪里没画超时处理、哪里没画补偿逻辑问题往往就在那些你没画出来的空白里。4.2 核心元素速查时序图的元素不算多掌握下面一组就能开始实战生命线Lifeline图上方的矩形框就是参与者或对象下方垂直的虚线是它的生命线表示这个角色在时间维度上存在。激活Activation生命线上细长的矩形条表示这个对象正在执行某个操作期间它是有状态的。消息箭头同步调用用实线实心箭头返回用虚线箭头异步调用用实线箭头加一个横向的半箭头。消息上标注方法名和参数。自调用箭头从自身生命线发出又指向自身表示递归或同一对象内的方法调用。组合片段Combined Fragment用一个大矩形框住一组消息左上角的小标签表示类型。alt表示条件分支loop表示循环opt表示可选步骤par表示并行执行。组合片段是时序图上信息量最大的备注区也是很多工程师不爱用、一用就乱的地方。我的经验是在接口设计阶段的时序图上多画alt和opt这能逼着所有联调方把异常分支当作一等公民对待。很多线上事故的根源就是大家联调时只测了happy path而alt框里的失败分支只在代码里默默存在。4.3 一个完整的实战场景订单超时关闭光讲概念没有体感我拿一个最常见的电商场景来拆一下——订单超时未支付自动关闭。这是时序图的经典应用场景因为它跨了多个服务还涉及异步和定时。正常路径很简单用户下单订单服务创建订单返回待支付用户完成支付支付服务回调通知订单服务订单服务更新状态通知仓储服务扣减库存。异常路径是重头戏。如果用户下了单但一直没支付订单服务需要在30分钟后把订单关闭并把锁定的库存释放掉。这时候时序图里会多出两段逻辑。第一段下单时订单服务发送一条延时消息到消息队列消息里带着订单号和超时时间。第二段延时消息到期一个定时任务把它拉出来订单服务先查订单状态如果还是待支付就执行关闭操作同时调用库存服务释放库存如果已经是已支付就什么都不做直接丢弃消息。把这段逻辑画成时序图你立刻会看到几个关键判断点查单和改状态之间是不是原子的消息会不会重复消费如果关闭订单时库存服务调用失败了怎么办图上这些点的空白都会变成问题而这些问题在代码评审时往往被跳过。所以说时序图的真正价值不在于描述现在是什么样而在于逼问你没想到什么。4.4 时序图和协作图通信图怎么选热词里提到了UML协作图这里专门说一下。协作图现在标准里叫通信图在UML里和时序图可以互相转换表达的信息本质上是等价的——它们都描述对象之间的消息交互。区别只在于视角时序图强调消息在时间上的先后顺序协作图强调对象之间的连接关系网络。我自己的使用倾向很明显接口联调、排查线上问题、描述业务流程驱动的调用链一律用时序图因为大家关心的是先干什么、后干什么。但有一种场景协作图更合适——你要梳理一个遗留系统里谁和谁建立了连接的时候。比如你要评估如果要把支付模块替换成新服务影响面有多大协作图一画所有跟支付模块有消息来往的对象一目了然比在时序图里一行一行捋时间轴直观得多。协作图在工程实践中确实用得少但如果你是系统架构师考试备考者这个知识点是躲不开的而且理解了时序重时间、协作重结构这个本质两者之间的转换题就是送分题。5. 活动图与状态机图流程和状态的显影液5.1 活动图把复杂分支流程可视化活动图是UML家族里最接近传统流程图的一类但多了两个利器泳道Swimlane和并发分叉。泳道是把不同角色的职责在图上用纵向的栏隔开每个人只在自己的泳道里画活动。这样做最大的好处是职责边界问题无处遁形。举个例子画一个退款流程如果退款审批这个活动被画在用户泳道里你立刻就能发现问题——用户怎么有权审批自己的退款这就是活动图在流程梳理时的高价值。并发分叉用粗横线分叉线表示一个活动完成后多个并行活动同时启动用汇合线表示它们都完成后再继续。这在描述用户提交申请后同时通知财务和库存部门这类真实业务场景时非常有用普通流程图根本没这个能力。活动图适合用在三种场景复杂的审批流、对账流程、有多分支和并发规则的业务逻辑。它也是测试设计的好帮手每个分支路径都可以映射一组测试数据。5.2 状态机图状态和迁移的权威定义如果说活动图描述的是一系列动作那状态机图描述的就是一个对象的完整生命周期。它关注的是一个对象在什么状态下遇到什么事件会迁移到什么状态迁移过程中执行什么动作。还是拿订单举例。一个订单的状态机图会包含待支付、已支付、已发货、已完成、已关闭、已退款。图上每个箭头都是一条规则比如待支付 -[30分钟超时]- 已关闭待支付 -[支付成功]- 已支付已支付 -[申请退款]- 已退款。画状态机图的价值在于它会把非法迁移暴露在阳光下。比如你画着画着发现已发货状态上居然没有用户申请退款的出口那说明业务流程有漏洞。这类逻辑问题你在PRD或者代码里很难一眼瞅出来但在状态机图上每个缺失的箭头都是一个巨大的空洞。5.3 什么时候用活动图、什么时候用状态机图我的判断口诀很简单流程复杂看活动图状态复杂看状态机图。如果一个业务的核心矛盾在于做事的步骤多、分支多、涉及角色多用活动图如果一个业务的核心矛盾在于一个对象的状态太多了、迁移规则太复杂、总有非法状态乱窜用状态机图。实际项目里这两者经常互补出现。比如工单系统整体处理流程用活动图画工单对象本身的生命周期用状态机图画。前者回答这件事要经过哪些环节、谁来做后者回答这个工单在什么状态下可以被谁改到什么状态。搞清楚自己的核心问题是哪一种再决定画哪张图比两个都画一遍更高效。6. 组件图、部署图这些架构师视角图程序员要不要了解6.1 组件图与部署图的真实使用场景组件图和部署图在系统架构师考试里是热门但在普通软件工程师的日常里出场率不高。可这不代表它们没用只是它们的适用层级不一样。组件图描述的是系统的高层模块划分和模块之间的依赖关系——比如订单服务依赖库存服务、消息队列、支付回调接口。它的粒度比类图大得多动辄就是整个微服务、整个子系统。我建议软件工程师在两种场景下接触组件图一是你接手一个自己不熟悉的核心系统需要快速理解这块地盘由哪些模块构成、边界在哪里时二是你做技术方案设计、准备跟架构师汇报我这个改造要动哪些模块时。部署图则描述软件和硬件环境的关系——哪个服务部署在哪些节点上、走什么协议通信、有没有负载均衡器和数据库、缓存节点的位置。这个图在排查环境类问题时特别好用它能把哪台机器上有什么进程、依赖什么端口一次性铺开省去你挨个登录服务器排查的时间。平时不画但出重大生产问题时画一张当下的部署图能救命。6.2 从用例图一路画到部署图的完整视角我一直觉得一个合格的软件工程师不应该只会低头画类图至少要有能力用一组图把系统从需求到运行讲一个完整的故事用例图画边界和承诺组件图画模块和依赖类图画核心模型时序图画关键交互部署图画运行环境。这五张图不需要每个项目都画全但你要有把它们串起来的能力。平时写技术设计文档时我至少会保证三张图存在一张用例图标清需求和边界一张时序图标清核心交互链路一张类图或组件图标清代码组织。加上系统架构师考试的备考视角你会发现考试里那么多图并非孤立的题型它们本来就是同一套建模语言的不同视角组合起来才是一个完整系统。7. 画图工具与经验教训把 UML 从文档变成武器7.1 工具选型我实测过的几条路线工具选择直接决定你愿不愿意持续画图我的建议是选对你阻力最小的那一个。下面是我实测过的几条路线。工具形式优势劣势适合场景PlantUML文本描述生成图可进Git仓库做版本管理支持批量生成修改方便上手需要记语法复杂布局调校难代码库配套文档、持续更新的图draw.iodiagrams.net拖拽式可视化免费、上手快、支持多种导出协同方便图多了容易乱版本管理麻烦白板替代品、临时方案草图StarUML桌面端专业UML工具支持标准UML元素齐全可导入代码生成类图商业付费、协同能力弱正式设计文档、考试练习白板 手机拍照物理白板沟通效率极高改造成本为零不可检索、无法更新需求评审、方案头脑风暴我个人在工程代码里的主力是PlantUML因为它可以用文本维护能放进代码仓库、跟着代码评审走。需求评审时我用白板沉淀文档时把白板内容落成PlantUML。7.2 画图粒度一张图控制在一个焦点的体量内画了这么多年图我栽过最大的跟头就是贪多。有一次画组件图为了全面把中间件、数据库、所有微服务、所有依赖关系全塞进去了结果打印出来A1纸都放不下最终谁也没看。血的教训总结成一句话一张图只讲一个事超过50个节点就应该拆图。如果一张图的信息量太大就按维度拆成多张。比如先画调用链全景图粗粒度只画服务之间的箭线再单独画订单域精细时序图。宁可图多不要图大。每张图遵循一个屏幕内能看完的体量读者才愿意打开。7.3 我踩过的三个坑提前帮你避开最后分享三个我亲身踩过的坑。第一个坑是图与代码分家。图画得再漂亮代码一改就过时最后图成了历史文物。解决办法是把图变成文本化描述比如PlantUML放在代码仓库里每次改代码时同步更新图示。做不到的话至少给图加一个生成日期和版本让人知道它可能已过时。第二个坑是用例图画成了流程图。用例图是行为约定不是操作步骤用户点击登录按钮后系统校验输入这不是用例是流程。再犯就把自己拉回参与者完整业务目标这个定义上。第三个坑是滥用UML画不适合的东西。UML是用来描述软件系统的不是组织结构图不是甘特图也不是数据库ER图的完全替代品。选对工具类型比画好一张图更重要。我个人现在的做法是每次画完一张图都会问自己一句——三个月后再翻开这张图不看任何代码我还能不能看懂当时的设计意图和潜在风险如果答案是能这张图就值得放进仓库如果答案是废了那它就只是一张一次性草图拍个照扔进聊天记录就完事。UML图说到底不是交付物而是思考过程的可视化沉淀。把注意力从画得对不对转移到画完有没有帮我把问题想清楚上你就真正入门了。
返回列表