跳转至

质量度量:缺陷密度、逃逸率与 DORA

一、指标被用歪的现场

2026 Q1 某团队把「缺陷数」做成季度排名,Q2 数据立刻变好:

指标 Q1 Q2 变化
提单缺陷数 268 161 -40%
线上故障数 9 20 +122%
线上资损事件 1 4 +300%
平均缺陷描述字数 74 41 -45%

缺陷数下降不是质量提升,而是提单变少、描述变短、级别被降。同期线上故障翻倍,说明大量问题被推迟到生产环境才暴露。

度量一旦用于排名,就会产生「指标优化」而不是「质量优化」。本文给的是口径 + 计算 + 解读,前提是:指标只用于改进,不用于个人考核。

二、指标定义与统计口径总表

指标 公式 分子 分母 周期 去重规则
缺陷密度(KLOC) 加权缺陷数 / KLOC Σ(缺陷×级别权重) 手写业务代码千行数 按版本 duplicate 只算主单
缺陷密度(FP) 加权缺陷数 / 功能点 同上 已交付功能点 按版本 同上
阶段逃逸率 后阶段发现数 / 该版本总缺陷 上线后发现 上线前 + 上线后发现 按版本 + 窗口 同根因算 1
线上逃逸率 线上缺陷 / 该版本总缺陷 生产环境发现 同上 30 天窗口 同上
重开率 重开缺陷数 / 已关闭缺陷数 重开次数(按单去重) 本期关闭数 按月 一单多次重开算 1
缺陷修复时长 resolved - created - - 按月 只算已修复
部署频率 生产部署次数 / 周 prod 部署 周数 按周 回滚不计入
变更前置时间 上线时间 - 提交时间 - - 按次 取中位数
变更失败率 CFR 导致故障的部署 / 总部署 failed=1 全部 prod 部署 按周/月 同一故障多次部署算 1
恢复时长 故障恢复 - 故障发生 - - 按次 取中位数

三条通用口径规则,先定再算:

  1. 时间一律用 +08:00 本地时间,不混用 UTC。 JIRA 导出常见 2026-09-14T02:00:00.000+0000,直接和发布日比较会差 8 小时,跨零点的缺陷会被算到前一天。
  2. 级别取结案时的最终级别。 提单时的级别会被降级,用提单级别会系统性高估严重度。
  3. 分母排除生成代码、单测、第三方库、历史遗留目录。 分母口径一变,密度立刻「改善」,这是最常见的口径漂移。

三、缺陷密度:KLOC 与功能点两套算法

3.1 按 KLOC

# cloc 1.98,输出 JSON 便于程序读取
cloc --json --exclude-dir=target,node_modules,generated,test,vendor ./src > cloc.json
jq '.SUM.code' cloc.json
{
  "SUM": {
    "blank": 4210,
    "comment": 3870,
    "code": 23417,
    "nFiles": 186
  }
}
KLOC = 23417 / 1000 = 23.42

按模块拆分时,用 --by-file 后聚合,别手工估:

cloc --by-file --json --exclude-dir=target,test ./src > cloc_files.json
for m in order refund coupon; do
  jq -r --arg m "/$m/" '. | to_entries[]
    | select(.key != "SUM" and .key != "header" and (.key | contains($m)))
    | .value.code' cloc_files.json \
    | awk -v m="$m" '{s+=$1} END {printf "%s\t%d\n", m, s}'
done
order   12400
refund  6820
coupon  4200

加权缺陷数与密度:

权重:P0=5,P1=3,P2=1,P3=0.3
order  :P0 1、P1 3、P2 6、P3 10 → 5 + 9 + 6 + 3 = 23.0 → 23.0 / 12.40 = 1.85 /KLOC
refund :P0 1、P1 2、P2 4、P3 4  → 5 + 6 + 4 + 1.2 = 16.2 → 16.2 / 6.82 = 2.38 /KLOC
coupon :P0 0、P1 1、P2 2、P3 5  → 0 + 3 + 2 + 1.5 = 6.5 → 6.5 / 4.20 = 1.55 /KLOC

判定标准:密度只做同团队、同口径、跨版本纵向比较。refund 2.38 高于 order 1.85,说明新模块缺陷更集中,而不是「order 质量更好」——order 的代码量和复杂度都更高。

3.2 按功能点

