# 开发规范
> 本文档记录 `poyee-data-warehouse` 数仓数据开发流程与项目管理规范。
> 与 `数仓命名规范.md`、`90-重构路线.md` 配合使用。
## 1. 项目管理规范(TPAD)
目前 TPAD 已创建**数据需求工作流**和**技术需求工作流**。
### 1.1 TPAD 任务建档要求
| 耗时 | 要求 |
|------|------|
| **2 小时以下**临时需求 | 需要聊天记录做留档 |
| **2 小时以上** | 必须建立 TPAD 任务 |
| **8 小时以上** | 必须有方案或设计文档 |
## 2. 数据开发流程
### 2.1 数据开发全流程图
```mermaid
flowchart TD
A[数据需求] --> B[需求沟通]
B --> C[需求确认]
C --> D{是否涉及敏感数据?}
D -->|是| E[敏感数据使用审批]
E --> F{是否审批通过?}
F -->|否| Z[结束]
F -->|是| G{是否需要新的采集方案?}
D -->|否| G
G -->|是| H[采集需求流程]
H --> G
G -->|否| I{所需指标维度
是否已进仓?}
I -->|否| J[数据接入流程]
J --> I
I -->|是| K[排期开发]
K --> L[开发完成]
L --> M[需求方验收]
M --> N{是否验收通过?}
N -->|否| K
N -->|是| O{是否追溯历史数据?}
O -->|否| Z
O -->|是| P[追溯数据]
P --> Q[追溯及验收完成]
Q --> Z
```
### 2.2 关键节点说明
| 环节 | 说明 |
|------|------|
| **敏感数据审批** | 涉及用户身份证、联系方式、支付信息等敏感字段需走专项审批 |
| **采集需求流程** | 数据源尚未接入时,先走 DataX/Kafka 采集接入流程 |
| **数据接入流程** | 数据已采集但尚未建模入仓时,先落 ODS/DWD |
| **指标维度复用判定** | 先检查所需指标是否已在字典,避免重复建模 |
| **数据探查** | 源数据行数、空值率、主键唯一性、字段分布 |
| **建模评审** | 对照总线矩阵与维度建模五步法(见 `数仓分层与建模.md`) |
| **命名合规自检** | 对照 `数仓命名规范.md` 第 7 节 Checklist |
| **口径对齐** | 对照 `指标体系.md` 的指标字典 |
| **数据质量校验** | 在 DolphinScheduler 工作流中加入质量校验节点 |
| **历史回溯** | 确保任务支持按 `dt` 回跑;追溯完成后需要验收 |
## 3. 代码开发规范
### 3.1 Python / PySpark
- PEP 8 风格
- 禁用 `dict.__contains__(key)`,改用 `key in dict`
- 禁止 SQL 字符串拼接(防 SQL 注入),使用参数化查询
- 硬编码配置项必须外置到 `conf/`(见 `90-重构路线.md` §2)
- 敏感信息(数据库账密)**不得入库**
- 新增 UDF 必须带注释与单元测试,再登记到 `kb/31-UDF手册.md`
### 3.2 SQL
- 表名、字段名遵循五段式命名
- 所有字段必须带 `COMMENT`
- 分区字段统一 `dt`(日期)/ `hr`(小时)
- 存储格式统一 ORC
#### 3.2.1 为什么不对齐 AS
字段别名**不对齐** AS 关键字是刻意的,理由:
1. **diff 噪音**:对齐 AS 需要按同一 SELECT 内最长字段补空格。任意字段改名会触发整列空格重算,一个字段改动变成 N 行 diff,code review 里真实逻辑变更被空白变更淹没
2. **与"逗号前置"冲突**:逗号前置的动机就是让每一行独立、增删行不污染邻居;对齐 AS 又把"相邻行耦合"引回来了,自相矛盾
3. **git blame 失真**:纯空白重排会把一堆行的作者改成最近那次格式化的人
4. **Hive/Spark SQL 字段常很长**:数仓里 `trd_order_pay_amt_rmb_total_1d` 这种 30+ 字符的字段名很常见,对齐后右边要留 40+ 空格,一屏横向放不下反而更难读
5. **可读性不需要靠对齐**:每个字段一行 + 逗号前置已足够清晰
### 3.3 Shell
- 环境变量统一从 `conf/env.sh` 读取
- 避免在业务脚本中重复环境检测逻辑(统一交给 Python 入口)
### 3.4 Git 协作规范
本节定义数仓项目团队使用 Git 协作的分支模型与工作流程,保证主干历史整洁可追溯,降低多人协作冲突风险。
#### 3.4.1 分支模型
##### 3.4.1.1 分支总览
| 分支 | 定位 | 受保护 | 合并来源 | 触发动作 |
|------|------|--------|----------|----------|
| `master` | 稳定版本归档 | 是 | `release` | 打 Tag(里程碑、冒烟通过等) |
| `release` | 线上运行版本 | 是 | `feature` | 发布到线上 |
| `feature` | 公共开发分支 | 是 | `feature-xxx`(个人分支) | 测试通过后合入 `release` |
| `feature-xxx` | 个人开发分支 | 否 | —— | 通过 PR 合入 `feature` 后自动删除 |
##### 3.4.1.2 分支结构
```
[tag: datax+spark-smoke-2026-04-20]
│
master ──────────────────────────●─────────────────────▶ (稳定版本归档)
↗
↗ merge (里程碑 / 冒烟通过)
↗
release ────●────●────●──────●──────────────────────────▶ (线上版本)
↑ ↑ ↑
│ │ │ merge (测试通过后发布)
│ │ │
feature ─●──●─●──●─●──●──────────────────────────────────▶ (公共开发)
↑ ↑ ↑
│ │ │ PR + Review (管理员合并)
│ │ │
│ │ └── feature-lisi-dwd-trd-20260418 ✗ (PR 合入后自动删除)
│ └────── feature-wangwu-dim-shp-20260419 ✗ (PR 合入后自动删除)
└─────────── feature-zhangsan-ods-usr-20260420 ✗ (PR 合入后自动删除)
```
##### 3.4.1.3 保护策略
`master`、`release`、`feature` 三个分支在仓库层面禁用远程推送,所有变更必须通过 PR(Pull Request / Merge Request)流转,仅允许管理员通过合并操作更新。
#### 3.4.2 代码流转路径
```mermaid
flowchart LR
A[feature-xxx
个人分支] -->|PR + Review| B[feature
公共开发]
B -->|测试通过
线上发布| C[release
线上版本]
C -->|里程碑/冒烟通过
打 Tag| D[master
稳定归档]
```
代码单向汇聚到 `master`,各合并节点由管理员操作,`release` → `master` 的合并提交需打 Tag。
#### 3.4.3 开发者工作流
```mermaid
sequenceDiagram
participant Dev as 开发者
participant Local as 本地仓库
participant Remote as 远端仓库
participant Admin as 管理员
Dev->>Remote: fetch origin
Dev->>Local: 基于最新 origin/feature 新建个人分支
Note over Dev,Local: 每次新任务必须重新拉取 feature 建分支
loop 日常开发
Dev->>Local: 编码 + commit
end
Note over Dev,Remote: 提 PR 前必须同步远端 feature
Dev->>Remote: fetch origin feature
Dev->>Local: rebase origin/feature
alt 有冲突
Dev->>Local: 本地解决冲突后继续 rebase
end
Dev->>Remote: push --force-with-lease 推送个人分支
Dev->>Remote: 网页端发起 PR
Admin->>Remote: Code Review
alt Review 通过
Admin->>Remote: 合并 PR 到 feature
Remote-->>Remote: 自动删除 feature-xxx
else 需要修改
Admin-->>Dev: 提出修改意见
Dev->>Local: 修改后重复 rebase + 强推
end
```
**关键约束**:
- **新建分支与提 PR 前必须先 `fetch` 远端**,以 `origin/feature` 的最新状态为基准,禁止以本地 `feature` 为基准(本地 `feature` 可能滞后于远端)
- **个人分支必须基于最新 `origin/feature` 创建**,禁止复用已合并 PR 的旧个人分支
- **提 PR 前必须 rebase `origin/feature`**,冲突在本地解决后继续 rebase
- **强推一律使用 `--force-with-lease`**,禁用 `--force`
- **PR 合并后远端个人分支自动删除**,下次任务重新 fetch 后基于 `origin/feature` 建新分支
日常通过 PyCharm 的 Git 面板与仓库网页端完成上述操作即可。
#### 3.4.4 要求使用 Rebase 而非 Merge
- **历史线性整洁**:没有冗余的 merge 提交,可视化合并树清晰
- **二分查找友好**:`git bisect` 定位问题提交更精准
- **Review 聚焦**:PR 里只有本次任务的提交,Reviewer 不会看到与本次无关的 merge commit
#### 3.4.5 Hotfix 紧急修复流程
线上 `release` 分支出现紧急故障时,走独立的 hotfix 流程,避免等待 `feature` 中未完成功能的流转。
##### 3.4.5.1 流转路径
```mermaid
flowchart LR
A[release
线上版本] -->|基于 release 拉出| B[hotfix-xxx
修复分支]
B -->|PR #1 + Review| A
B -->|PR #2 + Review| D[feature
公共开发]
A -->|修复验证通过
打 hotfix tag| C[master
稳定归档]
```
**关键点**:同一个 `hotfix-xxx` 分支需要发起**两个 PR**,分别合入 `release` 和 `feature`,保证后续开发基于已修复的代码。两个 PR 都合并后再删除 hotfix 分支。
> 为什么不是 `release` 合入 `feature`?`release` 本身由大量 merge commit 构成,反向合入会污染 `feature` 的线性历史。用 hotfix 分支发两个 PR,两边拿到的都是同一个独立的修复提交。
##### 3.4.5.2 操作步骤
1. 开发者基于最新 `origin/release` 创建 `hotfix-xxx` 分支,**只做 bug 修复,不夹带其他改动**
2. 提 PR #1 至 `release`,管理员 Review 通过后合入,**暂不删除 hotfix 分支**
3. 管理员 / QA 验证修复并部署线上
4. 开发者基于同一 hotfix 分支提 PR #2 至 `feature`
5. 管理员 Review 通过后合入 `feature`,删除 hotfix 分支
6. 管理员将 `release` 合入 `master` 并打 hotfix tag
##### 3.4.5.3 冲突异常处理
正常情况下 PR #2 不应产生冲突——hotfix 职责单一,`feature` 上不应存在针对同一代码的并行修改。
**若产生冲突,说明 `feature` 上有人改动了 hotfix 修复的同一区域**。此时**不要自行解决冲突**,否则会出现 `release` 与 `feature` 修复版本不一致,后续发布可能导致 bug 复现。正确做法:暂停合并,由管理员召集 hotfix 作者与 `feature` 侧的改动者协调,确认最终版本后再继续。
#### 3.4.6 分支 / Tag 命名
##### 3.4.6.1 个人分支命名
格式:`feature-<姓名拼音>-<描述>-<日期>`
| 示例 | 含义 |
|------|------|
| `feature-lisi-dwd-trd-20260418` | 李四开发交易域 DWD 层,2026-04-18 建分支 |
| `feature-wangwu-dim-shp-20260419` | 王五开发商品维度,2026-04-19 建分支 |
| `feature-zhangsan-ods-usr-20260420` | 张三开发用户域 ODS 层,2026-04-20 建分支 |
##### 3.4.6.2 Hotfix 分支命名
格式:`hotfix-<姓名拼音>-<故障简述>-<日期>`
| 示例 | 含义 |
|------|------|
| `hotfix-zhangsan-ods-usr-duplicate-20260421` | 修复用户域 ODS 层数据重复 |
| `hotfix-lisi-ads-prd-null-20260422` | 修复产品宽表空值异常 |
| `hotfix-wangwu-dws-mkt-stale-20260423` | 修复营销宽表数据滞后 |
##### 3.4.6.3 Tag 命名
格式:`<描述>-<类型>-<日期>`
| 示例 | 含义 |
|------|------|
| `datax+spark-smoke-2026-04-20` | DataX + Spark 链路冒烟测试通过 |
| `dim-calendar-smoke-2026-04-18` | dim_calendar 维度表冒烟通过 |
| `ods-usr-duplicate-hotfix-2026-04-21` | ODS 用户域数据重复修复上线 |
**规则**:
- 阶段类型:`smoke`(冒烟通过)、`hotfix`(紧急修复)
- Tag 打在 `release` → `master` 的合并提交上,由管理员操作
#### 3.4.7 Commit 信息(Conventional Commits)
每条 commit message 必须以 `: <简短描述>` 开头,type 从下面固定列表里选一个;长说明放正文,与标题空一行。
| type | 用途 | 示例 |
|------|------|------|
| `feat` | 新功能 / 新 job / 新表 | `feat(raw/crm): 新增 ods_crm_ent_contact_di` |
| `fix` | Bug 修复 / 数据订正 | `fix(dwd/trd): 修复订单金额币种换算错误` |
| `docs` | 只改文档(含 `kb/`、注释、README) | `docs(kb): 更新 21-命名规范.md §3.9 示例` |
| `refactor` | 不改变外部行为的重构 | `refactor(dw_base): tendata → dw_base 模块改名` |
| `perf` | 性能优化 | `perf(dws): 拆 tmp 表减少 shuffle` |
| `test` | 只增/改测试 | `test(udf): 补 safe_cast_decimal 边界用例` |
| `chore` | 构建、依赖、CI、打包 | `chore: 精简 requirements.txt` |
| `style` | 空白 / 格式 / import 顺序(不改逻辑) | `style: 统一 SQL 字段缩进` |
| `build` | 打包脚本 / publish.sh / Dockerfile | `build: publish.sh 支持 -env 参数` |
| `ci` | DolphinScheduler / GitHub Actions 配置 | `ci: DS 工作流加质量校验节点` |
| `ops` | 运维类操作(补数、回刷、重跑、人工干预) | `ops(dwd/trd): 补 20260101-20260131 订单分区` |
| `revert` | 撤销某次提交 | `revert: feat(raw/crm): ...` |
**约定**:
1. **标题 ≤ 50 字符**,祈使句(「新增 xxx」而不是「新增了 xxx」)
2. **scope 可选但推荐**:数仓项目里 scope 常取 `{层}/{域}`(如 `dwd/trd`)或模块名(`dw_base`、`bin`、`kb`、`conf`)
3. **破坏性变更**:标题末尾加 `!`,正文以 `BREAKING CHANGE:` 开头说明迁移方式
- 例:`refactor(dw_base)!: 拆分 __init__.py` + `BREAKING CHANGE: 需要在调用处显式 import findspark`
4. **一次提交做一件事**:不要把"新增表 + 顺手修 bug + 改文档"塞一起,按 type 拆成 3 个 commit
5. **关联 TPAD / issue**:正文末尾加 `Refs: TPAD-1234` 或 `Closes: #42`
**反例**(会被 reject):
- `update` / `修改` / `提交` —— 没有 type,描述空洞
- `feat: xxx fix: yyy docs: zzz` —— 一条 commit 混多个 type
- `feat: 新增了一大堆表` —— scope 和具体目标不明
## 4. 测试规范
见 `90-重构路线.md` §6。核心要点:
- UDF 单测:纯 Python,不依赖 Spark
- DataX 配置生成单测
- Spark 集成测试:`local[*]` 模式
- 数据质量校验:行数、空值率、主键唯一性
## 5. manual/ 临时 SQL 规范
`manual/` 目录存放**一次性、非幂等**的 SQL 脚本,与 `jobs/` 的语义完全独立。详细目录约定见 `00-项目架构.md` §8,这里只列开发团队必须遵守的核心规则:
1. **严禁接入定时调度**:`manual/` 下任何脚本都不得被 DolphinScheduler 定时工作流引用;仅允许通过一次性工作流或命令行手动触发
2. **命名必须带日期前缀**:`{yyyymmdd}_{层}_{域}_{简述}.sql`,便于按时间排序与过期归档
3. **文件头必须声明元信息**:作者、日期、工单号、目的、执行状态(待执行 / 已执行 yyyy-mm-dd)
4. **`fix/` 和 `backfill/` 强制 Review**:涉及线上数据订正和历史回刷的脚本,合并前至少 1 人 Review
5. **与 `jobs/` 的边界**:
- 表结构变更 → **只在 `manual/ddl/` 写一个新的 ALTER 文件**,**不要回头改 `manual/ddl/{表名}.sql`**(migration 模式,详见 `00-项目架构.md` §9.6)。
- 历史数据回刷 → 优先复用 `jobs/` 原 SQL + 日期参数,`manual/backfill/` 只放调用包装
- 临时取数给业务方 → `manual/adhoc/`
- 脏数据订正 → `manual/fix/`,必须附 TPAD 工单号
## 6. 相关文档
- [命名规范](21-命名规范.md)
- [数仓分层与建模](20-数仓分层与建模.md)
- [重构路线](90-重构路线.md)