文档目录

7.8 配套代码:三问纪律与收益报告

对应小节:7.8 三问纪律与收益报告 三件事:① 三问检查清单;② 收益报告生成器;③ 否决记录管理。

一、三问检查清单(交互式)

#!/usr/bin/env bash
# tools/optimization-three-questions.sh
#
# 每项优化都必须回答的三个问题。
# 缺任何一问,这次改动的证据链就是断的。
set -uo pipefail

echo "═══════════════════════════════════════════════════════════════"
echo "优化的三问检查"
echo "═══════════════════════════════════════════════════════════════"
echo

FAIL=0

ask_required() {
  local question="$1"
  local hint="$2"
  echo "── $question"
  echo "   ($hint)"
  read -r -p "   已完成?[y/N] " ans
  if [[ "${ans,,}" != "y" ]]; then
    echo "   ❌ 未完成 —— 这一问不能跳过"
    FAIL=$((FAIL+1))
  else
    echo "   ✅"
  fi
  echo
}

echo "【第一问:提升多少?】"
echo
ask_required "有 before/after 数据吗?" \
  "必须是同一套脚本、同一环境、≥3 轮"
ask_required "报了中位数与百分位(不是平均值)吗?" \
  "至少 P50/P95/P99 + 错误率"
ask_required "做了统计检验吗?" \
  "Mann-Whitney U 或 Bootstrap 置信区间(第 5 章)"
ask_required "与噪声底线/MDD 对比了吗?" \
  "差异必须超过 MDD 才可信(第 5 章 5.7)"

echo "【第二问:代价是什么?】"
echo
ask_required "显式列出了代价吗?" \
  "复杂度/一致性/资源/风险 —— 代价不会自己消失"
ask_required "有降级方案吗?" \
  "新组件挂了会怎样?服务还能用吗?"

echo "【第三问:回归验证了吗?】"
echo
ask_required "功能正确性验证了吗?" \
  "单元测试 + 关键路径手工验证"
ask_required "性能回归验证了吗?" \
  "⚠️ 优化 A 可能让 B 变慢 —— 用同一套脚本复跑基线"
ask_required "长稳(浸泡)验证了吗?" \
  "引入新组件或改并发模型时必须做(第 3 章 3.5)"

echo "═══════════════════════════════════════════════════════════════"
if [ "$FAIL" -eq 0 ]; then
  echo "✅ 三问全部通过 —— 这次优化可以写进报告"
else
  echo "❌ 有 $FAIL 项未完成"
  echo
  echo "提醒:答不出第三问的优化,本质上是【技术债】而不是优化。"
  echo "      它会在此后的每一次故障排查中增加不确定性。"
fi
echo "═══════════════════════════════════════════════════════════════"

二、收益报告生成器

# tools/generate-optimization-report.py <optimizations.json> [--out report.md]
"""
从结构化数据生成优化收益报告。

输入格式:
{
  "title": "订单查询接口优化",
  "root_cause": "缺索引导致全表扫描(贡献占比 72%)",
  "experiments": {"before": "E03", "after": "E08"},
  "noise_floor_pct": 13.2,
  "mdd_pct": 26.4,
  "optimizations": [
    {
      "name": "给 status 加索引",
      "level": 2,
      "level_name": "② 算法与数据访问",
      "changes": "CREATE INDEX idx_orders_status_created ON orders(status, created_at DESC)",
      "rationale": "消除全表扫描,Rows Removed by Filter 从 900000 降到 200",
      "metrics": {
        "before": {"p50": 20.1, "p99": 210.4, "qps": 498, "error_rate": 0.0005},
        "after":  {"p50": 8.3,  "p99": 95.2,  "qps": 499, "error_rate": 0.0003}
      },
      "p_value": 0.008,
      "costs": [
        "索引占用额外磁盘(约 120 MB)",
        "写入时有轻微的索引维护开销(实测 < 2%)"
      ],
      "rollback": "DROP INDEX idx_orders_status_created",
      "regressions": {
        "functional": "单元测试全通过;手工验证了 5 个关键路径",
        "performance": "其他接口 P99 变化 < 3%(在噪声范围内)",
        "soak": "40 分钟浸泡,内存与连接数稳定"
      }
    }
  ],
  "rejected": [
    {
      "name": "加 Redis 缓存",
      "expected_benefit": "P50 -70%",
      "reason": "批量查询已解决主要问题;key 分散导致命中率预估仅 40%,P99 不会改善;引入一致性风险",
      "reconsider_when": "命中率能到 90% 以上,或数据量增长导致即使批量查询也变慢"
    }
  ],
  "not_covered": ["冷启动路径", "数据量增长 10 倍后的表现", "多副本部署"],
  "next_steps": ["监控 P99 一周,确认稳定", "把索引加入部署脚本"]
}
"""
import json
import sys
from datetime import datetime


