lead · Stage 5

コードレビューを協働品質システムとして運営する

変更リスクからpriorityを導き、evidenceとactionableな提案でauthor fixを支援し、別reviewerの独立再評価までを一つの品質ループとして設計する。

学習時間
300分
難易度
advanced
更新日
2026-07-30
到達証拠
成果物・説明・判断根拠・転用

到達目標

  1. 変更のrisk kindと観測証拠からreview findingのpriorityを再現可能に分類できる

    • 優先度、根拠、修正可能性を備えたレビューと改善後の再評価
    • comment量やcommand successを品質と誤認しないreview判断
  2. 人格ではなく変更へ向けたevidenceとactionable fixでauthorの修正を支援できる

    • 優先度、根拠、修正可能性を備えたレビューと改善後の再評価
    • reviewをgatekeepingではなく別contributorの判断能力を増やす仕組みとして説明する5分発表
  3. 修正済みsnapshotを別reviewerが独立再評価し、system outcomeまで検証できる

    • 優先度、根拠、修正可能性を備えたレビューと改善後の再評価
    • risk kindだけを変えた同じsample changeのreviewと独立再評価

能力の進行

  1. recognize

    blocking risk、改善提案、質問、称賛を区別し、priorityの根拠を識別できる

    証拠: 優先度、根拠、修正可能性を備えたレビューと改善後の再評価

  2. explain

    evidenceとactionable fixがauthorの学習と変更速度を両立する理由を説明できる

    証拠: reviewをgatekeepingではなく別contributorの判断能力を増やす仕組みとして説明する5分発表

  3. apply

    sample changeからfinding、priority、evidence、修正案を作りauthor fixへ結べる

    証拠: 優先度、根拠、修正可能性を備えたレビューと改善後の再評価

  4. diagnose

    好みの強制、priority欠落、証拠のない断定、同一reviewerだけの再確認を反証できる

    証拠: comment量やcommand successを品質と誤認しないreview判断

  5. lead

    別contributorが同じrisk判断と独立再評価を再現できるreview境界を運営できる

    証拠: risk kindだけを変えた同じsample changeのreviewと独立再評価

なぜ重要か

コードレビューの目的はcomment数を増やすことでも、reviewerの好みへauthorを従わせることでもない。変更がsystemへ与えるriskを早く見つけ、根拠を共有し、author fixと学習を助け、改善後の結果を確かめる協働品質システムを作ることである。

The Standard of Code Reviewは、完全な変更を待つのではなくcode healthを継続的に改善する判断を示す。How to write code review commentsとuseful code review研究は、何が問題かだけでなく、なぜ重要か、どの証拠があり、何をすれば解消できるかを伝える必要性を補う。SWEBOK Guide Version 4.0aのreview観点も、検証可能な品質活動として位置付ける。

良いreviewはauthor一人を通過させるだけでなく、次に同じ領域を扱う人がpriorityとevidenceを再現できるようにして、enable another contributorを実現する。

メンタルモデル

reviewを、sample changeからpriority / evidence / actionable commentを作る初回review、author fix、別reviewerによるindependent re-evaluationのループとして捉える。priorityは声の強さではなく、対象riskと影響から導く。

初回reviewerと再評価者を分けるのは儀式ではない。最初の仮説へ引きずられる可能性を下げ、修正後snapshotから証拠を作り直すためである。review budgetは重要だが、時間切れをapprovalへ変換してはならない。

sample changeから共同で品質を改善し独立再評価するreview loop

author fix後に別reviewerがどのprobeを再実行し、blocking findingの解消を判断するか。

  • Scope
    changeとrisk kind、review budgetを固定する。
  • Initial review
    priority、evidence、actionable fixを同じ入力から導く。
  • Author fix
    指摘IDに対応するpatchをactual artifactへ適用し、patch IDを残す。
  • Independent re-evaluation
    初回とは別のreviewerがprobeを再実行し、actualとexpectedからblocking findingとsystem outcomeを確認する。
  • Enablement
    判断根拠を残し、他のcontributorが再利用できるようにする。
  • Review evidence ready
    別reviewerのprobe、blocking finding解消、system outcome、再利用可能な判断根拠を一つの完了証拠として確定する。
  • Scope → Initial review: 次の証拠を得る
  • Initial review → Author fix: 次の証拠を得る
  • Author fix → Independent re-evaluation: 次の証拠を得る
  • Independent re-evaluation → Enablement: 次の証拠を得る
  • Independent re-evaluation → Author fix: blocking findingが残れば再修正する
  • Enablement → Scope: 次のchangeでriskを再評価する
  • Enablement → Review evidence ready: 独立再評価とsystem outcomeの証拠を確定する
