尧图网站设计 尧图网站设计YAOTU DESIGN
ARTICLE DETAIL

资讯详情

深耕网站设计与一线实操的经验洞察。

系统分析与设计实战:需求分析、UML建模与架构落地指南

系统分析与设计实战:需求分析、UML建模与架构落地指南 1. 系统分析与设计到底在解决什么问题系统分析与设计这门东西最尴尬的地方在于考试的时候背得滚瓜烂熟一到真做项目就全还给老师了。我前后参与过七八个从零到一的后台系统也接手过几个别人留下的半成品烂摊子越往后越发现真正决定一个系统能不能活得久、改得动的往往不是用了什么框架、什么中间件而是分析和设计阶段有没有把该想清楚的事情想清楚。框架可以换数据库可以迁但如果一开始业务边界就是糊的、模块依赖是乱的后面每加一个需求都像在拆迁现场上盖楼。这篇内容我想按自己实际干活的顺序来梳理一遍不搞那种名词解释大礼包而是把为什么这么选什么场景用什么图哪里最容易翻车讲透。适合两类人看一类是正在学这门课、被 UML 和 DFD 绕晕的学生另一类是已经工作两三年、发现自己天天写代码但说不清楚系统该怎么拆的开发者。前者可以把它当成知识点的落地注解后者可以当成一次自查清单。1.1 从能跑就行到改得动的分水岭刚入行那会儿我的判断标准特别朴素功能点了能出结果就算完成。后来系统慢慢长大问题全冒出来了——同一个订单状态订单模块里叫 status结算模块里叫 orderState报表里又有第三种叫法改一个字段的校验规则要在五个地方同步改漏一个就出线上问题。这时候我才明白系统分析与设计真正的交付物不是代码而是共识对边界的共识、对数据含义的共识、对交互时序的共识。有句话我经常跟新人说代码是写给人看的顺便给机器执行。一个系统能跑靠的是机器执行一个系统能改靠的是人看得懂。系统分析与设计就是那个让人看懂的过程——它把脑子里的模糊想法翻译成一张张图、一份份规约、一套套接口定义最后才落到代码上。所以这门知识的价值不在于图有多漂亮而在于它逼你提前回答那些你本能想跳过的问题这个功能谁来触发失败路径怎么办数据从哪来、到哪去、中间存不存两个人同时操作同一条记录会怎样这些问题在编码阶段才发现返工成本是设计阶段的十倍不止。1.2 分析、设计、架构三者的边界与交付物很多人把这三个词混着用其实它们关注的东西不一样交付物也不一样。我用一个装修的类比来说明分析是问清楚你要几间房、做饭多不多、有没有老人设计是画水电图和家具布局图架构则是决定承重墙能不能动、走明线还是暗线、地暖还是暖气片。分析阶段的产出核心是需求规约和业务模型。它回答要做什么不碰用什么技术做。这里的关键动作是把用户嘴里的我想要一个方便管理的东西拆成一条条能验证的条目。设计阶段的产出是模块划分、接口定义、数据结构、交互时序回答怎么做。而架构是设计里层次最高的那部分决策回答整体的骨架长什么样、关键的非功能需求靠什么保证。表里我把三者的区别列清楚这个对照在面试和实际评审里都用得上维度分析架构详细设计回答的问题要做什么骨架怎么搭每个模块怎么实现核心产物需求规约、用例、领域模型分层图、部署图、技术选型类图、时序图、接口定义、表结构变更成本最低高中等主要风险需求遗漏或误解选型错误、扩展性不足逻辑错误、耦合过高验收方式需求评审、可追溯矩阵架构评审、非功能压测代码评审、单元测试这个边界不是死板的。实际项目里迭代很快的时候分析和设计会交替进行但逻辑上一定是先搞明白问题再决定方案。我见过最典型的翻车就是跳过分析直接设计团队花两周把技术架构画得漂漂亮亮结果业务方一句我们的审批流程其实是三级不是两级整套设计推翻重来。2. 需求分析把所有我觉得翻译成可验证的条目需求分析是整门课里最不技术、但最容易决定成败的部分。它不需要你懂什么高深算法需要的是把模糊语言翻译成精确语言的能力。我总结下来翻译的过程分三步先分清需求类型再选择合适的建模方式表达最后用可追溯的方式管理起来。2.1 功能性需求与非功能性需求的拆解方法功能性需求好理解就是系统要提供哪些行为比如用户可以按手机号搜索订单。它天然适合用动词短语来描述谁对什么做了什么得到什么结果。真正容易被忽略的是非功能性需求也就是那些描述做到什么程度的要求。它不写出来系统也能跑但一到真实压力下就崩。常见的几类我列一下这个清单我每次做需求梳理都会过一遍性能响应时间、吞吐量、并发用户数。别写要快要写95% 的查询请求在 500ms 内返回。容量数据量级、增长速率、保留周期。是十万条还是十亿条设计完全两回事。可用性允许的停机时间、降级策略、故障恢复时间。安全性认证方式、权限粒度、敏感数据的处理要求。可维护性日志规范、监控指标、配置化程度。兼容性需要支持的客户端版本、浏览器、数据格式。拆解的时候有个技巧把每条非功能需求都绑定到一个可测的指标上。我见过一份需求写着系统应具备良好的扩展性评审时我问良好是什么标准没人答得上来。后来改成新增一种支付渠道的接入工作量不超过 3 人天且不需要修改订单核心逻辑这条需求立刻就变得可验证、可验收了。注意非功能需求如果写成形容词等于没写。评审时凡是看到良好高效友好这类词都要当场追问成具体数字或具体场景。2.2 用例图、用例规约与用户故事的取舍表达需求有三套主流工具用例图、用例规约、用户故事。它们不是互相替代的关系而是粒度不同。用例图解决的是范围和角色问题。它一眼就能看出系统有哪些参与者、每个参与者能触发哪些用例、哪些用例被多个角色共享。我在项目启动会上必画一张就为了跟业务方确认这个功能到底有没有这个人用。画的时候注意两点一是参与者是角色不是具体的人二是用例是完整的业务目标而不是单个操作步骤。登录算不算用例有争议我倾向于把登录当成使用系统这个用例的前置条件除非它本身有复杂的业务价值。用例规约是把一个用例写细通常包含前置条件、主成功场景、扩展场景、后置条件。很多人嫌它啰嗦但扩展场景才是真正值钱的地方。主流程谁都会写异常路径才是 bug 的温床。举个例子提交订单的主流程五步写完扩展场景却可能有十几条库存不足怎么办、优惠券失效怎么办、支付超时怎么办、重复提交怎么办。我习惯在写规约时强迫自己至少列五条扩展场景列不满就说明还没想透。用户故事则是敏捷里常用的轻量表达格式是作为某角色我希望做某事以便获得某价值。它的优势是贴合用户视角劣势是容易漏掉非功能和边界。我的做法是用用户故事做需求池管理用用例规约做详细设计前的补充。两者配合既保持了灵活性又不至于在边界情况上掉链子。2.3 需求可追溯矩阵与评审需求做完不是结束而是管理开始。需求可追溯矩阵Requirements Traceability Matrix听着很重其实就是一个表把需求 ID 和它的来源、对应的设计模块、对应的测试用例连起来。它的价值在变更时体现得淋漓尽致。线上出了个 bug通过矩阵能立刻反查这条逻辑对应哪条需求、当初是谁提的、相关测试用例有没有覆盖。我做重构项目时矩阵帮我快速圈出了哪些需求其实已经没人用了直接砍掉三成历史包袱。评审环节我建议分两轮。第一轮是业务评审只问这是不是你要的不谈技术第二轮是技术评审只问这个能不能做、有没有歧义、边界是否完整。两轮混在一起开业务方会被技术细节带偏技术方会被业务语言干扰效率极低。3. 建模工具箱什么场景用哪张图建模是系统分析与设计里最容易为画而画的部分。教科书上动不动列出十几种图实际工作中真正高频使用的也就五六种。我的原则是图的目的是沟通不是炫技一张图讲不清的事往往不是图不够而是问题没拆开。3.1 数据流图与ER图结构化分析的看家本领数据流图DFD适合描述数据在系统里怎么流动。它有两个层次顶层图把整个系统当成一个黑盒只画外部实体和输入输出逐层分解图则把加工过程拆开一直到足够细为止。我常用它跟业务方梳理流程因为业务人员对单据从哪来、经过谁的手、变成什么这种叙述天然有感觉。画 DFD 有个铁律父图和子图的数据流必须平衡。父图里进出一个加工的数据流在子图里必须都能找到对应。我刚学的时候老犯这个错父图画了三个输入子图只画了两个一看就是漏了。这个平衡检查是最有效的自查手段。ER 图则是数据建模的基础描述实体、属性和联系。它和后面的表结构设计直接对应所以我在做 ER 图时会顺便把三个信息标记上主键、是否可空、基数一对一、一对多、多对多。多对多关系在物理设计时必须拆成中间表这一点在 ER 图上提前标出来能省掉后面很多返工。DFD 和 ER 图的关系可以这样理解DFD 关注动数据怎么流转ER 图关注静数据怎么存。两者结合一个业务系统的面貌就基本清晰了。3.2 UML 静态视图类图、对象图、包图面向对象方法里类图是最核心的静态视图。它描述类、属性、方法以及类之间的关系。关系有六种依赖、关联、聚合、组合、泛化、实现。很多人画类图只画关联和泛化但其实聚合和组合的区分在领域建模里非常重要因为它决定了生命周期归谁管。举个实际例子订单和订单项是组合关系订单没了订单项必须跟着消失而订单和客户是关联关系订单删了客户还在。这个区别直接影响数据库的外键设计和级联删除策略。我在代码评审时经常用这个点考人如果订单和订单项画成了聚合那删除订单时订单项该不该保留想清楚这个设计就清楚了。对象图是类图的实例快照实际用得不多主要在解释复杂数据结构时用。包图用来表达模块分组和依赖关系在架构层面的作用更大。我习惯用包图来做依赖方向的自查画出来之后看有没有循环依赖有的话说明模块划分出了问题得回去重新切。3.3 UML 动态视图时序图、活动图、状态机图动态视图描述系统运行时的行为三张图各有分工。时序图表达对象之间按时间顺序的消息交互是做接口设计和联调时最实用的图。我通常会在设计一个新接口时画一张简化的时序图标清楚调用方、服务方、数据库、缓存之间的交互次序。很多并发问题就是在这个阶段被发现的比如先查缓存再查数据库还是反过来、失败时要不要回滚、幂等键放在哪一层。活动图表达业务流程和控制流适合描述有分支、并行、循环的复杂流程。审批流、状态流转这类需求用活动图比用文字描述清楚十倍。状态机图专门描述单个对象的状态变迁。订单、工单、审批单这类有明确状态的生命周期对象我都建议画一张。它的关键价值在于逼你把所有非法跃迁堵死一个已取消的订单能不能再变成已支付从状态机图上一眼就能看出有没有漏掉守卫条件。这类 bug 在生产环境特别难查因为它往往是数据被人为改坏或者并发导致的。3.4 建模的够用原则与常见画错点我见过太多团队在建模上走两个极端要么完全不画图全靠口头对齐要么画了几十张精细到属性的图结果没人看改代码时图早就过期了。我的建议是按需建模够用即止。判断标准很简单这张图能不能帮你在评审时少吵一次架、在编码时少返一次工能就画不能就是自嗨。常见的画错点我整理成一张速查表都是在真实项目里踩过的问题现象根本原因修正做法类图里全是 getter/setter把实现细节当设计只保留业务方法属性按业务含义命名时序图画出所有内部调用粒度太细失去沟通价值只画跨模块的关键消息内部实现省略状态图缺少取消、超时等终止态只考虑了正常生命周期强制补全异常出口和终态包图存在循环依赖模块职责划分不清抽出下层公共模块或引入事件解耦用例图把增删改查都当用例混淆业务目标和操作用例只保留有完整业务价值的场景心得图是活的。我习惯在图旁边写一个最后更新日期 对应代码分支一旦发现图跟代码对不上要么改图要么删图绝不留下过期文档污染后来人的判断。4. 从分析到设计架构与详细设计怎么落地分析做完接下来是把要做什么翻译成怎么做。这一步的难点不在于会不会用某个框架而在于决策的取舍分层分几层、数据要不要冗余、接口同步还是异步、缓存加在哪一层。每一个选择都有代价关键是知道自己放弃了什么。4.1 分层与依赖方向分层是最基础也最有效的设计手段。经典的四层是表现层、应用层、领域层、基础设施层。它解决的核心问题是依赖方向必须单向也就是上层依赖下层下层不知道上层的存在。为什么这点这么重要因为依赖方向决定了修改的影响范围。如果领域层反过来依赖了表现层那你改一个接口参数可能要动到业务核心逻辑。我接手过一个项目业务逻辑里直接 new 了 HTTP 请求对象导致这部分逻辑完全没法单元测试。这就是依赖方向反了。实际操作时领域层需要通过接口访问数据库、消息队列这些外部资源而不是直接依赖具体实现这就是依赖倒置的用法接口定义在领域层实现在基础设施层。这样领域层保持纯净外部资源可以随时替换。层次职责常见错误表现层参数校验、协议转换、返回封装写业务判断应用层编排流程、事务边界、权限校验塞入领域规则领域层核心业务规则、状态变迁依赖具体数据库或框架基础设施层持久化、外部服务调用、缓存承载业务逻辑这张表我贴在自己电脑旁边写代码前扫一眼能省掉很多这行代码该放哪的纠结。4.2 数据库设计范式与反范式的取舍数据库设计是详细设计里最考验功力的部分。教科书强调三范式但真实系统里到处是反范式。理解两者为什么并存比背范式定义重要得多。范式化的核心目的是消除冗余、保证一致性。同一份数据只存一处更新时就不会出现多份不一致。所以涉及频繁变更、对一致性要求高的核心数据我坚持范式化设计。反范式则是为了查询性能和实现简单。比如订单表里冗余一个客户名称虽然客户改名后历史订单会显示旧名字但如果业务上本来就要求订单快照这个冗余反而是正确设计。又比如报表统计把计算结果预计算存下来用空间换时间是常见做法。我的判断方法是问三个问题这份数据变更频率高不高不一致会造成业务损失吗查询压力和写入压力哪边更大三个问题答完范式还是反范式基本就定了。索引设计也要在详细设计阶段定下来而不是等线上慢了再加。原则是为查询条件建索引为排序字段考虑联合索引写多读少的表要克制索引数量。索引不是越多越好每个索引都会拖慢写入并占用空间。4.3 接口契约与异常路径设计接口设计最容易犯的错是只管成功路径。一个健壮的接口契约至少要定义清楚四件事入参的校验规则、成功返回的结构、业务失败的返回约定、系统异常的处理方式。我见过太多接口成功时返回{code:0, data:...}失败时有时返回{code:1, msg:...}有时直接抛 500前端根本没法统一处理。正确做法是约定一个统一的响应外壳业务错误和系统错误分层处理。业务错误如库存不足是预期内的用明确的错误码返回系统错误如数据库连接失败是不可预期的走异常通道并记录日志。// 统一响应结构示例 public class ResultT { private int code; // 0 成功非 0 为业务错误码 private String message; // 面向调用方的可读信息 private T data; // 成功时的业务数据 public static T ResultT ok(T data) { ResultT r new Result(); r.code 0; r.data data; return r; } public static T ResultT fail(int code, String msg) { ResultT r new Result(); r.code code; r.message msg; return r; } }错误码的设计也有讲究。我习惯按模块分段1000 段是用户模块2000 段是订单模块以此类推。这样日志里一看到错误码就知道大概是哪块出的问题排查效率高很多。面向用户的提示要友好面向开发者的日志要详细两者分开不要混在一起。4.4 设计模式的落点设计模式在系统分析与设计里经常被过度神化好像不用几个模式就不够专业。我的观点很明确模式是解决问题的结果不是设计的目标。先有痛点再想模式。举几个真实的落点。策略模式适合处理多种算法可替换的场景比如不同支付渠道的对接每个渠道的实现细节不同但对上层暴露统一接口。工厂模式适合对象创建复杂、需要集中管理的场景比如根据类型创建不同的消息处理器。观察者模式适合一对多的状态通知比如订单状态变化后需要通知库存、积分、消息三个模块。反面案例也说一个我见过有人为了解耦给每个 Service 都套了一层接口加一层实现结果每个接口只有一个实现类改个方法要跳三次。这不是解耦是增加阅读成本。只有当存在真正的多实现、多变化点时抽象才有价值。5. 常见问题与排查技巧实录前四部分讲的是应该怎么做这一部分讲讲实际会出什么岔子。系统分析与设计的问题很少是技术难题更多是沟通、节奏和管理层面的。5.1 需求变更与范围蔓延需求变更是常态怕的是一边开发一边无限加需求。我应对的经验是把变更显性化任何需求变更都记录在案标注影响范围涉及哪些模块、需要多少工时、会影响哪条已有需求然后让决策者明确拍板做还是不做、这次做还是下次做。范围蔓延最隐蔽的形式是顺便。开发过程中有人说顺便把这个也加上吧听起来工作量很小但每个顺便都会带来测试、文档、兼容性的连带成本。我的做法是设置一个变更缓冲小改动攒到一起按批次处理而不是零散插入当前迭代。还有一个技巧区分必须满足和可以协商。需求评审时把每条需求标注优先级核心路径必须做锦上添花的功能排后面。这样资源紧张时砍什么一目了然不会砍到关键功能上。5.2 图表与文档的实际用途图表过期是团队协作里的慢性病。我的解决方案是把图放进代码仓库和代码一起评审。改一个核心接口对应的时序图必须同步更新否则评审不通过。这比单独维护一份文档有效得多因为文档一旦脱离工作流就注定被遗忘。另一个思路是少而精。一个中型系统我通常只维护这几份图一张整体架构分层图、一张核心领域模型类图、三到五张关键流程时序图、一张订单类的状态机图。其余细节交给代码和注释。图少了维护成本低大家才愿意更新。5.3 高频问题速查表我把这些年被问得最多、也踩得最狠的问题整理成一张表方便快速对照症状可能原因排查与解决思路线上状态数据错乱状态跃迁缺少守卫条件补状态机图所有变更走统一入口校验改一个功能牵动多个模块模块边界不清、数据被多处修改梳理数据所有权一份数据只有一个模块可写接口联调反复扯皮契约没定义清楚异常路径缺失先定契约文档再开发明确错误码规范需求频繁返工分析阶段跳过业务确认增加业务评审环节输出可追溯矩阵系统越改越慢缺少索引或存在隐式全表扫描对照慢查询日志逐个补索引控制索引数量图文档没人看与代码脱节、更新不及时图入仓库、随代码评审同步更新并发下数据重复缺少幂等设计引入唯一键或幂等表关键操作去重这张表我建议打印出来贴在工位上遇到问题先对照能解决一大半的日常困惑。6. 一些个人体会做系统分析与设计这些年我最大的感受是这门功夫的进步不体现在画出多复杂的图而体现在能提前预判多少坑。刚开始我总是急于进入编码觉得画图是浪费时间后来被返工教训多了才慢慢养成先把问题想清楚再动手的习惯。现在我做一个中等规模的功能通常花两成时间梳理需求和建模剩下八成才是设计和编码但整体交付反而比以前快。如果让我给正在学这门知识的人一条建议那就是找一个小而完整的真实需求从需求分析一路做到表结构设计中间不要跳过任何一步。哪怕只是一个图书借阅或者会议室预订的小系统完整走一遍比你背十遍 UML 语法都有用。理论只有在被应用过一次之后才会变成你自己的东西。最后分享一个我一直在用的自检习惯。每次设计完一个模块我会问自己三个问题这个模块的职责能不能用一句话说清它依赖了谁、谁又依赖了它如果业务规则明天变了我需要改几个地方这三个问题答得干脆设计基本就是合格的答得含糊那就说明还有地方没想透得回去重新拆。
返回列表