☰
数据流图DFD四要素、父子平衡与逐层分解实战
2026/11/3 13:42:41 网站建设 项目流程

简介:这份PPT面向管理信息系统、软件工程与系统分析初学者,围绕数据流图(DFD)的概念、符号体系与绘制方法展开,帮助读者理解如何用图形化方式描述信息流动与处理逻辑,不涉及具体实现技术。压缩包内仅含1个PPT文件,大小约312KB,内容以课程讲义形式组织,适合课堂演示、自学梳理与复习查阅。课件从数据流、加工、数据存储、外部项四类基本元素讲起,说明命名与编号规则,并给出自顶向下逐层分解的绘制步骤、主要原则和常见注意事项;末尾以银行取款处理为例,演示关联图与下层DFD的分解过程,便于对照练习。目前已有626人学习,适合需要快速建立DFD知识框架、完成课程作业或系统分析入门的中初级读者。

1. 数据流图:四个符号为什么能提前暴露系统的结构性问题

数据流图(DFD)最反直觉的地方,是它几乎不描述任何「怎么做」。没有表结构,没有接口协议,没有并发模型,只有四种符号在纸面上移动。正是这种「什么都不管」的克制,让它在系统分析阶段拥有一种少见的能力:把「信息从哪来、被谁变换、停在哪、到哪去」这四件事单独抽出来,与硬件、软件、数据结构、文件组织彻底解耦。一个完全不懂信息技术的业务人员,也能指着一张数据流图说「这里不对,这张单子不该直接进总账」。这种沟通效率并非来自图形好看,而是来自模型边界划得足够清楚。

后面几章要做的事情,就是把这种清楚拆开看:四种符号为什么刚好够用、逐层分解靠什么保证不散架、父子图之间那条没写进符号表的规则到底在约束什么。

2. 四种符号的封闭组合:DFD 的四要素为什么刚好够用

任何建模语言都是在做减法,问题在于减到哪一刀为止。DFD 减到只剩四个元素——外部项、加工、数据流、数据存储——并非偷懒,而是因为描述信息运动恰好需要四个维度:谁在系统之外、什么在改变数据、改变后的数据怎么流动、流动的数据停在哪里。少一个维度,模型就说不清事;多一个维度,模型就开始侵入物理实现的领地。

2.1 外部项与加工:把「边界」和「变换」切开

外部项又称源点和终点,是系统环境中的实体,与系统之间存在信息交流。从源点流向系统的信息是系统的输入,从系统流向终点的信息是系统的输出。同一个端点可以是人,也可以是另一个系统。引入外部项的目的很单纯:让读者知道系统的边界在哪里,所以 DFD 明确不要求详细描述它们,通常只给一个编号,以S开头。这个「不描述」是有意的克制——一旦开始描写外部项内部的逻辑,系统边界就模糊了,后面的分解会失去参照。

加工则完全不同。加工的符号分成上下两部分:标识部分写编号,编号必须唯一,以P开头;功能描述部分写加工名,要求简单、能概括对数据的加工行为,详细定义放到数据字典里去。这个「图上只写一句、细节另存」的分工,是 DFD 能在一张纸上容纳复杂系统的关键。如果把每个加工的内部规则都画在图上,图会立刻退化成流程图,失去抽象能力。

这两个符号真正的分野,不在于「人还是程序」,而在于「是否在建模范围内负责变换」。同一个实体在不同层次的图上身份会变:图书预订系统里,顾客和出版社是外部项;如果把「出版社订单接收」也纳入系统范围重新画一张图,出版社的接收动作就可能变成一个加工。所以 DFD 的第一条绘制原则是「明确系统界面」,界面画在哪,外部项和加工的分界线就在哪。这条原则听起来像套话,实际是整个图能否自洽的前提。

2.2 数据流与数据存储:把「瞬时」和「持久」切开

数据流由一个或一组确定的数据组成,名字要能直观反映含义,必须有明确流向。这里有几条容易被忽略的规则:数据流可以同名,也可以有相同的数据结构,但必须对应不同的数据或不同的含义;两个符号之间可以同时存在多条数据流,而 DFD 并不表明它们之间的次序、主次关系。最后这条尤其重要——它等于宣布 DFD 放弃表达时序,只表达存在哪些传递关系。

数据存储负责存贮数据。在分层 DFD 中,数据存储一般只属于某一层或某几层,所以又叫局部文件。它的画法有几个硬要求:名字写在开口的长方框内,要概要说明文件中的主要数据;数据存储上一定要有数据流;编号写在符号左端的小方格中,以D开头;为了避免交叉线,同一个数据存储可以在多处重复画出,用重复符号标记。