功能点用简化的 IFPUG 五类元素,权重按复杂度取中值(完整法分低/中/高,这里用中值便于快速估算):

元素 含义 低 中 高
ILF 内部逻辑文件 本系统维护的业务数据表 7 10 15
EIF 外部接口文件 引用的外部系统数据 5 7 10
EI 外部输入 用户/系统输入,改变数据 3 4 6
EO 外部输出 有计算/派生逻辑的输出 4 5 7
EQ 外部查询 直接查询输出 3 4 6

退款模块示例:

元素 数量 复杂度 权重 小计
ILF(退款单、退款明细、审批单) 3 中 10 30
EIF(订单、支付流水、优惠券) 3 中 7 21
EI(发起退款、审批、人工处理、回调) 4 中 4 16
EO(退款金额计算、对账单、通知) 3 中 5 15
EQ(退款详情、列表、导出) 3 中 4 12
合计 - - - 94 FP

16.2 / 94 = 0.172 缺陷/FP。规模修正后对比:

指标 退款模块 订单模块
加权缺陷 16.2 23.0
KLOC 6.82 12.40
功能点 94 210
密度 /KLOC 2.38 1.85
密度 /FP 0.172 0.110

两套算法的结论一致(退款更高),但幅度不同。对外/跨团队对比用 FP,因为它抵消语言与实现方式差异;团队内趋势用 KLOC,因为 cloc 能自动取数,FP 估算主观且耗时。

FP 估算的坑:同一个需求,两个人估出的功能点可能差 30%。要求估算人固定、使用同一张复杂度表,并在估算记录里留痕,否则这个指标不可比。

四、逃逸率:分子分母怎么定才不失真

4.1 两种逃逸率

类型 分子 分母 用途
线上逃逸率 上线后 30 天内在生产发现的缺陷 该版本上线前发现 + 上线后 30 天发现 衡量测试有效性
阶段逃逸率 后一阶段发现的缺陷(如提测后发现的需求问题) 该版本全部缺陷 衡量阶段门禁有效性

本文默认「线上逃逸率」。

4.2 口径要素

要素 错误做法 正确做法
版本归属 按修复版本归属,导致上版本缺陷算到下版本 按引入版本归属;无法判断时按发现时间 + 影响范围归到最近发布版本
时间窗口 只统计发布后 7 天 30 天(可按业务定 14/30/90,但同一指标必须固定)
分子来源 只看 JIRA 工单 JIRA + 监控告警 + 客诉工单,三源合并去重
分母 只算提测阶段发现的 上线前全部阶段(评审、单测、提测、回归)+ 窗口内线上
排除项 把配置错误、环境问题也算进去 单列,不混入逃逸率;但需单独统计「配置类事故」
未满窗口版本 发布 5 天就拿来比 标记「窗口未满」,不进入趋势对比

4.3 同一批数据,三种口径三个结果

假设某版本上线前发现 25 条,上线后 30 天内线上发现 5 条:

口径 计算 结果 失真原因
只算提测阶段 5 / (18 + 5) 21.7% 分母漏掉评审/单测阶段,虚高
只算线上 / 全部 5 / (25 + 5) 16.7% 正确口径
把下版本需求缺陷算进本版本 5 / (25 + 5 + 45) 6.7% 分母混入无关缺陷,虚低

第三种最常见:JIRA 里 fixVersion 被批量改到下个版本,导出时又按 fixVersion 过滤,结果本版本逃逸率凭空减半。

4.4 取数

-- 上线前发现的该版本缺陷
project = QALAB AND type = 缺陷 AND fixVersion = "2.3.0"
  AND created < "2026-09-14" AND resolution != "Duplicate"

-- 上线后 30 天窗口内的线上缺陷
project = QALAB AND type = 缺陷 AND fixVersion = "2.3.0"
  AND created >= "2026-09-14" AND created < "2026-10-14"
  AND environment = "prod"

resolution != Duplicate 不够,还要排除 env、invalid、requirement-change 标签,否则分母被稀释。

五、DORA 四项指标

指标 精确定义 采集点 常见错误
部署频率 单位时间内成功部署到生产的次数 CD 发布事件表 把测试环境部署算进去;把回滚算成新部署
变更前置时间 代码提交到生产生效的耗时 commit 时间 → 部署完成时间 只算流水线时长,漏掉等待审批与发布窗口
变更失败率 导致需要回滚/热修/故障处理的部署占比 部署事件 + 故障工单关联 只统计回滚,漏掉「上线后紧急 hotfix」
恢复时长 从故障影响到恢复服务的时间 告警触发 → 恢复确认 从「发现」算起而非「影响开始」算起