終了状態
Review evidence ready
回復状態
Author fix

riskとfinding IDからpatchを追跡し、feedback cycleを保ちながら独立再評価の証拠をterminalへ確定できる。

動く例で考える

correctness riskからauthorization riskへのtransfer

前提
lesson-defined synthetic sample changeを使う。実repository、実利用者、production authorizationを観測したfixtureではない。
入力
2件のchange artifact、2件のfinding、2件のpatch、2件のprobe、30分のreview budget、correctnessまたはauthorizationというrisk kindを与える。
操作
初回reviewをpriority順へ並べ、author patchを別artifact snapshotへ適用する。さらに別reviewerがprobe checkerを再実行し、actualとexpectedを比較する。transferでもreview、fix、独立再評価の全cycleを繰り返す。
観測
baselineのhighest priorityはimportant、risk kindだけをauthorizationへ変えたtransferではcriticalになる。両cycleでpatch適用後のprobeはactualとexpectedが一致し、blocking findingは0件になる。
結論
review command successだけではsystem outcomeを証明できない。入力由来のprobe、actual artifactへのpatch、actual/expected evidence、独立reviewerのapprovalが揃って初めてreview cycleを完了できる。
python3.13 - <<'PY'
import json

HARNESS = "collaborative_code_review_lab_v1"
PRIORITY_ORDER = ("critical", "important", "minor")
CHANGE_ORDER = ("change-correctness", "change-authorization")
FINDING_ORDER = ("finding-correctness", "finding-authorization")
PROBE_ORDER = ("probe-negative-quantity", "probe-override-scope")
PATCH_ORDER = ("patch-negative-quantity", "patch-override-scope")
BASELINE_REVIEW_CONTEXT = {
    "risk_kind": "correctness",
    "review_budget_minutes": 30,
}
TRANSFER_REVIEW_CONTEXT = {
    "risk_kind": "authorization",
    "review_budget_minutes": 30,
}
SAMPLE_CHANGE = {
    "change_set_id": "checkout-policy-v1",
    "changes": [
        {
            "change_id": "change-correctness",
            "component": "cart-total",
            "risk_kind": "correctness",
            "observed_evidence": "negative quantity changes total below zero",
            "expected_outcome_evidence": "negative quantity is rejected",
            "fixed": False,
        },
        {
            "change_id": "change-authorization",
            "component": "discount-policy",
            "risk_kind": "authorization",
            "observed_evidence": "caller scope is not checked before override",
            "expected_outcome_evidence": "override without scope is denied",
            "fixed": False,
        },
    ],
}
FINDING_SPECS = [
    {
        "finding_id": "finding-correctness",
        "change_id": "change-correctness",
        "actionable_fix": "reject quantity below zero before total calculation",
    },
    {
        "finding_id": "finding-authorization",
        "change_id": "change-authorization",
        "actionable_fix": "require discount override scope before mutation",
    },
]
CHANGE_PROBES = [
    {
        "probe_id": "probe-negative-quantity",
        "change_id": "change-correctness",
        "probe_input": {
            "request_kind": "negative-quantity",
            "value": -1,
        },
        "expected": "rejected",
    },
    {
        "probe_id": "probe-override-scope",
        "change_id": "change-authorization",
        "probe_input": {
            "request_kind": "override-without-scope",
            "value": 0,
        },
        "expected": "denied",
    },
]
AUTHOR_PATCHES = [
    {
        "patch_id": "patch-negative-quantity",
        "change_id": "change-correctness",
        "field": "fixed",
        "value": True,
    },
    {
        "patch_id": "patch-override-scope",
        "change_id": "change-authorization",
        "field": "fixed",
        "value": True,
    },
]
COMMAND_INPUT = {"exit_code": 0}

def is_nonempty_text(value):
    return type(value) is str and bool(value)

def context_difference(baseline, transferred):
    return [
        field
        for field in sorted(set(baseline) | set(transferred))
        if (
            field not in baseline
            or field not in transferred
            or baseline[field] != transferred[field]
        )
    ]

