30-开发规范.md 19 KB

开发规范

本文档记录 poyee-data-warehouse 数仓数据开发流程与项目管理规范。 与 数仓命名规范.md90-重构路线.md 配合使用。

1. 项目管理规范(TPAD)

目前 TPAD 已创建数据需求工作流技术需求工作流

1.1 TPAD 任务建档要求

耗时 要求
2 小时以下临时需求 需要聊天记录做留档
2 小时以上 必须建立 TPAD 任务
8 小时以上 必须有方案或设计文档

2. 数据开发流程

2.1 数据开发全流程图

flowchart TD
    A[数据需求] --> B[需求沟通]
    B --> C[需求确认]
    C --> D{是否涉及敏感数据?}
    D -->|是| E[敏感数据使用审批]
    E --> F{是否审批通过?}
    F -->|否| Z[结束]
    F -->|是| G{是否需要新的采集方案?}
    D -->|否| G
    G -->|是| H[采集需求流程]
    H --> G
    G -->|否| I{所需指标维度<br/>是否已进仓?}
    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 IDE SQL 格式化配置

sql_style.xml(项目根目录)是 JetBrains 系 IDE(PyCharm / DataGrip / IntelliJ)的 SQL Code Style 导出文件,团队统一从此文件导入,避免每人格式化后 diff 里一堆空白噪音。

导入方式(PyCharm 为例):

  1. FileSettingsEditorCode StyleSQL
  2. 右上角齿轮图标 → Import SchemeIntelliJ IDEA code style XML
  3. 选择 sql_style.xml → 确认覆盖当前 scheme
  4. 应用后,Ctrl+Alt+L 触发格式化即按此风格

关键风格约定(所有 SQL 方言统一生效):

配置项 说明
KEYWORD_CASE 2 关键字强制大写(SELECT / FROM / WHERE / JOIN 等)
TYPE_CASE 2 类型名强制大写(STRING / BIGINT / DECIMAL 等)
SELECT_EL_WRAP / FROM_EL_WRAP 2 SELECT 字段 / FROM 表每个独占一行
CORTEGE_COMMA_1ST true 逗号前置, col 而不是 col,),减少增删行的 diff 噪音
SELECT_USE_AS_WORD 1 别名写 AS xxx,不省略
SELECT_ALIGN_AS false 不对齐 AS 关键字(见 §3.2.3)
FROM_PLACE_ON / FROM_INDENT_JOIN 10 / true JOIN 换行、相对 FROM 缩进一级
INSERT_INTO_NL 2 INSERT 强制换行(表名 / PARTITION / SELECT 各独占一行)
EXPR_CASE_END 1 CASE / END 换行
CONTINUATION_INDENT_SIZE 4 续行缩进 4 空格

XML 仅覆盖把握确定的 option。§3.2.2 的换行规则(WHERE/HAVING 条件、CASE 分支、CTE、UNION、OVER()、分号等)若 XML 未完整覆盖,以 §3.2.2 文字规则为准。开发者自行在 IDEA GUI 里配齐后 Export Scheme 覆盖 sql_style.xml

3.2.2 换行与缩进规则

XML 未穷举的格式约定,以本节为准。示例均以 Hive / Spark SQL 写法呈现。

WHERE / HAVING 条件换行

按逻辑单元分行,AND / OR 置于行首并缩进一级(与 SELECT 字段逗号前置一致):

WHERE dt = '${bizdate}'
  AND is_deleted = 0
  AND pay_amt > 0

CASE 表达式

WHEN / THEN / ELSE / END 各自独立换行,WHEN / ELSE 缩进一级于 CASEEND 回到 CASE 同列:

CASE
    WHEN status = 'paid'   THEN amt
    WHEN status = 'refund' THEN -amt
    ELSE 0
END

JOIN / ON

JOIN 关键字相对 FROM 缩进一级,ON 子句相对 JOIN 再缩进一级:

FROM fact_order o
    LEFT JOIN dim_user u
        ON o.user_id = u.user_id
    LEFT JOIN dim_shop s
        ON o.shop_id = s.shop_id

