很多刚接触软件工程的人,第一次画UML用例图时都会陷入一种奇怪的纠结:对着空白的画布,脑子里全是"这个该不该画成小人""这个虚线箭头到底朝哪边""为什么别人画的图看起来干净利落,我画的像蜘蛛网"。我带过几届做课程设计的学生,也帮不少转行的朋友梳理过需求文档,发现用例图这块的坑高度集中——不是不会用工具,而是没搞明白用例图到底在表达什么、边界在哪里。
用例图(Use Case Diagram)是UML里最"轻"的一类图,轻到很多人觉得它没什么技术含量,随便画画交差。但恰恰是这张图,决定了你后续需求分析、功能拆解、测试用例设计的走向。画歪了,后面全歪。这篇内容我打算把用例图从"为什么画"到"怎么画好"完整拆一遍,包括那些教程里很少提、但实际画图时天天遇到的判断难题。不管你是刚学软件工程的学生,还是需要写需求文档的开发者、产品岗,都能从里面找到能直接抄作业的东西。
1. 先搞清楚用例图到底在解决什么问题
1.1 用例图不是功能清单的图形化
我见过太多人把用例图画成了"功能列表配小人"。左边一个用户,右边拉出十几条线,每条线连一个椭圆,椭圆里写着"登录""注册""查询""修改""删除"。这种图画完,除了证明你会用绘图工具,几乎没有任何分析价值。
用例图的本质是从外部视角描述系统能提供什么服务。注意两个关键词:外部视角、服务。它不关心系统内部怎么实现,不关心数据库怎么设计,只关心"谁"(参与者)为了达成什么目的,会跟系统发生什么交互。所以用例的命名应该是一个动宾结构的完整目标,比如"提交订单""查询物流状态",而不是"订单模块""物流功能"这种名词化的模块名。
这个区别很关键。当你用"目标"来思考时,你会自然地去问:这个目标是谁发起的?他为什么要做这件事?做完之后系统状态发生了什么变化?这些问题会逼着你去补全需求里模糊的地方。而用"功能"来思考,你只是在罗列,罗列是不产生洞察的。
1.2 参与者的识别比用例识别更容易出错
新手往往觉得找用例难,其实找参与者才是最容易翻车的地方。参与者(Actor)不一定都是人。定时任务、外部系统、硬件设备,只要它主动跟你的系统交互,它就是参与者。比如一个电商系统里,"支付网关"就是一个外部参与者,它会在支付完成后回调通知你的系统。
另一个常见误区是把参与者的"角色"和"具体的人"混为一谈。同一个物理人,可能同时是"买家"和"卖家",这时候在图上应该画成两个参与者,因为他在两种角色下的目标和权限完全不同。我一般建议学生先列一张角色-目标对照表,把每个角色想达成的核心目标写下来,再决定图上画几个小人。这张表比图本身更有价值,因为它是你后续写用例规约的素材。
还有一个容易忽略的点:参与者之间可以有泛化关系。比如"VIP会员"和"普通会员"都继承自"会员",VIP能做的事普通会员不一定能做。这种继承关系在图上用带空心三角的实线表示,箭头指向父参与者。很多人不知道参与者能泛化,结果把公共用例重复画了好几遍,图面乱成一团。
1.3 系统边界画不画,取决于你想强调什么
系统边界就是那个大方框,把所有用例框在里面,参与者在框外。有些教程说必须画,有些说可以省略。我的经验是:当你的系统需要和多个外部系统交互时,边界框一定要画,因为它能清晰地划分"哪些是我的责任,哪些是别人的责任"。如果只是单系统、单用户群的简单场景,边界框可以省,省了图更干净。
画边界框的时候,框的命名也有讲究。不要写"系统"两个字就完事,要写清楚这个系统的定位,比如"在线课程学习平台""仓库库存管理子系统"。名字起得越具体,后面讨论需求时越不容易跑偏。我见过一个团队,边界框上写着"后台",结果讨论到一半大家才发现,有人理解的"后台"是运营管理端,有人理解的是服务端API,白吵了半小时。
2. 用例之间的四种关系,画错一个整张图就废了
用例图里最让人头大的就是关系线。包含、扩展、泛化、关联,四种关系长得像但含义完全不同。我下面逐个拆,重点讲什么时候该用哪个,而不是干巴巴地背定义。
2.1 关联关系:参与者与用例之间的那根实线
关联关系就是参与者和小人连到用例的那根实线,没有箭头(或者可以有箭头表示谁发起)。它表示参与者与用例之间存在交互。这是最基础的关系,但有个细节很多人做错:一个用例可以被多个参与者关联,这时候要判断谁是主参与者、谁是次参与者。主参与者是主动发起用例的人,次参与者是被动参与的人。
举个例子,"查询成绩"这个用例,学生是主参与者(他主动查),教务系统可能是次参与者(提供成绩数据)。在图上,主参与者的关联线可以带箭头指向用例,次参与者用普通实线即可。这个区分在写用例规约时很有用,因为主参与者的操作流程才是你描述的重点。
提示:不要给关联线加箭头来表示"数据流向"。关联线不是数据流,箭头只表示"谁发起"。数据流是另一类图(数据流图)该干的事,混在一起画会让图产生歧义。
2.2 包含关系:把公共步骤抽出来,但别抽过头
包含关系(Include)用带箭头的虚线表示,箭头指向被包含的用例。它的语义是:基用例在执行过程中一定会执行被包含的用例。典型场景是把多个用例里重复的步骤抽成一个公共用例。
比如"下单""退款""查询订单"三个用例,都需要"验证用户登录状态",那就可以抽一个"身份验证"用例,让前三个都包含它。这样改需求时只改一处,图也更清爽。
但包含关系有个大坑:抽得太细会把图变成流程图。我见过有人把"输入用户名""输入密码""点击登录"都抽成独立用例,然后用包含关系串起来。这不是用例图,这是把代码逻辑画成了图。用例的粒度应该保持在"对用户有价值的目标"这个层级,内部步骤不属于用例图的表达范围。判断标准很简单:如果一个用例单独拿出来给用户看,用户会觉得"这也算一个功能?",那它就抽得太细了。
2.3 扩展关系:可选行为用扩展,别用包含硬凑
扩展关系(Extend)也是带箭头的虚线,但箭头方向和包含相反:箭头从扩展用例指向基用例。语义是:扩展用例在特定条件下可能会插入到基用例的执行流程中,是可选行为。
典型例子:"下单"是基用例,"使用优惠券"是扩展用例。用户下单时不一定用优惠券,用了才触发扩展。图上画法是"使用优惠券"虚线箭头指向"下单"。
包含和扩展最容易混淆,我总结一个判断口诀:包含是"必须做",扩展是"可能做";包含的箭头指向被抽出的公共部分,扩展的箭头指向被扩展的主体。再补一句:包含关系里,基用例离开被包含用例就完成不了;扩展关系里,基用例离开扩展用例照样能独立完成。用这个标准去套,基本不会错。
2.4 泛化关系:用例也能继承,但别滥用
用例泛化用带空心三角的实线表示,箭头指向父用例。它表示子用例继承了父用例的行为,并可以增加或覆盖自己的行为。典型场景是"支付"作为父用例,"支付宝支付""银行卡支付""余额支付"作为子用例。
泛化在用例图里用得相对少,因为大部分"不同方式"其实更适合用扩展或者直接在用例规约里描述。我一般只在子用例确实有独立参与者或者独立触发条件时才用泛化。如果只是内部处理逻辑不同,画泛化反而增加了图的复杂度,收益不大。
下面这张表把四种关系放在一起对比,画图时对着查:
| 关系类型 | 线条样式 | 箭头方向 | 语义 | 典型场景 |
|---|---|---|---|---|
| 关联 | 实线 | 可无箭头或指向用例 | 参与者与用例交互 | 用户发起某个操作 |
| 包含 | 虚线 | 指向被包含用例 | 必定执行的公共步骤 | 多个用例都要验证登录 |
| 扩展 | 虚线 | 指向基用例 | 条件触发的可选行为 | 下单时使用优惠券 |
| 泛化 | 实线空心三角 | 指向父用例 | 子用例继承父用例 | 多种支付方式 |
3. 从零画一张用例图的完整操作链路
光讲概念没用,我拿一个模拟的"图书借阅管理系统"走一遍完整流程。这个场景足够典型,参与者、用例、关系都能覆盖到,你可以直接套用到自己的项目上。
3.1 第一步:列参与者清单,别急着开画
打开绘图工具之前,先拿张纸或者开个文档,把参与者列出来。图书借阅系统里,我初步想到的参与者有:读者、图书管理员、系统定时任务(催还提醒)、外部支付系统(罚金缴纳)。
这里有个判断技巧:问自己"谁会对系统说'我要做某事'"。读者会说"我要借书""我要查借阅记录";管理员会说"我要登记新书""我要处理归还";定时任务不需要说话,但它会在每天固定时间触发"发送催还通知";支付系统会在罚金支付完成后回调"更新缴费状态"。这四个都是参与者。
列完之后再检查一遍:有没有哪个参与者其实只是另一个参与者的特例?比如"学生读者"和"教师读者",如果他们的借阅权限只是数量不同,那可以合并成一个"读者"参与者,权限差异在用例规约里写清楚,不必在图上拆成两个小人。图面简洁比"精确到每个细分角色"更重要。
3.2 第二步:按参与者逐个找用例,用目标句式命名
针对每个参与者,问"他想达成什么目标"。读者想达成的目标:借书、还书、查询借阅记录、缴纳罚金。管理员想达成的目标:登记新书、注销旧书、处理借还、维护读者信息。定时任务的目标:发送催还通知。支付系统的目标:回调缴费结果。
命名的时候统一用动宾结构,比如"借阅图书"而不是"借阅","登记新书"而不是"新书登记"。统一句式能让图看起来专业,也方便后续和用例规约对应。
这一步还要做一件事:合并重复目标。"借书"和"还书"虽然都是读者的目标,但它们是两个独立用例,不能合并。"查询借阅记录"和"查询罚金记录"如果查询逻辑高度相似,可以考虑合并成"查询个人借阅信息",但要看用户是否真的把它们当成一件事。合并的尺度是:用户会不会在同一个操作意图下完成它们。会,就合并;不会,就分开。
3.3 第三步:补关系,先画关联再考虑包含扩展
参与者和小人连好用例外,先只画关联线,把主干搭起来。这时候图应该是一堆小人连着各自的椭圆,看起来有点散但结构清晰。
然后审视:有没有多个用例共享的步骤?读者"借阅图书"和"缴纳罚金"都需要"验证读者身份",管理员"处理借还"和"维护读者信息"都需要"管理员登录验证"。这两个可以抽成包含用例。注意,抽出来的用例命名要体现"被复用"的性质,比如"身份验证"就比"验证读者身份"更通用,因为管理员那边也能用。
再审视:有没有可选行为?读者"借阅图书"时,如果读者有逾期未还的书,系统会触发"冻结借阅权限"。这是一个条件触发的行为,适合用扩展。画法是"冻结借阅权限"虚线箭头指向"借阅图书"。
最后审视泛化:这个系统里"缴纳罚金"可能支持"在线支付"和"线下缴纳"两种方式,如果这两种方式的参与者不同(在线支付涉及外部支付系统,线下缴纳涉及管理员),那可以用泛化,父用例是"缴纳罚金",子用例是"在线缴纳"和"线下缴纳"。如果参与者相同、只是内部流程不同,就别画泛化,在规约里写清楚即可。
3.4 第四步:布局调整,让图能一眼看懂
关系补完之后,图往往会变乱。这时候要做布局优化,几个实用原则:
- 参与者放在边界框的左右两侧,主动发起方放左边,被动接收方放右边。读者、管理员放左边,支付系统、定时任务放右边。
- 高频用例放中间,低频放边缘。借阅、归还放中间,注销旧书、维护读者信息放边上。
- 包含用例放在基用例附近,用虚线连过去,别让它飘到图的最角落。
- 避免线条交叉。如果两条线必须交叉,调整用例位置,或者用绘图工具的"跳线"功能。交叉线是图面混乱的头号元凶。
我一般会画两到三版,第一版求全,第二版求简,第三版求清晰。第一版把所有想到的都画上,第二版删掉冗余的、合并重复的,第三版调整布局和命名。这个过程花不了多少时间,但出来的图质量差别很大。
4. 那些教程不写、但实际画图天天遇到的判断难题
概念和流程讲完了,下面这部分是我觉得最有价值的内容——实际画图时那些"卡住"的瞬间,到底该怎么判断。这些判断没有标准答案,但有经验法则。
4.1 一个操作到底算一个用例还是多个用例
这是最高频的问题。比如"用户管理",包含新增、删除、修改、查询。是画一个"管理用户"用例,还是画四个用例?
我的判断标准是看操作的目标是否独立。新增用户和删除用户,目标完全不同,用户在做这两件事时的意图、输入、系统反馈都不一样,应该拆成两个用例。而"修改用户信息"和"查询用户信息",如果查询是为了修改做铺垫,且通常在同一界面完成,可以考虑合并成"维护用户信息"。但如果查询有独立的入口(比如管理员只想看看某个用户的状态,不修改),那就该独立。
再给一个更硬的判断:如果两个操作对应不同的权限控制,就拆开。新增和删除往往权限不同,拆开;查询和修改权限也可能不同,拆开。权限是需求分析里非常实在的维度,用它来判断用例粒度,比凭感觉靠谱。
4.2 参与者能不能主动触发系统内部的动作
有人问:系统自己定时执行的清理任务,算不算用例?算,但参与者是"时间"或者"调度器"。UML里允许把时间当作参与者,画一个小人标注"定时器"或者"调度任务"即可。这个用例的触发条件是时间,不是人,但它在图上依然是一个正常的用例,关联到时间参与者。
还有一种情况:系统A调用系统B的接口。对系统B来说,系统A就是参与者。这时候系统A画在系统B的边界框外面,用关联线连到被调用的用例。不要因为"都是系统"就把它们画在同一个框里,边界框的意义就是划分责任范围。
4.3 用例图要不要画异常流程
不画。用例图是目标层面的概览图,异常流程属于用例规约(Use Case Specification)的内容。用例规约里会有基本流、备选流、异常流,那才是描述细节的地方。用例图上如果画异常,图会爆炸,而且异常分支往往很多,画不全反而误导人。
但有一个例外:如果某个异常流程本身就是一个独立的用户目标,那它应该是一个独立用例。比如"申请退款"对用户来说是一个独立目标,它不是"下单"的异常流程,而是一个平级的用例。判断标准还是那个:用户会不会专门为了这件事来找系统。
4.4 图上的用例数量控制在多少合适
没有硬性规定,但经验值是单张图 10 到 20 个用例比较舒服。少于 10 个,可能拆得不够细或者系统太小;多于 20 个,图开始难读,建议按子系统或者按参与者分组,拆成多张图。
拆图的时候,每张图保持一个清晰的边界框,框名体现子系统。比如图书借阅系统可以拆成"借阅管理""图书维护""读者管理""罚金管理"四张图。拆图不是偷懒,是让每张图聚焦一个主题,读者看的时候不用在一堆椭圆里找目标。
4.5 工具选型:别在工具上纠结太久
用例图对工具的要求极低,能画椭圆、小人、虚线、实线、空心三角就行。常见的几类工具:
| 工具类型 | 代表 | 适合场景 | 注意点 |
|---|---|---|---|
| 专业建模工具 | 各类UML建模软件 | 需要生成代码、导出多种图 | 学习成本高,小项目没必要 |
| 通用绘图工具 | 各类在线白板、流程图工具 | 快速出图、团队协作 | 图形库要手动对齐,注意规范 |
| 文本转图工具 | 支持PlantUML语法的工具 | 版本管理、快速迭代 | 布局自动,但微调受限 |
我个人的习惯是:需求讨论阶段用通用绘图工具,因为改起来快,拖拽就行;定稿归档用文本转图工具,因为文本可以进版本库,改一个词重新生成,图永远和文档同步。PlantUML画用例图的语法很简单,几行代码就能出一张图:
@startuml left to right direction actor 读者 as R actor 管理员 as A rectangle 图书借阅系统 { usecase "借阅图书" as UC1 usecase "归还图书" as UC2 usecase "身份验证" as UC3 usecase "冻结借阅权限" as UC4 } R --> UC1 R --> UC2 UC1 ..> UC3 : include UC2 ..> UC3 : include UC4 ..> UC1 : extend @enduml这段代码生成的图,关系一目了然。注意left to right direction让布局横向展开,比默认的纵向布局更适合用例图。..>是虚线箭头,include和extend写在冒号后面作为标签,规范又清晰。
5. 画完之后的自查清单与常见返工点
图画完不等于画对。我每次交付用例图之前会过一遍自查清单,这里分享出来,你可以直接拿去用。
5.1 命名层面的自查
- 每个用例是不是动宾结构?"图书借阅"不如"借阅图书","用户管理"这种名词化的要警惕,它可能藏着好几个用例。
- 参与者命名是不是角色名而不是人名?"张三"不行,"读者"可以。
- 边界框命名是不是体现了系统定位?"系统"太泛,"图书借阅管理系统"更清楚。
- 有没有出现技术术语?用例图上不该出现"数据库""接口""缓存"这类词,出现了说明你在画实现而不是画需求。
5.2 关系层面的自查
- 每条包含关系的被包含用例,是不是真的被多个基用例共享?只被一个用例包含的,没必要抽出来。
- 每条扩展关系的扩展用例,是不是真的可选?如果每次都会执行,那它应该是包含或者直接合并进基用例。
- 泛化关系是不是有必要?子用例有没有独立的参与者或触发条件?没有的话,考虑降级为普通用例。
- 关联线有没有多余的箭头?箭头只表示发起方向,不表示数据流。
5.3 图面层面的自查
- 线条有没有大量交叉?有的话调整布局。
- 用例椭圆大小是否一致?不一致会显得不专业。
- 参与者是否都在边界框外?有一个跑进框里就是硬伤。
- 图的整体阅读方向是否清晰?从左到右或者从上到下,别让人找不到入口。
我踩过最典型的一个返工点是:把包含关系画反了箭头。包含关系的箭头指向被包含的用例,我一开始总记成指向基用例,结果图上的语义完全反了——变成了"身份验证包含借阅图书",逻辑上说不通。后来我记了个笨办法:包含的"含"字,想象成把东西"含"进去,箭头指向被含进去的那个。扩展的"扩"字,想象成从主体"扩"出来,箭头指向主体。这么记虽然土,但再没画反过。
另一个返工点是参与者重复。同一个外部系统,在两张子图里用了不同的名字,一张叫"支付平台",一张叫"支付网关",评审的时候被指出来,显得很不严谨。后来我养成了习惯:所有参与者先在文档里统一定义一次,画图时直接复制粘贴名字,不手打。
6. 用例图在整个需求链路里的位置
最后聊点"图之外"的东西。用例图画得好不好,很大程度上取决于你对它在整个需求链路里位置的理解。它不是孤立的交付物,而是承上启下的枢纽。
往上,它承接的是需求来源——用户访谈、业务调研、竞品分析。这些零散的信息经过你的梳理,变成参与者和用例,图就是梳理结果的呈现。所以画图之前的信息收集做得越扎实,图画起来越顺。我一般会要求先有一份参与者-目标清单,清单确认了再动笔,返工率能降一大半。
往下,它支撑的是用例规约和测试用例。每个用例椭圆,都应该对应一份用例规约,规约里写清楚前置条件、基本流、备选流、后置条件。测试同学拿到用例图,能快速圈出测试范围;拿到规约,能设计具体测试步骤。如果用例图上的用例和规约对不上号,说明中间漏了东西,得回去补。
还有一个容易被忽略的用途:用例图是跟非技术方沟通的好工具。给业务方看类图、时序图,他们一头雾水;看用例图,小人加椭圆,基本能看懂"谁能干什么"。所以图上的命名要尽量用业务语言,别用技术黑话。这一点在评审会上特别重要,图是给所有人看的,不是给画图的人自己看的。
我在实际项目里的体会是,用例图的价值不在于画得多漂亮,而在于画的过程中逼着团队把"谁用、用来干嘛、边界在哪"这三件事说清楚。很多时候图画到一半,大家发现某个参与者的目标定义有分歧,当场就对齐了,这比图本身有价值得多。所以别把画图当成任务,把它当成一次结构化的需求讨论,心态会完全不一样。
如果你刚开始学,建议找一个自己熟悉的场景——比如学校选课、外卖下单、图书馆借书——从列参与者清单开始,完整走一遍流程。画完第一版别急着交,放一天再看,你会发现一堆可以优化的地方。画到第三张图的时候,那些关系判断就会变成肌肉记忆,不用再对着口诀查了。