def validate_review_contexts():
    allowed_fields = {"risk_kind", "review_budget_minutes"}
    for context in (
        BASELINE_REVIEW_CONTEXT,
        TRANSFER_REVIEW_CONTEXT,
    ):
        if (
            type(context) is not dict
            or set(context) != allowed_fields
            or type(context["risk_kind"]) is not str
            or context["risk_kind"]
            not in {"correctness", "authorization"}
            or type(context["review_budget_minutes"]) is not int
            or context["review_budget_minutes"] <= 0
        ):
            raise AssertionError(
                "review-transfer-invariant: invalid review context schema"
            )
    if context_difference(
        BASELINE_REVIEW_CONTEXT,
        TRANSFER_REVIEW_CONTEXT,
    ) != ["risk_kind"]:
        raise AssertionError(
            "review-transfer-invariant: transfer changed wrong field"
        )

def validate_sample_change():
    change_fields = {
        "change_id",
        "component",
        "risk_kind",
        "observed_evidence",
        "expected_outcome_evidence",
        "fixed",
    }
    valid = (
        type(SAMPLE_CHANGE) is dict
        and set(SAMPLE_CHANGE) == {"change_set_id", "changes"}
        and is_nonempty_text(SAMPLE_CHANGE["change_set_id"])
        and type(SAMPLE_CHANGE["changes"]) is list
        and bool(SAMPLE_CHANGE["changes"])
        and all(
            type(change) is dict
            and set(change) == change_fields
            and is_nonempty_text(change["change_id"])
            and is_nonempty_text(change["component"])
            and change["risk_kind"]
            in {"correctness", "authorization"}
            and is_nonempty_text(change["observed_evidence"])
            and is_nonempty_text(change["expected_outcome_evidence"])
            and type(change["fixed"]) is bool
            and change["fixed"] is False
            for change in SAMPLE_CHANGE["changes"]
        )
    )
    change_ids = (
        [change["change_id"] for change in SAMPLE_CHANGE["changes"]]
        if valid
        else []
    )
    if (
        not valid
        or tuple(change_ids) != CHANGE_ORDER
        or len(set(change_ids)) != len(change_ids)
    ):
        raise AssertionError(
            "review-input-invariant: invalid sample change schema"
        )

def validate_finding_specs():
    valid = (
        type(FINDING_SPECS) is list
        and bool(FINDING_SPECS)
        and all(
            type(finding) is dict
            and set(finding)
            == {"finding_id", "change_id", "actionable_fix"}
            and is_nonempty_text(finding["finding_id"])
            and finding["change_id"] in CHANGE_ORDER
            and is_nonempty_text(finding["actionable_fix"])
            for finding in FINDING_SPECS
        )
    )
    finding_ids = (
        [finding["finding_id"] for finding in FINDING_SPECS]
        if valid
        else []
    )
    referenced_changes = (
        [finding["change_id"] for finding in FINDING_SPECS]
        if valid
        else []
    )
    if (
        not valid
        or tuple(finding_ids) != FINDING_ORDER
        or len(set(finding_ids)) != len(finding_ids)
        or len(set(referenced_changes)) != len(referenced_changes)
    ):
        raise AssertionError(
            "review-input-invariant: invalid finding schema"
        )

def validate_change_probes():
    probe_fields = {
        "probe_id",
        "change_id",
        "probe_input",
        "expected",
    }
    valid = (
        type(CHANGE_PROBES) is list
        and bool(CHANGE_PROBES)
        and all(
            type(probe) is dict
            and set(probe) == probe_fields
            and is_nonempty_text(probe["probe_id"])
            and probe["change_id"] in CHANGE_ORDER
            and type(probe["probe_input"]) is dict
            and set(probe["probe_input"])
            == {"request_kind", "value"}
            and probe["probe_input"]["request_kind"]
            in {"negative-quantity", "override-without-scope"}
            and type(probe["probe_input"]["value"]) is int
            and probe["expected"] in {"rejected", "denied"}
            for probe in CHANGE_PROBES
        )
    )
    probe_ids = (
        [probe["probe_id"] for probe in CHANGE_PROBES]
        if valid
        else []
    )
    referenced_changes = (
        [probe["change_id"] for probe in CHANGE_PROBES]
        if valid
        else []
    )
    if (
        not valid
        or tuple(probe_ids) != PROBE_ORDER
        or len(set(probe_ids)) != len(probe_ids)
        or tuple(referenced_changes) != CHANGE_ORDER
    ):
        raise AssertionError(
            "review-input-invariant: invalid change probe schema"
        )

