免费获取学习方案
ARTICLE DETAIL

资讯详情

深耕编程基础知识与建站技术分享的一线实战洞察。

总体设计、概要设计、详细设计:三层设计图到底该怎么画?

总体设计、概要设计、详细设计:三层设计图到底该怎么画? 上周帮人评审一个课程设计的文档包打开压缩包里面有三个文件总体设计.doc、概要设计.doc、详细设计.doc。第一份里面只有一张架构图截图第二份是模块图的截图第三份更离谱从网上扒了一套时序图模板连类名都没改。这种情况我见了太多次了。不少同学和刚入行的朋友都把这三类设计图当成答辩前凑数的材料其实它们是三层不同粒度的思考工具——用对了编码阶段根本不会迷路用错了画得再漂亮也只是废纸。今天不聊理论就按我这些年实际写文档、评审文档、带项目的经验把总体设计、概要设计、详细设计这三层图到底该画什么、画多细、用什么工具、怎么避免返工一次性说透。1. 三类设计图的分工先搞清楚每张图在回答什么问题很多人分不清三类设计的边界本质上是没有理解它们各自的服务对象和回答的问题。我用一个建房子的类比来说你一下就通了总体设计是建筑方案图。它回答的问题是这栋楼盖在哪、盖几层、每层用来干什么、水电暖怎么走、楼和楼之间的间距是多少。对应的软件产物就是系统架构图、模块划分图、技术选型、部署方案。它的读者是项目负责人、架构评审人、客户方他们关心的是你这个系统靠不靠谱、合不合理不关心某个按钮点击后调用哪个函数。概要设计是施工图。它回答的问题是每个房间的门窗开在哪、墙体多厚、管线从哪个墙里穿。对应的软件产物是模块接口定义、数据库表结构设计、模块间的交互流程。读者是开发团队内部大家靠这份文档对齐我提供什么接口、你调用什么接口、数据存哪张表。详细设计是钢筋配筋图。它回答的问题是这根梁里用几号钢筋、绑扎间距多少、混凝土标号多高。对应到软件就是类图、时序图、状态图、关键算法伪代码。读者是真正写代码的开发者自己以及未来的维护者目的是把一个模块做到照着画就能写的程度。这三者不是三份独立的文档而是一条逐层放大的透镜总体设计从万米高空看全貌概要设计降到一千米看模块边界详细设计落到地面看每一行逻辑。这里有个很实际的判断标准**如果在编码时你需要临时跟同事确认你这个接口参数到底是什么类型说明概要设计没做到位如果写一个复杂的状态流转逻辑时还要现想分支条件说明详细设计没做到位。**反过来如果在总体设计阶段就纠结某个类的属性列表那是浪费时间因为那根本不是这一层该有的粒度。2. 总体设计先把系统的骨架搭对别急着画界面2.1 总体设计必须产出的四样东西我评审过很多份总体设计凡是能一次通过的基本都包含以下四样产出缺一样到后面都会出问题**第一系统上下文图。**这张图非常简单但八成的人都漏了。它画的是你的系统作为一个整体和外部实体用户、管理员、第三方支付、短信服务、其他系统之间的关系。别小看这张图它能逼你想清楚系统边界在哪。我曾经带过一个项目组员在总体设计里画了一个数据同步模块但整个系统根本没有外部数据源这个模块就是凭空想象出来的直接砍掉省了两周工作量。第二架构分层图。就是最常见的展示层、业务逻辑层、数据访问层那种分层图。注意画分层图不是画三个方框叠起来而是要标清楚每一层之间通过什么方式通信。HTTP接口RPC消息队列共享数据库这决定了后续概要设计里接口定义的方式。**第三模块划分图。**把系统拆成若干个高内聚、低耦合的模块。这里要写下每个模块的职责说明哪怕只有一句话。比如用户模块负责注册、登录、个人信息管理一句话就能避免后面两个模块抢同一块功能的破事。**第四技术选型表。**用表格列出每个技术点选了什么、为什么选它、有没有备选方案。很多人不写选型理由这是大忌。你选了Spring Boot评审肯定会问为什么不用SSH你得能说出理由来。2.2 技术选型的真实决策逻辑技术选型这块我发现学生和刚入行的朋友普遍有两个极端一个是无脑用最新最火的技术栈另一个是只会用课上学过的旧技术。选型不是追新也不是守旧而是考虑四个约束的组合团队熟悉度、项目规模、部署环境、维护成本。举一个真实的例子。我之前做一个校园二手交易平台就是课程设计和毕设里最常见的题目技术栈选型时有人提议用微服务拆六个服务——用户服务、商品服务、订单服务、支付服务、消息服务、后台管理。听着很唬人对吧但这就是典型的过度设计。一个学期内要交付的项目并发量撑死几十个人同时用拆六个微服务意味着要维护六套配置、六套部署脚本、还要处理服务间通信和分布式事务。最后我拍板用单体架构模块化代码结构后端Spring Boot前端Vue数据库MySQL加一个Redis做缓存和会话管理。理由很简单**单体架构完全够用模块化结构保留了将来拆分的可能性但不需要现在付出分布式系统的复杂度。**这就是为当前需求设计为未来演化留余地。选型决策要写清楚比如MySQL为什么不用PostgreSQL——因为这个项目是课程设计组员对MySQL更熟备份恢复资料多出了问题容易查到解决方案Redis为什么需要——因为二手商品的热门列表要缓存不然每次查询都打MySQL性能扛不住。2.3 部署视图最容易被忽略的图还有一个高频遗漏部署图。很多总体设计画完架构分层和模块划分就结束了完全不提系统部署在哪。但部署方式直接影响很多设计决策。还是上面那个二手交易平台的例子——如果是部署在学校机房的一台服务器上那就没有必要设计什么负载均衡、CDN如果有个模块依赖了外部的图片存储服务那就要在总体设计里画清楚内网和外网的访问路径。部署图不需要多复杂画清楚服务器有几台、每台部署了什么服务、对外开放哪些端口、数据存在哪、备份策略是什么。我见过一个真实的返工案例有个项目在概要设计阶段设计了基于WebSocket的实时消息推送结果到部署的时候发现学校服务器防火墙不开WebSocket端口整个消息模块白设计最后改成轮询才过关。如果在总体设计的部署图里提前画一遍端口规划这个坑根本不会踩。3. 概要设计模块之间的契约比代码更早定型总体设计定完骨架进入概要设计阶段。这一层的核心任务就一个把模块之间的接口、数据格式、交互流程全部定义清楚明确到什么程度呢——每个模块的开发可以并行开工互相不等待。3.1 模块划分的边界感先回到总体设计产出的模块划分图概要设计要对每个模块做进一步细化。比如校园二手交易平台总体设计里分了用户、商品、订单、消息、后台管理五个模块。概要设计阶段要做的是把每个模块对外提供哪些功能点列出来形成一张模块-功能点清单。这里最常见的错误是模块之间职责不清。我评审过一份概要设计里面商品模块负责商品发布和商品搜索但用户模块里又有一个我的发布列表功能两个模块都要查商品表。这就在概要设计阶段埋下了数据访问冲突的雷。正确的做法是**一个数据实体只归一个模块负责写操作其他模块要读写这个实体的数据只能通过这个模块的接口。**商品表只能由商品模块写入我的发布列表在用户模块但用户模块通过调用商品模块的查询接口来拿数据而不是直接操作商品表。3.2 接口设计定的是数据契约接口定义是概要设计的重头戏。接口设计分两类前端向后端请求的HTTP接口以及后端模块之间互相调用的服务接口。很多人只设计前者忽略了后者结果编码阶段两个后端开发互相问你那个方法叫什么名字、参数顺序是什么。接口定义要包含接口名称、请求方式、路径、请求参数名称、类型、是否必填、约束、返回结果的数据结构、错误码约定。我习惯用表格列出来比如商品搜索接口项内容接口路径/api/products请求方式GET参数keyword(可选)、categoryId(可选)、page(默认1)、pageSize(默认10)、sortBy(可选newest/price)返回结构{ code: 0, data: { total: 52, items: [ { productId: 1001, title: 九成新《算法导论》, price: 35.00 } ] } }错误码40001参数非法、50001系统繁忙为什么接口设计要到这个粒度因为在前后端分离开发的项目里前端和后端是并行推进的。前端拿这份接口清单就可以开始写页面了先mock数据就行后端按接口清单去实现。如果接口定义不完整两边开发到一半就对不上反复扯皮。模块间服务接口同样要定义清楚。比如订单模块在下单时需要扣减商品库存。那就要定义商品模块提供的deductStock(productId, quantity)方法标注清楚这个方法在库存不足时抛什么异常走什么事务边界。这个不约定好就会出现一种典型的bug订单创建成功但库存没扣掉或者库存扣了但订单创建失败导致数据不一致。3.3 数据库设计在这一阶段的深度数据库设计在概要设计阶段要做到表级设计列出所有表名、每张表的字段名、类型、约束、主键外键关系但不需要精确到每个字段的索引策略和SQL怎么写。还是用二手交易平台的订单模块举例核心表至少有三张订单主表order订单明细表order_item支付记录表payment。设计订单主表时要定清楚订单号用什么规则生成时间戳用户ID随机数还是数据库自增订单状态字段存什么用一个int存状态码还是varchar存中文我的建议是int存状态码枚举在代码里统一管理存中文后期改状态名要写脚本。金额字段用什么类型这里有个几乎所有人都会犯的错金额用float。金额必须用decimalfloat的精度问题在涉及钱的时候是致命的。推荐 decimal(10,2)能存的最大金额是99999999.99一般场景够用了。3.4 概要设计评审看什么一份概要设计拿出来评审我作为评审人会重点关注三件事第一**接口清单是否完整覆盖了所有前端页面。**我会拿需求文档里的页面清单对比接口清单看有没有页面缺接口。缺接口意味着开发到一半才发现做不了得返工加这是最伤进度的。第二**数据流是否能闭环。**拿一个核心业务场景走一遍比如用户下单从前端提交订单到订单模块创建记录到扣库存到生成支付流水到用户支付后回调更新状态——每一步需要哪些接口、哪些表串一遍能不能走通。第三**有没有遗漏异常分支。**接口定义里只设计了成功路径的返回没人想到库存不足、余额不够、重复提交这些异常情况。评审时我会专门问库存扣减失败怎么办用户下单后不支付订单什么时候过期取消。这些异常分支如果不在概要设计阶段定清楚到了编码阶段就是各种紧急补丁代码里到处都是临时加的判断逻辑项目质量根本没法保证。4. 详细设计让编码变成照图施工而不是边写边想4.1 类图定的是代码骨架详细设计的第一件事是把模块内部的类结构画出来。很多人以为类图就是把JavaBean的属性列一遍其实类图的核心价值是把职责分配到具体的类并且把类之间的依赖关系定下来。以二手交易平台的订单模块为例详细设计阶段的类图至少要有这样一组类OrderController接收HTTP请求做参数校验不写业务逻辑OrderService/OrderServiceImpl业务逻辑层处理下单、取消、支付回调等核心流程OrderDao/OrderMapper数据访问层封装对order表和order_item表的增删改查OrderEntity订单实体的数据对象OrderDTO订单业务的传输对象StockServiceClient调用商品模块扣减库存的服务客户端画类图时要把关键方法也标出来比如OrderService至少要有createOrder(CreateOrderRequest req)、cancelOrder(Long orderId, Long userId)、handlePayCallback(PayCallbackRequest req)三个核心方法。方法签名在这里定好编码时就可以直接对着写了。注意一个常见的坑**详细设计的类图里不要塞太多工具类、配置类、常量类。**类图的作用是表达核心业务结构把各种Util、Config画进去只会让图变得巨复杂评审的人根本抓不住重点。工具类在代码里自然生长就好没必要在设计阶段就锁定。4.2 时序图把交互过程放到时间轴上类图定的是静态结构时序图定的是动态行为。我认为时序图是详细设计里性价比最高的一种图因为它能逼你把一次完整请求的所有调用路径走一遍中间但凡逻辑有漏洞画时序图必然暴露出来。拿用户下单这个最核心的流程来举例。时序图的参与者至少有六个用户浏览器、OrderController、OrderService、StockServiceClient指向商品模块、OrderMapper、支付网关。完整的调用序列是用户浏览器提交下单请求到 OrderControllerOrderController 校验参数调用 OrderService.createOrder()OrderService 先调 StockServiceClient 预扣库存库存扣减成功后OrderService 调 OrderMapper 插入订单记录插入成功后OrderService 调 OrderMapper 插入订单明细记录全部成功提交事务然后调支付网关生成支付链接把支付链接拼进下单响应返回给浏览器画这个时序图的过程中你必然会发现几个必须想清楚的问题第4步和第5步是不是必须在同一个数据库事务里如果第6步支付网关调用超时了订单应该是什么状态库存预扣了但是用户一直没支付什么时候释放这些问题在详细设计阶段不解决写代码的时候就只能临时拍脑袋拍出来的往往是错的。画时序图还有个技巧**消息边上要把关键参数标出来。**比如步骤3的箭头边上写deductStock(productId1001, quantity1)这样看到图的人不用翻接口定义就知道传了什么参数。4.3 状态图处理复杂状态流转的利器订单模块是状态图的典型应用场景。订单的状态流转如果不用状态图先画清楚写代码时一定会漏掉某个分支。我在实际项目里设计订单状态时用一张状态图画清楚了全部流转路径待支付PENDING是初始状态用户支付成功 → 待发货PAID用户在支付前取消 → 已取消CANCELLED超时未支付由定时任务扫描取消 → 已取消CANCELLED卖家发货 → 待收货SHIPPED用户确认收货 → 已完成COMPLETED用户申请退款卖家同意 → 已退款REFUNDED退款不经过已取消状态单独走退款流程状态图的价值在于它把合法的状态迁移路径和不合法的路径一次定死。比如已取消的订单不能变成待收货、已完成的订单不能申请退款超过七天之类的规则在状态图里一目了然。写代码的时候每个状态流转点就是一次校验照着状态图画分支即可。同样值得画状态图的场景包括支付记录的状态待支付、支付成功、支付失败、已退款、库存扣减的预占与释放、消息推送的发送状态。凡是那种状态多、流转路径复杂、还有定时任务介入的实体都值得画一张状态图。4.4 伪代码详细设计的度到底在哪很多教程都说详细设计要写到伪代码级别但伪代码到底写到多细是新手最容易迷惑的地方。我的经验是**核心业务的复杂逻辑要写伪代码普通CRUD接口不写。**因为伪代码的目的是把复杂逻辑的算法思路理清楚如果一个接口就是简单的查表返回写伪代码就是纯浪费时间。以超时未支付订单自动取消这个定时任务为例伪代码可以这样写扫描过去30分钟内创建、状态为待支付的订单 for each 订单: 尝试获取该订单的分布式锁防止与用户手动取消并发 如果获取锁失败跳过 再次确认订单状态仍为待支付乐观锁防止用户已支付但回调延迟 调用订单模块取消订单 释放预扣库存 记录取消日志这一段伪代码虽然不到10行但它把并发安全分布式锁、状态一致性二次确认、数据一致性释放库存、可追溯性记录日志四件大事全部点出来了。写代码的人拿到这段伪代码直接翻译成Java就是完整的实现而且不用再担心漏掉某个边界情况。详细设计做到这个程度编码阶段的工作量评估会变得非常准。我统计过详细设计到位的时候预估开发时间偏差能控制在20%以内跳过详细设计直接开发预估偏差经常翻倍。5. 画图工具怎么选好用、能协作、能版本管理是硬指标5.1 工具横向对比写设计文档离不开画图工具。工具选错了也很折磨人——我之前见过有人用PowerPoint画架构图图稍微改个结构就要拖动半天还不支持多人协作。下面我按实际使用体验做个对比工具适合阶段特点协作方式上手难度draw.iodiagrams.net全部阶段免费开源文件可存本地支持导入导出多种格式配合Git管理文件或用在线版共享极低ProcessOn全部阶段在线协作方便模板丰富免费额度够个人用在线实时协作极低Visio总体/概要微软老牌图形规范适合正式文档交付局域网共享团队协作一般低PlantUML全部阶段用代码画图文本文件可做版本比对适合极客流配合Git天然支持版本管理中StarUML详细设计专业的UML工具类图、时序图操作效率高文件不便于多人同时编辑中Enterprise Architect企业级项目重型全流程建模工具从需求到代码生成全覆盖有服务端协作方案高如果是课程设计或者三五人的小团队项目我的建议是**画图用 draw.io 或 ProcessOn建库设计用数据库工具导出ER图所有设计文档用Markdown或Word整理成一个目录。**理由很简单这些工具学习成本低画出来清晰够用文件也好归档。如果团队里有人熟悉PlantUML全组统一用PlantUML是更优的选择——因为设计图以文本形式入库每次改动都能diff评审的时候一眼能看到这次改了哪里这个优势是图形化工具没法比的。5.2 一份可以直接套用的设计文档目录不管用什么工具文档结构我觉得应该有一个基本范式。我这些年评审和写作设计文档沉淀了一套目录结构适合绝大多数Web业务系统可以直接抄项目名_设计文档/ ├── 1_总体设计.md │ ├── 1.1 项目背景与目标 │ ├── 1.2 系统上下文图 │ ├── 1.3 架构分层图 │ ├── 1.4 技术选型及理由 │ ├── 1.5 模块划分图 │ └── 1.6 部署视图 ├── 2_概要设计.md │ ├── 2.1 模块功能点清单 │ ├── 2.2 前端HTTP接口定义 │ ├── 2.3 模块间服务接口定义 │ ├── 2.4 数据库表设计 │ └── 2.5 核心业务数据流说明 ├── 3_详细设计.md │ ├── 3.1 核心模块类图 │ ├── 3.2 核心业务时序图 │ ├── 3.3 关键状态流转图 │ └── 3.4 复杂逻辑伪代码 └── images/ ├── context.png ├── architecture.png └── ...还有一个被很多人忽略的点**每张图下面必须有文字说明。**好多设计文档图一贴就不管了一张架构图没有任何说明文字评审人根本不知道各层之间那条箭头代表HTTP调用还是消息推送。画完图用三五行字写下这张图表达了什么、关键决策是什么、有哪些权衡这才是一份完整的设计文档。5.3 用Git管理设计文档的好处如果你的设计文档是Markdown形式、图片单独存放强烈建议纳入Git版本管理。这样做有三个直接好处一是每一次设计变更都有迹可循。评审提出订单模块的接口从同步改为异步你改了文档提交一个commit下一次评审直接看diff所有人都知道改了哪里、为什么改。二是设计文档和代码可以联动评审。在代码评审时可以对照当时提交的设计文档看实现是否偏离比口头说我记得当时设计的是这样靠谱一万倍。三是文档不会被改得面目全非却没有备份。本地存文档最怕的就是改崩了没有后悔药在Git里随便回滚。6. 设计阶段常见的坑每一个都是真金白银换来的教训6.1 坑一设计文档和代码脱节这是最普遍的问题没有之一。很多团队设计文档是写完了评审也过了但开发过程中需求一变、时间一紧代码就开始走样。等答辩或者项目验收时打开设计文档一看里面写的是状态机方案代码里早改成if-else硬编了。这个问题没有灵丹妙药只有一个笨办法**设计文档要跟着代码一起改。**每次需求变更先在文档里改再动代码。如果你发现自己太忙了根本没时间更新文档那就说明文档该精简了——一份没有人愿意更新的文档本质上是太重了。最好的文档不是最厚的是刚好能反映当前系统真实状态的。6.2 坑二过度设计用不上也要画和文档脱节相反过度设计是把设计往复杂里堆。一上来就设计分布式事务、消息队列问题是你的场景里根本没有两个服务需要消息解耦。我在课程设计里见过一个图书管理系统总体设计里画了完整的微服务架构每个服务都配了独立数据库还引入了消息队列做服务间通信——但实际业务就是简单的图书增删改查连十个并发都没有。判断是不是过度设计有个标准**这个设计决策是不是当前可证实的业务需求逼出来的**如果回答是未来可能会用到那大概率就是过度设计。未来的问题留给未来解决前提是你的设计不堵死未来的路。比如模块划分清楚、接口定义规范将来拆微服务随时可以拆但不必现在就拆。6.3 坑三评审流于形式设计评审变成走过场比没有评审更可怕。我见过太多评审会所有人盯着投影仪上的讲话者没人真正看文档最后评审结论草率通过。评审有效的关键在于每一个参与评审的人都有自己的视角负责测试的人要看接口定义里有没有异常码边界条件是什么没有这些测试用例没法写。负责前端的人要看接口的返回结构是否方便前端渲染字段是不是够用负责运维部署的人要看部署视图里有没有写清楚环境要求、端口号、外部依赖。负责项目进度的人要看每个模块的开发量评估是否合理有没有模块划分过于不均。如果评审会只拉着后端几个人看那这份设计文档从源头上就缺失了多方视角。6.4 坑四把三类设计当成三份独立的文档来写最后一个坑也是这篇文章最想强调的**总体设计、概要设计、详细设计不是三份割裂的作业而是一组层层细化的图纸。**评审时最常问的问题就是你总体设计里的模块X到概要设计里变成了哪些接口到详细设计里落在了哪些类上答不上来说明你根本没有真正理解自己设计的系统。所以写的时候建议一个模块一个模块往后推先定总体的模块划分然后挑一个核心模块把它在概要设计里做细再在详细设计里画类图时序图走通一个完整样例后再做下一个。这样写出来的三份文档彼此印证答辩老师问哪个层次你都答得上来。我在实际项目中最大的体会是设计图不是给别人看的面子工程而是帮自己想清楚问题的思考工具。一张图你自己画不出来说明你根本没想清楚一张图画出来了但说不明白说明你还没完全理解自己的设计。把这些图真正用好你会发现编码阶段的返工量会断崖式下降——那些在画图时就已经暴露出来的问题不需要再等到代码里二十层堆叠之后才被一个偶发bug炸出来。好设计不会让代码写得快但会让人少熬很多夜。
返回列表