简介:这份PPT面向管理信息系统、软件工程等课程的学习者及需要掌握结构化系统分析方法的初学者,围绕数据流图(DFD)的读图与绘制展开,重点解决逻辑模型表达、符号使用和分层分解中的常见困惑。内容从DFD的四类基本元素入手,讲解数据流、加工、数据存储与外部项的命名、编号和连接规则,并梳理识别外部项、确定输入输出、划分主要处理功能、自顶向下逐层细化及检查完善等绘制步骤,同时补充布局原则与注意事项。课件以银行取款处理为例,展示关联图向顶层图、二层图逐层分解的过程,便于对照理解各层DFD之间的关系。压缩包共1个PPT文件,大小约312KB,适合课堂讲授或自学查阅。已有626人学习,可作为系统分析入门与DFD实例练习的参考材料。
1. 数据流图不是流程图:先分清 DFD 画的到底是“什么在流动”
有个开发者拿着一张三页纸的图来找我,说照着画完就能写代码了。我翻到第二页发现箭头从“客户”直接连到“账簿”,中间一个加工都没有。这不是画漏了,是把数据流图当成了流程图。数据流图(DFD,Data Flow Diagram)是组织里信息运动的抽象,是信息逻辑模型的主要形式,它刻意不碰硬件、软件、数据结构和文件组织,只回答一件事:这套系统在信息处理上要做什么。所以它能在完全不懂信息技术的业务人员面前摊开讲,也能作为后面建物理模型的依据。
这份讲稿值钱的地方在于它把符号、编号、分层、评审这一整条链拆开了,还配了取款和图书预订两个可以照着推演的实例。适合三类人:需求阶段要跟业务方对齐的、做系统设计前要交逻辑模型的、带课程设计需要一套可检查规范的。
2. 四种符号撑起一张图:外部项、加工、数据流、数据存储的边界
DFD 的全部家当就四个符号,但绝大多数错误都出在这四个符号的边界没划清。很多人一上手就画箭头,画完发现分不清哪个是存储哪个是外部系统,返工成本极高。先把每个符号“能写什么、不能写什么”定死,后面分层才有意义。
2.1 外部项和数据存储:一个在框外,一个躺在库里
外部项又叫源点和终点,是系统之外的实体,它存在于环境之中,与系统有信息交流。从源点流进系统的信息叫输入,从系统流向终点的信息叫输出。同一个端点既可以是人,也可以是另一个系统。引入外部项的唯一目的是让人看懂系统的边界在哪,所以不需要详细描述它内部怎么运作,编号以 S 开头即可。
这里有个高频误判:把“数据库”“报表系统”“Excel 台账”当成外部项。数据存储才是用来存数据的,在分层 DFD 里一般只属于某一层或某几层,所以又叫局部文件。它的三条硬规矩是:名字写在开口的长方框里,要概括说明文件里的主要数据而不是写字段清单;符号上一定要有数据流进出,一个没有任何数据流的数据存储等于画了个孤岛;编号写在小方格中,以 D 开头。
| 符号 | 编号前缀 | 写法位置 | 常见误用 |
|---|---|---|---|
| 外部项 | S | 方框内写实体名 | 把数据库、报表当外部项 |
| 加工 | P | 上半部分编号,下半部分加工名 | 加工名写成一句操作步骤 |
| 数据流 | F | 箭头旁写流名 | 写成“数据传输”“处理结果”这类空名 |
| 数据存储 | D | 开口长方框内写主要数据,左侧小方格写编号 | 存储上没有任何数据流 |
同一数据存储在多处出现时,为避免交叉线可以重复绘制。常见做法是在重复出现的那个框左侧多加一条竖线(或写“同 D3”),让人一眼看出这是同一个存储的两处引用,而不是新建了一个库。这一点在二层图、三层图上尤其关键,否则评审时会被反复追问“D5 和 D8 是不是一个东西”。
2.2 加工编号与数据流命名:P、D、S 三套编号怎么排
加工又叫处理或变换,表示对数据流的操作。符号分上下两部分:上面是标识部分,标注加工编号,必须唯一,以 P 开头;下面是功能描述部分,写加工名。加工名要简单、能概括地说明对数据的加工行为,详细逻辑放到数据词典里去写,不要在图上的框里塞一整段伪代码。
分层之后编号要有章法,我一般这么排:
- 顶层图的加工用 P1、P2、P3 顺序编号;
- 某个加工往下分解,子加工用父编号加点号扩展,P2 拆出来就是 P2.1、P2.2、P2.3;
- 数据存储同层顺序编号 D1、D2…,跨层不重号,被多个加工共用的存储保留一个编号;
- 数据流按 F1、F2… 编号,同一对起止点之间有多条流时,编号必须分开,不能合并成一条“数据交互”。
数据流的规则比想象中细。它由一个或一组确定的数据组成,名字要能直观反映含义,比如“取款单、存折”就比“用户输入”强得多。数据流可以同名、也可以有相同的数据结构,但必须是不同的数据或不同的含义。两个符号之间允许存在多条数据流,DFD 并不表明它们之间的次序和主次关系,这一点跟流程图、时序图是根本区别。
提示:数据流名里出现“信息”“数据”“内容”这类词,八成是没想清楚到底流的是什么,回头去问业务方要一张真实单据,照着单据上的字段命名。
2.3 拿取款关联图把四个符号串一遍
讲稿里的取款例子是最小可复现样本:储户把填好的取款单、存折交银行,银行审核并查对账目,不合格的退回,合格的送取款处理;取款处理修改账目,把存折、利息单、结算清单及现金交储户,同时把取款单存档。把它压成一张关联图,只需要三步。
第一步,定外部项。储户既是输入源也是输出终点,画两个 S 框(或一个框两条流),编号 S1。银行内部不画,因为关联图里系统整体就是一个加工。
第二步,确认输入输出流。输入流 F1 是“取款单、存折”;输出流 F2 是“存折、利率单、结算清单”。这里有个血泪经验:现金是实物,不能作为数据流。同理,打印出来的纸质账簿、快递寄出的实物、扫描件之外的原件,都不能当数据流画,只能作为加工的外部约束或补充说明写在图注里。
第三步,把整个系统压成一个加工 P,写“取款系统”,F1 从 S1 进 P,F2 从 P 出到 S1。这张图只有三个符号,但它把系统边界、输入、输出一次说清了,是后面所有分层图的锚点。
关联图画完先别急着往下拆,拿它跟业务方对一遍:进来的东西对不对、出去的东西全不全。这一步省下的返工时间,比后面画十张二层图都值。
3. 自顶向下逐层分解:从关联图到二层图的落地路径
分层是 DFD 最容易被做歪的部分。常见的翻车方式是:关联图画完直接跳到最细的功能,中间那层凭感觉补,结果父子图对不上,评审时被问一句“父图里 P1 的输入流去哪了”就卡住。正确的路径是固定的四步到七步,每一步都有可检查的产出物。
3.1 关联图:把整个系统压成一个加工
关联图的输入是前面定好的外部项和系统级输入输出流,产出是一张只有“外部项—加工—外部项”的图。它的作用不是描述功能,而是宣告边界:哪些东西在系统外,系统收什么、吐什么。
画的时候有两个动作要做实。一是确定所开发系统的外部项,即数据的来源和去处,把每一个都列出来,不要遗漏那些“只在月底出现一次”的角色。二是确定整个系统的输出数据流和输入数据流,把系统作为一个加工环节画出来。这里的判断标准很简单:凡是不能由系统内部产生的数据,都必须来自某个外部项的输入流;凡是系统产出的结果,都必须有明确的外部接收者,哪怕是“存档”这种内部动作,也要落到某个数据存储上而不是悬空。
关联图阶段最忌讳提前设计。有人在这里就开始画“登录校验”“权限判断”,那是物理设计,不是逻辑模型。DFD 在这一层只关心信息运动,不关心用什么技术实现。
3.2 顶层图的父子平衡怎么查
从关联图到顶层图的动作,是确定系统的主要信息处理功能,按此把整个系统分解成几个加工环节(子系统),并确定每个加工的输出与输入数据流,以及与这些加工有关的数据存储。取款系统的顶层图就拆成两个加工:P1 取款审核、P2 取款处理,加两个数据存储 D1 账目库、D2 取款记录。
父子平衡怎么查,我一般用三条对照:
| 检查项 | 具体做法 | 不平衡的表现 |
|---|---|---|
| 输入流守恒 | 子图边界上的输入流,必须能追溯到父图对应加工的输入流 | 子图凭空多出一条父图没有的输入 |
| 输出流守恒 | 子图边界上的输出流,必须能汇总回父图对应加工的输出流 | 父图有的输出在子图里消失了 |
| 数据存储归属 | 子图新增的存储,要么是父图存储的细化,要么在父图注里声明 | 子图冒出一个父图完全没有的库 |
取款顶层图里,P1 收 F1“取款单、存折”,吐出 F1.1“合格存折、取款单”给 P2,同时把 F1.2“不合格存折、取款单”退回 S1 储户;P2 读 D1 账目库、写 D2 取款记录,最后输出 F2。对照关联图,F1 进、F2 出,中间多了两条内部流和两个存储,边界上是守恒的,这就叫平衡。
注意:分解不是越细越好。判断一个加工是否还需要继续拆,标准是“它能不能用一句话说清且不需要再解释”,能就停手,不能就往下拆一层。拆过头会让图变成伪代码,业务方看不懂,评审也没法过。
3.3 图书预订系统的二层图拆解
讲稿里第二个例子更适合练手,因为它必须拆到二层才说得清。题意是:书店向顾客发放订单,顾客填好交回系统;系统先依据图书目录检查订单,合格订单再按顾客情况和订单数目分成优先订单与正常订单,优先订单随时处理,正常订单定期处理,最后按出版社要求汇总发给出版社。
先画关联图:S1 顾客、S2 出版社,输入 F1 订单,输出 F2 汇总订单,中间一个加工“图书预订系统”。这一步跟取款例子结构完全一样。
顶层图拆三个加工:P1 订单检查、P2 订单处理、P3 发送订单。数据存储四个:D1 图书目录(供 P1 查)、D2 合格订单(P1 写、P2 读)、D3 待发出订单(P2 写、P3 读)、D4 出版社要求(供 P3 读)。流的方向是:F1 进 P1,不合格走 F3 退回 S1;合格流进 D2;P2 从 D2 取数,处理完写 D3;P3 读 D3 和 D4,产出 F2 给 S2。
到这一步还没完,因为 P2 里同时塞了“分类”和“处理”两件事,说不清。二层图把 P2 拆成四个子加工:P2.1 数目统计、P2.2 订单分类、P2.3 随时处理、P2.4 定期处理。配套新增四个存储:D5 顾客情况、D6 订单数目、D7 优先订单、D8 正常订单。P2.2 依据 D5 和 D6 把 D2 里的合格订单分流到 D7 和 D8,P2.3 消费 D7,P2.4 消费 D8,两者产出都汇入 D3。
这张二层图值得反复看的地方在于:它在分解过程中逐步充实进数据存储,而不是一次性把所有库都摆出来。D5、D6、D7、D8 都是拆到 P2 内部才出现的局部文件,父图上没有它们,这在规则上是允许的,因为父图的 P2 是一个整体加工,它的内部细节在父层不可见。但反过来,如果二层图里突然出现一条流向 S2 的流,而父图 P2 并没有这条输出,那就是典型的父子不平衡,必须改。
4. 绘制原则落到纸面:系统界面、布局与用户对齐
图能画出来和图画得能被人用,是两回事。同一套符号,有人画出来评审一次过,有人画完被退回三次,差别基本都在这几条原则上:界面清不清、布局顺不顺、有没有跟用户真正对齐。
4.1 系统界面先划死,外部项不能漂
明确系统界面是第一条原则,也是最容易在迭代中松动的一条。项目前期定了五个外部项,画到二层图时有人觉得“这个角色不重要”就悄悄删了,或者把内部的一个模块提升成外部项,于是整张图的语义就变了。
我的做法是把外部项清单单独维护成一张表,跟 DFD 分开存,每次改图先改表:
| 外部项编号 | 名称 | 输入流 | 输出流 | 首次出现层 |
|---|---|---|---|---|
| S1 | 顾客 | F1 订单 | F3 不合格订单 | 关联图 |
| S2 | 出版社 | 无 | F2 汇总订单 | 关联图 |
表里“首次出现层”这一列很关键:任何在更下层新出现的外部项,都必须回溯说明为什么关联图阶段没识别出来。多数情况下答案是“它其实不是外部项,是内部角色”,那就改回加工或数据存储。
系统界面划不清的直接后果是范围蔓延。业务方看到图上有某个角色,就会顺口提“那顺便把这个功能也加上”,而这张图本来是限定范围的。
4.2 布局:交叉线、重复存储与编号分布
合理布局不是美观问题,是能不能读的问题。一张二层图如果有十几条交叉线,评审时没人有耐心顺着线找终点。几条实操经验:
- 外部项统一放在图的两侧,输入源在左、输出去向右,中间留给加工;
- 加工按信息流向从左到右排,父子编号相邻的加工尽量放在一起,P2.1 到 P2.4 横向排开;
- 数据存储统一放在图的下方或上方一侧,不要散落在加工中间;
- 同一存储被多处引用时重复绘制,重复的那个框加标记,避免长线横穿整张图;
- 数据流标签贴着线写,不要堆在箭头根部,多条流并行时错开位置。
编号分布也有讲究。同一层的加工编号应当连续,中间不要跳号,跳号意味着你删过东西但没重排,评审的人会一直找那个不存在的 P3。数据流编号同理,F1 到 F5 连续,删掉一条就重排一次,别留空洞。
4.3 评审节奏:什么时候该拉着用户看
讲稿里反复强调一句话:数据流图绘制过程就是系统逻辑模型的形成过程,必须始终与用户密切接触,详细讨论,不断修改,也要和其他系统建设者共同商讨以求一致意见。这话听着像套话,落到执行上其实是三个固定的评审点。
第一个点在关联图完成后。这时候图只有三个符号,业务方五分钟能看完,重点是确认边界和输入输出有没有漏。这个点改一个外部项,成本几乎为零。
第二个点在顶层图完成后。这时候功能被切成几块,业务方能判断“这么分对不对”“哪个环节我们其实不这么干”。图书预订例子里,如果业务方说“优先订单不是按顾客情况分的,是按库存”,那 P2.2 的输入流就得改,这时候改还来得及。
第三个点在二层图完成后、进入设计之前。这时候细节够多,能查出命名是否确切、分解是否彻底、各层之间有没有遗漏重复冲突。这个点之后就该冻结逻辑模型,转入物理设计。
三个点之外的时间不要随便拉会。图没画完就评审,讨论会变成需求收集会,收不完。
5. 避坑与排查:分层 DFD 里最容易翻车的五处
图画得越多,踩的坑越集中。下面这五条是我在评审里见得最多的,每条都按现象、原因、解决三步写,照着对一遍能省下不少返工。
5.1 悬空数据流与“黑洞”加工
现象:图上有一条数据流从外部项出来,箭头指向一个加工,然后就没有下文了;或者一个加工有输入没输出,有输出没输入。
原因:画图时先画了箭头再想逻辑,或者分解到一半停手,把中间态当成了终态。也有一种情况是加工内部逻辑没想清楚,索性留个口子。
解决:定两条死规矩。第一,数据流必须通过加工,任何一条流的两端都必须是“外部项、加工、数据存储”三者之一,不能悬空,也不能直接连两个存储或两个外部项。第二,每个加工至少一条输入流和一条输出流,只有输入没有输出的是“黑洞”,只有输出没有输入的是“奇迹”,两种都不允许出现在交付版本里。排查时从关联图开始,逐层把每条流的两端念一遍,念不通的就是问题点。
5.2 父子图不平衡
现象:父图里 P2 有一条输出流去 S2,拆到子图后这条流不见了;或者子图边界上多出一条父图没有的输入。
原因:分解时只盯着子图内部画,忘了回头对父图的边界。分层越多,这种漂移越容易发生。
解决:每画完一张子图,把父图里对应那个加工的输入输出流抄在纸上,逐条到子图边界上找对应。找得到的打勾,找不到的要么补流,要么回父图改。新增的流必须能说清来源,说不清就是凭空造出来的。这个检查我一般强制走一遍,三分钟的事,能拦住后面几小时的返工。
5.3 把实物当数据流
现象:取款例子里画一条“现金”从加工流向储户,或者图书例子里画一条“实体书”从仓库流向顾客。
原因:把业务流程里的物品流动直接搬进 DFD,没区分信息流和物流。
解决:DFD 描述的是信息运动,现金、纸质原件、货物这些实物不能作为数据流。它们要么作为加工的约束条件写在图注里,要么转换成对应的信息载体,比如“取款单”“发货单”“结算清单”。判断方法很直接:这条流上跑的东西能不能被一条记录或一份单据描述?不能,就不是数据流。
5.4 数据存储挂在单个加工上
现象:一个数据存储只有一条流入、没有任何流出,或者反过来只有流出没有流入,成了某加工的私有抽屉。
原因:把数据存储当成了加工的内部变量,忘了它在 DFD 里是两个加工之间的界面。
解决:数据存储一般作为两个加工环节的界面来安排,它是信息的落脚点,不是某个加工的私产。一个存储至少要有一条写流和一条读流,如果确实只被一个加工用,那它大概率应该被合并进那个加工,或者说明白它服务的是哪两个环节。排查时把每个存储的进出流列出来,只有单边的就是嫌疑对象。
5.5 编号混乱与命名含糊
现象:同一层出现两个 P2;数据流全叫“数据”“信息”“处理结果”;数据存储的编号在二层图里从 D1 重新开始。
原因:中途增删过元素没有重排,或者几个人分头画图没有统一约定。
解决:编号规则在开工前定死并写进模板:加工 P 开头、存储 D 开头、外部项 S 开头、数据流 F 开头,全项目全局唯一不重号,子加工用父编号加点号扩展。命名上,数据流名必须能让人想到具体单据或数据项,加工名必须是动宾结构且一句话说清做什么。交付前跑一遍编号去重和命名检查,机器能查的别靠眼睛。
6. 把 DFD 变成可版本管理的文本:DOT 描述与一个自检脚本
图画在画图工具里,改一版就得重新对一遍编号,人一多就乱。我后来习惯把 DFD 的元素清单和拓扑同时落成文本,用 Graphviz 出图、用脚本查错,评审时给图,改的时候改文本。
6.1 用 DOT 描述一张二层图
// 取款系统顶层图:节点前缀区分符号类型 digraph DFD { rankdir=LR; node [shape=box]; S1 [label="S1 储户", shape=box]; // 外部项,方框 P1 [label="P1 取款审核", shape=box]; // 加工,上下两段用 label 换行模拟 P2 [label="P2 取款处理", shape=box]; D1 [label="D1 账目库", shape=none]; // 数据存储,实际绘图用开口矩形 D2 [label="D2 取款记录", shape=none]; S1 -> P1 [label="F1 取款单、存折"]; // 输入流 P1 -> S1 [label="F1.2 不合格存折、取款单"]; // 退回流 P1 -> P2 [label="F1.1 合格存折、取款单"]; P2 -> D1 [label="F1.3 账目变更"]; // 写存储 D1 -> P2 [label="F1.4 账户余额"]; // 读存储 P2 -> D2 [label="F1.5 取款记录"]; P2 -> S1 [label="F2 存折、利率单、结算清单"]; }这段 DOT 的关键在于把“符号类型”和“边的方向”都显式写出来:节点 ID 用 S/P/D 前缀,跟编号规则对齐;每条边的 label 就是数据流编号加名字,改图时改 label 即可。rankdir=LR让信息流从左往右排,shape=none只是示意,正式出图时数据存储要换成开口矩形,加工要拆成上下两段。渲染用dot -Tpng dfd.dot -o dfd.png,参数-T指定输出格式,png、svg、pdf 都行,SVG 适合放进文档里缩放。
文本化的好处是能进 Git:谁改了哪条流、什么时候加的 D5,diff 里一目了然。评审时也不用来回截图,改完重新渲染一次就是最新版。
6.2 编号与命名自检
import re from collections import Counter # 元素清单:类型 + 编号 + 名称 + 所在层 elems = [ ("S", "S1", "储户", 0), ("P", "P1", "取款审核", 1), ("P", "P2", "取款处理", 1), ("D", "D1", "账目库", 1), ("D", "D2", "取款记录", 1), ] # 数据流清单:(起点, 终点, 流编号, 流名) flows = [ ("S1", "P1", "F1", "取款单、存折"), ("P1", "S1", "F1.2", "不合格存折、取款单"), ("P1", "P2", "F1.1", "合格存折、取款单"), ("P2", "S1", "F2", "存折、利率单、结算清单"), ] PREFIX = {"S": r"^S\d+$", "P": r"^P\d+(\.\d+)*$", "D": r"^D\d+$"} ids = [e[1] for e in elems] dup = [k for k, v in Counter(ids).items() if v > 1] # 编号去重 bad_prefix = [e for e in elems if not re.match(PREFIX[e[0]], e[1])] names = {e[1]: e[2] for e in elems} # 检查 1:每个加工至少一进一出 in_out = {i: [0, 0] for i in ids if i.startswith("P")} for s, t, _, _ in flows: if s in in_out: in_out[s][1] += 1 if t in in_out: in_out[t][0] += 1 lonely = [k for k, v in in_out.items() if v[0] == 0 or v[1] == 0] # 检查 2:数据流命名不许出现空泛词 vague = [f for f in flows if re.search(r"数据|信息|内容|处理结果", f[3])] print("重复编号:", dup or "无") print("前缀不合规:", bad_prefix or "无") print("孤立加工(黑洞/奇迹):", lonely or "无") print("空泛流名:", vague or "无")脚本分四块:elems是元素清单,四项分别是符号类型、编号、名称、层级,层级用来自动判断哪些编号应该带点号;flows是数据流清单,起止点直接写编号,这样能跟元素表对上;PREFIX是三类编号的正则,加工允许点号分层,外部项和存储不允许;后面三个检查分别对应编号唯一性、加工进出流完整性和流名空泛词。把清单换成自己的项目,跑一遍,四行输出里只要不是“无”,就还有活要干。
vague那条检查规则是可以调的,正则里加什么词取决于你们团队的命名习惯,但“数据”“信息”“内容”“处理结果”这四个建议一直留着,它们几乎等价于“我没想清楚”。
我最早是不做这个自检的,觉得图都画完了还能有什么错,结果一次交付前被业务方指着问“这个 P4 怎么只进不出”,当场翻车。从那以后我每次出图前都强制走一遍这个脚本,再对着父子平衡表核一遍边界,图才能拿出去评审。希望帮到你。
本文还有配套的精品资源,点击获取