def validate_author_patches():
    valid = (
        type(AUTHOR_PATCHES) is list
        and bool(AUTHOR_PATCHES)
        and all(
            type(patch) is dict
            and set(patch)
            == {"patch_id", "change_id", "field", "value"}
            and is_nonempty_text(patch["patch_id"])
            and patch["change_id"] in CHANGE_ORDER
            and patch["field"] == "fixed"
            and patch["value"] is True
            for patch in AUTHOR_PATCHES
        )
    )
    patch_ids = (
        [patch["patch_id"] for patch in AUTHOR_PATCHES]
        if valid
        else []
    )
    referenced_changes = (
        [patch["change_id"] for patch in AUTHOR_PATCHES]
        if valid
        else []
    )
    if (
        not valid
        or tuple(patch_ids) != PATCH_ORDER
        or len(set(patch_ids)) != len(patch_ids)
        or tuple(referenced_changes) != CHANGE_ORDER
    ):
        raise AssertionError(
            "review-input-invariant: invalid author patch schema"
        )

def validate_all_inputs():
    if PRIORITY_ORDER != ("critical", "important", "minor"):
        raise AssertionError(
            "review-priority-invariant: invalid canonical priority order"
        )
    validate_review_contexts()
    validate_sample_change()
    validate_finding_specs()
    validate_change_probes()
    validate_author_patches()
    if (
        type(COMMAND_INPUT) is not dict
        or set(COMMAND_INPUT) != {"exit_code"}
        or type(COMMAND_INPUT["exit_code"]) is not int
    ):
        raise AssertionError(
            "review-input-invariant: invalid command input"
        )

def independent_snapshot(value):
    # JSON値から独立snapshotを作り、author fixがbaseline入力を
    # 書き換えてfalse greenになることを防ぐ。
    return json.loads(json.dumps(value, ensure_ascii=False))

def materialize_findings(sample_change):
    changes = {
        change["change_id"]: change
        for change in sample_change["changes"]
    }
    return [
        {
            "finding_id": spec["finding_id"],
            "change_id": spec["change_id"],
            "risk_kind": changes[spec["change_id"]]["risk_kind"],
            "evidence": changes[spec["change_id"]]["observed_evidence"],
            "actionable_fix": spec["actionable_fix"],
        }
        for spec in FINDING_SPECS
    ]

def classify_priority(finding, context):
    if (
        finding["risk_kind"] == "authorization"
        and context["risk_kind"] == "authorization"
    ):
        return "critical"
    if finding["risk_kind"] == context["risk_kind"]:
        return "important"
    return "minor"

def build_review(findings, context):
    review = []
    for finding in findings:
        priority = classify_priority(finding, context)
        review.append(
            {
                "finding_id": finding["finding_id"],
                "change_id": finding["change_id"],
                "priority": priority,
                "priority_rank": PRIORITY_ORDER.index(priority),
                "evidence": finding["evidence"],
                "actionable_fix": finding["actionable_fix"],
            }
        )
    return sorted(
        review,
        key=lambda item: (
            item["priority_rank"],
            item["finding_id"],
        ),
    )

def apply_author_fix(sample_change, findings):
    fixed_snapshot = independent_snapshot(sample_change)
    finding_change_ids = {
        finding["change_id"]
        for finding in findings
    }
    applied_patch_ids = []
    changes = {
        change["change_id"]: change
        for change in fixed_snapshot["changes"]
    }
    for patch in AUTHOR_PATCHES:
        if patch["change_id"] in finding_change_ids:
            changes[patch["change_id"]][patch["field"]] = patch[
                "value"
            ]
            applied_patch_ids.append(patch["patch_id"])
    if tuple(applied_patch_ids) != PATCH_ORDER:
        raise AssertionError(
            "review-reevaluation-invariant: patches not fully applied"
        )
    return fixed_snapshot, applied_patch_ids

def actual_outcome_evidence(probe, actual):
    request_kind = probe["probe_input"]["request_kind"]
    if request_kind == "negative-quantity":
        return "negative quantity is " + actual
    return "override without scope is " + actual