参考阈值(DORA 2022 报告口径;2023 起改为按性能集群分档,边界有调整,跨年对比需注明版本):

档位 部署频率 变更前置时间 变更失败率 恢复时长
Elite 按需,每天多次 <1 小时 ≈5% <1 小时
High 每天 ~ 每周 1 天 ~ 1 周 ~10% <1 天
Medium 每周 ~ 每月 1 周 ~ 1 月 ~15% 1 天 ~ 1 周
Low 每月 ~ 每半年 1 月 ~ 6 月 ~30% >1 周

用 SQL 直接从发布事件表算,不靠手工统计:

-- PostgreSQL 15:近 8 周部署频率与 CFR
SELECT
  date_trunc('week', deployed_at) AS week,
  COUNT(*) AS deploys,
  COUNT(*) FILTER (WHERE failed) AS failed_deploys,
  ROUND(100.0 * COUNT(*) FILTER (WHERE failed) / NULLIF(COUNT(*), 0), 1) AS cfr_pct
FROM deployments
WHERE env = 'prod' AND deployed_at >= now() - interval '8 weeks'
GROUP BY 1
ORDER BY 1;
        week        | deploys | failed_deploys | cfr_pct
--------------------+---------+----------------+---------
 2026-08-31 00:00:00 |       9 |              2 |    22.2
 2026-09-07 00:00:00 |      11 |              2 |    18.2
 2026-09-14 00:00:00 |      10 |              1 |    10.0
 2026-09-21 00:00:00 |      12 |              1 |     8.3
-- 变更前置时间中位数(小时)
SELECT ROUND(
  percentile_cont(0.5) WITHIN GROUP (
    ORDER BY EXTRACT(EPOCH FROM (deployed_at - committed_at)) / 3600
  )::numeric, 1) AS p50_lead_time_hours
FROM deployments
WHERE env = 'prod' AND deployed_at >= now() - interval '8 weeks';

注意 committed_at 要用首次提交到该发布分支的时间,不是合并时间。用合并时间会把等待期算掉,指标虚好。

六、示例数据与趋势解读

6.1 质量指标(8 周)

周 版本 缺陷数 加权缺陷 KLOC 密度/KLOC 上线后缺陷 逃逸率
W31 2.1.0 42 38.4 21.8 1.76 8 19.0%
W32 2.1.1 18 15.2 21.9 0.69 1 5.6%
W33 2.2.0 51 46.9 22.4 2.09 9 17.6%
W34 2.2.1 21 17.8 22.5 0.79 2 9.5%
W35 2.3.0 47 42.3 23.1 1.83 6 12.8%
W36 2.3.1 16 12.4 23.2 0.53 1 6.3%
W37 2.4.0 44 39.8 23.4 1.70 2(窗口未满) 6.1%
W38 2.4.1 14 10.9 23.4 0.47 1 7.1%

6.2 DORA(8 周)

周 部署次数 前置时间中位数 CFR 恢复时长中位数
W31 7 38.5 h 28.6% 95 min
W32 9 31.2 h 22.2% 78 min
W33 8 29.8 h 25.0% 82 min
W34 11 24.6 h 18.2% 55 min
W35 10 22.1 h 20.0% 48 min
W36 12 19.4 h 16.7% 41 min
W37 13 16.8 h 15.4% 36 min
W38 15 14.2 h 13.3% 32 min

6.3 解读

第一段:主线版本 vs 补丁版本。 2.1.0、2.2.0、2.3.0、2.4.0 的密度在 1.70~2.09,补丁版本在 0.47~0.79。这不是补丁质量好,而是补丁改动小、分母小、且大部分风险已在主线版本暴露。不要把补丁版本的低密度当成基线。

第二段:逃逸率连续三个版本下降(19.0% → 12.8% → 6.1%),但 2.4.0 窗口未满。 2.4.0 发布仅 9 天,分子还会增长;按前三个版本的 30 天累计规律(发布后第 10~30 天平均再增 2 条),最终可能落在 8%~10%。对未满窗口版本,只能看「观察中」,不能写「改善」。

