lead · Stage 5
コードレビューを協働品質システムとして運営する
変更リスクからpriorityを導き、evidenceとactionableな提案でauthor fixを支援し、別reviewerの独立再評価までを一つの品質ループとして設計する。
到達目標
変更のrisk kindと観測証拠からreview findingのpriorityを再現可能に分類できる
- 優先度、根拠、修正可能性を備えたレビューと改善後の再評価
- comment量やcommand successを品質と誤認しないreview判断
人格ではなく変更へ向けたevidenceとactionable fixでauthorの修正を支援できる
- 優先度、根拠、修正可能性を備えたレビューと改善後の再評価
- reviewをgatekeepingではなく別contributorの判断能力を増やす仕組みとして説明する5分発表
修正済みsnapshotを別reviewerが独立再評価し、system outcomeまで検証できる
- 優先度、根拠、修正可能性を備えたレビューと改善後の再評価
- risk kindだけを変えた同じsample changeのreviewと独立再評価
能力の進行
recognize
blocking risk、改善提案、質問、称賛を区別し、priorityの根拠を識別できる
証拠: 優先度、根拠、修正可能性を備えたレビューと改善後の再評価
explain
evidenceとactionable fixがauthorの学習と変更速度を両立する理由を説明できる
証拠: reviewをgatekeepingではなく別contributorの判断能力を増やす仕組みとして説明する5分発表
apply
sample changeからfinding、priority、evidence、修正案を作りauthor fixへ結べる
証拠: 優先度、根拠、修正可能性を備えたレビューと改善後の再評価
diagnose
好みの強制、priority欠落、証拠のない断定、同一reviewerだけの再確認を反証できる
証拠: comment量やcommand successを品質と誤認しないreview判断
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へ変換してはならない。
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
トレードオフと失敗モード
| 観測 | 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を独立再評価する。
知識チェック
- critical、important、minorを声の強さではなくriskから導くには何を固定するか。
- evidenceのない正しい指摘がauthor enablementを妨げる理由は何か。
- actionable fixを設計判断の押し付けにしないため、どの問いをauthorへ返すか。
- 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と独立再評価を完遂する
提出成果物: 優先度、根拠、修正可能性を備えたレビューと改善後の再評価
- synthetic sample change、review context、priority orderのschemaと限界を固定する
- 初回reviewで各findingのpriority、input由来evidence、actionable fixを記録する
- authorが全findingへ修正を適用し、元snapshotと修正snapshotを分離する
- 初回reviewerとは別のcontributorがresolved findingとsystem outcomeを独立再評価する
- risk kindだけをcorrectnessからauthorizationへ変え、同じsample changeのpriorityを再評価する
説明して理解を確かめる
5分で、The Standard of Code Reviewとuseful review研究を、完璧さの要求ではなく継続的改善、明確なpriority、根拠、author enablementへ結ぶ。
アセスメント
問い: commentは多いがpriorityと再現証拠がない。authorが最初に直すべき対象をどう決めるか。
期待する証拠: risk kind、影響範囲、観測証拠、priority order、actionable fix
問い: author fix後に同じreviewerがテストcommandの成功だけを確認した。reviewを完了できるか。
期待する証拠: 独立reviewer、修正snapshot、resolved finding、system outcome、command successとの区別
別問題へ転用する
correctness変更からauthorization境界変更へrisk kindだけを変え、同じsample changeをレビュー、修正、独立再評価する
復習スケジュール
- 1日後
priorityと個人的な好みを分ける最小証拠は何か
- 7日後
actionable commentがauthor enablementになる条件は何か
- 30日後
command successだけでsystem outcomeを確認できない理由は何か
- 90日後
priorityと個人的な好みを分ける最小証拠は何か
評価ルーブリック
| 観点 | 未達 | 発展途上 | 熟達 | 卓越 |
|---|---|---|---|---|
| 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を減らす |
| evidence | reviewerの経験だけを根拠にする | テスト名はあるが変更入力とsystem outcomeへ結び付かない | sample change由来の観測、修正snapshot、resolved finding、outcomeを残す | 別contributorが同じ入力からpriorityと再評価を独立再現できる |
| communication | 命令や人格評価だけをcommentする | 丁寧だが何をなぜ直すかが曖昧である | priority、evidence、影響、具体的な次の行動を簡潔に伝える | authorの意図を確認しつつ、別contributorをenableする共有知識へ変える |
出典
以下の外部資料は利用者が選択したときだけ開きます。