数据流和数据存储的本质区别是时间维度。数据流描述的是「正在发生的传递」,数据存储描述的是「跨越加工的执行时点而依然存活的状态」。这个区分让 DFD 天然能表达一件流程图很难表达的事:某个动作早就结束了,但它的结果还在,并且还会被后面的动作读到。银行取款图里,D1 帐目库就是这样一个存在——取款审核读过它,取款处理改过它,它不属于任何一次单独的执行。

注意:数据存储「一定要有数据流」这条约束,翻译过来就是孤岛式存储属于非法状态。一个只有写入没有读取的存储,或者只有读取没有写入的存储,在 DFD 里都是可疑信号。

2.3 符号的约束不是审美,是可校验性

把这些规则归拢一下:数据流必须通过加工;数据存储一般作为两个加工环节的界面来安排;避免交叉线;命名要直观;编号要唯一;必须自顶向下逐层分解。它们看起来像一份画图风格指南,实际上是一套「图是否合法」的判定条件。区别在于:风格指南违反了只是不好看,判定条件违反了说明系统没想清楚。

举个最典型的例子。外部项直接连到数据存储,这条线在语法上完全画得出来,但语义上是有问题的——它意味着有一份数据被写进了系统状态,却没有任何一个处理环节为这次写入负责。校验、格式化、去重、权限,这些动作在模型里全部消失了。所以「数据流必须通过加工」并不是形式要求,它是在逼你把责任主体找出来。

这也是 DFD 相对容易被工具支持的原因:它的约束大多可以归结为图上的集合关系,而不是语义判断。只要把图表示成节点和边的集合,很多错误能被机械地检测出来。这一点在第 3 章会落到具体做法上。

3. 逐层分解的底层机制:DFD 靠什么保证越画越不散架

一张图装不下一个真实系统,这是所有建模方法都要面对的问题。DFD 的解法是分层:先把整个系统当成一个黑匣子,再一层层打开。分层本身不稀奇,稀奇的是它有一套隐式规则,保证打开之后还能对得上。这套规则比四种符号更值得研究,因为符号决定你能画什么,规则决定你画得对不对。

3.1 关联图:分解的起点必须是一个完整的黑匣子

绘制步骤的前两步,是先确定系统的外部项,再确定整个系统的输入输出数据流,把系统作为一个加工环节,画出关联图。关联图的信息量极小:几个外部项,一个代表整个系统的加工,以及进出这个加工的若干条数据流。

以银行取款为例。储户把填好的取款单和存折交给银行,银行审核并查对账目,不合格的存折和取款单退回储户,合格的送取款处理;取款处理修改账目,把存折、利息单、结算清单和现金交给储户,同时把取款单存档。画关联图时,储户既是源点也是终点,系统只有一个加工「取款处理」,输入是「取款单、存折」,输出是「存折、利息单、结算清单」。

这里有个特别容易出错的地方:现金是实物,不能作为数据流。很多人第一反应是把「现金」画成一条从系统流向储户的线,因为业务上现金确实交付了。但 DFD 描述的是信息运动,实物交付属于物理模型的事。这条线一旦画上去,图的语义就从「信息处理」滑向了「业务操作」,后面所有的分解都会跟着跑偏。

关联图真正的产出物,是一份输入输出契约。系统界面在这张图上被固定下来,后续所有分解都是在这个界面内部的细化,不允许悄悄扩大或缩小边界。这就是为什么关联图看起来最没用,却最不能跳过。

3.2 父子平衡:那条没写在符号表里的严格规则

第三步确定系统的主要信息处理功能,把整个系统分解成几个加工环节,并确定每个加工的输入输出数据流以及相关的数据存储。从这一步开始,分解正式启动,而支配分解的核心规则叫父子平衡。

父子平衡说的是:父图中某个加工的输入输出数据流,必须与把它展开后得到的子图的输入输出数据流保持一致。原文里这条规则藏在「检查各层 DFD 及同层 DFD 之间关系是否正确」这句话里,没有单独列出来,但它是整个分层结构能不能成立的关键。

为什么必须平衡?因为分解是细化,并非重新设计。如果子图多出一条父图没有的输入,说明分解过程中悄悄改了系统界面;如果子图少了一条输出,说明有数据凭空消失;如果子图里出现了一条从外部项直接进来的流,而父图里这条流本来是进入该加工的,那就意味着责任主体被转移了。

