
1. 为什么系统设计图不是画着玩的刚入行那几年我对系统设计图的态度很敷衍。需求评审会上产品经理画个草图我扫一眼就开始写代码心里想的是图有什么用代码才是真的。直到有一次接手一个图书管理系统的重构项目前任开发留下的文档里只有一堆零散的接口注释没有任何结构化的设计图。我花了整整两周才理清用户借书这个动作背后涉及了哪些实体、哪些状态流转、哪些模块调用。那两周的代价让我彻底改变了对设计图的看法。系统设计图本质上是一种压缩信息的手段。一张好的E-R图能把几十张数据表的关联关系压缩成一张图一张时序图能把跨五个服务的调用链路压缩成一条时间线。它的价值不在于好看而在于让不同角色的人在同一套语言体系下对齐认知。产品经理看用例图确认功能边界开发看类图和时序图确认实现逻辑DBA看E-R图确认表结构测试看流程图确认分支覆盖。如果这些图缺失或者画得含糊每个角色都会在自己的脑子里建一套模型等到联调的时候才发现大家对同一个需求的理解完全不同。这篇文章面向的是需要做系统分析与设计的开发者、准架构师以及正在做课程设计或毕业设计的同学。我会把E-R图、类图、时序图、功能结构图、流程图、用例图、架构图这七种图放在一个完整的系统设计流程里讲清楚每种图解决什么问题、什么时候该画、怎么画才不浪费时间、以及我在实际项目中踩过的那些坑。不会只讲UML语法而是讲在真实的项目节奏下怎么用这些图把设计做扎实。2. 七种图在系统设计流程中的分工2.1 从需求到实现每种图的定位很多人学UML的时候是孤立地学每一种图画法背得滚瓜烂熟但到了实际项目里不知道该先画哪个、后画哪个也不知道哪些图是必须的、哪些可以省略。我用一个图书管理系统的例子来串一下这七种图的分工。需求阶段先用用例图圈定系统边界和参与者。图书管理系统里参与者有读者、管理员、系统本身比如定时任务用例有借书、还书、查询图书、管理图书等。用例图不关心怎么实现只关心谁做了什么。需求明确之后用功能结构图把系统拆成模块。图书管理系统可以拆成用户管理模块、图书管理模块、借阅管理模块、查询模块、系统管理模块。功能结构图是树状的帮你确认模块划分是否合理、有没有遗漏。接下来进入设计阶段。E-R图负责数据建模把实体、属性、关系理清楚。读者和图书之间是多对多关系一个读者可以借多本书一本书可以被多个读者借过这个关系通过借阅记录实体来承载。类图负责面向对象建模把实体类、服务类、工具类的关系理清楚包括继承、组合、依赖。时序图负责交互建模把一次借书操作从Controller到Service到DAO的调用链路画出来。流程图负责业务逻辑建模把借书过程中的判断分支库存够不够、读者有没有超期、信用分够不够画清楚。架构图负责整体部署和技术选型把前端、网关、微服务、数据库、缓存、消息队列的拓扑关系画出来。这七种图不是每个项目都要全画。小项目可能只需要E-R图加流程图大项目可能需要全部。关键是理解每种图的不可替代性——它解决的那个问题用别的图替代不了。2.2 一张表看清七种图的差异图的类型核心问题面向角色典型使用时机是否必须用例图谁用系统做什么产品、测试需求分析中大型项目必须功能结构图系统分哪些模块产品、开发需求分析后期建议有E-R图数据怎么组织DBA、开发数据库设计必须类图代码怎么组织开发详细设计必须时序图调用怎么流转开发详细设计核心流程必须流程图逻辑怎么分支开发、测试详细设计必须架构图系统怎么部署架构师、运维概要设计中大型项目必须这张表不是死规矩。我做过一个内部工具项目用户就一个角色用例图直接省了用一段文字描述清楚就行。但E-R图和流程图一张没少因为数据关系和业务分支是绕不过去的。2.3 画图的顺序有讲究我见过不少团队一上来就画类图结果画到一半发现实体关系没理清又回头补E-R图类图跟着大改。正确的顺序应该是先理数据再理对象最后理交互。E-R图先行因为数据模型是整个系统的地基。实体有哪些、关系是一对多还是多对多、主键外键怎么设这些定了之后类图的实体类基本就定了。类图定了之后时序图里涉及的对象和接口也就定了。最后画时序图的时候你只需要关注调用顺序和参数传递不用再纠结这个操作该放在哪个类里。流程图可以和数据建模并行因为它关注的是业务逻辑分支和数据结构相对独立。但流程图中涉及的数据读写操作需要和E-R图对齐。3. E-R图数据建模的起点3.1 实体、属性、关系的三要素E-R图看起来简单三个元素矩形表示实体椭圆表示属性菱形表示关系。但实际画的时候新手最容易犯的错误是把该独立成实体的东西当成了属性。举个例子。图书管理系统里作者这个信息很多人第一反应是把它作为图书的一个属性。但如果一个作者写了多本书而且你需要查询某个作者的所有作品那作者就应该独立成实体和图书之间是多对多关系。判断标准很简单如果这个信息需要被独立查询、独立维护、或者和其他实体产生关系它就应该是一个实体。再比如借阅记录。有人觉得借书就是读者和图书之间加个关系在关系上挂个借书日期就行了。但借阅记录需要记录借书时间、还书时间、续借次数、逾期天数这些属性挂在关系上会非常别扭而且后续要查某个读者所有的借阅历史时关系上的属性很难独立查询。所以借阅记录必须独立成实体。3.2 关系类型的判断与转换E-R图里的关系有三种一对一、一对多、多对多。判断方法是从两端分别看。读者和借阅记录一个读者可以有多条借阅记录一条借阅记录只属于一个读者所以是一对多。图书和借阅记录一本书可以出现在多条借阅记录里被不同读者借过一条借阅记录只对应一本书也是一对多。所以读者和图书之间通过借阅记录形成了多对多关系。多对多关系在物理建表时必须拆成两个一对多这是E-R图向关系模型转换的核心规则。具体做法是多对多关系独立成一张中间表把两端实体的主键作为外键放进去再加上关系本身的属性。关系类型转换方式图书管理系统示例一对一任一端主键作为外键读者和读者证一对多多端加外键读者和借阅记录多对多独立中间表读者和图书通过借阅记录3.3 从E-R图到建表语句的实操画完E-R图之后转换成建表语句是水到渠成的事。我以借阅记录为例CREATE TABLE borrow_record ( id BIGINT PRIMARY KEY AUTO_INCREMENT, reader_id BIGINT NOT NULL COMMENT 读者ID, book_id BIGINT NOT NULL COMMENT 图书ID, borrow_date DATETIME NOT NULL COMMENT 借出时间, due_date DATETIME NOT NULL COMMENT 应还时间, return_date DATETIME DEFAULT NULL COMMENT 实际归还时间, renew_count INT DEFAULT 0 COMMENT 续借次数, status TINYINT DEFAULT 1 COMMENT 状态1借出中 2已归还 3逾期, FOREIGN KEY (reader_id) REFERENCES reader(id), FOREIGN KEY (book_id) REFERENCES book(id) ) COMMENT 借阅记录表;这里有个经验状态字段的设计要提前想清楚。我一开始只设计了借出中和已归还两个状态后来需求加了逾期和续借中只能改表结构。后来我养成了习惯在设计E-R图的时候就把状态流转画出来确保状态字段的枚举值覆盖所有可能的流转路径。注意E-R图阶段不要过早考虑性能优化。我见过有人在E-R图阶段就开始纠结要不要加冗余字段、要不要反范式结果图改来改去。E-R图先把逻辑模型画对物理优化留到建表的时候再考虑。4. 类图面向对象设计的骨架4.1 类之间的关系不只是继承类图里的关系有六种继承、实现、组合、聚合、关联、依赖。新手最熟悉的是继承但实际项目里用得最多的是组合和依赖。继承是is-a关系比如管理员继承用户。但继承层次过深会导致代码僵化我一般建议继承层次不超过三层。组合是contains-a关系比如订单包含订单项订单项不能脱离订单独立存在。聚合是has-a关系比如书架包含图书但图书可以脱离书架独立存在。依赖是uses-a关系比如借阅服务依赖库存服务但库存服务不作为借阅服务的成员变量。在图书管理系统里我通常这样组织类User抽象类Reader和Admin继承它Book实体类和Category是多对一关联BorrowRecord实体类关联Reader和BookBorrowService服务类依赖BookRepository和BorrowRecordRepositoryNotificationService工具类被BorrowService依赖4.2 用StarUML和IDEA生成类图的取舍热词里有人问staruml类图怎么画和idea生成类图这两种方式我都用过各有适用场景。StarUML适合从零开始设计。你可以先画类再补属性和方法再拉关系线。它的好处是自由度高可以画得很细包括方法的参数类型和返回值。缺点是画完之后代码还得手写图和代码容易脱节。IDEA的类图生成适合从已有代码反推设计。在IDEA里选中一个包右键选择Diagrams就能生成类图。它的好处是永远和代码同步改完代码重新生成就行。缺点是只能反映代码现状不能用来做前瞻性设计。我的做法是设计阶段用StarUML画核心类图确认设计意图开发完成后用IDEA生成完整类图作为文档存档。两者结合既保证了设计的前瞻性又保证了文档的准确性。4.3 类图里的has/is/use关系怎么标热词里有个类图has is use这其实是三种关系的通俗说法。is对应继承has对应组合或聚合use对应依赖。在类图里标注这三种关系的时候有几个实操细节继承用空心三角形加实线箭头指向父类。组合用实心菱形加实线菱形在整体那一端。聚合用空心菱形加实线菱形也在整体那一端。依赖用虚线箭头箭头指向被依赖的类。我见过有人把组合和聚合画反了导致代码里该用强引用的时候用了弱引用该用弱引用的时候用了强引用。判断方法很简单生命周期是否同步。订单项的生命周期和订单完全同步订单删了订单项也没了这是组合。图书和书架的生命周期不同步书架删了图书还在这是聚合。5. 时序图把调用链路画在时间轴上5.1 时序图的四个核心元素时序图只有四个元素对象、生命线、激活条、消息。对象在顶部横向排列生命线从对象向下延伸激活条表示对象正在执行操作的时间段消息在生命线之间传递。画时序图的关键是确定对象的粒度和消息的层次。对象粒度太细图会变得极其复杂粒度太粗又看不出调用细节。我的经验是以Service层的方法为基本单位Controller到Service算一条消息Service到Repository算一条消息Service之间的调用算一条消息。不要细化到每个getter和setter。以借书为例时序图的对象有Reader前端、BorrowController、BorrowService、BookService、BorrowRecordRepository、NotificationService。消息序列是Reader发起借书请求BorrowController接收并调用BorrowServiceBorrowService调用BookService检查库存调用BorrowRecordRepository创建借阅记录调用NotificationService发送通知最后返回结果。5.2 同步消息、异步消息、返回消息的画法同步消息用实线加实心箭头表示调用者等待被调用者返回。异步消息用实线加开放箭头表示调用者不等待返回。返回消息用虚线加开放箭头表示方法返回。在借书场景里BorrowService调用BookService检查库存是同步消息因为必须等库存检查结果才能决定下一步。BorrowService调用NotificationService发送通知可以是异步消息因为发送通知失败不应该影响借书主流程。提示异步消息在时序图里容易被忽略但它在实际代码里对应着线程池、消息队列或者事件机制。画时序图的时候把异步消息标出来能帮你在编码阶段就确定哪些操作需要异步处理。5.3 从时序图直接推导接口定义时序图的一个隐藏价值是它能直接推导出接口定义。每条消息对应一个方法调用消息的参数对应方法的入参返回消息对应方法的返回值。比如BorrowService调用BookService的检查库存消息可以推导出public interface BookService { BookStockDTO checkStock(Long bookId); }BorrowService调用BorrowRecordRepository的创建借阅记录消息可以推导出public interface BorrowRecordRepository { BorrowRecord save(BorrowRecord record); }我习惯在画完时序图之后直接把接口定义写出来作为编码的起点。这样能保证时序图和代码的一致性避免图是图代码是代码的脱节。6. 流程图业务逻辑的完整分支覆盖6.1 流程图各种框的含义与选用热词里有人问流程图各种框的含义这里统一说一下。矩形表示处理步骤菱形表示判断分支平行四边形表示输入输出圆角矩形表示开始或结束箭头表示流转方向。在实际业务流程图里最常用的是矩形和菱形。矩形里写操作描述菱形里写判断条件。判断条件的分支要标注是和否或者具体的条件值。我见过有人把所有步骤都画成矩形判断逻辑写在矩形里结果流程图看起来像一串文字完全失去了可视化的意义。判断必须用菱形这是流程图的铁律。6.2 借书流程的分支拆解以借书流程为例完整的分支包括开始输入读者ID和图书ID判断读者是否存在菱形判断图书是否存在菱形判断图书库存是否大于0菱形判断读者是否有逾期未还菱形判断读者借阅数量是否达到上限菱形创建借阅记录矩形更新图书库存矩形发送借阅成功通知矩形结束每个菱形都有两个分支一个是继续一个是返回错误信息。错误信息也要有对应的处理步骤不能直接结束。6.3 用ProcessOn和LogicFlow画流程图的体验热词里提到了processon流程图示例和vue3 用 logicflow 做一个类似dify的流程图。这两种工具我都用过。ProcessOn适合快速画图拖拽式操作模板丰富协作方便。缺点是自定义能力有限复杂流程图的排版有时候会乱。我一般用它画业务流程图和功能结构图因为这两类图对样式要求不高快速出图最重要。LogicFlow适合把流程图集成到自己的系统里。它是一个前端流程图库基于Vue或React都可以用。如果你需要做一个让用户自己配置流程的功能比如审批流配置LogicFlow比ProcessOn合适得多。它的缺点是学习成本高需要写代码来定义节点和连线规则。选哪个取决于你的场景画给人看的图用ProcessOn画给系统用的图用LogicFlow。7. 用例图与功能结构图需求边界的可视化7.1 用例图的参与者与用例识别用例图的核心是找对参与者和用例。参与者不一定是人也可以是外部系统或定时任务。用例是参与者通过系统完成的一个完整功能。在图书管理系统里参与者有读者、管理员、系统定时任务。读者的用例有登录、查询图书、借书、还书、续借、查看借阅历史。管理员的用例有管理图书、管理读者、处理逾期、生成报表。系统定时任务的用例有计算逾期、发送到期提醒。识别用例的时候我习惯用**参与者动词名词**的格式来写。比如读者借图书、管理员添加图书、系统计算逾期。如果一个用例写不成这个格式说明它可能不是一个完整的用例或者参与者没找对。7.2 功能结构图的模块划分逻辑功能结构图是把系统功能拆成树状结构。顶层是系统名称第二层是模块第三层是子功能。图书管理系统的功能结构图图书管理系统用户管理模块读者注册读者登录读者信息维护图书管理模块图书录入图书编辑图书下架借阅管理模块借书还书续借逾期处理查询模块按书名查询按作者查询按分类查询系统管理模块权限管理日志管理数据备份模块划分的原则是高内聚低耦合。同一个模块内的功能应该紧密相关不同模块之间的依赖应该尽可能少。如果两个模块之间的调用非常频繁可能说明它们应该合并或者边界划错了。7.3 用例图和功能结构图的配合使用用例图和功能结构图是互补的。用例图回答谁做什么功能结构图回答系统分哪些部分。用例图里的每个用例都应该能在功能结构图里找到对应的模块。功能结构图里的每个模块都应该能追溯到至少一个用例。如果发现某个用例在功能结构图里找不到对应模块说明模块划分有遗漏。如果某个模块在用例图里找不到对应的用例说明这个模块可能是过度设计或者用例识别不完整。8. 架构图从代码结构到部署拓扑8.1 架构图的层次与视角架构图有很多种画法我一般分三个层次来画逻辑架构图、技术架构图、部署架构图。逻辑架构图关注系统的分层和模块依赖比如Controller层、Service层、Repository层、Entity层。技术架构图关注技术选型比如Spring Boot、MySQL、Redis、RabbitMQ。部署架构图关注物理部署比如几台服务器、怎么负载均衡、数据库是主从还是集群。不同角色关注不同的架构图。开发关注逻辑架构图架构师关注技术架构图运维关注部署架构图。如果只画一张架构图很容易顾此失彼。8.2 图书管理系统的架构图实例以图书管理系统为例逻辑架构图是这样的表现层Web页面、移动端页面接口层RESTful API、参数校验、权限拦截业务层用户服务、图书服务、借阅服务、通知服务数据层MySQL、Redis缓存、Elasticsearch搜索基础设施日志、监控、配置中心技术架构图会标注具体的技术栈Spring Boot做Web框架MyBatis做ORMRedis做缓存RabbitMQ做异步通知Nginx做反向代理。部署架构图会画两台应用服务器做负载均衡一台MySQL主库一台从库一台Redis服务器一台RabbitMQ服务器。8.3 架构图不是画一次就完事架构图是活文档需要随着系统演进不断更新。我见过很多项目的架构图还是三年前画的和现在的系统完全对不上。新来的同事看架构图以为系统是单体应用结果一看代码发现已经拆成微服务了。我的做法是每次做重大架构调整的时候同步更新架构图。不需要画得很精细但关键的分层、模块、技术选型必须准确。架构图的价值在于让新人和外部团队快速理解系统全貌如果它不准确反而会误导人。9. 画图工具选型与协作避坑9.1 各工具的适用场景对比工具适合画的图优点缺点StarUML类图、时序图、用例图UML规范画法严谨界面老旧协作弱ProcessOn流程图、功能结构图在线协作模板多复杂图排版容易乱Draw.io架构图、部署图免费导出格式多样式需要手动调IDEA Diagrams类图从代码生成和代码同步不能前瞻设计LogicFlow可交互流程图可集成到系统学习成本高Visio各类图功能强大收费笨重选工具的原则是画给人看的图选协作方便的画给系统用的图选可编程的从代码反推的图选自动生成的。9.2 团队协作中的图版本管理设计图最大的坑不是画得不好而是版本混乱。我经历过一个项目三个人分别画了时序图、类图、流程图结果三张图里的类名不一致时序图里叫BorrowService类图里叫LendService流程图里叫BookBorrowManager。联调的时候大家对着三张图吵架浪费了大量时间。解决办法很简单建一个统一的术语表把所有实体名、服务名、模块名定下来所有人画图的时候从术语表里取名字。术语表放在项目Wiki里改名字的时候同步更新所有图。另外设计图应该和代码一起做版本管理。我习惯把设计图的源文件比如.drawio、.mdj放在项目的docs/design目录下和代码一起提交。这样每次代码变更的时候能顺便检查设计图是否需要更新。9.3 画图的时间投入产出比画图要花时间但不是所有图都值得花同样的时间。我的经验是E-R图和类图值得花时间精雕细琢因为它们是代码的蓝图画错了后面全错。时序图只画核心流程非核心流程用文字描述就行。流程图要画全分支但可以用简化的画法不必每个步骤都画得一模一样。用例图和功能结构图快速出图即可它们主要是用来对齐认知的不需要太精细。架构图在项目初期画一版粗的后期再细化。注意不要为了画图而画图。如果一段逻辑用文字描述更清楚就不要强行画图。图的价值在于可视化复杂关系如果关系本身很简单画图反而是浪费时间。10. 我在实际项目中的几点体会做了这么多年系统设计踩过的坑比画过的图还多。分享几个我印象最深的教训。第一个教训是不要跳过E-R图直接建表。我曾经接手一个项目前任开发直接写了建表语句没有E-R图。我花了三天时间逆向推导实体关系发现有两张表其实是多对多关系但被错误地设计成了一对多导致数据冗余和一致性问题。后来重构的时候光数据迁移就花了一周。E-R图可能只花你两个小时但能省掉后面几十个小时的返工。第二个教训是时序图要画异常流程。大多数人画时序图只画正常流程但实际项目中异常流程才是最容易出问题的地方。比如借书的时候库存检查通过但创建借阅记录的时候数据库连接超时了这时候库存已经扣减了怎么办这种问题只有在画时序图的时候把异常分支画出来才能在编码阶段就设计好补偿机制。第三个教训是架构图要标注技术债务。我现在的习惯是在架构图上用红色标注已知的技术债务比如这个模块的缓存策略需要优化、这个服务的数据一致性方案待完善。这样每次看架构图的时候都能提醒自己还有哪些坑没填。技术债务不可怕可怕的是忘了它的存在。最后一个体会是关于工具的心态。工具只是手段设计思维才是核心。我见过有人用着最先进的工具画出来的图却逻辑混乱也见过有人用纸笔画的草图把系统设计得清清楚楚。不要纠结于工具的选择先把设计思路理清楚工具自然能找到合适的。