系统设计产出三样东西:功能结构树、业务流程图、目录结构。 前两样是给人看的,第三样是给编译器看的。 目录结构不是「整理癖」,它决定了改一个功能要动几个文件。
19.2 排出了用例的序,这一篇把序变成结构。 结构一旦定下来,19.5 的界面、19.6 的公共模块都往这个格子里填。 格子错了,后面全是补丁。 这一篇的 Demo 会真的把目录建出来,不是画个示意图。
一、功能结构树:为什么是三个分支
教材的功能树是三层:一个根、三个分支、九个叶子。 三个分支对应三个业务模块,九个叶子对应九个菜单项。
+ 人事管理系统
+ 人事管理
- 员工档案
- 部门管理
- 合同管理
+ 待遇管理
- 工资计算
- 工资发放
- 工资条查询
+ 系统维护
- 用户管理
- 权限分配
- 数据备份
「三三制」不是巧合,是照着菜单栏来的:一个菜单三个子项,界面上一眼看全。 如果变成四层,用户就要点三次才能到功能。 每多一层,找东西的成本翻一倍——这是导航设计的经验值。 所以功能树的第一条约束是【层数】,第二条才是分组。 分组可以商量,层数不行。 叶子超过十个就该分二级菜单,这条在 19.5 讲导航栏时还会用到。
二、业务流程图:拿「新增员工」走一遍
流程图不是画给客户看的,是拿来发现「这里要写两张表」的。 挑一个最典型的用例,一步一步走。
| 步 | 做什么 | 在哪 |
|---|---|---|
| 1 | 人事专员打开新增窗口 | 界面 |
| 2 | 填工号、姓名、部门、岗位 | 界面 |
| 3 | 点保存,前端校验必填 | 界面 |
| 4 | 查工号是否重复 | 数据库 |
| 5 | 查部门是否存在 | 数据库 |
| 6 | 插入员工表 | 数据库 |
| 7 | 同时生成一条初始工资记录 | 数据库 |
| 8 | 返回成功,刷新列表 | 界面 |
第 4、5 步的顺序有讲究:先查重再插,不要靠主键冲突兜。
靠主键冲突兜的写法,出错时给用户看的是 ORA-00001 这种天书。
先查一次,才能给出「工号 A001 已存在」这种能看懂的话。
代价是多一次查询,值。
第 7 步才是这张流程图真正的价值:它暴露了「一次业务写两张表」。
只要出现「写两张表」,就必须有一个事务包住。
否则第一步成功、第二步失败,就多了一个没有工资记录的员工。
流程图里的每一个「数据库」格子,都是在问一句:这里要不要事务。
三、目录结构:分层是为了改得动
教材的目录分四层:entity 放数据、dao 放 SQL、
service 放业务、view 放界面。
| 包 | 放什么 | 改了它影响谁 |
|---|---|---|
entity |
数据对象 | 全部(但很少改) |
dao |
增删改查 | 只影响调用它的 service |
service |
业务规则、事务 | 只影响界面 |
view |
窗口与面板 | 只影响它自己 |
这张表的第三列才是分层的目的:改一处,影响面可控。
数据库从 Oracle 换成 MySQL,只动 dao 和配置文件;
界面换个颜色,只动 view。
不分层的写法是:SQL 直接写在按钮的点击事件里。
这种代码改一个字段,要翻遍所有界面文件——而且无法判断改全了没有。
Demo 把目录真建了一遍:12 个目录、16 个文件、最深 4 层。
4 层是个上限,再深就该拆模块了。
四、系统预览:先画草图再写界面
界面草图(wireframe)不需要好看,只需要定三件事: 哪块区域放什么、谁在左谁在右、操作按钮在哪。 教材的主窗体是标准的四块:顶部工具栏、左侧导航、中间内容、底部状态。 这个布局二十年前就定了,因为它符合人眼从左到右、从上到下的扫描习惯。 草图的用处是【提前吵完架】。 在纸上改布局,成本是十分钟;代码写完再改,成本是两天。 所以草图上要标数字:宽度多少像素、能放几列。 不标数字的草图,实现时全靠猜,猜完必然不一致。
五、动手 Demo:把工程目录真建一遍,数一数层级和文件
这一篇的 Demo 是全书最朴素的一个:建目录、建空文件、数层级。 它证明不了任何性能,只能证明一件事——这个结构落得下去。 重点是最后一行数字:最深 4 层。 层级是设计约束,不是随手写的。
import os, tempfile
root = os.path.join(tempfile.gettempdir(), "hr_proj")
dirs = ["src/entity", "src/dao", "src/service", "src/view",
"src/view/dialog", "src/util", "src/image",
"cfg", "lib", "log", "doc", "bin"]
files = ["src/entity/Personnel.java", "src/entity/Dept.java",
"src/entity/Wage.java", "src/entity/User.java",
"src/dao/BaseDao.java", "src/dao/PersonnelDao.java",
"src/dao/WageDao.java", "src/service/WageService.java",
"src/view/MainFrame.java", "src/view/PersonnelPanel.java",
"src/view/WagePanel.java", "src/util/ToolBar.java",
"cfg/hibernate.cfg.xml", "cfg/Personnel.hbm.xml",
"cfg/Dept.hbm.xml",
"src/view/dialog/DeptTreeDialog.java"]
for d in dirs:
os.makedirs(os.path.join(root, *d.split("/")), exist_ok=True)
for f in files:
open(os.path.join(root, *f.split("/")), "w").close()
deep = max(len(f.split("/")) for f in files)
print("目录 %d 个,文件 %d 个,最深 %d 层" % (len(dirs), len(files), deep))
print("根目录 %s" % root)
实跑输出:
--------------------------------------------------------------------
第八十六课:系统设计——功能树、流程图、目录一次成型
--------------------------------------------------------------------
**这一课把上一课的用例变成三张图:功能结构树、业务流程图、目录结构。**
**三张图里只有第三张能在沙箱真跑,另外两张画出来看。**
一、功能结构树
--------------------------------------------------------------------
+ 人事管理系统
+ 人事管理
- 员工档案
- 部门管理
- 合同管理
+ 待遇管理
- 工资计算
- 工资发放
- 工资条查询
+ 系统维护
- 用户管理
- 权限分配
- 数据备份
**三个一级分支,九个二级功能。**
**这个「三三制」不是巧合,是照着菜单栏来的:一个菜单三个子项,界面上一眼看全。**
**十个以上就该分二级菜单了,那时候用户要点两次才能到功能。**
二、业务流程图:新增一个员工
--------------------------------------------------------------------
步 做什么 在哪
----------------------------------
1 人事专员打开新增窗口 界面
2 填工号、姓名、部门、岗位 界面
3 点保存,前端校验必填 界面
4 查工号是否重复 数据库
5 查部门是否存在 数据库
6 插入 tb_personnel 数据库
7 同时生成一条初始工资记录 数据库
8 返回成功,刷新列表 界面
已选择 8 行
**第 4、5 步的顺序有讲究:先查重再插,不要靠主键冲突来兜。**
**靠主键冲突兜的写法,出错时给用户看到的是 `ORA-00001` 这种天书。**
**先查一次,能给出「工号 A001 已存在」这种能看懂的话。**
**代价是多一次查询。**
**但第 7 步暴露了一个更重要的问题:新增员工要同时写两张表,必须放在一个事务里。**
**不然第一步成功、第二步失败,就多了一个没有工资记录的员工。**
三、目录结构:真的建一遍
--------------------------------------------------------------------
统计项 数值
-------------
目录数 12
文件数 16
最深层级 4
已选择 3 行
**这个结构要记住的是分层:`entity` 放数据、`dao` 放 SQL、`view` 放界面、`util` 放工具。**
**分层的意义在「改一处不动别处」。**
**数据库从 Oracle 换成 MySQL,只动 `dao` 和配置文件;界面换个颜色,只动 `view`。**
**不分层的写法是:SQL 直接写在按钮的点击事件里。**
**这种代码改一个字段,要翻遍所有界面文件。**
输出三行:12 个目录、16 个文件、最深 4 层。
12 和 16 这两个数看着平淡,但它们是可以验收的。
「目录结构设计好了」这句话没法验收,「12 个目录」可以。
16 个文件分布在 4 个包里,平均每包 4 个。
这个密度是健康的:一个包超过十个文件,就该想想是不是能拆。
src/img 和 bin、log、doc 是空目录,所以文件数比目录数少。
真实项目里这几个目录往往被忽略——结果图片散落在项目根目录,日志打进控制台,没有任何文档。
cfg 里放了四个 XML:一个主配置、三个映射文件。
19.6 会真的去解析其中那个主配置。
要说明一点:这里只建了「骨架」,没有写 Java 代码。
Java 编译不了,所以这一课能真跑的只有目录和文件本身。
30 秒实验
- 把
dirs里的"src/image"改成"src/img",看统计变不变——目录名和代码里的引用必须一致, 这一改就得同步改代码。 - 往
files里加一个"src/view/dialog/SelectEmpDialog.java", 看最深层级还是不是 4——层级由路径决定,不由文件数决定。 - 把
dirs删掉三项再跑,看makedirs会不会报错——它带了exist_ok=True, 但父目录不存在时会自动补建,这是它和mkdir的区别。
常见误区
- 「目录结构是小事,先写代码再说」。 结构决定影响面, 写到一半再调整,等于把已经写好的 import 全改一遍。
- 「分层越多越专业」。 四层够用,再多会出现「改一个字段要动五个文件」的情况, 那叫过度设计。
- 「流程图是给客户看的」。 它是给自己看的, 作用是发现「这里要写两张表」「这里要事务」这类实现级问题。
- 「草图不用标数字」。 不标宽度,实现时全靠猜;猜出来的界面在 1024 的机器上一定出滚动条。
自测三问(点开对答案)
1. 功能树为什么控制在三层? —— **每多一层,用户找到功能就多一次点击,成本翻倍。** **三层(菜单→子菜单→功能)是桌面软件的惯例上限。** 2. 「新增员工」流程图里,哪一步最重要? —— **第 7 步「同时生成工资记录」。** **它把一次业务变成两次写库,从而逼出一个事务边界。** 3. 分层的目的是什么? —— **让「改一处」的影响面可控。** 不是为了好看,**是为了能判断改了之后哪些地方要跟着改。** 4. 为什么草图必须标数字? —— **因为布局的本质是算术**:窗口宽度减去固定区域,剩下的才是内容区。 **不标数字就没法算,只能猜。**下一篇进入数据库:四张表、22 个字段、4 个外键。 还要做一次范式体检,把「以后一定要返工」的字段提前挑出来。
- ☐ (本篇完,下一篇 19.4 数据库设计)
相关笔记
- 系统分析
- 数据库设计
- 主窗体设计
-
- 🐬 MySQL必知必会/00-索引
- 🏺 Oracle从入门到精通 · 索引