def fmt_delta(before, after):
    if before == 0:
        return "n/a"
    return f"{(after - before) / before * 100:+.1f}%"


def generate(data):
    noise = data.get("noise_floor_pct", 0)
    mdd = data.get("mdd_pct", 0)

    lines = [
        f"# 优化收益报告:{data['title']}",
        "",
        f"> 生成时间:{datetime.now().isoformat(timespec='seconds')} | "
        f"关联实验:{' – '.join(data.get('experiments', {}).values())}",
        "",
        "---",
        "",
        "## 1. 问题与根因",
        "",
        data["root_cause"],
        "",
        "---",
        "",
    ]

    for i, opt in enumerate(data["optimizations"], 1):
        lines += [
            f"## 2.{i} 优化措施 {i}:{opt['name']}",
            "",
            f"- **金字塔层级**:{opt.get('level_name', opt.get('level', '?'))}",
            f"- **改动**:`{opt['changes']}`",
            f"- **原理**:{opt['rationale']}",
            "",
            "### 第一问:提升多少",
            "",
            "| 指标 | before | after | 变化 |",
            "| --- | --- | --- | --- |",
        ]

        b, a = opt["metrics"]["before"], opt["metrics"]["after"]
        for key, label in [("p50", "P50 (ms)"), ("p99", "P99 (ms)"),
                           ("qps", "QPS"), ("error_rate", "错误率")]:
            if key in b and key in a:
                bv, av = b[key], a[key]
                if key == "error_rate":
                    bv_s, av_s = f"{bv:.2%}", f"{av:.2%}"
                    delta = f"{(av - bv) * 100:+.2f}pp"
                elif key == "qps":
                    bv_s, av_s = f"{bv:.1f}", f"{av:.1f}"
                    delta = fmt_delta(bv, av)
                else:
                    bv_s, av_s = f"{bv:.1f}", f"{av:.1f}"
                    delta = fmt_delta(bv, av)
                lines.append(f"| {label} | {bv_s} | {av_s} | {delta} |")

        lines += ["", "**显著性判定**:", ""]
        lines.append(f"- 环境噪声底线:±{noise}%")
        lines.append(f"- 最小可检测差异(MDD):±{mdd}%")
        if "p_value" in opt:
            p = opt["p_value"]
            sig = "✅ 显著" if p < 0.05 else "❌ 不显著"
            lines.append(f"- 统计检验:Mann-Whitney U,p = {p}({sig})")

        # 判断可信度
        if "p99" in b and "p99" in a:
            delta_pct = (a["p99"] - b["p99"]) / b["p99"] * 100
            if abs(delta_pct) < noise:
                lines.append(f"- **结论**:⚠️ 变化 {delta_pct:+.1f}% 在噪声范围内,**无法确认优化有效**")
            elif abs(delta_pct) < mdd:
                lines.append(f"- **结论**:⚠️ 变化 {delta_pct:+.1f}% 超过噪声底线但小于 MDD,方向可信、幅度需验证")
            else:
                lines.append(f"- **结论**:✅ 变化 {delta_pct:+.1f}%,超过 MDD,**优化有效且可信**")

        lines += [
            "",
            "### 第二问:代价是什么",
            "",
        ]
        for c in opt.get("costs", []):
            lines.append(f"- {c}")
        if opt.get("rollback"):
            lines += ["", f"**回滚方式**:`{opt['rollback']}`"]

        lines += [
            "",
            "### 第三问:回归验证",
            "",
        ]
        reg = opt.get("regressions", {})
        lines.append(f"- [{'x' if reg.get('functional') else ' '}] **功能正确性**:{reg.get('functional', '未验证')}")
        lines.append(f"- [{'x' if reg.get('performance') else ' '}] **性能回归**:{reg.get('performance', '未验证')}")
        lines.append(f"- [{'x' if reg.get('soak') else ' '}] **长稳(浸泡)**:{reg.get('soak', '未验证')}")
        lines += ["", "---", ""]

    # 被否决的方案
    if data.get("rejected"):
        lines += [
            "## 3. 被否决的方案",
            "",
            "| 方案 | 预期收益 | 否决理由 | 重新评估的条件 |",
            "| --- | --- | --- | --- |",
        ]
        for r in data["rejected"]:
            lines.append(
                f"| {r['name']} | {r['expected_benefit']} | {r['reason']} | {r.get('reconsider_when', '—')} |")
        lines += ["", "---", ""]

    # 未覆盖场景
    lines += ["## 4. 未覆盖的场景", ""]
    for s in data.get("not_covered", []):
        lines.append(f"- {s}")
    lines += ["", "---", ""]

    # 后续
    lines += ["## 5. 后续建议", ""]
    for s in data.get("next_steps", []):
        lines.append(f"- {s}")
    lines.append("")

    return "\n".join(lines)