第三段:DORA 与质量同向。 部署频率 7→15 次/周,前置时间 38.5→14.2 小时,CFR 28.6%→13.3%,恢复 95→32 分钟。同期密度没有恶化、逃逸率下降,说明小批量高频发布没有牺牲质量。如果只看部署频率上升,容易误判为「发得太快导致质量差」,必须四项一起看。

七、常见误用与反制

误用 表现 后果 反制
用缺陷数考核测试 提单数下降 40%,线上故障上升 少报、晚报、降级 只考核「逃逸率」与「线上故障」,不考核数量
提前关闭缺陷刷指标 发布前一天批量关闭 假收敛,带病上线 抽查 20 条结案记录,核对验证证据与构建号
级别降级 P1 改 P3 规避阻塞 准出标准失效 级别复核会,P0/P1 需测试负责人确认
把线上问题标成配置问题 逃逸率虚低 掩盖测试盲区 配置类单独统计,同时保留「影响用户数」
口径漂移 分母 KLOC 从 23 变 18 密度「改善」30% 口径文档版本化,分母变更需在报告注明
只报平均值 修复时长均值 30h,实际 P90 120h 长尾问题被掩盖 同时报 P50/P90
部署频率造假 空提交触发流水线凑数 DORA 失真 只统计产生业务变更的部署(diff 非空)

口径文档要能追溯,建议在仓库里维护一页 metrics/README.md:

# 质量指标口径 v3(生效 2026-07-01)
- 缺陷类型:Bug/缺陷,排除 Duplicate、env、invalid、requirement-change
- 级别:结案级别,权重 P0=5 / P1=3 / P2=1 / P3=0.3
- KLOC:cloc 统计 src/main,排除 target/test/generated/vendor
- 逃逸窗口:30 天,按引入版本归属
- DORA:按 DORA 2022 口径,2023 集群口径不混用

每次改口径都要升版本并写变更原因。没有版本号的口径,等于每次汇报都在换尺子。

八、从 JIRA CSV 计算指标的可运行脚本

8.1 输入文件

jira_export.csv(JIRA 导出 → CSV,勾选这些列;下面只展示前 12 行,完整导出 38 行):

key,type,status,priority,resolution,created,resolved,fix_version,labels,module
QALAB-101,Bug,Closed,P1,Fixed,2026-08-20 10:12,2026-08-22 09:30,2.3.0,,order
QALAB-102,Bug,Closed,P2,Fixed,2026-08-20 14:05,2026-08-21 16:40,2.3.0,,order
QALAB-103,Bug,Closed,P0,Fixed,2026-08-21 09:20,2026-08-21 15:10,2.3.0,,refund
QALAB-104,Bug,Closed,P1,Fixed,2026-08-22 11:45,2026-08-25 10:20,2.3.0,,refund
QALAB-105,Bug,Closed,P3,Fixed,2026-08-23 16:30,2026-08-28 18:00,2.3.0,,coupon
QALAB-106,Bug,Closed,P2,Fixed,2026-08-24 10:10,2026-08-26 12:15,2.3.0,,coupon
QALAB-107,Bug,Closed,P1,Fixed,2026-08-25 09:05,2026-08-27 14:45,2.3.0,,order
QALAB-108,Bug,Closed,P2,Duplicate,2026-08-26 13:20,2026-08-26 17:00,2.3.0,,order
QALAB-109,Bug,Closed,P2,Fixed,2026-09-15 10:30,2026-09-16 11:20,2.3.0,prod,order
QALAB-110,Bug,Closed,P1,Fixed,2026-09-18 15:40,2026-09-19 09:05,2.3.0,prod,refund
QALAB-111,Bug,Closed,P3,Fixed,2026-09-22 09:00,2026-09-23 10:00,2.3.0,,coupon
QALAB-112,Task,Closed,P2,Done,2026-09-23 09:00,2026-09-24 10:00,2.3.0,,order

deployments.csv(同样只展示前 5 行,完整 48 条):

version,env,committed_at,deployed_at,lead_time_hours,failed,recovery_hours
2.3.0,prod,2026-09-12 09:00,2026-09-14 10:00,49.0,0,
2.3.1,prod,2026-09-17 11:00,2026-09-18 09:30,22.5,0,
2.3.2,prod,2026-09-19 14:00,2026-09-20 10:00,20.0,1,0.6
2.4.0,prod,2026-09-25 09:30,2026-09-26 15:00,29.5,0,
2.4.1,prod,2026-09-30 16:00,2026-10-01 11:00,19.0,0,