拿图书预订系统的二层分解看。顶层图里P2 订单处理的对外接口是:输入F1 订单,输出F2 汇总订单(流向P3 发送订单)。二层图把P2拆成P2.1 数目统计、P2.2 订单分类、P2.3 随时处理、P2.4 定期处理,中间新增了D2 合格订单、D3 待发出订单、D5 订单数目、D6 顾客情况、D7 优先订单、D8 正常订单这些数据存储。乍一看元素多了很多,但跨过P2边界的流仍然只有F1和F2两条,其余全是P2内部的状态。这就是平衡。

平衡被破坏通常有两种形态。第一种是出口丢失:父图里P2明明输出F2,子图里却找不到任何一条汇聚成F2的流。第二种是边界泄漏:子图里凭空出现一条从S1 顾客到P2.1的流,而父图上顾客的流只进到P1 订单检查。第二种更隐蔽,因为图看上去是通的,只是系统的边界被悄悄挪动了。

3.3 编号体系:被当成装饰的一致性索引

四类元素各自有一套编号前缀,这套设计经常被当成排版习惯,实际上它是跨层引用的索引。没有它,父子平衡只能靠肉眼比对。

前缀元素编号方式在分解中的作用
P加工逐层带父号,如P2展开为P2.1~P2.4父子关系直接编码进编号,看到编号就知道归属
D数据存储全局唯一,D1、D2……跨层共享的存储靠编号对齐,避免同名不同物
F数据流全局唯一,子流可带父号,如F1.1、F1.2与数据字典条目一一对应
S外部项全局唯一,S1、S2……标识系统边界,跨层必须保持不变

数据流其实也能逐层细化。银行取款的顶层图里,关联图上的F1 取款单、存折在下一层被拆成F1.1 合格存折、取款单和F1.2 不合格存折、取款单,两条子流带上父号,读者一眼就能看出它们是从哪条流分出来的。这个技巧在复合数据流很宽的图里特别有用。

既然规则大多能归结为集合比较,就可以写成代码来校验。下面这段不是要做一个画图工具,而是把「父子平衡」从口头规则变成可执行判断:

from dataclasses import dataclass, field @dataclass class Flow: fid: str # 数据流编号,如 "F1" name: str # 数据流名,如 "取款单、存折" src: str # 起点编号:S1 / P1 / D1 dst: str # 终点编号 @dataclass class Diagram: level: str # "context" / "top" / "level2" flows: list # 本层所有数据流 children: dict = field(default_factory=dict) # 加工编号 -> 展开后的子图 def boundary_flows(diagram, pid): """取父图上挂在某个加工身上的所有流,即该加工的对外接口""" return {(f.fid, f.name) for f in diagram.flows if pid in (f.src, f.dst)} def check_balance(parent, pid): """父子平衡:父图加工的对外流,应当与子图的对外流等量""" up = boundary_flows(parent, pid) child = parent.children[pid] # 子图里连到外部项的流,才是真正跨出该加工边界的流 down = {(f.fid, f.name) for f in child.flows if f.src.startswith("S") or f.dst.startswith("S")} return up, down, up == down

boundary_flows遍历父图,把起点或终点等于pid的流全部取出来,得到的是一组(编号, 名称)二元组,代表这个加工的契约。check_balance再进入子图,只挑出连到外部项的那些流,因为内部数据存储之间的往来不算跨边界。两边做集合相等判断,不等就说明分解过程中接口被动了。

参数pid是父图里的加工编号,parent.children保存的是「这个加工被展开后的那张子图」。这段代码能抓出前面说的两种破坏:出口丢失会让down少元素,边界泄漏会让down多元素。

提示:严格相等在一种情况下会误报——父图里一条复合流在子图里被拆成多条,编号不同但语义相同。工业上的做法是靠数据字典做语义对齐,而不是靠编号硬等。这也说明 DFD 的自动校验只能覆盖结构一致性,语义一致性仍然要人来看。

4. 绘制步骤拆解:七个步骤每一步在防什么错

把绘制过程拆成七步,看起来像流程规范,实际上每一步都在堵一类特定的错误。顺序不能换,因为后一步依赖前一步的产出:没有边界就定不了契约,没有契约就切不准功能。

4.1 前三步:定边界、定契约、定主要功能

第一步确定外部项,也就是系统的数据来源和去处。这一步的难点不在识别,而在取舍:一个实体要不要算外部项,取决于它在不在系统界面之内。图书预订系统里,顾客和出版社都在外面,因为系统只负责订单的检查、分类、汇总和发送,不负责顾客下单的动作,也不负责出版社收到订单之后的编目。