if __name__ == "__main__":
    if len(sys.argv) < 2:
        print(__doc__)
        sys.exit(1)

    data = json.loads(open(sys.argv[1]).read())
    report = generate(data)

    if "--out" in sys.argv:
        out = sys.argv[sys.argv.index("--out") + 1]
        open(out, "w", encoding="utf-8").write(report)
        print(f"✅ 报告已生成 → {out}")
    else:
        print(report)

三、否决记录管理

# tools/rejection-log.py
"""
管理「被否决的方案」——它们不是垃圾,而是有价值的决策记录。

用法:
    python3 tools/rejection-log.py add <EXP_ID> "<方案>" "<预期收益>" "<否决理由>" "<重新评估条件>"
    python3 tools/rejection-log.py list <EXP_ID>
    python3 tools/rejection-log.py review <EXP_ID>    # 检查是否有需要重新评估的
"""
import json
import pathlib
import sys
from datetime import datetime


def log_path(exp_id):
    d = pathlib.Path("docs/experiments") / exp_id
    d.mkdir(parents=True, exist_ok=True)
    return d / "rejections.json"


def load(exp_id):
    p = log_path(exp_id)
    return json.loads(p.read_text(encoding="utf-8")) if p.exists() else {"rejections": []}


def save(exp_id, data):
    log_path(exp_id).write_text(json.dumps(data, ensure_ascii=False, indent=2), encoding="utf-8")


def cmd_add(exp_id, name, benefit, reason, reconsider):
    data = load(exp_id)
    data["rejections"].append({
        "id": f"R{len(data['rejections']) + 1}",
        "name": name,
        "expected_benefit": benefit,
        "reason": reason,
        "reconsider_when": reconsider,
        "date": datetime.now().isoformat(timespec="seconds"),
    })
    save(exp_id, data)
    print(f"✅ 已记录否决:{name}")


def cmd_list(exp_id):
    data = load(exp_id)
    if not data["rejections"]:
        print("(无否决记录)")
        return
    print("═" * 88)
    print(f"被否决的方案:{exp_id}")
    print("═" * 88)
    print()
    for r in data["rejections"]:
        print(f"【{r['id']}】{r['name']} ({r['date']})")
        print(f"  预期收益  : {r['expected_benefit']}")
        print(f"  否决理由  : {r['reason']}")
        print(f"  重新评估条件: {r['reconsider_when']}")
        print()