CTE(WITH 子句)

多个 CTE 之间空一行,提高可分辨度:

WITH base AS (
    SELECT ...
)

   , agg AS (
    SELECT ...
)

SELECT ...
FROM   agg

UNION / UNION ALL

UNION / UNION ALL 关键字前后各空一行:

SELECT ... FROM a

UNION ALL

SELECT ... FROM b

窗函数 OVER()

PARTITION BY / ORDER BY / 帧子句各独占一行:

ROW_NUMBER() OVER (
    PARTITION BY user_id
    ORDER BY pay_time DESC
)

INSERT OVERWRITE

表名 / PARTITION / SELECT 各独占一行(业界 Hive / Spark 主流写法):

INSERT OVERWRITE TABLE trd.dwd_trd_order_di
PARTITION (dt = '${bizdate}')
SELECT
    col_a
  , col_b
  , ...
FROM ...

分号

每条语句末尾分号独占一行(对应 NEW_LINE_AROUND_SEMICOLON=true)。

3.2.3 为什么不对齐 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 保护策略

masterreleasefeature 三个分支在仓库层面禁用远程推送,所有变更必须通过 PR(Pull Request / Merge Request)流转,仅允许管理员通过合并操作更新。

3.4.2 代码流转路径

flowchart LR
    A[feature-xxx<br/>个人分支] -->|PR + Review| B[feature<br/>公共开发]
    B -->|测试通过<br/>线上发布| C[release<br/>线上版本]
    C -->|里程碑/冒烟通过<br/>打 Tag| D[master<br/>稳定归档]

代码单向汇聚到 master,各合并节点由管理员操作,releasemaster 的合并提交需打 Tag。

3.4.3 开发者工作流

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 流转路径
flowchart LR
    A[release<br/>线上版本] -->|基于 release 拉出| B[hotfix-xxx<br/>修复分支]
    B -->|PR #1 + Review| A
    B -->|PR #2 + Review| D[feature<br/>公共开发]
    A -->|修复验证通过<br/>打 hotfix tag| C[master<br/>稳定归档]

关键点:同一个 hotfix-xxx 分支需要发起两个 PR,分别合入 releasefeature,保证后续开发基于已修复的代码。两个 PR 都合并后再删除 hotfix 分支。

为什么不是 release 合入 featurerelease 本身由大量 merge commit 构成,反向合入会污染 feature 的线性历史。用 hotfix 分支发两个 PR,两边拿到的都是同一个独立的修复提交。

3.4.5.2 操作步骤
  1. 开发者基于最新 origin/release 创建 hotfix-xxx 分支,只做 bug 修复,不夹带其他改动
  2. 提 PR #1release,管理员 Review 通过后合入,暂不删除 hotfix 分支
  3. 管理员 / QA 验证修复并部署线上
  4. 开发者基于同一 hotfix 分支提 PR #2feature
  5. 管理员 Review 通过后合入 feature,删除 hotfix 分支
  6. 管理员将 release 合入 master 并打 hotfix tag
3.4.5.3 冲突异常处理

正常情况下 PR #2 不应产生冲突——hotfix 职责单一,feature 上不应存在针对同一代码的并行修改。

若产生冲突,说明 feature 上有人改动了 hotfix 修复的同一区域。此时不要自行解决冲突,否则会出现 releasefeature 修复版本不一致,后续发布可能导致 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 打在 releasemaster 的合并提交上,由管理员操作

3.4.7 Commit 信息(Conventional Commits)

每条 commit message 必须以 <type>: <简短描述> 开头,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_style.xml 全局格式化
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_basebinkbconf
  3. 破坏性变更:标题末尾加 !,正文以 BREAKING CHANGE: 开头说明迁移方式
    • 例:refactor(dw_base)!: 拆分 __init__.py + BREAKING CHANGE: 需要在调用处显式 import findspark
  4. 一次提交做一件事:不要把"新增表 + 顺手修 bug + 改文档"塞一起,按 type 拆成 3 个 commit
  5. 关联 TPAD / issue:正文末尾加 Refs: TPAD-1234Closes: #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. 相关文档