第二步把系统当成一个加工,画出关联图。这一步产出的是输入输出契约,前面已经说过它的分量。这里补一个细节:关联图上只能有一个加工。有人会忍不住把「订单检查」和「订单发送」先拆出来,理由是它们明显是两件事。但关联图的职责是划定边界,并非描述内部结构,提前拆分等于跳过了边界确认,后面很容易发现少识别了一个外部项,整张图要重画。

第三步确定系统的主要信息处理功能,把系统分解成几个加工环节,同时确定每个加工的输入输出流以及相关的数据存储。这一步是分解的第一次落地,切分的依据是信息处理的自然接缝,并非组织架构。按部门切是新手最容易犯的错——按部门切出来的图,每个加工都对应一个科室,看起来整齐,但数据流的走向会变得毫无逻辑,因为部门边界和信息边界本来就不重合。

4.2 后四步:分解、终止、检查、对齐用户

第四步自顶向下逐层分解,对上层图中全部或部分加工进行分解。注意「部分」这两个字:不是每个加工都必须往下展开,展开到什么程度由业务复杂度决定。

第五步重复分解,直到逐层分解结束。这里的「结束」需要一个判据,原文给的判据是分解后的加工功能简单、易于理解。换成可操作的表述,就是到达基本加工——业务上不可再分或不必再分。判据是业务上的,并非技术上的。一个加工内部就算有一百行判断逻辑,只要业务人员认为它是一件事,就可以停。

第六步检查和合理布局,检查分解是否恰当彻底,各层是否有遗漏、重复、冲突,各层之间及同层之间的关系是否正确,命名和编号是否确切合理。这一步做的其实就是父子平衡检查和命名审查。

第七步和用户交流,在用户完全理解图的内容的基础上征求意见。这一步放在最后并不是走过场。DFD 的全部价值建立在「非技术人员能读懂」这个前提上,如果用户看不懂,前面六步的抽象就白做了。

4.3 两个例子的分解路径对比

银行取款是个两层就够的例子。关联图之后,顶层图把系统拆成P1 取款审核和P2 取款处理:P1读D1 帐目库做校验,产出F1.1 合格存折、取款单送P2,不合格的走F1.2退回储户;P2修改D1,写D2 取款记录,输出F2 存折、利息单、结算清单。整张图里没有第三个加工需要展开,业务链条到此为止。

图书预订则是必须三层的例子。顶层图拆出P1 订单检查、P2 订单处理、P3 发送订单,配D1 图书目录、D2 合格订单、D3 待发出订单、D4 出版社要求。到这里还不能停,因为P2内部藏着一个策略分支:订单要按顾客情况和订单数目分成优先订单与正常订单,优先订单随时处理,正常订单定期处理。这个分支不展开,读者根本看不出「随时」和「定期」的差别,而这恰恰是业务规则的核心。于是二层图把P2拆成P2.1 数目统计、P2.2 订单分类、P2.3 随时处理、P2.4 定期处理,并补进D5 订单数目、D6 顾客情况、D7 优先订单、D8 正常订单。

两个例子摆在一起,能看出分解深度的决定因素:取决于业务里有没有策略分支,跟图好不好看无关。银行取款的每一步都是确定的线性流程,展开只会得到一堆琐碎动作;图书预订的分类动作背后有一条决策规则,不展开就丢信息。

5. 常见问题排查:DFD 画错的五种典型症状

图上出现的多数错误,根源都不在画法,而在对模型边界的理解。下面五条是我见过频率最高的,每条都按「现象、原因、解决」拆开写。

5.1 症状一:把现金、货物、人员当成数据流

现象很典型:银行取款图里多出一条从系统指向储户的「现金」流,或者物流系统里画出一条「货物」流。

原因在于把物质流和信息流混在了一起。DFD 明确不涉及硬件、软件、数据结构与文件组织,与物理描述无关,它只描述信息运动。现金交付在业务上当然发生了,但它属于物理模型,属于系统设计阶段要处理的事。

解决方式是换一个信息载体来表达同一件事。取款的现金交付,在 DFD 里体现为「结算清单」「利息单」这类信息流;实物什么时候递出去、怎么递,留给后面的物理模型。这不是遗漏,是分层建模必然要做的取舍。

5.2 症状二:外部项直接连到数据存储

现象:一条线从S1 顾客直通D2 订单表,中间没有任何加工。

原因是跳过了「谁负责这次写入」这个设计问题。数据流必须通过加工,这条规则就是在逼你把责任主体找出来——谁校验格式、谁去重、谁判权限、谁写库。图上一根线省掉的东西,实现阶段会变成一堆没人认领的逻辑。