def cmd_review(exp_id):
    """检查是否有需要重新评估的否决"""
    data = load(exp_id)
    print("═" * 78)
    print("否决记录复查")
    print("═" * 78)
    print()
    print("提醒:以下否决是【有条件的】——如果条件已经变化,应该重新评估。")
    print()
    for r in data["rejections"]:
        print(f"  {r['id']} {r['name']}")
        print(f"     → 当「{r['reconsider_when']}」时,重新评估")
        print()
    print("═" * 78)
    print("为什么要复查:")
    print("  ① 数据量可能增长(原来命中率低的缓存现在可能有效)")
    print("  ② 业务可能变化(原来不需要实时,现在需要)")
    print("  ③ 技术可能演进(新的序列化器、新的 GC)")
    print("  ④ 瓶颈可能转移(原来占 5% 的部分现在占 40%)")
    print("═" * 78)


if __name__ == "__main__":
    if len(sys.argv) < 3:
        print(__doc__)
        sys.exit(1)
    cmd, exp = sys.argv[1], sys.argv[2]
    if cmd == "add" and len(sys.argv) >= 7:
        cmd_add(exp, sys.argv[3], sys.argv[4], sys.argv[5], sys.argv[6])
    elif cmd == "list":
        cmd_list(exp)
    elif cmd == "review":
        cmd_review(exp)
    else:
        print(__doc__)

使用示例:

# 记录一个否决
python3 tools/rejection-log.py add E08-orders-opt \
  "加 Redis 缓存" \
  "P50 -70%" \
  "批量查询已解决主要问题;key 分散导致命中率预估仅 40%,P99 不会改善;引入一致性风险" \
  "命中率能到 90% 以上,或数据量增长导致批量查询也变慢"

# 查看
python3 tools/rejection-log.py list E08-orders-opt

# 定期复查(每季度)
python3 tools/rejection-log.py review E08-orders-opt

四、完整使用流程

#!/usr/bin/env bash
# tools/optimization-workflow.sh <EXP_ID>
#
# 优化工作的完整流程:三问 → 报告 → 否决记录
set -uo pipefail

EXP_ID="${1:?usage: optimization-workflow.sh <exp_id>}"
DIR="docs/experiments/${EXP_ID}"
mkdir -p "$DIR"

echo "═══════════════════════════════════════════════════════════════"
echo "优化工作流程:$EXP_ID"
echo "═══════════════════════════════════════════════════════════════"
echo

echo "【步骤 1】三问检查"
tools/optimization-three-questions.sh
echo

echo "【步骤 2】记录被否决的方案"
echo "  即使在实施优化时,也要记录【没有采纳】的方案:"
echo "    python3 tools/rejection-log.py add $EXP_ID \"方案\" \"预期收益\" \"否决理由\" \"重新评估条件\""
echo

echo "【步骤 3】生成收益报告"
echo "  1. 创建 $DIR/optimizations.json(参考 tools/generate-optimization-report.py 的格式)"
echo "  2. 运行:python3 tools/generate-optimization-report.py $DIR/optimizations.json --out $DIR/REPORT.md"
echo

echo "【步骤 4】收尾检查"
echo "  □ 每项优化都有三问"
echo "  □ 至少记录了一项被否决的方案"
echo "  □ 报告里有「未覆盖的场景」"
echo "  □ 有效的优化已固化(加了监控或门禁)"
echo

echo "═══════════════════════════════════════════════════════════════"

五、动手改造

改动 观察什么
用 generate-optimization-report.py 生成一份报告 看自动判定的「可信/不可信」是否符合你的直觉
故意填一个小于噪声底线的改进(如 5%) 报告会标记「无法确认优化有效」
用 rejection-log.py 记录 Lab 7 的否决 体会「把否决变成资产」
每季度跑一次 rejection-log.py review 检查是否有否决应该翻案
用三问脚本检查你过去的优化 大概率会发现「第三问(回归验证)经常被跳过」

六、这段代码的局限

  • 自动判定的「可信度」依赖你提供的噪声底线:如果 noise_floor_pct 填错,判定也会错。
  • 报告生成器只做格式整理:内容质量取决于你提供的数据与分析。
  • rejection-log.py 的复查是提醒性的:它无法自动判断「条件是否已经变化」——需要人来看。
  • 三问脚本是自评:它可以被敷衍(全部答 y)——它的价值在于提醒,而不是强制。
  • 真正的保证来自流程:把三问做成报告模板的必填项、把否决记录做成评审要求,比靠自觉更有效。