ARTICLE DETAIL

资讯详情

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

概要设计与详细设计:从系统蓝图到代码落地的实操指南

概要设计与详细设计:从系统蓝图到代码落地的实操指南 1. 先搞清楚一件事你写的是给谁看的地图不是施工图纸我这些年参加过的项目评审里最常出现的一个场面就是设计师把概要设计书写得密密麻麻类名、方法名、字段类型全列上去五六百页等到了详细设计阶段又拿不出东西翻来覆去还是那几百页。评审专家问一句你这个模块到底怎么实现的全场安静。这事归根到底是没搞清楚概要设计和详细设计各自的定位。我打个比方你去感受一下概要设计是画一张城市总规图。它回答的是这个城市有几大片区、路网怎么走、功能分区在哪里、水电管网怎么接。你把每条街道的门牌号都标上去这不是规划图这是地图册。详细设计是出单栋楼房的施工图钢筋怎么配、混凝土标号多少、线管怎么埋、插座离地多高每一处都要落到位因为施工队要照着干活。两类文档的差异不在写得细不细而在决策颗粒度。概要设计决策的是系统怎么切分、模块间怎么通信、数据怎么流动详细设计决策的是单个模块内部怎么实现、类怎么划分、函数怎么组织、异常怎么处理。前者回答what和why后者回答how——但这个how是怎么实现某个具体功能不是整个系统怎么搭建。后面我以一套完整的实操拆解来讲清楚这两者到底是什么、各自要输出什么、以及我踩过的那些坑。2. 概要设计决定系统长什么样的那张蓝图2.1 概要设计要回答的核心问题写概要设计之前先问自己四个问题答不出来就别动笔系统要拆成哪几个部分这些部分之间的边界在哪里每个部分对外提供什么能力依赖什么能力数据在系统里怎么流转从哪里产生、存到哪里、被谁消费部署和运行环境怎么组织各个部分怎么联通这四个问题覆盖了概要设计的四大块总体架构、模块划分、数据结构、部署方案。别小看这四个问题项目能不能顺利进入编码阶段全看它们有没有想清楚。举个例子。前年我接手一个物联网设备管理平台前任团队做概要设计时把设备接入和设备数据处理放在同一个模块里理由是逻辑上它们都跟设备相关。结果开发到一半发现接入服务要支持高并发长连接数据服务要做流式计算两者对资源的需求完全不一样硬绑在一起导致每次发布都要同时重启线上抖动互相影响。后来花了三周拆模块等于概要设计推倒重来。这就是典型的模块划分没有考虑非功能性需求。模块划分不能只看业务的高内聚还要看运行时的隔离性、扩展维度的一致性、以及团队协作的便利性。2.2 总体架构图怎么画才可信很多概要设计里的架构图说白了就是画几个方框连几条线。真正的架构图要能经得起追问每一个方框是什么它存在的理由是什么它跟上下左右是怎么交互的我习惯用分层分区的方式来组织架构图。分层解决的是职责边界问题比如接入层、业务层、数据层分区解决的是运行边界问题比如管理端、客户端、开放接口区。整体上架构图要能清楚回答三个问题用户请求从哪进来经过了哪些环节最后落到哪里每个环节如果是无状态的状态存在哪每个环节如果挂掉了影响范围是什么有没有冗余或降级方案。画架构图还有个容易被忽略的点要标出协议和接口风格。模块之间是走HTTP还是RPC消息是同步还是异步用的是什么格式这些不标清楚概要设计评审的时候架构师一定追问因为通信方式直接决定了后续的详细设计怎么做。2.3 模块划分的实操原则模块划分这块我现在用的是几个硬性原则都是交了学费换来的。第一个原则一个模块只有一个变化的理由。如果两个功能点会因为同一个业务需求而同时修改它们就应该在一起如果会因为不同的需求而修改它们就该分开。比如订单创建和订单导出表面上都是订单相关但导出会随着报表需求频繁变创建则相对稳定放一个模块里等于每次报表调整都要重新发布订单模块。第二个原则依赖方向必须单向。上层模块依赖下层模块的接口但下层模块绝对不能反过来依赖上层。违反这个原则的代码改起来就是牵一发动全身。我在评审时看到一个模块引用另一个模块的内部实现类直接打回。模块间只能通过公共接口交互这是底线。第三个原则模块的对外接口要按业务能力设计而不是按实现方式设计。举个反例有个项目把用户信息查询和用户画像计算拆成两个接口乍一看挺合理但业务上每次调用都要先查用户再算画像产生大量往返调用。正确的做法是提供一个获取用户完整信息的聚合接口底层再分成基础信息和画像信息两条实现路径。接口是给调用方用的设计接口的人要有换位思考的能力。2.4 接口设计和数据设计的要点概要设计阶段的接口定义粒度是模块级的。你要确定的是这个模块向外部暴露哪些服务、每个服务的输入输出大致是什么、出错时用什么错误码体系。不需要定义到字段级、更不需要定义数据库表结构——那是详细设计的事但接口的语义必须清晰。数据设计在概要设计阶段要做的是确定核心实体、实体之间的关系、数据的存储策略哪些用关系型、哪些适合缓存、哪些要进消息队列。注意这里说的是存储策略不是具体表结构。什么时候你会明显感觉概要设计的数据设计做得不好就是你发现实体的归属说不清楚。比如订单这个实体到底是订单服务自己管理还是由用户服务统一管理这种归属不定清楚详细设计阶段做出来一定是混乱的。2.5 部署方案概要设计里最容易被低估的部分部署方案在很多团队的概要设计里只有一张图画几个服务器标注一下nginx、MySQL、Redis。但真正有经验的架构师会在这里花很多精力因为部署方案直接决定了系统的可用性、性能上限和运维成本。你要在这个阶段决策的是每个模块部署几个实例单机还是有集群考虑到预期的访问压力实例数怎么估算有没有需要区分环境的配置比如测试环境和生产环境的连接串不同这些差异怎么管理数据存储放在哪里是否需要读写分离如果需要分库分表分片键怎么选我见过一个典型的反面案例概要设计阶段没有考虑数据量增长的问题所有业务数据都放在一个MySQL实例上上线半年后单表过亿查询慢到超时只好半夜停机做迁移。如果概要设计阶段就把未来的数据量级作为一个约束条件带入设计至少会给存储层多留一些扩展的空间。3. 详细设计让程序员不需要动脑就能写代码的实操文档3.1 详细设计的输入和输出详细设计的输入是概要设计确定的模块划分、接口契约和数据存储策略输出的是可以指导编码的一切细节。换句话说详细设计做完以后一个合格的开发应该不需要再思考和模块相关的设计问题直接照着文档写代码就行。我习惯把详细设计比作一张菜谱。概要设计说了今天要做一桌川菜有哪些菜详细设计则是每道菜的食谱——主料配料各多少克、火候多大、焯水几分钟、分几步出锅。没有食谱厨师全凭经验做十个人能做出十个味道有了食谱来一个新厨子也能做出统一的味道。详细设计应包含的内容至少包括以下几个层面类设计有哪些类、类的职责、类的属性方法、类之间的关系继承、组合、依赖流程设计关键业务场景的时序流程、状态流转、异常分支接口详细定义方法签名、参数校验规则、返回值结构、错误码映射数据库详细设计表结构、索引策略、事务边界、数据约束关键算法说明如果有需要特定算法支撑的场景比如限流算法、推荐策略、数据聚合逻辑给出算法选型和核心逻辑说明异常与边界处理列出可能出现的异常场景和处理策略保证代码的健壮性。3.2 类设计的正确打开方式类设计这块很多刚入行的开发容易犯一个毛病只画一个静态的类图列出一堆类名但你看不出这些类是怎么协作完成一个功能的。真正的类设计要围绕职责来界定每个类的边界并且用典型的调用序列把类的协作关系演一遍。我常用的做法先写核心业务场景的时序图时序图里需要出现哪些参与者就自然想到了需要哪些类然后根据时序图中每个对象的职责去定义类的属性和方法。这样做出来的类设计是长出来的不是造出来的可靠得多。举个例子一次在做用户积分系统的详细设计时我先写用户完成一笔订单后积分增加的时序Controller接收请求→校验参数→调用积分服务→积分服务计算本次积分→更新积分明细→返回结果。这个流程里涉及的参与者包括积分Controller、积分Service、积分规则引擎、积分明细仓储。每个参与者的职责边界在设计时就定清楚了。后来写代码的时候基本就是把这些定义翻译成Java类几乎没有设计上的返工。3.3 状态机设计状态流转必须穷举到位业务系统里最怕的不是复杂算法而是状态流转没有穷举。比如订单状态你有待支付、已支付、已发货、已完成、已取消看起来很简单但一问具体场景就漏洞百出支付超时了怎么处理支付成功但回调晚到了怎么处理发货后用户申请退款状态怎么走退款被驳回后能不能重新申请我强烈建议在详细设计里给核心业务实体画一份完整的状态机图并且配合一张状态迁移表把每个可能的状态变迁、触发事件、前置条件、后置动作都列出来。别偷懒觉得这个写代码的时候自然处理真正写代码的时候你会发现没有穷举清楚的状态就是一个隐藏的地雷。我做过一个售后系统当时设计退款状态机穷举了用户发起退款、商家同意、商家拒绝、用户修改退款申请、平台介入、退款关闭等十几个状态和二十多组迁移关系。列出来之后想清楚了几个容易出问题的场景退款关闭后用户能不能重新发起能但要重新走审核流程此前的流转记录必须保留。这些细节如果不写进详细设计开发时一定会漏。3.4 数据库表结构与索引设计详细设计阶段的数据库设计要精细到每个表的每个字段。字段类型、长度、是否可空、默认值、注释一样都不能少。表设计做好了代码写起来非常顺表设计做烂了再牛的开发也无米下锅。字段设计有几点经验主键策略要明确是自增、UUID还是雪花算法所有表都要有创建时间和更新时间这是排查问题的生命线状态字段的类型要选对能用整数就别用字符串除非你的枚举值语义非常复杂金额字段不要用float/double用decimal或整数分单位存储逻辑删除字段要有但索引设计时要想清楚它会不会影响唯一索引。索引设计这块很多人只看查询快不快却忽略了索引的维护成本。我遇到过一个真实案例生产环境一张表因为加了七八个索引每次写入要做大量索引维护高峰期数据库CPU被打满。索引不是越多越好要结合实际的查询模式、写入频率、数据量综合取舍。3.5 写详细设计时我要求团队必须做的三件事这三条是我最近几年带团队总结出来的每一条都对应着真实事故。第一所有外部接口调用必须定义超时时间和重试策略。做微服务拆分的系统最大的风险就是依赖链上的某个下游服务变慢把整个链路上的线程池打满。详细设计里如果没有定义超时和重试策略开发写代码时的选择就是凭感觉上线后出了问题就很难定位。第二所有状态变化必须考虑日志留痕。详细设计里要定义清楚哪些操作必须记录审计日志日志的字段结构是什么。我做过金融相关的项目因为日志不规范最后对账时非常痛苦只好翻原始请求报文。早知如此设计阶段就该把日志字段定清楚。第三所有批量操作必须定义分批大小和失败处理策略。一个定时任务要处理十万条数据是一次性查出来还是分批处理中途失败了是整个重跑还是断点续跑这些问题在详细设计里写清楚开发就不需要临场发挥了。4. 一个贯穿案例用订单超时自动关闭看两种设计的差异理论讲再多不如一个具体的例子。我拿一个电商系统里比较经典的场景——订单超时自动关闭来演示同一个功能在概要设计阶段和详细设计阶段分别是怎样呈现的。4.1 概要设计阶段怎么描述这个功能概要设计阶段不会直接告诉你订单超时怎么实现而是把它放在消息和定时任务的架构框架里。具体来说概要设计里会写在平台整体架构中订单模块需要支撑超时订单的自动关闭能力。考虑到系统的可扩展性和实时性要求超时事件通过延迟消息或定时任务触发订单模块提供关闭超时订单的对外接口供触发方调用。该接口应具备幂等性防止重复触发导致的重复关闭。同时订单状态的变更需按照订单状态机的定义流转并产生对应的事件消息供下游业务感知。看见没有概要设计关心的是这件事放在整个系统的哪个位置、跟谁交互、接口要有幂等性、状态流转要符合既定规则但它不关心具体是延迟消息实现还是定时扫描实现更不关心查哪张表、执行哪条SQL。这些实现细节是详细设计要做的。4.2 详细设计阶段怎么描述这个功能详细设计阶段则是这样呈现的实现方案采用延迟消息方案。创建订单成功后发送一条延迟消息延迟时长对应订单超时时间如30分钟延迟消息到期后消费者消费消息调用订单关闭服务。类设计包括OrderTimeoutConsumer消息消费者、OrderCloseService订单关闭服务、OrderRepository订单仓储等类每个类的职责和关键方法都会定义清楚。时序流程用户下单→订单服务创建订单→发送延迟消息→消息队列到期→消费者消费→调用关闭服务→校验订单状态是否为待支付→是则更新状态为已关闭→发送订单关闭事件消息。异常处理如果消息消费失败进入重试队列重试3次如果3次后仍然失败进入死信队列并由人工介入处理。同时关闭操作依赖订单当前状态为待支付若状态已变为已支付则跳过不处理。数据库操作更新订单状态的SQL语句以及更新前需要校验状态的SQL条件。这就是两种设计的不同颗粒度。概要设计告诉你有这个功能、它在体系中的位置、它跟外部的契约详细设计告诉开发这个功能如何一步步写出来。4.3 边界的灰色地带什么该放概要设计什么该放详细设计实际操作中设计师最挠头的是这件事到底该写进概要设计还是详细设计。我提供一个常用的判断标准如果这个决策影响了其他模块或整个系统的运行方式放概要设计如果只影响当前模块的内部实现放详细设计。比如订单超时用延迟消息还是定时任务这个决策会影响消息中间件的选型、部署架构、运维方式所以应该在概要设计阶段就定下来。而延迟消息的消费者类名叫什么、重试次数是3次还是5次属于实现细节留在详细设计阶段即可。再比如接口设计接口的路径、核心参数、返回结构、错误码这些属于概要设计因为别的模块要用接口内部怎么解析参数、怎么校验、怎么调用底层服务属于详细设计。5. 常见误区、评审标准和一份可以直接套用的编写清单5.1 我见过的四个高频误区误区一把概要设计写成详细设计的目录。我看到有人写概要设计每个模块下面只写一句话详见详细设计文档。这等于没写。概要设计是独立的、能指导高层次决策的文档不是索引页。误区二详细设计里又开始讨论模块划分和接口归属。这就是设计返工。详细设计阶段如果发现模块划分有问题应该停下来找架构师讨论而不是悄悄在详细设计里改掉否则概要设计就失去了它作为契约的作用。误区三两份文档只有一个作者。有些团队概要设计和详细设计是同一个人写的好处是思路连贯坏处是缺少制衡。概要设计是架构师视角详细设计是技术实现视角视角不同发现的问题也不同。误区四文档写完了就完了不跟代码保持同步。这是我见过最普遍的问题。代码改着改着设计文档早就过时了。我建议团队至少在每个迭代结束的时候做一次文档与代码的对照更新。设计文档是活的不是用来应付验收的材料。5.2 怎么评审一份设计文档我评审设计文档有一套自己的关注清单分享出来供你参考概要设计评审清单是否清楚描述了系统的整体架构以及每个组成部分存在的必要性模块划分是否遵循了高内聚、低耦合原则依赖方向是否单向对外接口的语义是否清晰、稳定是否定义了关键数据的流转路径和存储策略是否考虑了非功能性需求性能、可用性、安全性部署方案是否合理有没有明显瓶颈详细设计评审清单是否有完整的类设计和关键时序图协作关系是否清晰核心业务的状态流转是否穷举完整异常处理和边界条件是否覆盖到位数据库设计与接口设计之间是否有矛盾开发人员拿到这份文档是否可以不再依赖设计师的支持5.3 编写清单照着这份内容写基本不会返工详细的编写大纲做了一张表团队新人在动笔之前都会先过一遍这个清单。概要设计的核心章节和关键内容引言目的、范围、术语、参考资料总体架构架构风格、分层分区、部署形态模块划分与职责定义模块清单、模块间依赖关系接口框架模块对外提供的服务及核心数据契约数据结构核心实体、实体关系、存储策略关键技术决策关键技术选型及理由非功能性设计性能、安全、可用性、可运维性详细设计的核心章节和关键内容模块概述回顾该模块在整体架构中的位置与职责类设计类职责、关键属性与方法、类间关系流程设计关键时序、状态机、业务流程图接口设计详细方法签名、参数、返回值、异常定义数据库设计表结构、索引、约束、事务边界关键设计细节算法、并发控制、缓存策略、幂等方案异常与边界异常清单、处理策略、告警和日志设计我的经验是概要设计写完至少要让另一个不参与本项目的人读懂并回答出这是一个什么样的系统详细设计写完要让一个没参与过的开发能照着写出代码。两者都做到了你的设计文档就是合格的。6. 踩坑实录从文档齐全到开发流畅我改了三版才想明白的事最后分享一段我自己的经历。早年间我负责过一个中型系统自认为文档写得特别全概要设计一百多页详细设计三百多页。结果开发阶段还是问题不断说得最多的就是这个我没法开始文档里没说清楚。第一版我以为是团队经验不足后来发现是我把自己脑子里已经知道的常识当成了不证自明的前提压根没写进文档里。比如我写用户模块提供查询用户信息服务心里想的是这里应该走缓存缓存没有就查库但我没写。开发拿到文档不知道该不该加缓存来问我我还觉得问得蠢——现在回想他问得对我没写清楚。第二版我把这些常识补上了文档厚了一倍问题变了大家说信息太多找不到关键内容。我又发现文档不是越厚越好而是检索效率要够高。第三版我开始按照读者视角重写每一章开头先明确读完这一章你应该知道什么然后把结论放在前面推导过程放在后面表格和图示都配齐。这一版的效果明显好了开发基本不需要频繁来问设计问题各模块的编码节奏顺畅了很多。这件事给我的启发是设计文档的价值不是我写了而是别人用了不卡壳。写文档的时候一定不要站在我什么都知道的视角要站在一个只看了概要设计、没有参与前期讨论的开发的视角去审视每一段话是否足够清晰、足够直接。你替读者省掉的每一个疑惑都是项目进度表上多出来的保障。
返回列表