def run_change_probes(artifact_snapshot):
    changes = {
        change["change_id"]: change
        for change in artifact_snapshot["changes"]
    }
    observations = []
    for probe in CHANGE_PROBES:
        change = changes[probe["change_id"]]
        request_kind = probe["probe_input"]["request_kind"]
        if request_kind == "negative-quantity":
            actual = "rejected" if change["fixed"] else "accepted"
        else:
            actual = "denied" if change["fixed"] else "allowed"
        derived_evidence = actual_outcome_evidence(probe, actual)
        observations.append(
            {
                "probe_id": probe["probe_id"],
                "change_id": probe["change_id"],
                "probe_input": independent_snapshot(
                    probe["probe_input"]
                ),
                "actual": actual,
                "expected": probe["expected"],
                "actual_outcome_evidence": derived_evidence,
                "expected_outcome_evidence": change[
                    "expected_outcome_evidence"
                ],
                "passed": (
                    actual == probe["expected"]
                    and derived_evidence
                    == change["expected_outcome_evidence"]
                ),
            }
        )
    return observations

def independently_reevaluate(
    fixed_snapshot,
    findings,
    initial_reviewer,
    independent_reviewer,
):
    if independent_reviewer == initial_reviewer:
        raise AssertionError(
            "review-reevaluation-invariant: reviewer is not independent"
        )
    finding_by_change = {
        finding["change_id"]: finding["finding_id"]
        for finding in findings
    }
    outcome_evidence = run_change_probes(fixed_snapshot)
    failed_outcomes = [
        evidence
        for evidence in outcome_evidence
        if not evidence["passed"]
    ]
    blocking_findings = [
        finding_by_change[evidence["change_id"]]
        for evidence in failed_outcomes
    ]
    resolved_findings = [
        finding["finding_id"]
        for finding in findings
        if finding["finding_id"] not in blocking_findings
    ]
    # failed outcomeが0件であることをall(empty)=Trueとして評価する。
    # bool(empty)=Falseとの差がcausal bypassを検出可能にする。
    system_outcome_verified = all(
        [not failed_outcome for failed_outcome in failed_outcomes]
    )
    if (
        set(resolved_findings)
        != {finding["finding_id"] for finding in findings}
        or blocking_findings
        or not system_outcome_verified
    ):
        raise AssertionError(
            "review-reevaluation-invariant: fix outcome not verified"
        )
    return {
        "reviewer": independent_reviewer,
        "resolved_findings": resolved_findings,
        "blocking_findings": blocking_findings,
        "approval": (
            "approved"
            if not blocking_findings
            else "changes-requested"
        ),
        "system_outcome_verified": system_outcome_verified,
        "outcome_evidence": outcome_evidence,
    }

def execute_review_cycle(
    sample_change,
    context,
    initial_reviewer,
    author,
    independent_reviewer,
):
    artifact_snapshot = independent_snapshot(sample_change)
    initial_probe_results = run_change_probes(artifact_snapshot)
    findings = materialize_findings(artifact_snapshot)
    initial_review = build_review(findings, context)
    fixed_snapshot, applied_patch_ids = apply_author_fix(
        artifact_snapshot,
        initial_review,
    )
    re_evaluation = independently_reevaluate(
        fixed_snapshot,
        initial_review,
        initial_reviewer,
        independent_reviewer,
    )
    author_fix = {
        "author": author,
        "applied": bool(applied_patch_ids)
        and all(
            change["fixed"] is True
            for change in fixed_snapshot["changes"]
        ),
        "applied_patch_ids": applied_patch_ids,
        "resolved_finding_ids": [
            finding["finding_id"]
            for finding in initial_review
        ],
        "fixed_snapshot": fixed_snapshot,
    }
    return {
        "sample_change": artifact_snapshot,
        "initial_probe_results": initial_probe_results,
        "initial_reviewer": initial_reviewer,
        "initial_review": initial_review,
        "author_fix": author_fix,
        "independent_re_evaluation": re_evaluation,
    }