解决方式是补一个加工,比如「订单登记」,把校验和写入的职责明确挂上去。如果补不出来,那说明两种情况之一:这个存储根本不该在系统里,它属于外部系统的数据;或者这个动作压根不是系统做的,那这条线就该通向外部项而不是数据存储。

5.3 症状三:父子图不平衡

现象:父图上P2只有F1一条输入,子图上却多出一条从S1直接进来的流,或者父图的输出流在子图里找不到汇聚来源。

原因有两个。一是分解时偷偷改了系统边界,画着画着觉得「这条流直接进来更方便」,就顺手连上了。二是把本该属于兄弟加工的流塞进了子图,等于抢了别人的职责。

解决方式是先冻结父图的边界流,再画子图。具体操作是把父图上挂在P2上的所有流列成一张清单,子图画完后逐条核对进出。第 3 章那段check_balance做的就是这件事,手工做也行,关键是别凭印象。

5.4 症状四:数据存储挂错层

现象:关联图上就出现了D1 帐目库,或者同一个存储在四张图上各挂一次,编号还不一样。

原因是没有理解数据存储的层级归属。关联图只该有外部项、一个加工和进出系统的流,数据存储是系统内部状态,不该出现在这一层。而在分层 DFD 里,数据存储一般只属于某一层或某几层,它通常是作为两个加工环节的界面来安排的。

解决方式是先问「哪一层的哪两个加工需要共享它」。如果共享发生在P2内部,就挂在二层图;如果P1和P2都要用,就挂在顶层图。跨层共享的存储要么上提到共同祖先层,要么拆成两个存储加一条同步流——后者往往会暴露一个原本被掩盖的数据一致性问题。

5.5 症状五:编号混乱、交叉线爆炸

现象:同一张图上出现两个P1,数据流干脆不编号,或者线交叉得像一团毛线。

原因是把编号当成了装饰,而没把它当成跨层引用的索引。编号一乱,父子平衡就没法核对,数据字典也没法对齐,整张图退化成一张示意图。

解决方式是三条:加工的编号逐层带父号,P2展开就是P2.1到P2.4,绝不重新从P1开始;D、F、S全局唯一,跨层引用同一个东西必须用同一个编号;交叉线用重复画数据存储的方式消除,同一个存储在图上出现多次,用重复符号标记,这是规则允许的,不算冗余。

6. 设计取舍:DFD 放弃了什么,换来了什么

把 DFD 和流程图、时序图放在一起看,会发现它主动放弃了很多东西:不表达控制流,不表达执行顺序,不表达数据类型,不表达物理实现,甚至不表达两个数据流之间的先后和主次。这些放弃不是能力不足,而是为了换取三样东西——可被非技术人员验证、结构可机械校验、跨层可追溯。

第一样决定了它的沟通价值。业务人员看不懂类图,但看得懂「这张单子从哪来、经过谁、最后进哪个账本」。第二样决定了它的工程价值,只要约束能写成集合比较,工具就能帮你抓错,第 3 章那段校验代码就是这个思路的最小演示。第三样决定了它的文档价值,从关联图到二层图,编号一路带下来,任何一个加工都能追溯回它在上一层的父节点。

代价也很清楚。DFD 不表达时序,所以它无法暴露并发冲突、无法表达异常路径、无法说明重试和回滚。这也是为什么它必须和数据字典、加工说明配套使用:DFD 是骨架,数据字典是血肉,加工说明是肌肉,缺一样都撑不起一个完整的逻辑模型。很多团队画完图就收工,然后在编码阶段反复返工,问题往往出在这里——骨架画对了,但没人写清楚每根骨头上挂的是什么。

今天有不少替代品,用例图、BPMN、事件流图各占一块地盘。但 DFD 那个最核心的区分被完整继承下来了:把「变换」和「状态」分开,把「边界」显式化。事件驱动架构里区分事件流和事件存储,流处理里区分流和表,本质上都是同一个动作——先问清楚哪些东西是流动的、哪些东西是留存的,再谈怎么实现。这个区分看起来朴素,却是我见过的、在需求评审阶段最能提前暴露结构性问题的动作。

血泪经验是:图画得好不好看根本没人关心,边界画错一次,后面所有分解全废。从那以后我每次动笔前都强制先做两件事——把所有外部项列成一张清单,把所有数据存储列成另一张清单,两张清单对不上,就先别画加工。希望帮到你。

本文还有配套的精品资源,点击获取

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询