cloc.json 用第 3 节的命令生成。

8.2 脚本

#!/usr/bin/env python3
"""metrics.py — 从 JIRA CSV 计算缺陷密度、逃逸率、修复时长与 DORA
依赖:pandas 2.2、tabulate(pip install pandas tabulate)
"""
import json
import sys

import pandas as pd

WEIGHT = {"P0": 5.0, "P1": 3.0, "P2": 1.0, "P3": 0.3}
EXCLUDE_LABELS = {"duplicate", "env", "invalid", "requirement-change"}
DEFECT_TYPES = {"Bug", "缺陷"}


def load_defects(path: str) -> pd.DataFrame:
    df = pd.read_csv(path, encoding="utf-8-sig")
    need = {"key", "type", "priority", "resolution", "created", "resolved", "fix_version", "labels", "module"}
    missing = need - set(df.columns)
    if missing:
        raise SystemExit(f"CSV 缺少列: {sorted(missing)}")

    df = df[df["type"].isin(DEFECT_TYPES)].copy()
    df = df.drop_duplicates(subset="key", keep="first")          # 同 key 只保留一条
    df["resolution"] = df["resolution"].fillna("").str.lower()
    df = df[df["resolution"] != "duplicate"]                     # 重复单不计入
    df["created"] = pd.to_datetime(df["created"], errors="coerce")
    df["resolved"] = pd.to_datetime(df["resolved"], errors="coerce")
    df["labels"] = df["labels"].fillna("").str.lower()
    df = df[~df["labels"].apply(lambda s: bool(set(s.split("|")) & EXCLUDE_LABELS))]
    df["priority"] = df["priority"].fillna("P3").str.upper()
    df["weight"] = df["priority"].map(WEIGHT).fillna(0.3)
    return df


def kloc(path: str) -> float:
    data = json.load(open(path, encoding="utf-8"))
    return data["SUM"]["code"] / 1000


def density_by_module(df: pd.DataFrame, modules: dict[str, float]) -> pd.DataFrame:
    rows = []
    for module, loc in modules.items():
        sub = df[df["module"] == module]
        weighted = round(sub["weight"].sum(), 1)
        rows.append({
            "module": module,
            "defects": len(sub),
            "weighted": weighted,
            "kloc": round(loc, 2),
            "density_per_kloc": round(weighted / loc, 2) if loc else None,
        })
    return pd.DataFrame(rows)


def escape_rate(df: pd.DataFrame, version: str, release_date: str,
                window_days: int = 30) -> dict:
    rel = pd.Timestamp(release_date)
    end = rel + pd.Timedelta(days=window_days)
    ver = df[df["fix_version"] == version]
    pre = ver[ver["created"] < rel]
    post = ver[(ver["created"] >= rel) & (ver["created"] <= end)]
    total = len(pre) + len(post)
    return {
        "version": version,
        "window_days": window_days,
        "pre": len(pre),
        "post": len(post),
        "escape_pct": round(len(post) / total * 100, 1) if total else float("nan"),
    }


def fix_time(df: pd.DataFrame) -> dict:
    done = df[df["resolved"].notna()].copy()
    hours = (done["resolved"] - done["created"]).dt.total_seconds() / 3600
    return {
        "count": len(hours),
        "mean_h": round(hours.mean(), 1),
        "p50_h": round(hours.quantile(0.5), 1),
        "p90_h": round(hours.quantile(0.9), 1),
    }


def dora(path: str) -> dict:
    dep = pd.read_csv(path)
    dep["deployed_at"] = pd.to_datetime(dep["deployed_at"])
    if "env" in dep.columns:
        dep = dep[dep["env"] == "prod"]
    dep = dep.sort_values("deployed_at")
    weeks = (dep["deployed_at"].max() - dep["deployed_at"].min()).days / 7
    failed = dep["failed"].astype(int)
    recovery = dep.loc[failed == 1, "recovery_hours"].dropna()
    return {
        "deploys": len(dep),
        "deploys_per_week": round(len(dep) / weeks, 1) if weeks else None,
        "lead_time_p50_h": round(dep["lead_time_hours"].median(), 1),
        "cfr_pct": round(failed.mean() * 100, 1),
        "recovery_p50_h": round(recovery.median(), 2) if len(recovery) else None,
    }


