ARTICLE DETAIL

资讯详情

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

从DFD到程序结构图:结构化设计方法与实操详解

从DFD到程序结构图:结构化设计方法与实操详解 结构化开发方法里从数据流图DFD导出程序结构图算是从需求分析跨进软件设计最关键的一步。很多教材把这部分讲得特别抽象什么变换分析、事务分析背了一堆定义真拿到一张DFD照样不知道从哪下笔。我这些年做系统设计评审、带团队画架构图反复用这套方法印象最深的一点是只要掌握了从DFD到结构图的映射逻辑画出来的程序结构图基本不会跑偏而且后端的模块划分、接口设计都会顺很多。这篇文章我不讲虚的直接把这套方法拆开揉碎从DFD上怎么找中心变换到结构图怎么分层、模块怎么命名、参数怎么标再到真实案例完整走一遍流程。最后再把我实际踩过的坑、审图时总结的排查清单一并分享出来。无论你是正在学软件工程的学生还是工作中需要画设计文档的开发人员这篇都值得花十几分钟认真看完。1. 内容整体设计与思路拆解1.1 核心需求解析DFD到结构图到底在解决什么问题先想清楚一个问题我们为什么要把DFD转换成程序结构图DFD描述的是数据在系统中怎么流动、被哪些加工处理它回答的是系统做什么。但软件设计不能只停留在做什么还必须回答软件怎么组织。程序结构图也叫软件结构图、模块结构图描述的是系统由哪些模块组成、模块之间怎么调用、数据怎么传递它回答的正是系统怎么做。换句话说从DFD到结构图本质上是完成一次从问题域到解决域的映射。DFD是需求分析阶段的产物结构图是设计阶段的起点。两者之间的桥梁就是结构化设计方法中的变换分析和事务分析。很多人以为这个转换是自然而然的过程实际完全不是。DFD是数据流视角结构图是调用视角两者之间存在根本性的视角切换。DFD里一个加工可能对应结构图里的多个模块也可能多个加工合并成一个模块DFD里的数据存储到了结构图里可能变成数据模块或者被模块内部封装。如果不掌握系统的转换规则画出来的结构图就只是DFD的翻译版而不是真正的设计版。1.2 为什么选择结构化方法从DFD导出结构图的独特优势市面上现在有大量面向对象分析和设计方法很多人会问为什么还要学结构化这套老方法我的看法是结构化方法的最大优势在于可追溯性。DFD的每个加工、每条数据流、每个存储都能在程序结构图中找到对应物。这种图纸到图纸的严格映射让设计过程变得非常可控。面向对象方法里类图、时序图、用例图之间也有追溯关系但论述的严格程度远不如DFD到结构图的推导过程那么明确。另外结构化方法特别适合那些数据处理为主的系统——管理信息系统、报表系统、订单处理系统本质上都是数据输入、加工、输出的模型。这类系统用DFD加结构图的方式设计逻辑非常清晰开发阶段几乎不会出现模块职责说不清的混乱局面。还有一个实际好处DFD和结构图都是少画细节、多画关系的图。它们不像类图那样需要把每个属性方法都列出来非常适合作前期方案评审。我们团队做技术方案时经常先在白板上画DFD确认需求理解一致后再导出结构图评审模块划分是否合理。整个流程非常轻量但效果出奇地好。1.3 方案选型的适用场景什么时候该用这套方法要说明的是不是所有系统都适合从DFD导出结构图。我列一下适合和不适合的场景大家按需取用场景适合程度原因数据密集型管理信息系统非常适合数据流清晰加工边界明确业务流程型系统订单、审批、报销非常适合核心是数据在不同状态间流转实时控制系统工业监控、嵌入式部分适合需要结合状态转换图单靠DFD不够算法密集型系统图像处理、AI推理不太适合核心是计算逻辑而非数据流转高交互UI为主的应用不太适合界面逻辑和数据流之间耦合复杂微服务架构的系统部分适合DFD可帮助识别服务边界但还需补充接口设计如果你负责的是前两类系统这套方法几乎是你必须掌握的技能。我当年从校园项目过渡到企业级系统设计时最大的一个顿悟就是照着DFD导出结构图设计出来的模块划分比我自己凭直觉拍脑袋画出来的稳定得多。2. 核心细节解析与实操要点2.1 变换分析从DFD找出中心变换变换分析是DFD导出结构图的核心技术之一。简单说变换分析做的事情是把DFD里的加工按输入流、中心变换、输出流三段式划分。中心变换就是系统真正完成核心业务处理的地方输入流是它之前的加工链输出流是它之后的加工链。以图书借阅系统为例后面会反复用到这个例子DFD里有这些加工解析借阅请求、校验读者资格、检查图书库存、创建借阅记录、更新库存、生成借阅结果、输出借阅信息。按变换分析划分输入流解析借阅请求、校验读者资格数据从外部进来逐步格式化和验证中心变换检查图书库存、创建借阅记录、更新库存真正的业务核心输出流生成借阅结果、输出借阅信息数据加工完成往外送关键经验是中心变换的加工通常有两个特征。一是它们会操作数据存储读写数据库文件二是它们会引发业务状态的改变。输入流和输出流的加工更多是做数据格式转换、参数校验、结果呈现不触碰核心业务状态。这个特征在实站中非常好用拿不准一个加工该归哪段时就看它是否直接读写数据存储。2.2 事务分析针对事务型DFD的设计策略和变换分析并列的另一个核心方法是事务分析。当DFD的形态是一个输入扇出多个并行加工路径时这就不是变换流而是事务流。典型例子一个请求处理加工根据请求类型分发到处理查询处理修改处理删除处理新增每条路径相对独立。事务分析的做法是识别出事务中心负责分发的那个加工把它映射为结构图中的事务控制模块然后为每个事务流路径设计一个收编模块。比如上例中事务控制模块请求分发控制收编模块分支1查询处理模块收编模块分支2修改处理模块收编模块分支3删除处理模块收编模块分支4新增处理模块实际系统中纯变换流和纯事务流都比较少见大多数是混合流。处理办法也很简单以变换分析为骨架搭出整个结构图的框架在遇到明显的事务分发特征时局部用事务分析细化那一部分。这就是教科书上说的变换分析为主、事务分析为辅实际操作中很好用。2.3 结构图的分层原则与模块归属程序结构图的形态是树状层次结构顶层是系统根模块往下逐层细化。分层设计时有几个原则必须记住这是我审图时最爱检查的点1. 上层模块负责控制与协调下层模块负责具体执行。这个原则看着简单实际很多初学者的结构图里上层模块写得比下层还具体比如把打印借阅清单的具体输出逻辑直接画到了顶层模块上。这就违背了分层的基本思想上层管调度、管分支、管逻辑下层管功能实现。2. 模块的作用域应在其控制域之内。这句话是结构化设计里最经典的原则翻译成大白话就是一个模块能影响到的范围作用域应该被限制在它能指挥的范围内控制域。如果某个判断的结果会影响一个离它很远的模块那么这个结构就有病了——要么把判断往上提要么把受影响的模块往下移。这个原则对可维护性影响非常大。3. 数据流是结构图的血脉数据守恒是关键。导出结构图时每条调用路径上传递的数据必须保持连续和守恒。简单说如果一个加工输入的数据流是A和B输出的数据流是C那么在结构图中这个模块接收来自父模块的数据就必须包含A和B返回给父模块的数据就必须包含C。数据不守恒的结构图在编码阶段一定出问题而且问题非常隐蔽经常到联调才会暴露。结构图的层次感还有一个小技巧画图时保持同一层级的模块之间是水平对齐的父模块在子模块的正上方。这个排版习惯看似无关紧要但人眼对对齐和层次非常敏感排版整齐的结构图审图效率会高出一大截。层次感更清晰甚至能看出来哪些模块是被谁调用的、哪些模块属于同一个家族、哪些模块是公共祖先的后代。2.4 模块划分的合理性我的自查标准模块划分是导出结构图过程中最考验经验的一步。这里分享一些我自己总结的健康检查标准模块职责要单一一个模块只做一件事。如果模块名里含有和与这类连接词比如校验图书和读者基本可以确定应该拆分为两个模块。模块粒度要适中过小则模块数爆炸过大数据会变得不可读。我的经验是以一个模块大概能在一屏内看懂其伪码逻辑为粒度标准。高扇入低扇出扇入是指有多少上层模块调用它扇入高说明复用性好扇出是指一个模块直接控制的下层模块数扇出过高说明控制逻辑复杂。一般扇出控制在7正负2以内是经验值。模块之间低耦合模块间通过数据耦合或控制耦合最好尽量避免内容耦合一个模块直接访问另一个模块的内部数据。结构图中表现为连线上的标注尽量是数据名而不是控制标志满天飞。2.5 从一个实例理解映射规则借阅系统完整示例为了把上面的规则落到实站我设计一个简化版的图书借阅系统DFD然后完整导出结构图。0层DFD加工清单加工编号加工名输入数据流输出数据流1解析借阅请求借阅请求原始借阅请求格式化2校验读者资格读者ID校验结果3检查图书库存图书ID库存状态4创建借阅记录借阅信息借阅记录确认5更新库存数量图书ID、扣减数量更新结果6生成借阅结果借阅记录确认、更新结果借阅结果信息7输出借阅信息借阅结果信息借阅结果外部可见先做变换分析。我快速划分输入流加工1解析借阅请求、加工2校验读者资格中心变换加工3检查图书库存、加工4创建借阅记录、加工5更新库存数量输出流加工6生成借阅结果、加工7输出借阅信息于是程序结构图的顶层就出来了图书借阅管理系统根模块 ├── 输入流控制模块 │ ├── 解析借阅请求 │ └── 校验读者资格 ├── 中心变换控制模块 │ ├── 检查图书库存 │ ├── 创建借阅记录 │ └── 更新库存数量 └── 输出流控制模块 ├── 生成借阅结果 └── 输出借阅信息这个结构图的关键之处在于三个控制模块输入流控制、中心变换控制、输出流控制正好对应DFD三个段每个加工都映射为结构图中的一个功能模块模块之间的连线体现了调用关系——根模块依次调用三个控制模块控制模块再调用各自下属的功能模块。这就是一张初始结构图。后续优化时我还会做几件事把检查图书库存和更新库存数量合并到一个库存管理控制模块下因为它们都在操作同一个数据存储——图书库把生成借阅结果和输出借阅信息合并成借阅结果输出模块因为一个负责拼装、一个负责格式化职责太细合并更合理。优化后的结构图会层次更清晰也更贴近最终实现时的模块划分。2.6 结构图与DFD的一致性检查清单最后分享一个我最常用的检查清单。每当画完结构图我都会按这个清单逐项自查一遍基本能过滤掉90%的低级错误检查项具体问题对应方法加工完整性DFD中的每个加工是否都映射到了结构图逐个加工编号对照模块合法性结构图中的每个模块是否都能在DFD中找到根源反向追溯防止多余模块数据守恒性模块的输入输出数据是否与DFD加工一致核对数据流名称和组成控制范围是否有模块作用域超出了控制域检查判断逻辑的影响范围扇入扇出各模块的扇入扇出是否合理计数检查扇出建议≤7层次深度层次是否过深一般不超5~7层人眼实测可读性命名规范模块名是否都是动词宾语结构读名字判断职责清晰度这个清单我和团队成员已经用了好几年每次画完结构图都会执行一遍。有人觉得这样太教条但实际发生的场景往往是清单检查出来的问题比评审时别人提出的问题还多。写设计文档这件事最怕的就是交出去被人挑一堆低级问题那比能力不足更显得态度有问题。3. 实操过程核心环节逐步演示这一节我完整演示一遍从DFD到结构图的实操流程包含每一步的中间产物。为保证可读性我用的例子还是上面那个图书借阅系统但流程完全一样你可以直接把方法套用到自己的项目里。3.1 准备工作工具选择与底图画法画DFD和结构图的工具我用过不少。最轻量的是纸笔和绘图白板适合前期头脑风暴正式文档阶段我推荐用支持分层和自动布局的工具。个人常用的是draw.io免费、导出方便、支持各种模板或者PlantUML代码化、适合版本管理。如果是团队协作可以用在线协作白板方便多人实时修改。画DFD时建议用标准的Gane-Sarson或Yourdon记号选一种就行团队统一最重要。我的习惯是加工用圆角矩形数据流用带箭头的线数据存储用开口矩形外部实体用方框。这些记号在工具里都能找到对应图形。前期底图阶段最容易被忽略的一件事是给每个加工、每条数据流编号。比如加工编号为P1、P2、P3数据流编号为D1、D2、D3。这个编号看似多此一举但等导出结构图时你就知道它的价值了——模块与加工之间的映射表全靠编号维护没编号的话稍复杂的系统马上就会产生混乱。3.2 第一步确定主加工与中心变换在0层DFD上用笔标出三条分界线输入流与中心变换的分界、中心变换与输出流的分界。这一步就是前文说的变换分析实操时动作要快不要纠结。拿不准时有一个辅助方法从外部实体开始沿着数据流往系统内部走观察数据形态的变化。数据从原始格式/外部格式转变为内部规范化格式的节点通常就是输入流结束的位置数据从内部业务结果转变为外部展示格式的节点通常就是中心变换结束的位置。这个方法在复杂系统里尤其好用。3.3 第二步画出初始结构图框架确定了中心变换后开始画结构图的骨架在顶层画一个矩形写上系统名如图书借阅管理系统。在第二层画三个模块输入流控制模块、中心变换控制模块、输出流控制模块。用连线把根模块与三个第二层模块连接起来标上对应的数据流名称输入数据、处理数据、输出数据。这一步产出的三模块框架图是整个结构图最基础的骨架。后续所有加工模块都挂在这三个模块下面。3.4 第三步逐个映射加工到结构图模块现在开始干搬运的活把0层DFD中的每个加工按照它所在的分段输入流/中心变换/输出流挂到对应的控制模块下面。映射时注意DFD中的加工名往往很口语化结构图中的模块名需要更规范。我的做法是统一改成动词宾语结构解析借阅请求保留已经很规范校验读者资格保留检查图书库存保留创建借阅记录保留更新库存数量保留生成借阅结果与输出借阅信息合并为输出借阅结果加工名与模块名不需要完全一致但语义上必须可追溯。建议在映射表里记录DFD加工P1对应结构图模块M1保证设计文档可查。3.5 第四步优化结构图消除不良结构初始结构图出来后优化是必经阶段。我这里列出几个最常见的优化动作动作一合并冗余模块。生成借阅结果和输出借阅信息都是中心变换完成后的后处理合并成一个输出借阅结果模块减少层次不损失信息。动作二调整模块归属。检查图书库存和更新库存数量都涉及图书数据存储把它们归到库存数据管理控制模块下比直接挂在中心变换控制模块下更合理。因为它们在业务上天然成组后续如果要扩展图书库存盘点只需在库存数据管理下再挂一个新模块即可。动作三补充必要的公共模块。检查DFD如果多个加工都需要读写同一个数据存储结构图中应该体现一个公共的数据操作模块。借阅系统里借阅记录存储被创建借阅记录查询借阅记录更新借阅记录共享考虑增加一个借阅记录数据访问模块提供给相关功能模块调用。这就是通过数据耦合降低模块间耦合的重要手法。优化后最终结构图如下图书借阅管理系统 ├── 输入控制模块 │ ├── 解析借阅请求 │ └── 校验读者资格 ├── 业务处理控制模块 │ ├── 借阅处理 │ │ ├── 检查图书库存 │ │ ├── 创建借阅记录 │ │ └── 更新库存数量 │ └── 借阅记录数据访问 └── 输出控制模块 └── 输出借阅结果注意借阅记录数据访问模块挂在了借阅处理下面但它可能被其他模块比如归还处理也调用这时扇入会增加这是好事。如果它在结构图中被多个父模块共用画图时可以用两个箭头指向同一个模块或者画成虚线连接表示共享关系。3.6 实操心得我这几年画结构图的几条经验控制评审轮次里的改图成本。画结构图最忌讳的是一个人闷头画到完美再拿出去评审。我一般第一版草图出来后立马找同事花十分钟过一遍重点确认模块划分符不符合大家的心理预期而不是图例对不对。早改永远比晚改便宜。版本管理很重要。结构图是设计文档的一部分必须纳入版本管理。我习惯用PlantUML这种文本化工具画图方便git diff也方便从代码注释里直接生成文档。不要过度追求一次画对。DFD到结构图的转换是一个设计过程设计就意味着权衡和取舍。第一版结构图不合理非常正常重要的是快速迭代、快速修正而不是让自己陷入分析瘫痪。复杂系统先画局部放大图。如果系统规模很大DFD有几十个加工别指望一张结构图画满所有模块。我会先画系统级的顶层结构图然后对复杂分支单独画放大版结构图标注细节。图面整洁度也值得花时间。结构图最终是要给人看的字号统一、连线清晰无交叉、模块间距一致这些视觉因素对交流效果的影响非常大。我会花至少10%的制图时间在美化上——这个时间绝对值得。4. 常见问题与排查技巧实录4.1 常见问题速查表问题现象可能原因排查思路解决建议DFD画完了却找不准中心变换中心变换被输入输出混杂边界不清晰先按读入—处理—写出三阶段重新整理DFD把输入解析、格式转换等辅助加工归入输入/输出段保留核心业务加工为中心结构图上的模块在DFD中找不到为了分层而创造了多余的模块反向追溯该模块的数据来源删除该模块或调整其在结构图中的位置DFD上的加工在结构图中没有体现漏映射对照DFD加工清单逐一核对补充模块确保每个加工有归属两个模块之间有大量数据交互耦合过高接口设计不合理统计连线上标注的数据名和数量引入中间数据模块或数据聚合DTO结构图太宽太深层次控制失败检查顶层模块的扇出、最低层模块的深度增设控制模块或合并无关紧要的模块结构图更新了DFD还是旧版文档管理混乱建立DFD和结构图的映射表每次变更两个图同步修改模块命名是数据管理这类抽象词职责边界模糊逐字问它到底做了什么动作改成更新图书状态这类具体动词短语审图人看不懂连线逻辑结构图设计重于装饰请审图人尝试口头描述一次调用链按谁调用谁、传什么数据、有什么返回重绘4.2 高频问题深度拆解找不到中心变换怎么办这个问题在带新人时几乎必现。他们的DFD画得极其标准每一个加工都有模有样但一到标定中心变换就卡壳。后来我给他们一个几乎无脑的办法**把DFD里的数据格式转换类加工全部标成输入/输出段剩下的全部标为中心变换段。**因为解析请求格式化响应校验权限这些加工的输入输出紧贴着外部实体它们更多是包的拆装不是核心业务算法核心业务是那些直接操作存储、直接计算状态、直接改变业务结果的加工。当你把包装类加工都归入输入输出段之后剩下的中心变换自然凸显出来。结构图里数据流对不上怎么办一个常见坑是在结构图里父模块调用了子模块但传下去的参数和子模块期望的不一致。比如归还控制调用了计算逾期费用传过去的是图书ID子模块却要用借阅记录ID。排查这个问题的硬办法是**拿着结构图从根节点开始沿着每条调用路径把路径上标注的数据名写一遍再对照DFD中对应的数据流检查数据是否连续、无断流、无改名不改内容的情况。**如果某条数据在DFD中叫归还请求在结构图中突然变成了bookId那中间一定有包装或解包的过程没有画出来。审图人看不懂我的结构图怎么办结构图是给人看的不是给IDE看的。如果审图人看不懂多半是结构图本身不够直观。我在团队内部总结过一个口头调用链法让作者不看结构图在一分钟内口述一条链路用户发起借阅请求借阅控制模块接到请求先做资格校验再查询图书信息然后更新图书库存最后生成借阅记录并返回结果。如果他能说清楚结构图的方向和层次基本没问题如果说得含混不清、卡顿迟疑结构图大概率需要重构。这个方法屡试不爽比任何抽象理论都管用。5. 从DFD到结构图的扩展应用与落地建议5.1 与面向对象设计的衔接不是二选一很多人误以为结构化方法和面向对象方法是互斥的实际不是。从一个稍微大一点的项目来看完全可以先用DFD导出结构图再把结构图作为设计基础转成面向对象风格的类图或组件图。具体的衔接思路是结构图中的每个控制模块在面向对象设计中往往对应一个控制类比如借阅控制类每个功能模块对应一个业务类或服务类每个数据操作模块对应一个数据访问类或仓储类。模块之间的调用连线对应面向对象中的依赖关系或关联关系。结构图里标注的数据流对应类之间方法的参数与返回值。我做过好几个项目都是先用结构化分析搞定整体框架再用面向对象设计实现局部细节。这种结构化打底、面向对象细化的路线特别适合团队成员经验参差不齐的情况结构图保证了整体架构的稳定性面向对象设计提供了局部的灵活性两者互补效率很高。5.2 自动化辅助工具与图转代码的实践虽然从DFD导出结构图的核心是脑力劳动但还是有一些工具辅助可以减少重复劳动。结构化设计辅助插件部分建模工具支持从DFD建立模块映射表我还没有见过真正全自动的转换工具。因为加工归入哪一段这个判断需要业务语义的理解纯靠规则很难做对。但辅助工具能帮你维护映射关系、检查数据一致性非常值得用。文本绘图工具PlantUML或Mermaid可以直接用文本描述结构图保存进git这样每次修改可追溯。也方便写文档时嵌入。从结构图生成代码骨架一些IDE插件可以根据类图生成代码骨架虽然结构图不是类图但模块之间的调用关系可以翻译成方法签名。这一步翻译如果做成模板化流程能省不少体力。我个人的工作习惯是前期用白板或在线白板快速迭代DFD和结构图的草图确定框架后再用文本化工具画正式版然后基于结构图手动编写模块接口清单作为开发的起点。整个过程配合起来效率很高。5.3 在团队中推广这套方法的建议如果你想让团队系统性地使用DFD导出结构图这套方法我有几个落地建议先在一个模块试点别全面铺开。找个业务边界清晰的子系统带着一两个人完整走一遍DFD到结构图的流程把过程中的产物DFD、映射表、结构图、接口清单沉淀成模板。试点跑通了再推广到整个团队。模板比培训更重要。与其做两小时方法论培训不如给团队一套填写即可用的模板DFD要素表加工、输入、输出、存储、映射表加工编号、模块名、所属段、结构图异常检查清单、模块接口清单。大家照着模板走一遍方法自然就学会了。评审机制要跟上。把结构图评审设置为设计阶段的必经环节至少要有一个人是熟悉结构化方法的。评审重点不是图好看不好看而是DFD到结构图的映射是否正确、模块划分是否合理、接口是否清晰。坚持两三个项目之后团队的设计文档质量会有明显提升。允许在运用中灵活裁剪。小项目可以不画严格的DFD直接从大致的流程草图上导出结构图大项目则必须完整走DFD到结构图的流程。方法是为项目服务的项目复杂度不同严谨程度自然可以不同。6. 结语这套方法过了这么多年我还在用从学生时代第一次接触DFD到工作后带着团队用结构化方法做系统设计再到如今在更大规模的项目里做架构评审这个方法给我的帮助是持续性的。哪怕后来我大量使用面向对象设计、微服务架构DFD和结构图依然是我梳理思路的首选工具——因为这两张图逼着你把数据从哪来、到哪去、谁来处理这些最基本的问题回答清楚而任何一个靠谱的系统底层逻辑都逃不开这三个问题。我踩过不少坑最深刻的一条是设计文档不是给别人检查用的是给自己理思路用的。画DFD和结构图的过程本身就是在倒逼自己把模糊的需求澄清、把混乱的业务理顺。如果你觉得自己对某个系统已经理解了却迟迟画不出一张清晰的DFD那大概率不是画图技术问题而是需求理解还有黑洞。这时候认真补一张DFD比闷头写代码有效得多。这篇内容能给你带来的最大价值不是复制某个项目的设计图而是掌握一套可以把抽象需求一步步变成具体模块的方法论。无论你是在校学生、初级开发还是有经验的设计师把DFD导出结构图这个基本功练扎实你画任何架构图都会更胸有成竹。希望这篇实操拆解对你有用。如果你画图过程中遇到具体问题欢迎带着你的图来交流——我见过太多看起来没问题但实现就废的结构图这些问题往往在图纸阶段就能解决完全不需要推到编码阶段才爆炸。
返回列表