数据流图(DFD)这个概念,我刚开始学的时候也犯过迷糊。大学上软件工程课,老师放出一张花花绿绿的图,里面全是圆圈和箭头,说这就是数据流图,十个人里有八个都会在心里嘀咕:这不就是个流程图吗?和流程图有什么区别?特别是毕业设计要画银行取款流程、查询修改功能的时候,照着例子画得四不像,不是少条边就是多一个箭头,然后就开始自我怀疑:数据流图,我是不是理解错了。
后来工作做得多了,接触过需求分析、系统设计、代码评审,再回头看DFD,才慢慢搞清楚它到底是什么、该怎么用、为什么教材里的例子要那样画。这里把我在实际项目里对数据流图的理解梳理一遍,尤其结合三个高频场景:上下文图如何一步步往下分解、银行取款过程的DFD怎么画、查询修改类功能的数据流图为什么特别容易画错。内容适合正在学软工的学生、准备面试的应届生,以及工作中需要写设计文档但没系统学过DFD的开发同学。如果你觉得自己“好像懂了但又不太确定”,这篇文章应该能帮你把最后那点模糊感去掉。
1. 先解决“DFD到底是个图什么”的问题:它是数据的流动路线,不是业务的执行流程
1.1 数据流图和流程图的本质差别
很多人在DFD上理解跑偏,根源都在把DFD当成流程图来画。流程图描述的是一个处理过程的时序逻辑——先做什么、判断什么、再做什么,它有起点、有终点、有判断分支,焦点是控制流。而DFD描述的是数据的流向和变换——数据从哪里来,经过哪些处理,存储在哪里,最后又流到哪里去。它不关心某个操作发生的先后顺序,也不关注某个条件下走哪条分支,它只关心“数据在这个系统里是怎么走的”。
这个差别怎么说呢,我经常用快递来类比。流程图像是一个仓库里的拣货SOP:接到订单、判断库存够不够、够就走A仓库、不够就走B仓库、然后打包贴单。而DFD更像是物流网络图:你的包裹从客户手里收进来,进分拣中心,上干线运输车,到目的地网点,再由派件员送到收件人手上。至于派件员是先打电话再上楼,还是先上楼再打电话,这不是DFD要管的事。
如果脑袋里你始终挂着一个判断框“库存够不够”,那画出来的就是流程图,不是DFD。DFD里没有判断,没有条件分支。这就是理解DFD的第一道坎,也是必须迈过去的坎。
1.2 DFD只回答三个问题:数据从哪来?数据经历了什么?数据到哪去?
站在实用角度,画DFD的时候,整个思考过程其实就是反复回答这三个问题:
- 数据从哪来:哪些外部实体朝系统发数据?用户提交了一个表单?上游系统推送了一个报文?传感器上传了一串数值?
- 数据经历了什么:它进入到系统之后,经过了哪些加工环节?是校验、计算、拆分、合并、查询、修改,还是落库?每个环节输入什么数据,输出什么数据?
- 数据到哪去:加工之后的数据是被谁拿走了?是展示给用户,还是发了给下游系统,或者只是静静地躺在数据库里?
你会发现这个过程里完全没有“先做什么后做什么”的问题。比如一个银行取款系统,客户输入取款金额和密码,系统校验密码、校验余额、记账、吐钞。DFD不关心是先验密码还是先查余额,更不关心密码错误三次要锁卡这种分支逻辑。DFD只关心:用户提交的“取款请求”这个数据,经过“验证和信息核对”处理,变成“校验结果”“更新后的账户信息”,再往外输出“现金支付确认”之类的数据。处理与处理之间没有顺序关系,它们是由数据流串联起来的。
1.3 一个教科书级的例子帮助建立直觉
如果你第一次接触DFD,我最推荐先看一个非常简单的场景:查询修改数据流图。这个场景之所以经典,是因为它同时包含了DFD的全部四种元素,但逻辑又非常简单。
假设系统有一个“学生信息管理模块”,支持用户查询和修改学生信息。它的DFD可以这样理解:
- 外部实体:管理员(要查询、修改数据的人)。
- 数据流:“查询条件”“修改请求”“查询结果”“操作结果”。这些是流动中的数据。
- 处理:“1. 查询学生信息”和“2. 修改学生信息”。处理是对数据的加工。
- 数据存储:“D1 学生信息表”。数据存这里。
画出来就是一个简单得不能再简单的图:管理员通过“查询条件”连接“1. 查询学生信息”,再连接“D1 学生信息表”;同时通过“修改请求”连接“2. 修改学生信息”,也连接“D1 学生信息表”。你发现没有,处理只管输入数据流和输出数据流,数据存储只管被读和被写。没有任何箭头表示“先查询再修改”或“管理员点了按钮后系统做xxx”的时序。这不是流程图,这是数据的流通网络。
2. 数据流图的核心组成:四个图元,每一个都有严格约定
2.1 一张表看懂DFD的四元素
DFD从图元上看,只有四种基本符号。不管你的系统多复杂,拆到什么层级,都逃不出这四种符号的组合。不同教材用的符号风格略有不同,但语义基本一致,这里用最通用的Yourdon-DeMarco标记法说明:
| 图元 | 符号 | 含义 | 命名规范 | 常见错误 |
|---|---|---|---|---|
| 外部实体 | 矩形(方框) | 系统外的人、组织、系统,与系统有数据交互 | 名词,如“客户”“管理员”“银行系统”“第三方支付平台” | 把数据库当外部实体;把系统的子模块当外部实体 |
| 数据流 | 带箭头的线 | 流动的数据,方向代表数据流动方向 | 名词性短语,如“取款请求”“查询结果”“学生信息” | 用类似“进行查询”“计算总额”这样的动词命名;或表示控制信号的流 |
| 处理 | 圆角矩形(或圆圈) | 对数据的加工、变换 | 动词+名词,如“验证密码”“更新账户余额”“计算订单总额” | 一个处理太复杂,动辄包含多个子功能 |
| 数据存储 | 开口矩形(双线) | 数据的静态存储,可能是数据库表、文件、缓存 | 名次,如“用户表”“订单表”“D1 学生信息表” | 把存储画成处理;没有考虑数据从哪里来就凭空出现一个存储 |
如果你去看文献,还可能碰到一张只画圆和箭头的简化DFD,那些圆圈就是处理,箭头就是数据流,外部实体和数据存储在另外的层面去画。教材之间画法有差异,但底层逻辑是一样的:矩形代表人,圆角矩形是加工动作,开口矩形是存放数据的地方,箭头就是在它们之间跑的数据。
2.2 数据流的命名是DFD质量的照妖镜
在实际业务里画DFD,最容易出问题的就是这个数据流命名。
很多新手画DFD,数据流上面写“查询信息请求”,看起来没问题,但细想又没说明白。到底是“按学号查询信息的请求”还是“按姓名查询信息的请求”?处理“查询学生信息”读入的是“查询条件”,输出的是“学生信息列表”。如果这条数据流叫“数据”,那图的价值就基本归零了,因为任何一条箭头都可以叫“数据”。数据流的命名一定要让读者不看图例,光看名字就能说出“这条数据是什么内容”。比如:
- “银行卡号”比“卡信息”好;
- “已校验的取款请求”比“数据1”好;
- “余额不足提示消息”比“结果”好。
同理,
- 外部实体命名要区分人和角色。如果系统有一个后台管理员和一个普通用户,它们交互的数据不同,建议分开画成两个实体,或者至少实体名用“系统管理员”“普通用户”,不要统一叫“用户”。
- 数据存储的命名建议直接使用业务表名,或者模块名+存储名。比如“D1 账户表”“D2 交易流水表”,这样后续做数据字典映射的时候很顺畅。
- 处理的命名最好包含明确的动作和对象。“处理数据”“加工信息”这种就算了,这等于没命名。“生成取款回执”“更新学生成绩”这才说得过去。
2.3 数据流不能凭空起,也不能凭空灭
DFD有个非常重要的守恒思想,虽然教材里不会强调这个词,但理解了这个,后面画图基本不会犯大错。这个思想就是:一条数据流从一个处理输出,它必然是从某个输入经过加工得来的;一个处理有输入数据流,必然至少有对应的数据流输出,或者产生了状态变化写入存储。
听起来抽象,我举一个反例。有次看到一个学员画的“银行取款”DFD,处理“1. 验证密码”只画了一条输入流“取款请求”,没有输出流。等于说这个处理只有数据进来,没有任何数据出去,这在DFD里是说不通的。你的密码验证总要给一个结果吧?这个结果要么作为数据流输出到“2. 处理取款”,要么作为“验证通过标志”传递给下一环,总之必须有一条数据流出去。反过来,如果一个处理有一条输出流“取款凭证”,但它没有任何输入流,这也是有问题的,除非它读了一个数据存储,比如“D2 操作日志”。记住这个约定,画图时经常自查,能排查掉一半的低级错误。
3. 上下文图和数据流图的分解:这是DFD的灵魂
3.1 从“系统只有一个处理”开始:画上下文图
DFD最妙的地方在于它的分层(leveling)能力。你不需要一上来就把系统里的所有处理、所有存储全部铺在一张图里,那样画出来必然乱成一锅粥。规范的做法是自顶向下:
第一步画上下文图(Context Diagram)。上下文图又叫顶层图,是整个系统最高层的DFD。它的特点是:全图只有一个处理,这个处理就是整个系统本身。外部实体画在周围,它们与系统之间的数据流,就是系统的输入输出边界。
拿“银行取款过程”来举例。系统的上下文图大概是这样的:
- 外部实体:客户。
- 数据流:客户和系统之间,有“取款请求”流入系统,“现金”“交易凭条”“错误提示”流出系统。
- 处理:整个“银行取款系统”(可以命名为“1. 银行取款系统”或“ATM取款子系统”)。
你有没有发现,上下文图完全不需要知道系统内部是怎么处理的,也完全没有数据存储。它只告诉读者:这个系统的边界是什么,外面谁在跟它交互,系统的输入输出是什么。这其实就是需求分析里的“系统边界”概念,非常直观。
画上下文图是DFD分解的起始点,也是最不容易画错的一步,只要搞清楚“谁使用这个系统”以及“系统对外提供什么信息”,上下文图就八九不离十。
3.2 一层分解:把系统拆成主要功能大类
上下文图画完之后,下一步是0层图,也就是对上下文图里的那个大处理做第一次分解。系统在这个层面被拆分成若干个核心处理,一般建议控制在5到9个之间,对应人的短期记忆容量,也方便评审。
继续拿银行取款来举例,0层图可以这样分解:
- “1. 验证客户身份和账户”
- “2. 校验账户余额与取款限额”
- “3. 更新账户余额与交易记录”
- “4. 启动现金出钞”
- “5. 生成交易回执”
然后加上必要的数据存储:账户表、交易流水表、现金库存表。外部实体还是客户。数据流在实体、处理、存储之间流转。
0层图的绘画原则是:每个处理都是一个相对独立的功能集群,处理之间通过数据流连接。我见过很多人把0层图画成了“模块架构图”,一个框代表一个功能模块,模块间画带箭头的“调用关系”,这不是DFD,因为箭头上的东西不是数据,而是控制流。0层图上的箭头必须是“数据”,比如“取款请求”“账户余额”“交易记录”。
3.3 子图分解:处理里还有自己的DFD
如果0层图的某个处理还太复杂,可以继续分解。比如“2. 校验账户余额与取款限额”内部还可以拆成“2.1 查询账户余额”“2.2 校验取款限额”“2.3 生成校验结果”三个更细的处理。这相当于给这个处理单独画一张子图,这张子图叫“2的分解图”。子图可以继续往下拆,直到每个处理都足够简单、在实现阶段能够直接对应到一个函数或一个方法为止。
这里有一个核心概念要严格注意,分解后的子图必须与父图保持平衡。简单说就是:父处理“2”的输入数据流和输出数据流,必须和“2”的子图中整体输入输出数据流保持一致。如果父处理“2”有一条输入流“取款请求”,一条输出流“校验结果”,那么画子图的时候,“2”这个处理的边界上,也应当看到“取款请求”流入、“校验结果”流出。内部怎么拆那是内部的事,边界不能多一条流,也不能少一条流。
这个平衡检查是DFD审查里的硬性要求,也是画图过程中容易翻车的点。我曾经帮一个团队评审数据字典时,发现某张子图里多了一条“读取账户类型”的数据流,而父图中没有。这就意味着要么父图少画了数据流,要么子图多画了。这个如果不核对,到开发阶段就是需求遗漏或者过度设计的问题。
3.4 上下文数据和子图的命名规范
在实际交付时,DFD的分层命名是有通用约定的,用这套约定项目对接会很顺畅:
| 层级 | 名称 | 图内处理数量 | 编号规则 |
|---|---|---|---|
| 顶层 | 上下文图 | 1个处理(系统) | 处理编号为1,或直接命名系统名 |
| 0层 | 系统主要功能图 | 5~9个处理 | 处理编号1、2、3…… |
| 1层 | 0层图中某个处理的分解图 | 每个子图3~7个处理 | 处理编号1.1、1.2、1.3…… |
| 2层 | 1层图中某个处理的分解图 | 每个子图3~7个处理 | 处理编号1.1.1、1.1.2…… |
数字编号的好处是,你在代码评审或者文档评审的时候,说“1.2处理那条数据流有问题”,大家马上能定位到具体是哪张图哪个环节,不用来回翻页。
4. 实操一把:从零开始画“查询修改数据流图”
4.1 场景描述与需求整理
前面概念说了一堆,现在实操一下,看看搭配“查询修改数据流图”这个搜索热词,一套完整的DFD到底怎么画出来。假设现在要做一个小功能,叫“员工信息管理”,需求如下:
- 管理员输入查询条件,查看员工信息列表。
- 管理员选中某个员工,修改其基本信息并保存。
- 系统需要记录操作日志,方便审计。
- 外部系统HR系统会同步最新的员工部门信息到本系统。
这个场景不大,但刚好覆盖了外部实体、系统处理、数据存储、外部系统交互,用来演示非常合适。
4.2 第一步:画上下文图
上下文图只有一个处理,即“员工信息管理系统”。外部实体有两个:一个是“管理员”,一个是“HR系统”。
- 管理员发给系统的数据流:“查询条件”“修改请求”。
- 系统发给管理员的数据流:“员工信息列表”“操作结果”。
- HR系统发给本系统的数据流:“员工部门同步数据”。
- 本系统发给HR系统的数据流:“同步确认消息”。
不要小看这一步。上下文图的价值在于给你划清系统的边界:哪些事在系统内做,哪些事系统外自己做。比如“管理员修改密码”这个功能,如果不在本期需求里,就不要出现在上下文图里。画完上下文图建议交给产品经理或需求方确认一遍,确认没问题了再进行下一步分解。很多项目后期扯皮,就是因为边界没定清楚,需求没进来就往里塞。
4.3 第二步:画0层图
把上下文图里的“员工信息管理系统”这个处理,分解成4个处理:
- “1. 查询员工信息”
- “2. 修改员工信息”
- “3. 记录操作日志”
- “4. 同步部门信息”
数据存储有3个:D1 员工信息表、D2 操作日志表、D3 部门信息表。
画出数据流的时候,注意每个处理的输入输出:
处理“1. 查询员工信息”:
- 输入:来自管理员的“查询条件”;来自D3的“部门信息”。
- 输出:给管理员的“员工信息列表”。
处理“2. 修改员工信息”:
- 输入:来自管理员的“修改请求”;来自D1的“员工信息(当前值)”(读出来做修改前后对照);来自D3的“部门信息”。
- 输出:给D1的“更新后的员工信息”;给“3. 记录操作日志”的“操作日志数据”。
处理“3. 记录操作日志”:
- 输入:来自处理2的“操作日志数据”;来自管理员的“操作人信息”(这里简化一下,也可以并入修改请求)。
- 输出:给D2的“日志记录”。
处理“4. 同步部门信息”:
- 输入:来自HR系统的“员工部门同步数据”。
- 输出:给D3的“同步后的部门信息”;给HR系统的“同步确认消息”。
这里要注意:每个处理的所有输入流经过加工后,必须变成对应的输出流。比如“4. 同步部门信息”,输入是“员工部门同步数据”,输出两条数据流,一条写存储,一条回给上游系统。没有哪条输入是凭空消失的,也没有哪个输出是没有来源的。
4.4 第三步:检查与完善
0层图画好后,别急着发图,先做一轮自查:
父图与上下文图是否平衡:上下文图里管理员有“查询条件”“修改请求”流入系统,有“员工信息列表”“操作结果”流出系统。0层图里有没有覆盖这两进两出?0层图处理“1”“2”分别有输入流,但“操作结果”这个输出流在哪?我上面是让处理2输出“操作结果”给管理员,图上需要补上。这点特别容易漏。
数据存储是否都有读写流:D1被处理1读取吗?实际场景里查询员工信息,如果是直接查员工表而不需要部门信息,那D1不一定出现在处理1里。但如果查询结果里需要显示员工部门名称,就一定要通过“部门信息”这条流去关联D3。每条存储、每条流的存在都要有业务解释。
处理命名是否可执行:把图给一个不熟悉需求的开发看,他能只看图就说出“这个处理就是根据查询条件去读员工表,关联部门表,返回列表”。如果能,说明处理分解得比较干净。
4.5 完整数据字典示例
DFD画完只是第一步,真正落地的时候还要配数据字典。数据字典是对图中每条数据流、每个数据存储、每个处理的精确解释。画图能帮人对系统有整体理解,数据字典才能保证开发时不产生歧义。这里给出一条数据流的数据字典示例:
数据流名称:修改请求 数据流编号:DF-02-01 别名:员工修改指令 来源:管理员 去向:2. 修改员工信息 数据结构: 前端页面收集以下字段: - employeeId:员工ID,必填,String(32) - name:姓名,选填,String(50) - position:岗位,选填,String(50) - departmentId:部门ID,选填,String(32) - phone:联系电话,选填,String(20) - updatedBy:操作人ID,必填,String(32) 触发时机:管理员点击保存按钮 备注:字段校验通过后才能作为本数据流提交这样做的好处是,后续写接口文档、设计数据库表、写前后端联调文档的时候,直接拿数据流名称做引用,整个链条非常清晰。项目越大,数据字典越值钱。
5. 再走一个经典场景:银行取款过程的DFD图,教材没讲透的细节
5.1 “银行取款”DFD为什么到处都是搜索热点
银行取款过程是DFD最常见的教学案例,它场景简单、名词统一、流程完整,非常适合用来理解DFD的基本结构。但很多同学对着教材里的标准答案,仍然画不对,主要原因有两个:一是很多教材画的0层图并不规范,强加了顺序语义;二是取款过程里包含“现金”这种非数据元素,画着画着就混了。
这里先把整个系统从上下文图到一层分解走一遍。
5.2 上下文图:ATM取款系统的边界
取款系统在顶层图就是“ATM取款系统”一个处理,外部实体是“客户”以及“银行后台系统”。
- 客户输入的数据流:“银行卡信息”“取款密码(可合入凭证)”“取款金额请求”。
- 系统输出的数据流:“现金”“交易凭条”“余额提示信息”“错误提示”。
- 银行后台系统与ATM系统之间的数据流:“账户验证请求”“账户验证结果”“交易记录”。
很多人会问一个问题:取款的时候,系统吐出来的“现金”是数据流吗?这在DFD语义上是有争议的。严格讲,现金不是数据,但在实际画DFD时,很多教材把“现金”当作数据流画,因为现金本质是“客户的取款请求被处理后的产出物”。我的建议是:如果是课程设计、考试,就按教材习惯画,把“现金”作为数据流出系统;如果是公司内部设计文档,建议明确标注“现金(物理介质)”,避免评审时被追问。
5.3 0层图分解:不被时序绑架
取款系统0层图可以做如下分解:
- “1. 读取卡片信息与密码”
- “2. 验证账户与密码”
- “3. 校验余额与取款限制”
- “4. 更新账户与交易流水”
- “5. 出钞与打印凭条”
这里的数据存储包括D1 账户表、D2 交易流水表、D3 现金库存表。
每条数据流这么走:
处理1:
- 输入:“银行卡信息”(来自客户)。
- 输出:“卡片账号”(给处理2)。
处理2:
- 输入:“卡片账号”;“密码”(来自客户)。
- 输出:“验证结果”(给处理3和处理4);“验证失败提示”(给客户)。
处理3:
- 输入:“验证结果”(来自处理2);“账户余额信息”(来自D1);“本次取款金额”。
- 输出:“可用性校验结果”(给处理4);“余额不足提示”(给客户)。
处理4:
- 输入:“可用性校验结果”(来自处理3);“账户余额信息”(来自D1)。
- 输出:“更新后的账户余额”(到D1);“交易流水记录”(到D2)。
处理5:
- 输入:“交易成功通知”(来自处理4);“现金库存信息”(来自D3)。
- 输出:“现金”(给客户);“交易凭条”(给客户);“扣减后的库存信息”(到D3)。
这整张图里没有箭头上写着“如果密码正确则进入下一步”,也没有那种“判断余额是否充足”的判断箭头。所有逻辑都体现在数据流上:“验证成功”的“验证结果”才会传给处理3和处理4,“验证失败提示”直接流出系统。这就是DFD。
5.4 教材里没细说的“数据存储读写问题”
银行取款场景里,处理2“验证账户与密码”到底会不会读D1账户表?如果它不读,它凭什么验证密码?所以处理2要有一条从D1来的“账户密码信息”输入流。处理3“校验余额”也读D1账户表,这也是一条输入流。需要注意,同一个存储可以被多个处理读,这在DFD里完全允许,只要数据流命名能够区分读取的具体数据内容。
实践中有个常见失误:只画一个处理读存储,后面处理用到的数据也默认“反正存储里有数据,直接读就行”。结果图上处理2没接D1,处理3接了D1,后面代码评审的时候,开发看了图说“处理2不读存储怎么验密”,又要返工。所以在DFD里,一个处理如果需要读取某个存储,就明确画出一条从存储指向处理的数据流,流上写清楚是什么数据。这样做确实会让图显得密一些,但信息完整度比简洁更优先。
5.5 处理编号和子图划分的实操建议
银行取款如果继续分解,比如处理4“更新账户与交易流水”可以继续拆成“4.1 扣减账户余额”和“4.2 插入交易流水”。同时要保持父图平衡:处理4的输入流“可用性校验结果”“账户余额信息”,输出流“更新后的账户余额”“交易流水记录”,子图边界上必须也有同样的流。内部可以在“4.1”和“4.2”之间加中间数据流“扣减指令”,但边界线外那一圈数据流保持不变。
在实操时,我习惯用Excel或者飞书文档做一张“处理-输入流-输出流编号对照表”,每拆一层就同步更新一次。表结构很简单:
| 处理编号 | 处理名 | 输入数据流 | 来自 | 输出数据流 | 去向 | 是否平衡 |
|---|---|---|---|---|---|---|
| 1 | 读取卡片信息与密码 | 银行卡信息 | 客户 | 卡片账号 | 2 | 是 |
| 1 | 读取卡片信息与密码 | 密码 | 客户 | 验证失败提示 | 客户 | 是 |
这个表看起来简单,但在多级分解时特别管用,每次父图子图平衡检查都能节省大量时间。
6. 数据流图的常见错误与排查技巧实录
6.1 四大高频错误,我几乎在每份设计文档里都见过
在评审DFD的过程中,我总结出四类出现频率极高的错误,这里直接列成速查表,画完图后对着自查一遍,能救回很多分。
| 错误类型 | 现象 | 危害 | 纠正方式 |
|---|---|---|---|
| 时序污染 | 图上出现判断框、分支箭头,或处理旁边标注“先”“后” | DFD失去数据流本质,回归流程图 | 把条件判断转化为数据流内容,如“通过/不通过”作为数据流命名 |
| 命名空泛 | 数据流叫“信息”,处理叫“处理” | 图不可读,无法指导开发 | 按“动词+名词”“名词+量词”重新命名 |
| 父子不平衡 | 父处理的输入输出和子图不符 | 需求漏项或过度设计 | 用数据流清单逐项比照父图与子图 |
| 越级画流 | 两个隔层处理之间直接画数据流,没有经过中间处理 | 破坏分层结构,图混乱 | 先自顶向下分层,下层之间的数据流必须出现在同一层图中 |
6.2 数据流画成控制流的辨析技巧
一个很实际的问题:怎么一眼识别出自己画的箭头到底是数据流还是控制流?技巧很简单,看箭头上写的内容能不能“被存储、被查询、被传递”。比如“查询请求”可以被系统接收,可以被日志记录,可以被传给下一个处理,它是数据。但“开始查询”呢?它是一个命令,是控制信号,它没有内容可以被存储或加工。如果箭头上的东西是“触发的命令”“调用关系”“事件信号”,那它就不应该在DFD上出现。
一个常见的翻车现场:某位同学在画“管理员删除员工”时,在图里加了一条从管理员到“2. 删除员工”的数据流,命名为“点击删除按钮”。这个“点击删除按钮”不是数据,它是操作事件。正确的数据流应该叫“删除请求”,内容包含employeeId、操作人ID、删除原因等。这个概念一拐过来,画图水平立刻提升一个档次。
6.3 排查技巧:用问题清单做“最终审稿”
我自己画完DFD之后,无论多自信,都会拿着下面这份问题清单过一遍。不做这一步,早晚会在需求评审会上被别人问住:
- 是否每个外部实体在上下文图和0层图中都有对应的输入输出数据流?
- 是否所有数据存储都有“从哪写入”和“被谁读取”的路径?
- 是否所有处理都有输入流和输出流?有没有只有输入没有输出的“黑洞处理”?
- 是否所有数据流的命名都能精确反映数据内容?
- 父图子图边界数据流是否一致?
- 是否混入了控制流、事件流、调用关系?
- 图上的处理是否还能继续分解?如果能,分解到哪层为止?
- 有没有数据存储从图里“凭空出现”?它第一次出现时是否有处理向它写入?
6.4 工具选择:画DFD用什么最顺手
工具这个东西,影响效率但没有绝对高低,关键看场景。如果要快速迭代、团队协作、无门槛分享,可以用在线绘图工具,比如ProcessOn、boardmix、excalidraw、draw.io这类。draw.io的好处是免费、可以本地离线,且导出清晰度高,适合放进论文和设计文档。ProcessOn更适合国内团队协作,模板比较多,直接搜“DFD模板”可以省很多时间。
值得一提的是:不建议用PPT和Word画复杂的DFD,对齐和修改的体验太折磨人。如果只是简单的小图,纸笔直接画完拍照物理扫描也行,毕竟我们画图的核心是逻辑,不是美术。
如果团队里都是程序员,也可以考虑用Graphviz的dot语言画DFD。它是代码驱动的,写一个规范文本文件,自动生成图,适合放进代码仓库统一版本管理。缺点是需要学习dot语法,而且处理较多时排版需要调参。我个人的建议是:日常设计文档用draw.io,自动化导出需求用Graphviz,团队协作评审用在线白板。
7. 我自己的理解方法:把DFD当成“数据快递地图”
画了很多年DFD,我最后沉淀下来的理解方式特别简单:把系统想象成一个快递网络,外部实体是发货人和收货人,处理是分拣中心,数据存储是仓库,数据流就是包裹在站点之间的运输路线。
我不会去问“这张图上的第3个处理什么时候执行”,我只会问“包裹从哪个站点发出,经过哪个分拣中心,暂存在哪个仓库,最后送到谁手上”。一旦适应了这种视角,DFD其实非常好懂。后面做需求分析,我和产品经理确认逻辑的时候,也习惯先在白板上画一张上下文图和0层图,画完大家再讨论功能细节。图一出来,好多“这个数据到底是你们系统管的,还是我们系统管的”这种边界问题一下就聊明白了。
如果你现在还在为DFD头疼,我建议你不要死磕理论,找一个最熟悉的业务场景,就画一个查询修改类的简单功能,从上下文图开始,走一遍分解流程,检查一下平衡,再配一个简单的数据字典。整个流程走完,你对DFD的很多疑问会自然消失,后面再遇到银行取款这种复杂场景也能轻松应对。
最后分享一个小技巧:画图前,先在文档里把所有外部实体和它们相关的输入、输出数据流用文字列出来,写完再画图。文字是思考,图是表达。思考清楚了,图怎么画都不会跑偏。这个习惯我一直用到现在,效率非常高。