def main():
    validate_all_inputs()
    baseline_context = independent_snapshot(BASELINE_REVIEW_CONTEXT)
    transferred_context = independent_snapshot(
        TRANSFER_REVIEW_CONTEXT
    )
    baseline_change = independent_snapshot(SAMPLE_CHANGE)
    transferred_change = independent_snapshot(SAMPLE_CHANGE)
    baseline_cycle = execute_review_cycle(
        baseline_change,
        baseline_context,
        "reviewer-alpha",
        "author-charlie",
        "reviewer-bravo",
    )
    transferred_cycle = execute_review_cycle(
        transferred_change,
        transferred_context,
        "reviewer-delta",
        "author-charlie",
        "reviewer-echo",
    )
    initial_review = baseline_cycle["initial_review"]
    transferred_review = transferred_cycle["initial_review"]
    if (
        initial_review[0]["priority"] != "important"
        or transferred_review[0]["priority"] != "critical"
    ):
        raise AssertionError(
            "review-priority-invariant: priority not input-derived"
        )
    if baseline_change != transferred_change:
        raise AssertionError(
            "review-transfer-invariant: sample snapshots drifted"
        )
    changed_fields = context_difference(
        baseline_context,
        transferred_context,
    )
    re_evaluation = baseline_cycle["independent_re_evaluation"]
    contributor_enablement = {
        "other_contributor": "reviewer-bravo",
        "review_evidence": re_evaluation["outcome_evidence"],
        "enabled_action": (
            "priorityと修正証拠から独立にreviewを再実行する"
        ),
    }
    report = {
        "harness": HARNESS,
        "review_cycle": baseline_cycle,
        "risk_kind_transfer": {
            "changed_fields": changed_fields,
            "same_sample_change": baseline_change
            == transferred_change,
            "baseline_context": baseline_context,
            "transferred_context": transferred_context,
            "baseline_highest_priority": initial_review[0][
                "priority"
            ],
            "transferred_highest_priority": transferred_review[0][
                "priority"
            ],
            "transferred_review": transferred_review,
            "transferred_review_cycle": transferred_cycle,
        },
        "contributor_enablement": contributor_enablement,
        "input_derived_evidence": {
            "initial": [
                finding["evidence"]
                for finding in initial_review
            ],
            "after_fix": re_evaluation["outcome_evidence"],
        },
        "fixture_metadata": {
            "kind": "synthetic",
            "provenance": "lesson-defined collaborative review fixture",
            "limitations": (
                "実repository、production利用、権限境界を観測しない"
            ),
            "synthetic_or_observed_explicit": True,
        },
        "runtime_bound": {
            "records": (
                len(SAMPLE_CHANGE["changes"])
                + len(CHANGE_PROBES)
            ) * 2,
            "subprocesses": 0,
            "maximum_iterations": max(
                len(FINDING_SPECS),
                len(CHANGE_PROBES),
                len(AUTHOR_PATCHES),
            ),
        },
        "external_network_used": False,
        "command_success_distinction": {
            "command_completed": COMMAND_INPUT["exit_code"] == 0,
            "system_outcome_checked": re_evaluation[
                "system_outcome_verified"
            ],
            "command_success_equals_system_outcome": False,
            "outcome_evidence": re_evaluation["outcome_evidence"],
        },
        "mastery_evidence": {
            "lab_steps": [
                {"step": 1, "evidence": baseline_context},
                {"step": 2, "evidence": initial_review},
                {
                    "step": 3,
                    "evidence": baseline_cycle["author_fix"],
                },
                {"step": 4, "evidence": re_evaluation},
                {"step": 5, "evidence": transferred_cycle},
            ],
            "assessments": [
                {
                    "assessment": 1,
                    "evidence": initial_review[0],
                },
                {
                    "assessment": 2,
                    "evidence": re_evaluation,
                },
            ],
            "rubric_dimensions": [
                "technical-correctness",
                "judgment",
                "evidence",
                "communication",
            ],
            "transfer": {
                "task": (
                    "correctness変更からauthorization境界変更へ"
                    "risk kindだけを変え、同じsample changeをレビュー、"
                    "修正、独立再評価する"
                ),
                "changed_assumption": "risk-kind",
                "evidence": {
                    "changed_fields": changed_fields,
                    "baseline": initial_review[0]["priority"],
                    "transferred": transferred_review[0]["priority"],
                },
            },
            "contributor_enablement": contributor_enablement,
        },
    }
    print(json.dumps(report, ensure_ascii=False, sort_keys=True))

if __name__ == "__main__":
    try:
        main()
    except AssertionError as error:
        raise SystemExit(str(error)) from None
PY

トレードオフと失敗モード

