资讯动态

总体设计五个基本原理:让软件结构图从画得漂亮到切得明白

发布时间:2026/10/16 16:44:02 来源:尧图企业网站定制
一张结构图画得再漂亮如果背后没有一套能说服人的判断标准那它在评审会上基本撑不过三分钟。我见过太多总体设计文档图很工整层数清晰模块命名也无懈可击但评审一追问为什么这个模块放在这一层这两个模块的边界凭什么这么划答不上来。总体设计阶段最核心的产出其实不是那张软件结构图而是图背后那套划分系统的决策逻辑。长期写总体设计、评审别人设计的经历让我深刻体会到五个基本原理和软件结构图从来不是两件事——原理是图的判断依据图是原理的可视化表达。这篇文章不打算复述教科书目录而是想聊聊我实际做总体设计时怎么用这五个原理去拆系统、怎么把结构图画得真正可落地。1. 总体设计为什么需要原理打底一张结构图背后的三层判断先说一个我印象很深的场景。几年前我负责某个企业管理系统的总体设计评审有位同事提交了一张软件结构图第一眼看上去几乎挑不出毛病顶层分了四五个模块每个模块下又挂了两三层子模块连线干净排版整齐。但评审会开始后我指着其中一个公共数据服务模块问了一句这个模块被顶层所有业务模块调用它内部到底管哪些数据如果业务模块的数据结构变了对这个公共模块的改动范围有多大对方支吾了半天最后承认这个模块其实只是把大家都要用的工具函数攒在一起。会开到一半整个设计推倒重来。这件事让我反复想一个问题为什么图看起来没问题但实现起来就是处处别扭答案很简单——图只反映了有哪些模块没反映模块边界为什么这么定。模块化不只是画几个框抽象不只是把相似的框合并信息隐藏不只是把字段设为私有。这些都是实实在在的架构决策而软件结构图承担的使命就是把这些决策固化下来。我后来在带团队做总体设计时会刻意把设计过程分成三层来推进避免空对空谈架构第一层是语义层搞清楚系统要解决什么问题核心业务实体有哪些哪些功能是主体流程哪些是支撑性能力。第二层是决策层基于语义层的信息按五个基本原理进行模块划分明确每个模块的职责边界、对外接口、隐藏的内部实现。第三层是表达层把决策层的结论画成软件结构图并辅以模块接口说明让开发人员能照着图去开展详细设计和编码。很多人把精力全部放在第三层结果就是画了一张看上去合理的图但经不起追问。真正花时间的应该是第二层五个基本原理在这一层起着决定性作用。有人可能会问这些原理太教科书写法了真能指导具体工作吗我举一个最简单的例子。你拿到需求后先不要急着打开画图工具而是拿出一张白纸把系统的功能点列出来然后尝试用一句话概括每个候选模块的职责。如果一个模块的职责需要用三个和字才能描述清楚大概率是抽象不够,或者边界没切好。这个判断标准听着很虚但背后的逻辑就是内聚性原理——好的模块应该能用一个清晰的句子说明白。所以总体设计的第一步不是画图而是先建立判断模块边界的思维框架。下面我逐个拆解我实际用得最多的五个基本原理讲清楚每一条在面对真实系统时该怎么落地。2. 五个基本原理逐条拆解每一条都在回答边界在哪2.1 模块化原理分而治之但不是越碎越好模块化原理的本质是把一个复杂系统拆成可单独理解、开发、测试、替换的模块。系统整体的复杂度并不等于各部分复杂度之和因为模块之间还有交互成本。好的模块化追求的是局部易懂、整体可控而不是一味追求数量多。我习惯用一个简单的例子向团队成员说明这个平衡。假设一段业务逻辑有一千行代码全部塞在一个文件里任何人想理解其中一段都得把整条链路读完心智负担极高。但如果你把它拆成一百个模块每个模块只有十行代码虽然单个模块容易理解但模块之间的调用关系可能变成一张蜘蛛网拼接和维护成本反而更高。所以模块化的关键判断标准不是拆了多少个而是**能不能在不读完整系统代码的前提下理解并修改某个模块**如果答案是可以说明模块的边界、接口、依赖关系都清晰如果不行说明拆得不够或者拆错了。实际操作中我通常用修改影响范围来验证模块化是否合理当我要改A模块的行为时需不需要连带修改B、C、D如果需要说明这些模块之间的耦合超出了合理范围。某个模块出问题时即使换了实现方案外部模块也不应该受影响这才算是拆到了点子上。2.2 抽象原理把复杂问题按共性和差异切开抽象原理通俗地说就是在不同层次上忽略细节差异只关注共性特征。我们常说的接口就是抽象的主要载体。上层模块只依赖抽象接口底层模块负责具体实现两者解耦之后系统在面对变化时就会从容很多。举个例子一个系统需要对接短信、邮件、站内信三种通知方式。如果三个功能分别散落在各业务模块里每加一种通知渠道所有业务模块全要改。更合理的做法是抽象出一个通知发送接口对外只暴露发送通知接收人内容渠道这一个动作具体的渠道实现隐藏在各个适配模块里。业务模块完全不关心底层用的是哪家短信服务商。抽象原理在总体设计阶段的使用要点先把系统的功能点列全找出那些输入输出类似、流程骨架相同的功能。分析这些功能之间的共性定义出高层的抽象接口。把差异部分下沉到具体模块去实现不要让差异外溢到整个调用链。在设计文档里标清楚抽象层和实现层让开发人员明确新增功能该放在哪一层。有一点需要提醒抽象不是多多益善。为了抽象而抽象会在上层堆出一堆看似通用但没人用的接口结果大家谁也不依赖它反而增加了理解成本。好的抽象应该是有真实调用方、有清晰语义的。2.3 逐步求精原理画结构图时别一次扎进细节里逐步求精原理是自顶向下、逐层细化的设计策略。它和我画软件结构图的过程完全同构先确定顶层模块再逐层拆分上一层解决系统由哪几部分组成的问题下一层解决每个部分由哪些子部分组成的问题直到粒度达到可以进入详细设计为止。我见过不少初学者画总体设计图时第一版就深入到数据库连接池怎么封装缓存Key怎么命名这种细节。这不是不该考虑但不应出现在总体设计阶段。原因很简单顶层架构还没稳定的时候细节表达得越具体返工成本就越高而且细节往往会反过来绑架高层决策。逐步求精的原则可以理解为先框架后细节、先接口后实现。我在画图时会给自己立个规矩每一层只关注当前层应该关注的粒度绝不越级。顶层模块画完先检查模块划分是不是合理确认之后再进入下一层到了叶子节点只需要明确它的职责边界和接口语义不必展开内部实现。这个原理还有一个实用价值它可以帮你控制总体设计的工作量。很多团队做总体设计遥遥无期就是因为在顶层都没定的时候就开始讨论第二层甚至第三层的技术选型会议开了无数次图改了无数版最后什么决策都没留下。2.4 信息隐藏原理哪些内部秘密必须锁在模块内部信息隐藏原理是总体设计里最容易被人忽视、但后患最大的一条。它要求模块内部的设计决策——数据结构、算法、实现方式——不暴露给外部调用者。外部只知道能做什么不需要知道怎么做的。这样内部修改才不会传导到外部。我以前负责过一个报表统计模块早期版本直接对外暴露了一张数据表结构其他模块可以绕过统计接口直接查表。后来因为统计口径调整表结构必须改结果牵连了五六个模块一起改光回归测试就花了两周。这个教训让我意识到凡是外部模块可能因为内部实现变化而被迫修改的地方就是信息隐藏没做到位的地方。后来我把报表模块重构了一遍对外只暴露查询统计数据维度时间范围这一类接口内部无论用SQL查数、内存计算还是引入缓存外部一概不关心。后来我们把统计逻辑从单机改为并行计算性能提升了几倍而其他模块的代码一行没动。判断信息隐藏是否到位我常用一个启发式问题如果我改变这个模块的内部实现需要同时通知多少个调用方如果答案是一个都不用说明隐藏得很好如果答案是好多个说明内部细节泄漏了。2.5 模块独立性原理高内聚、低耦合是最终标尺模块独立性原理本质上是前四条原理的综合检验标准。模块独立性包含两个维度内聚和耦合。内聚衡量模块内部各元素之间联系的紧密程度。从差到好大致有七种内聚类型我平时主要关注几个分界点偶然内聚代码凑在一起纯属巧合这是最差的需要坚决拆开。逻辑内聚一个模块通过参数区分执行不同的功能表面看在一个模块里实际做的事截然不同。时间内聚只因为都在同一时间被调用就放进了一个模块。通信内聚多个功能因为使用同一个数据集合而聚合在一起。顺序内聚一个步骤的输出是下一步骤的输入天然串成一条线。功能内聚模块内部所有元素共同完成一个完整功能这是最理想的状态。实际拆模块时能否把职责用一句话说清楚是最直接的功能内聚检验法。一个负责用户管理的模块是模糊的一个负责用户注册、登录凭证校验和账户状态管理的模块虽然听起来场景多但只要它是同一个业务统一体仍然可以算作功能内聚。耦合衡量模块之间的连接紧密程度。从强到弱分为内容耦合、公共耦合、外部耦合、控制耦合、标记耦合和数据耦合。总体设计阶段的目标是把大多数依赖控制在数据耦合层面——模块之间只通过参数传递数据不互相传递控制指令更不直接访问对方内部数据。说句实话完全零耦合的系统既不可能也没必要。重点在于控制耦合的方向和依赖的稳定性让高级别模块依赖低级别模块的抽象接口让公共基础模块尽量稳定业务模块可以频繁变化但不要反过来影响基础模块。把这条理清了结构图的骨架基本就是可用的。3. 软件结构图画什么、怎么画从符号规范到结构指标3.1 先分清结构图、流程图、体系结构图别在一张图里混三种语义软件结构图Structure Chart在软件工程里特指描述软件模块层次结构和调用关系的图。它和流程图完全不是一回事。流程图表达的是一个处理过程的先后次序箭头代表控制流转移而结构图表达的是模块之间的层级和调用关系箭头代表调用方向。一个典型的区别在结构图里用户登录只表现为登录控制模块调用账号校验模块这样一条调用线而用户填表单→提交→验证账号→初始化会话→跳转主页这类处理顺序应该由详细设计阶段的流程图或时序图去表达。把这两种语义混在一张图里是最常见的画图错误因为看的人会分不清箭头到底是先做谁后做谁还是谁调用谁。另外别把软件结构图当成部署架构图或组件架构图。组件架构图关注的是进程、容器、服务、网络的物理分布关系软件结构图关注的是模块的逻辑划分与调用关系。虽然一个系统可以同时用这两种图来描绘但它们出现在总体设计中的目的和读者完全不同。3.2 画图时的符号与基本布局规则我画软件结构图时会沿用软件工程教材里那套已被广泛接受的符号约定这也是为了保证跨团队沟通时大家能读懂同一张图矩形框表示一个模块框内写模块名。模块名尽量用动词名词表示职责比如更新库存生成对账单而不是写模块A这种无信息量的名字。带箭头的连线表示模块之间的调用关系箭头由调用方指向被调用方即从上级指向下级。连线旁加一个空心小圆或菱形标记表示该调用是条件调用即下级模块只在特定条件下才被调用。用带斜向弧度的循环箭头标记表示循环调用即该下级模块会被反复调用。布局上我坚持几条纪律根节点模块放在最顶端被调用模块在下方逐层展开形成清晰的树状层次。同一层级内模块的左右排列不表示执行顺序只表示组织关系。要表达执行顺序回到流程图里去做。模块框内只写模块名不写输入输出参数不写处理逻辑。结构图的叶子节点应当是可以直接作为详细设计单元的模块不要再往下深挖到函数级。全图尽量控制在 A3 或两页以内。如果结构图超过三页说明要么系统太大需要按子系统分图要么模块粒度切得过碎需要重新聚合。3.3 扇入扇出、深度宽度的实际意义画完第一版结构图后我会马上统计几个结构指标每个模块的扇出、扇入、系统深度和宽度。这些指标不是学术指标它们能快速暴露设计问题。扇出Fan-out指一个模块直接调用的下级模块数量。扇出过高通常意味着这个模块承担的职责太多或者它正在协调太多细节拆分层级不够。扇入Fan-in指一个模块被多少个上层模块直接调用。扇入高说明该模块基础性强、复用度高但要当心出现假公共模块——所有模块都依赖它但它的职责定义得含糊不清一旦改动就会炸一片。深度Depth从顶层到最底层模块的层数。深度过深说明层次嵌套太多信息在链路中传太多层容易失真深度过浅说明高层模块可能直接操作了太多细节没有做好抽象。宽度Width同一层上的模块数量。每层模块过多且无法归组说明这一层的抽象粒度可能过细。下面是我平时在总体设计阶段用来做初步体检的参考表数值不是硬性标准但偏离太多时我会回到原理层面重新检查指标我会留意的范围偏离时可能存在的问题单个模块扇出3 到 8 个左右超过 8 个多为职责过重需要继续做抽象归组单个模块扇入没有硬上限越高越像公共能力扇入高但职责表述模糊说明是假公共模块系统深度3 到 6 层之间超过 7 层链路过长考虑合并中间层单层宽度4 到 8 个模块比较顺眼超过十几且无法归组说明抽象粒度有问题用这些指标进行初步体检往往能发现看不出毛病但实现就出问题的隐患。不过请注意指标只是雷达不是答案。数值偏离时不要急着机械调整要回到模块化、抽象、信息隐藏、独立性这些原则上重新判断。3.4 画完图之后必须附上的模块接口说明很多总体设计文档只有一张结构图这是不够的。结构图只回答了系统有哪些模块、谁调用谁但没有回答模块之间到底以什么方式协作。因此我要求每个叶子模块在文档里附一段模块接口说明内容包括模块职责一段话说明这个模块负责什么不负责什么。对外接口列出提供给上层模块调用的接口清单包括调用参数和返回结果。依赖关系模块依赖了哪些下层模块或公共服务。关键约束比如本模块不允许直接读写其他模块的内部数据不得在接口层做业务规则判断等。预期的状态变化调用该模块后系统或数据的哪些状态可能改变。写接口说明的过程就是一遍历信息隐藏原理的过程。如果你发现自己无法用一段话把模块职责说清楚说明模块边界没有想透如果你发现接口清单里混入了太多内部细节说明信息隐藏没做好。这个过程远比画图本身重要因为它在逼你思考边界在哪。4. 从一段需求到第一版结构图一个在线请假审批系统的推演全过程4.1 从一句话需求到顶层抽象用最近常接触的一类项目举例某企业内部在线请假审批系统。需求概括成一句话就是员工在线提交请假申请直属主管审批超出权限时自动转交上级审批流程结束通知相关人管理员维护假期额度系统可出统计报表。这句话看着简单信息量极大。我没有第一时间画图而是先把功能点列出来再用抽象原理做第一轮归并员工提交、修改、撤销、查询请假单这些都属于申请单管理。主管审批、转交上级、多级审批流转这些都属于审批流程管理。假期额度配置、扣减、刷新、阈值检查这些属于假期额度管理。站内信、邮件、系统通知的发送属于通知服务。请假单类型、审批级别、组织架构等基础数据维护属于基础配置服务。于是顶层模块就出来了在线请假审批系统 ├── 请假申请管理 ├── 审批流程管理 ├── 假期额度管理 ├── 通知服务 └── 基础配置服务这个划分不是拍脑袋它的依据是抽象的共性原则每个顶层模块都对应一组高内聚的业务责任。申请单是员工视角的核心业务对象审批流程是管理者视角的工作流核心额度管理是规则和资源约束通知是跨业务的支撑能力基础配置是稳定底座。五者之间的交互边界清晰而且每一块都可以继续独立演化。4.2 用信息隐藏定清各个子系统的边界有了顶层模块接下来不是急着往下层画而是先回答每个模块对外暴露什么、内部隐藏什么这是信息隐藏原理的主场。以审批流程管理为例。对上层来说它只需要向请假申请管理暴露一个语义完整的接口比如提交审批申请单据类型单据数据发起人。上层不需要关心审批链路到底是两级还是三级也不该关心审批状态表怎么设计、有没有用工作流引擎。审批链路的计算规则、状态转移逻辑、超时自动提醒机制全部锁在审批流程模块内部。反过来如果设计时把审批配置表直接开放给申请模块查询一旦调整流程配置结构申请模块也要跟着改这就是典型的内部细节泄漏到外部属于强耦合。信息隐藏做得好不好直接决定后续重构成本。我还会在文档里明确模块间的数据流方向。比如请假申请管理向审批流程管理传的是申请单上下文而不是是否应该进入下一环节这种控制命令。传递控制命令会让两个模块纠缠不清属于控制耦合应当尽量避免。4.3 逐步求精逐层拆解直至可详细设计的粒度顶层边界清晰之后才开始逐层细化。以请假申请管理为例往下拆一层得到四个叶子模块新建申请、修改申请、撤销申请、查询申请单。再往下拆还拆吗我的判断是不拆了——这四个子模块已经是功能内聚的单元后面详细设计时可以单独设计数据结构和界面交互不需要继续分成更小的软件模块。再看审批流程管理。继续拆一层流程启动根据单据类型创建审批实例。环节判断计算当前审批节点和可审批人。审批操作通过、驳回、转交。流程状态存储记录流程历史、当前节点状态。这四个子模块也足够支撑下一步详细设计。注意我没有画出所谓数据库缓存消息队列之类的底层组件因为它们属于详细设计或技术框架范畴不属于模块结构。画到这一步我会提醒自己用逐步求精的原则进行反向检查每个叶子模块是否在设计语义上只承担一类职责如还有多重身份就继续拆。如果已经足够单一就适度停止。总体设计不需要追求粒度极细过度细化会让后续实现束手束脚。4.4 用模块独立性反推拆分粒度第一版结构图画完后我用模块独立性的尺子从头量了一遍发现两个典型问题。第一个问题出在审批流程管理里的一个子模块我最初命名叫相关人查询它既可以查询流程上下文又要获取组织人员信息。被请假申请管理审批流程管理通知服务同时调用表面看复用度很高但它的职责实际上混杂了两类不同来源的数据。这就是逻辑内聚——一个模块里塞了本质上不同的功能。修法也简单拆成流程上下文查询和组织人员查询两个模块分别归属到审批流程管理模块和基础配置服务模块让它们各管一摊。第二个问题是通知服务。为了偷懒我曾把是否触发通知、拼通知模板、发送通知全部塞进了通知服务结果这个服务被业务节点破环越来越像一个业务模块而不是通用支撑模块。按信息隐藏和逐步求精原则最终改成两个层次底层是通用的通知发送模块只管把消息送到指定渠道上层在各业务模块里维护通知该不该发、发什么内容的规则。这样通知服务保持通用、可复用业务变化不再传导到底层基础能力。经过这轮返修最终结构图比初稿多出几个模块但每个模块的职责都能用一句话说清评审时大家讨论的是业务逻辑而不是这个模块到底是什么。我个人体会是多花一个下午做独立性检查能省下后面几周的开发返工时间。5. 评审他人总体设计时我必看的几个检查点5.1 一张看着不错的图为什么实现就垮了我评审过不少结构图其中有一类看着没问题、一实现就垮的图问题几乎出在同一个地方图上的模块名太笼统比如系统管理公共处理数据库操作。这种模块名一听就有问题——它回答不了这个模块到底干什么。还有一类图的问题是呼叫关系混乱。有些图的上层模块直接跳到第三层模块跳过中间层有些图同一层左右之间拉了一堆箭头。跨层调用和同层交互一旦泛滥结构图就退化成一张网状图。网上图为啥有问题因为它丧失了层级抽象所有模块平等地互相依赖复杂度并没有被管理起来只是被摊到了图面上。第三类问题是接口语义不清。图上显示了模块A调用模块B但A传给B的到底是数据还是控制指令图上完全不写接口说明文档也没有。实现的时候开发A和开发B对这个接口该是什么样的理解完全靠猜最后联调时各种对不上。这些问题在结构图阶段不解决到了编码阶段就是灾难。5.2 我习惯先用六个问题过一遍结构图现在我拿到一张待评审的结构图不太会急着看细节先问自己六个问题每个叶子模块的职责是不是可以用一句话说清说不清就是内聚有问题。模块之间的调用方向是否符合依赖方向比如业务模块是否反过来依赖了公共基础模块的内部数据。有没有出现跨层调用或同层强依赖跨层意味着中间抽象被跳过同层强依赖意味着边界不清。扇出最高的模块是否正在协调过多细节如果是说明该层缺少一层中间抽象。扇入最高的公共模块是否名符其实明确无误的共享能力可以接受含糊的工具大杂烩必须打回去重拆。每个模块是否都有明确的接口说明和职责边界没有接口说明的模块图基本只完成了三分之一的总体设计工作。我通常还会以如果只读模块名和接口说明不读代码我能不能定位一个新需求该改哪个模块作为终极测试。如果答案是能说明结构图是合格的如果不能说明抽象和信息隐藏都没做到位。把这个问题问完大部分结构图都能在十分钟内被判定为通过、修改后通过、重新设计三种结果。这几年我用这套做法减少了大量无效评审时间也给团队建立了一套统一的设计语境。5.3 一个真实评审案例结构图不是画完就算了最后说一个印象深刻的返工案例。某团队负责一个订单处理子系统的总体设计第一版结构图把订单创建、支付对接、库存扣减、发票开具全部挂在同一个订单服务模块下并由这个模块直接调度所有子模块。图看着很一体化但评审时发现订单服务模块的扇出高达九个它既负责业务编排又直接操作支付和库存接口任何底层渠道变更都会引起订单模块联动修改。我当时没有直接说这图不行而是让他们按原理自查这个订单服务模块的职责用一句话说得清吗说不清因为它既是编排层又要对接外部细节。支付网关的差异、发票规则的差异应不应该让订单业务层全看见不应该因为那是实现细节。于是返工的方向就明确了把支付流程和发票流程各自提升为独立模块对外暴露统一的发起支付开具发票接口内部再封装渠道差异订单模块只负责单据生命周期管理不直接面对渠道细节。改完之后结构图里的订单模块扇出从九个降到了四个职责边界清楚后续接新支付渠道时只需要新增一个适配器其他模块完全不动。这个案例让我更加确信总体设计的核心竞争力不是画图技巧而是对模块化、抽象、信息隐藏、独立性这些原理的理解深度。我在实际项目里还有一个习惯结构图评审通过后不要把图丢进文档库不管。让实现团队拿着图和接口说明去做详细设计每完成一个模块就对着图核对一次边界。这样做的好处是图纸上抽象的边界会落地成代码里的实际依赖关系哪里的边界切错了、哪里的接口已经失真都能在开发过程中被尽早发现。总体设计从来不是交一份文档就结束它真正的作用是让后面的每一步都知道该往哪里走。

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价 →
↑