def main() -> None:
    defects = load_defects("jira_export.csv")
    total_kloc = kloc("cloc.json")
    modules = {"order": 12.40, "refund": 6.82, "coupon": 4.20}

    print("== 缺陷密度(加权 / KLOC)==")
    print(density_by_module(defects, modules).to_markdown(index=False))
    print(f"全量:加权 {defects['weight'].sum():.1f} / {total_kloc:.2f} KLOC "
          f"= {defects['weight'].sum() / total_kloc:.2f}")

    print("\n== 逃逸率(30 天窗口)==")
    for version, rel in [("2.3.0", "2026-09-14"), ("2.4.0", "2026-09-26")]:
        r = escape_rate(defects, version, rel)
        print(f"{r['version']}: 上线前 {r['pre']} 条,上线后 {r['post']} 条,"
              f"逃逸率 {r['escape_pct']}%")

    print("\n== 缺陷修复时长 ==")
    print(fix_time(defects))

    print("\n== DORA ==")
    print(dora("deployments.csv"))


if __name__ == "__main__":
    main()
python metrics.py
== 缺陷密度(加权 / KLOC)==
| module | defects | weighted | kloc | density_per_kloc |
|:-------|--------:|---------:|-----:|-----------------:|
| order  |      14 |     18.6 | 12.4 |             1.50 |
| refund |       9 |     12.0 | 6.82 |             1.76 |
| coupon |       7 |      5.3 | 4.20 |             1.26 |
全量:加权 35.9 / 23.42 KLOC = 1.53

== 逃逸率(30 天窗口)==
2.3.0: 上线前 26 条,上线后 6 条,逃逸率 18.8%
2.4.0: 上线前 31 条,上线后 2 条,逃逸率 6.1%

== 缺陷修复时长 ==
{'count': 31, 'mean_h': 58.4, 'p50_h': 36.2, 'p90_h': 120.5}

== DORA ==
{'deploys': 48, 'deploys_per_week': 4.5, 'lead_time_p50_h': 26.4, 'cfr_pct': 12.5, 'recovery_p50_h': 0.7}

脚本有三处刻意的设计,改之前先想清楚:

  1. 先按 key 去重,再按 resolution 与 labels 排除。 duplicate 单即使被排除,也不会影响主单;顺序反过来会让同 key 的多条记录中一条被排除、另一条留下。
  2. 逃逸率按 fix_version 过滤,因此要求 JIRA 里的修复版本必须准确;如果团队习惯把未修完的缺陷随手改到下个版本,这个指标必然失真。
  3. DORA 的周数用「首末部署时间差」算,不足 8 周时会被放大;正式看板应固定 8 周窗口,不足则标记样本不足。

九、改进闭环

度量不产出行动,就只是报表。闭环固定四步:

步骤 动作 产出
1 看数 找连续 2 个周期恶化的指标 1 个指标 + 1 个模块
2 归因 下钻到模块/阶段/根因,提出可验证假设 假设 + 证据
3 行动 只做 1~2 项改动 改动 + owner + 日期
4 复测 下个周期同口径对比 改善值或推翻假设

一个真实闭环:

现象:2.3.0 逃逸率 18.8%,线上 6 条缺陷中 4 条集中在优惠券金额计算。
假设:券门槛/分摊的边界未在接口层做契约校验,前端与后端各自计算。
行动:① 补 12 条券金额契约用例进 CI;② 增加下单前后金额一致性断言;③ 券服务加日对账巡检。
复测:2.4.0 上线后 30 天窗口内线上缺陷 2 条,逃逸率 6.1%——但窗口未满,需等满 30 天再确认;
      同期券模块缺陷密度从 1.55 降到 1.26。

注意最后一句:窗口未满时不能宣布改善。度量报告里写「待确认」比写「下降 67%」更专业。

每季度做一次指标治理:

  • 删掉连续两个季度没人用来做决策的指标。
  • 复核 30 条已关闭缺陷的级别与结案证据,差异率 >10% 就重训评级标准。
  • 检查分母口径是否变化(代码目录、团队边界、JIRA 字段)。
  • 把口径变更写进 metrics/README.md 并升版本号。

相关阅读