risk、evidence、修正状態に限定したreview decision table
観測 priority 判断 次の行動
authorization境界と一致 critical approveを保留する 最小再現とdeny evidenceを要求する
対象riskと一致 important 修正または明示的反証を求める 具体的なfixと確認方法を示す
対象外だが有用 minor blockingにしない follow-up候補として分離する
author fix済み 再分類しない 独立再評価へ進む resolved IDとsystem outcomeを照合する
  • 誤診: comment数が多いreviewerほど品質へ貢献している。反証: findingごとのpriority、変更入力に結び付くevidence、actionable fix、解消後outcomeで有用性を評価する。
  • 誤診: authorが修正してtest commandが成功したのでreviewは完了した。反証: 元入力と修正snapshotを分け、別reviewerが全findingとsystem outcomeを独立再評価する。

知識チェック

  1. critical、important、minorを声の強さではなくriskから導くには何を固定するか。
  2. evidenceのない正しい指摘がauthor enablementを妨げる理由は何か。
  3. actionable fixを設計判断の押し付けにしないため、どの問いをauthorへ返すか。
  4. independent re-evaluationでcommand success以外に確認するsystem outcomeを三つ挙げよ。

出典と次の学習

The Standard of Code Review、How to write code review comments、SWEBOK Guide Version 4.0a、Characteristics of Useful Code Reviewsを、継続改善、明確なcomment、review品質、実証的な有用性の根拠として用いた。

次は自分のrepositoryから小さな変更を一つ選び、risk kindとreview budgetを宣言する。priority、evidence、actionable fix、author response、独立再評価を同じreview記録へ残し、他のcontributorが判断を再現できるか確認する。

実践ラボ

sample changeをレビューしauthor fixと独立再評価を完遂する

提出成果物: 優先度、根拠、修正可能性を備えたレビューと改善後の再評価

  1. synthetic sample change、review context、priority orderのschemaと限界を固定する
  2. 初回reviewで各findingのpriority、input由来evidence、actionable fixを記録する
  3. authorが全findingへ修正を適用し、元snapshotと修正snapshotを分離する
  4. 初回reviewerとは別のcontributorがresolved findingとsystem outcomeを独立再評価する
  5. risk kindだけをcorrectnessからauthorizationへ変え、同じsample changeのpriorityを再評価する

説明して理解を確かめる

5分で、The Standard of Code Reviewとuseful review研究を、完璧さの要求ではなく継続的改善、明確なpriority、根拠、author enablementへ結ぶ。

アセスメント

  1. 問い: commentは多いがpriorityと再現証拠がない。authorが最初に直すべき対象をどう決めるか。

    期待する証拠: risk kind、影響範囲、観測証拠、priority order、actionable fix

  2. 問い: author fix後に同じreviewerがテストcommandの成功だけを確認した。reviewを完了できるか。

    期待する証拠: 独立reviewer、修正snapshot、resolved finding、system outcome、command successとの区別

別問題へ転用する

correctness変更からauthorization境界変更へrisk kindだけを変え、同じsample changeをレビュー、修正、独立再評価する

復習スケジュール

  1. 1日後

    priorityと個人的な好みを分ける最小証拠は何か

  2. 7日後

    actionable commentがauthor enablementになる条件は何か

  3. 30日後

    command successだけでsystem outcomeを確認できない理由は何か

  4. 90日後

    priorityと個人的な好みを分ける最小証拠は何か

評価ルーブリック

4段階の評価基準
観点未達発展途上熟達卓越
technical-correctness好みや印象だけでapproveまたはblockする問題を指摘するがrisk、priority、再現条件が結び付かないinput schema、risk、priority、evidence、fix、再評価を一貫して追跡する順序、重複、snapshot drift、causal bypassをfail closedで検出できる
judgment全commentを同じ重要度で扱うpriorityはあるが影響とreview budgetの関係を説明しない重大度、確度、範囲、可逆性からreview effortと修正順を決める小さな継続改善とsystem riskの境界を保ち、不要なgatekeepingを減らす
evidencereviewerの経験だけを根拠にするテスト名はあるが変更入力とsystem outcomeへ結び付かないsample change由来の観測、修正snapshot、resolved finding、outcomeを残す別contributorが同じ入力からpriorityと再評価を独立再現できる
communication命令や人格評価だけをcommentする丁寧だが何をなぜ直すかが曖昧であるpriority、evidence、影響、具体的な次の行動を簡潔に伝えるauthorの意図を確認しつつ、別contributorをenableする共有知識へ変える

出典

以下の外部資料は利用者が選択したときだけ開きます。