简介:围绕库存管理系统数据流图整理的 doc 文档,面向信息系统、软件工程课程设计及库存业务分析学习者,用于梳理采购入库、领料出库、库存查询与统计分析之间的数据流转关系。压缩包内共 1 个 doc 文件,大小约 67KB,以文字说明配合分层 DFD 图示呈现系统边界、外部实体、处理过程与数据存储。已有 3139 人学习,说明其对课程作业和业务建模具有较高参考价值。内容从顶层 DFD 展开到第一、二、三层,覆盖入库单审核与入库处理、出库单审核与出库处理、管理员审核及合格与不合格单据分流、单据记账、退货与库存信息更新、库存流水账、库存统计表及库存分析预警等环节,可帮助读者快速理解部门间数据交换、单据凭证流转和库存信息维护逻辑,适合用作需求分析、系统设计说明或数据流图绘制对照材料。
1. 从一张对不上的库存流水说起:这套 DFD 方案要解决什么
见过一个很典型的翻车场景:某同学的系统功能都写完了,入库、出库、查询三个页面都能跑,答辩前一周被问「你这条库存流水是谁写的、谁读过、退货那笔怎么冲的」,当场答不上来。问题不在代码,在动手之前没有把库存管理系统数据流图(DFD)画清楚。这份资源解决的正是这件事:用顶层、第一层、第二层、第三层四张分层 DFD,把采购部门、领料部门、统计部门三个外部实体,入库管理、出库管理、库存查询、库存分析预警四个加工,以及库存流水账、库存信息、库存明细三个数据存储之间的读写关系一次定死。它适合两类人:一是要交库存管理系统课设或毕设、需要一份能自证自洽的图面材料的学生;二是接手了一套库存模块、想把「谁改库存」这件事梳理成文档的开发者。图不是画给老师看的装饰,它是后面建表、写接口、做验收的依据。
2. 分层数据流图总体设计:四层分解与外部实体边界
分层 DFD 的核心不是「画得好看」,而是「每往下一层,只展开一个加工,展开前后数据流必须守恒」。这套图的分层路径是:顶层给出系统边界,第一层把入库和出库各自拆成「审核 + 处理」,第二层把入库审核继续拆成单据审核、填写退货单、单据记账、更新删除库存信息、打印输出五个子加工,第三层再落到具体的单据与记录输出。四层下来,最底层的每个加工都小到可以用一段伪代码或者一条 SQL 描述,这才是分层该停的地方。
我一般会先把编号规则定死再动笔:外部实体用 S 开头(S1 采购部门、S2 领料部门、S3 统计部门及有关人员),加工用 P 加层号(P1、P1.1、P1.1.1),数据存储用 D 开头(D1 库存流水账、D2 库存信息、D3 库存明细),数据流用 F 开头(F1 入库单、F2 库存流水账、F3 出库单、F4 领料单、F5 库存信息、F6 库存明细、F7 出入库统计表)。编号规则先立,后面父子图对照才有依据;反过来先画图再补编号,十有八九会出现同一条流在两个层里叫两个名字。
2.1 顶层 DFD 的边界划分:三个外部实体与四个加工
顶层只回答一个问题:系统外面有谁,系统里面干几件大事。这套方案的顶层把边界切成三进三出——采购部门 S1 送进来 F1 入库单,领料部门 S2 送进来 F4 领料单,统计部门及有关人员 S3 提出查询请求;系统往外回的是入库结果、出库单 F3、库存明细 F6 和出入库统计表 F7。四个加工里,P1 入库管理、P2 出库管理是写操作,P3 库存查询、P4 库存分析预警是读操作,读写分开是顶层最重要的一个决定。
为什么一定要把 P3 和 P4 单列,而不是塞进 P1、P2 里当附属功能?因为写操作和读操作的数据来源不同、失败后果也不同。写操作错了要冲账,读操作错了顶多重算一次报表。把它们在顶层就分开,后面做接口限流、做权限控制、做验收用例,都有清晰的边界可切。同理,P4 的输出必须回到 S1 采购部门——库存分析预警如果不指向采购动作,那它在图里就是一个只吃不吐的黑洞,这在评审时是一眼就能看出来的硬伤。
顶层的另一条约定是「不画存储」。很多图在顶层就把库存流水账摆上去,看着信息量大,实际上破坏了分层抽象:顶层应该只体现系统与外部世界的交互,存储属于系统内部结构,放到第一层再出现。这条约定不是形式主义,它直接决定了你第一层能不能顺畅展开——顶层没画存储,第一层加存储时才有「细化」的感觉,而不是「重复」。
2.2 逐层分解的平衡规则:父子图数据流守恒怎么检查
父子图平衡是 DFD 评审的第一道关,也是最容易被追问的地方。规则只有两条:父图中某个加工的每一条输入流和输出流,在它展开后的子图里必须都能找到对应;子图里不允许凭空出现父图没有的、跨越系统边界的外部数据流。前者叫「不漏」,后者叫「不增」。
拿 P1 举例。顶层里 P1 的输入是 F1 入库单,输出是入库数据 F2。展开到第一层,P1 变成 P1.1 审核和 P1.2 入库处理两个子加工,那么 F1 必须仍然从 S1 进入 P1.1,F2 必须仍然从 P1.2 输出到外部。中间可以新增 P1.1 到 P1.2 的内部流(合格入库单)、P1.1 到 S1 的退货信息,这些是子图内部的细化,不算越界。但如果你在子图里突然画了一条「财务部门 → 付款单」,而顶层根本没有财务部门这个实体,那就破坏了平衡。
实际检查时我用一张对照表逐条过,比盯着图看靠谱得多:
| 父图数据流 | 父图方向 | 子图对应流 | 是否守恒 | 备注 |
|---|---|---|---|---|
| F1 入库单 | S1 → P1 | S1 → P1.1 | 是 | 审核在第一层 |
| F2 库存流水账 | P1 → 外部 | P1.2 → D1 | 是 | 落库位置细化 |
| F3 出库单 | P2 → 外部 | P2.2 → S2 | 是 | 合格出库才回传 |
| F5 库存信息 | P1/P2 → D2 | P1.1.4 / P2.2 → D2 | 是 | 两处写同一存储 |
表里最后一行值得多说一句:同一个数据存储被两个加工写,是完全合法的,但必须在图上把箭头方向标清楚,并且在数据字典里写清各自写哪些字段。库存信息 D2 里,P1.1.4 负责入库后的数量增加与属性维护,P2.2 负责出库后的数量扣减,两者写的是不同字段集合,冲突面很小。如果两个加工都整行覆盖写,那就是设计问题,不是画图问题,得在实现层用乐观锁或者字段级更新解决。
2.3 数据字典:F1 到 F7 七条数据流与三个存储的定义
图能看懂,靠的是数据字典。评审老师或者接手的人不会满足于「这里有一条入库单」,他要问这条流入库单由哪些数据项组成、哪个加工产生、哪个加工消费。这套方案里七条主要数据流的定义大致如下,字段是按库存场景的常规做法补的,具体项目可以增减但不要留空:
- F1 入库单:单号、供应商编码、SKU 编码、数量、单价、到货日期、单据状态。由 S1 产生,进入 P1.1 审核。
- F2 库存流水账:流水号、业务类型(入库/出库/退货冲销)、来源单号、SKU、变动数量、结存数量、操作人、操作时间。由 P1.2 与 P2.2 写入 D1。
- F3 出库单:单号、领料部门、SKU、申请数量、实发数量、用途、单据状态。由 P2.2 输出给 S2。
- F4 领料单:单号、领料部门、SKU、申请数量、领用人、需求日期。由 S2 产生,进入 P2.1 审核。
- F5 库存信息:SKU、现存量、锁定量、安全库存、最大库存、最近更新人、更新时间。由 P1.1.4 与 P2.2 维护,P3 读取。
- F6 库存明细:SKU、批次、库位、现存量、入库日期。P3 按条件查询输出给 S3。
- F7 出入库统计表:统计期间、SKU、入库总量、出库总量、期末结存。P4 汇总后输出给 S3 与 S1。
三个存储里,D1 库存流水账是只增不改的账本,D2 库存信息是当前状态的快照,D3 库存明细按批次或库位展开。这个「账本 + 快照」的双存储结构是库存系统的通用套路:账本负责可追溯,快照负责查询性能。只留快照,一旦数量对不上就永远查不出是哪一笔错了;只留账本,每次查询都要全表累加,库存一多就慢。两者在图上必须是两条独立的箭头,不能合并成一条「库存数据」含糊过去。
提示:数据字典不要写成字段清单就完事,每条流最好补一句「产生时机」和「消费方」,答辩时被问到「这条流入库单审核完去哪了」能直接指出来。
3. 入库管理链路设计:P1 到 P1.1.5 的审核、记账与退货分支
入库这条链路是整个图里分支最多的地方,因为它同时存在「合格」和「不合格」两条路径,而不合格路径又牵出退货单、库存信息回滚、打印输出三个动作。很多图在这里画崩,不是不会画,是没想清楚业务上到底允不允许「不合格入库单」直接作废。这套方案的答案是:不允许作废,必须走退货单,因为采购合同已经成立,单据要有闭环去向。
3.1 入库单审核 P1.1:合格与不合格的两条路径
P1.1 的输入是 F1 入库单,输出分两路:合格入库单进 P1.2,不合格入库单进 P1.1.2 填写退货单。审核判定的内容通常包括四项:单据要素是否齐全(单号、供应商、SKU、数量)、SKU 是否在物料主数据中存在、数量是否为正且不超过采购订单余量、供应商是否在合格供方名单内。四项全过才算合格,任何一项不过就打回。
这里有个设计细节值得单独讲:审核结果不应该作为一条数据流画出来。审核状态是单据的一个属性,它随 F1 一起流动,画成独立的「审核结果」数据流会让图迅速膨胀。正确的做法是把状态字段放进数据字典的 F1 定义里,在加工说明中写清「P1.1 将 F1 的单据状态由待审核改为已审核或已驳回」。数据流是数据,状态是数据的一部分,不是控制信号。
审核逻辑落地成代码大概是这样,注意函数只做判定不做写库,写库交给后面的记账加工,职责分开才好测:
def audit_inbound(order, sku_master, supplier_ok): """P1.1 入库单审核:只判定,不改库存 返回 (是否合格, 驳回原因) """ if not order.get("order_no") or not order.get("sku_code"): return False, "单据要素缺失" # 单号或 SKU 为空 if sku_master.get(order["sku_code"]) is None: return False, "SKU 不存在于物料主数据" # 防止脏 SKU 进库存 if order["qty"] <= 0: return False, "数量必须为正数" if order["qty"] > order.get("po_remain_qty", 0): return False, "超出采购订单余量" # 防超收 if not supplier_ok(order["supplier_code"]): return False, "供应商不在合格供方名单" return True, ""这段代码对应图上 P1.1 这个圆圈,参数po_remain_qty是采购订单未收数量,防的是同一张采购单重复收货;supplier_ok是个外部校验函数,实际项目里查供方表。判定的顺序有讲究:先判空再判存在性,最后判业务规则,这样驳回原因给出来最具体,操作员一看就知道改哪里。如果顺序反了,一个空 SKU 可能先撞上「超出余量」,提示就误导人了。
3.2 单据记账 P1.1.3 与库存流水账的写入规则
合格的入库单进入 P1.2 入库处理,再细化到 P1.1.3 单据记账。记账这一步做三件事:往 D1 库存流水账插入一条正数流水、更新 D2 库存信息的现存量、把单据状态置为已记账。三件事必须在一个事务里,否则会出现「流水有了库存没加」的鬼账。
流水表的设计决定了后面所有统计能不能对上,所以字段要给足:
CREATE TABLE stock_flow ( -- D1 库存流水账,只增不改 flow_id BIGINT PRIMARY KEY AUTO_INCREMENT, biz_type TINYINT NOT NULL, -- 1 入库 2 出库 3 退货冲销 src_no VARCHAR(32) NOT NULL, -- 来源单号:入库单/出库单/退货单 sku_code VARCHAR(32) NOT NULL, qty DECIMAL(12,2) NOT NULL,-- 入库为正,出库为负 balance_qty DECIMAL(12,2) NOT NULL,-- 记账后的结存,便于对账 operator VARCHAR(32) NOT NULL, created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, UNIQUE KEY uk_biz_src_sku (biz_type, src_no, sku_code) -- 幂等键 );uk_biz_src_sku这个唯一索引是整张表最值钱的一行。网络重试、用户连点两次提交、消息队列重复投递,在库存系统里都是家常便饭,有了这个约束,重复记账会直接被数据库挡掉,抛唯一键冲突,业务层捕获后当成「已处理」返回即可,比在代码里先查后插可靠得多。balance_qty存的是记账后的结存,它的作用是让对账变成一次单表扫描,而不是把流水从头累加一遍。
记账的伪代码按「先锁库存行 → 再写流水 → 再更新快照」的顺序:
def post_inbound(order, conn): """P1.1.3 单据记账:同一事务内完成流水与快照""" with conn.begin(): row = conn.execute( "SELECT qty_on_hand FROM stock_item WHERE sku_code=%s FOR UPDATE", (order["sku_code"],)).fetchone() # 行锁,防并发覆盖 new_balance = row.qty_on_hand + order["qty"] conn.execute( "INSERT INTO stock_flow(biz_type,src_no,sku_code,qty,balance_qty,operator)" " VALUES(1,%s,%s,%s,%s,%s)", (order["order_no"], order["sku_code"], order["qty"], new_balance, order["operator"])) conn.execute( "UPDATE stock_item SET qty_on_hand=%s, updated_at=NOW() WHERE sku_code=%s", (new_balance, order["sku_code"]))FOR UPDATE是不能省的,两个人同时给同一个 SKU 入库,没有行锁就会出现经典的丢失更新:都读到 100,都加 10,最后库存是 110 而不是 120。这类问题在单机测试里几乎不会出现,一上并发就爆,属于典型的「演示很稳、上线翻车」。
3.3 退货单与库存信息更新删除 P1.1.2 / P1.1.4 / P1.1.5
不合格路径的三个子加工,在第二层图上是一条链:P1.1.2 填写退货单 → P1.1.5 打印输出退货信息 → 回到采购部门 S1。注意退货单并没有直接冲减库存,因为不合格入库单压根没记过账,无账可冲。这和「已入库后才发现质量问题要退货」是两回事,后者要走 P1.1.4 更新库存信息,产生一条biz_type=3的冲销流水,数量为负。图面上这两种退货要有区分,否则实现时必然混在一起写,最后账越冲越乱。
P1.1.4 的名字叫「更新删除库存信息」,这里的「删除」一定要在加工说明里写清楚是逻辑删除。库存信息一旦物理删除,历史流水就失去了对应的主数据,F7 统计表按 SKU 汇总时会出现「有流水无 SKU」的空洞。常规做法是加一个is_active标志位,停用的 SKU 不参与新增业务,但历史数据照常可查。这一点在答辩时经常被追问,因为「删除」两个字太容易被理解成DELETE。
P1.1.5 打印输出是第三层的落点,输出三类东西:退货信息给 S1 采购部门、库存记录表给管理员、入库记录归档。打印类加工在 DFD 里很容易被画成「只有输入没有输出」的黑洞,或者反过来「只有输出没有输入」的奇迹,正确画法是它从 D1 或 D2 读数据,再输出到外部实体,一进一出都要有。实际实现里打印往往就是一次报表查询加导出,但它在线下流程中承担了签字确认的职责,所以在图里不能省。
注意:入库链路的三条分支——合格记账、不合格退货、已入库冲销——在图上必须是三条不同的路径,合并成一条「异常处理」会让第二层失去意义。
4. 出库管理链路与库存查询分析:P2、P3、P4 的数据来源设计
出库和入库在结构上对称,但业务约束完全不同:入库是「有多少收多少」,出库是「够不够发」。这个差异决定了 P2.1 审核的核心是可用量校验,而不是单据要素校验。再加上 P3 查询和 P4 预警都要读同一份库存快照,三个加工对 D2 的读法必须统一口径,否则报表和页面会互相打架。
4.1 领料单驱动的出库审核与出库处理
P2.1 收到 F4 领料单后,第一件事是算可用量:可用量 = 现存量 − 锁定量。为什么要减锁定量?因为领料单审核通过到实际出库之间有时间差,如果只按现存量判断,两张单子可能同时通过审核,最后一张发不出货。锁定量就是为此存在的:审核通过时先锁定,出库时再扣减现存量并释放锁定量。
def audit_outbound(req, item): """P2.1 出库审核:可用量校验,通过则锁定""" available = item.qty_on_hand - item.qty_locked # 可用量口径全系统统一 if req["qty"] <= 0: return False, "申请数量非法" if req["qty"] > available: return False, f"可用量不足,当前可用 {available}" return True, "" def do_outbound(req, conn): """P2.2 出库处理:扣现存量、释放锁定量、写负流水""" with conn.begin(): item = conn.execute( "SELECT qty_on_hand,qty_locked FROM stock_item WHERE sku_code=%s FOR UPDATE", (req["sku_code"],)).fetchone() new_on_hand = item.qty_on_hand - req["qty"] new_locked = item.qty_locked - req["qty"] assert new_on_hand >= 0 and new_locked >= 0, "库存不能为负" conn.execute("UPDATE stock_item SET qty_on_hand=%s,qty_locked=%s WHERE sku_code=%s", (new_on_hand, new_locked, req["sku_code"])) conn.execute( "INSERT INTO stock_flow(biz_type,src_no,sku_code,qty,balance_qty,operator)" " VALUES(2,%s,%s,%s,%s,%s)", (req["req_no"], req["sku_code"], -req["qty"], new_on_hand, req["operator"]))assert new_on_hand >= 0这行看着多余,因为审核阶段已经校验过了,但它防的是「审核后库存被别的单子吃掉」的竞态。线上环境里把断言换成显式的业务异常更好,至少要保证负库存永远不会落库。负库存在库存系统里是个玄学问题,一旦出现,后面所有统计都会失真,而且很难倒推是哪一单造成的。
P2.2 输出 F3 出库单给 S2 领料部门,同时更新 D2 库存信息、写入 D1 库存流水账。注意出库流水记的是负数,这样账本累加的结果天然等于现存量,对账时一条SUM(qty)就能验,不需要按业务类型分正负号。
4.2 库存查询 P3 的明细与统计表口径
P3 是纯读加工,输入是 S3 的查询条件,输出是 F6 库存明细和 F7 出入库统计表。它最容易出的问题不是功能,是口径:同一个「本月出库量」,页面显示 1200,报表显示 1350,差的那 150 就是退货冲销算没算进去。解决办法是在加工说明里把口径写死,并且写进数据字典的 F7 定义。
我一般会固定三条口径规则:统计期间按业务发生时间(created_at)而不是审核时间,避免跨月单据来回跳;退货冲销(biz_type=3)计入发生期间,用负数参与汇总,不单独列一栏;未记账的单据一律不进统计,只统计已落流水的数据。三条规则写在文档里,后面谁改报表都得先看这三条。
查询本身不需要复杂代码,但索引要给对。库存明细按 SKU 加库位查询、流水按时间区间加 SKU 汇总,是最常见的两种访问路径,对应两个索引:
-- 流水按期间汇总的覆盖索引 CREATE INDEX idx_flow_time_sku ON stock_flow (created_at, sku_code, biz_type); -- 明细按 SKU 精确查 CREATE INDEX idx_item_sku ON stock_item (sku_code, is_active);覆盖索引的意思是查询用到的列都在索引里,不用回表。流水表是只增不改的,数据量涨得最快,一年几十万条很常见,没有索引的报表查询从秒级掉到分钟级一点都不奇怪。这类优化在画图阶段就该想到,因为 P3 的性能瓶颈最终会反馈到 DFD 上——如果统计表实在太慢,就得考虑加一个汇总存储,那图就要改。
4.3 库存分析预警 P4 的阈值与触发规则
P4 是这套图里唯一一个「主动输出」的加工,它不等人来查,而是定时跑出来推给 S1 采购部门。这一条数据流如果缺失,整个系统就退化成被动的记账工具,而库存管理真正的价值恰恰在预警。
预警规则通常是两条线:低于安全库存报短缺,高于最大库存报积压。阈值放在 SKU 维度上,不同物料的安全库存差别很大,用统一阈值等于没预警。
def scan_warning(items, inbound_map): """P4 库存分析预警:定时任务,逐个 SKU 判定 inbound_map 是在途入库量,避免刚下单就重复报警 """ alerts = [] for it in items: available = it["qty_on_hand"] - it["qty_locked"] on_the_way = inbound_map.get(it["sku_code"], 0) if available + on_the_way < it["safe_stock"]: alerts.append({"sku": it["sku_code"], "type": "短缺", "gap": it["safe_stock"] - available - on_the_way}) elif available > it["max_stock"]: alerts.append({"sku": it["sku_code"], "type": "积压", "excess": available - it["max_stock"]}) return alerts把在途量加进来是关键一步。只按现存量报警,采购刚下完单还没到货的那几天会天天报短缺,用不了两周所有人都会把预警当噪音关掉。加上在途量之后,预警才有可执行性——它告诉采购的是「还差多少」,而不是「你又缺货了」。
阈值本身建议做成配置而不是硬编码:
{ "sku_code": "SKU-1001", "safe_stock": 200, "max_stock": 1500, "check_cron": "0 0 8 * * ?", "notify_to": ["S1", "admin"] }check_cron表示每天早八点跑一次,notify_to对应图上的输出箭头。预警结果输出给 S1 采购部门和管理员,形成「分析 → 采购 → 入库 → 再分析」的闭环,这也是顶层 DFD 里 P4 必须有一条流回到 S1 的原因。
5. 避坑与常见问题排查:这张图最容易被追问的五个地方
图画完只是开始,真正让人头大的是评审或者交接时对方指着某个圆圈问一句「这个加工的输出呢」。下面五条是这套库存 DFD 里出现频率最高的问题,每条按现象、原因、解决三段写,照着自查一遍能省掉大半返工。
5.1 编号重号:F2 既像流水账又像入库数据
现象是父子图对不上:顶层写 F2 是库存流水账,展开到第一层,同一个 F2 又变成了「入库数据」,评审时被问「F2 到底是哪条流」,只能现场解释。原因很直接——编号是画到哪想到哪随手起的,没有先建数据字典。这套图里还有一个更隐蔽的重号:S1 在第一层既指采购部门又指统计部门,外部实体编号被复用了。
解决办法是把编号当成契约而不是标签。定稿前先出一张编号清单,实体 S1 至 S3、加工 P1 至 P4 及其下级、存储 D1 至 D3、数据流 F1 至 F7,全局唯一,一个号只对应一个东西。数据流改名不改号,实在要拆分就新增编号并且标注废弃关系。这张清单放在文档第一页,后面所有图都从它取号,重号问题就绝迹了。
5.2 黑洞与奇迹加工:查询没输入、预警没输出
现象是 P3 库存查询只画了一条输出到 S3 的箭头,没有从 D2 读数据的箭头;或者 P4 库存分析预警只有输入没有输出。前者叫奇迹加工,凭空造数据;后者叫黑洞加工,数据进去就没了。评审老师抓这两种错误一抓一个准,因为它们违反的是 DFD 最基本的守恒常识。
原因是画图时把「加工」当成了功能菜单,而不是数据变换。查询在脑子里是「点一下出结果」,很自然就忘了它必须先从存储读。解决方法是给每个加工强制配平:至少一条输入流、至少一条输出流,读类加工的输入必须来自数据存储,写类加工的输出必须落到数据存储或外部实体。画完挨个圈检查一遍,缺哪边补哪边,比事后解释便宜得多。
5.3 层级跳跃:第三层的细节画到了第一层
现象是第一层 DFD 里直接出现了「打印退货信息」这种第三层才该有的动作,而第二层反倒空着;或者顶层就画出了库存流水账这个存储。这样画出来的图信息量看着大,实际上层次感全无,读者不知道哪一层该看什么。
根本原因是没定「一层只展开一个加工」的规矩。正确节奏是:顶层只画系统与外部实体的交互,第一层展开 P1 和 P2 得到 P1.1、P1.2、P2.1、P2.2,第二层只展开 P1.1 得到五个子加工,第三层再落到具体输出物。每层只往前推一格,粒度自然一致。如果某个加工在三层之内还展不开,说明它本身粒度太粗,得回到上一层重新切。
5.4 把状态和控制信号画成数据流
现象是图上出现了「审核通过」「通知采购员」「提醒」这类箭头。它们看起来像流,实际上不是数据,而是控制信号或者动作。DFD 只描述数据流动,控制逻辑属于加工说明的范畴。
原因是画图时把业务流程原样搬了进来。解决办法是把状态塞进数据字典:审核结果作为 F1 入库单的一个字段随单流动,不在图上单画箭头;「通知」这类动作写成加工说明里的一句「P4 生成预警记录并输出至 S1」,也不画成独立数据流。判断标准很简单——这条箭头传的东西能不能存进一张表?能,就是数据流;不能,就是控制。
5.5 存储只写不读,或只读不写
现象是 D3 库存明细在图上只有 P3 读它,找不到任何加工写它;或者 D1 库存流水账被 P1.2 写进去之后,再没有任何加工读它。前者意味着这份明细没有数据来源,后者意味着账本记了没人用,两种都是设计漏洞。
原因是画图时只关注了主干流程,把存储当成了装饰。解决方法是给每个存储列一张读写清单:谁写、写哪些字段、什么时机;谁读、读来做什么。D1 至少要被 P1.2、P2.2 写,被 P3、P4 读;D2 被 P1.1.4、P2.2 写,被 P2.1、P3、P4 读;D3 被 P1.2 写,被 P3 读。清单列完,图和实现对得上,答辩时被问「这个存储谁维护」也能立刻答出来。
6. 验收与答辩:用一张守恒对照表证明你的图是自洽的
图交上去之前,我建议做一次「指流问答」:找个人随机指图上任意一条数据流,你要能在十秒内答出四个问题——从哪来、到哪去、被哪个加工写、被哪个加工读。答不上来的那条,就是图上最脆弱的地方。这个方法比反复通读有效得多,因为评审的提问路径和它是同构的。
配套的工具是一张验收对照表,把抽象的自洽性变成可勾选的项:
| 检查项 | 判定标准 | 证据材料 |
|---|---|---|
| 父子图平衡 | 父图每条跨边界流在子图都能找到 | 第 2.2 节对照表 |
| 加工配平 | 每个加工至少一进一出 | 加工清单逐条核对 |
| 存储读写 | 每个存储在图上至少一写一读 | 存储读写清单 |
| 编号唯一 | S/P/D/F 四类编号无重号 | 编号总表 |
| 数据字典 | F1 至 F7 均有字段与产生时机 | 数据字典章节 |
| 口径一致 | F7 统计规则与 P3 查询一致 | 口径三条规则 |
答辩时的节奏也值得设计一下。不要从头讲图,先讲边界:三个外部实体、四个加工、三个存储,三十秒把全局摆出来;然后挑一条最有代表性的链路走一遍,入库最合适,因为它有合格和不合格两条分支,能体现你考虑过异常;最后主动交代一个你权衡过的设计决定,比如「库存流水账设计成只增不改,用幂等键防重复记账」,这一句往往比前面十分钟更能让评委记住你。主动暴露取舍,比等着被问出来强。
还有一个具体技巧:把 DFD 和建表语句放在同一页对照。图上的 D1 库存流水账旁边直接贴stock_flow的建表语句,D2 旁边贴stock_item,评委一眼就能看出图不是画着玩的,是能落到代码的。这套图里 P1.1.3 记账、P2.2 出库处理、P4 预警扫描三处我都习惯配上伪代码,图与码互证,比任何文字说明都有说服力。
从那以后我每次交图前都强制走一遍指流问答,哪怕时间再紧也不省这一步——省下来的半小时,通常会在答辩现场以三倍的代价还回去。希望帮到你。
本文还有配套的精品资源,点击获取