Исправлять один и тот же баг дважды — это провал процесса
У каждой команды есть тот самый дефект, который постоянно возвращается. Off-by-one в пагинации. Пропущенная проверка на null в middleware аутентификации. Race condition в checkout, который кто-то “исправил” три спринта назад.
Вы не написали один и тот же баг три раза. Вы написали три разных бага с одной причиной. Исправление устранило симптом. Причина осталась скрытой.
Fagan inspection были созданы для поиска дефектов до того, как они уйдут. Большинство команд останавливаются на фазе доработки. Автор исправляет зарегистрированные проблемы, модератор проверяет исправления, и все двигаются дальше. Это ошибка. Фаза последующего анализа — это то место, где должен находиться причинный анализ. Пропустите её, и вы планируете следующую inspection для тех же типов дефектов.
Что на самом деле означает причинный анализ
Причинный анализ — это не анализ корневых причин. Анализ корневых причин спрашивает “какая строка кода сломалась и почему.” Причинный анализ спрашивает “что в нашем процессе позволило существовать этой категории дефектов.”
Корневая причина null pointer exception — “мы забыли проверить на null.” Находка причинного анализа — “наш review checklist не включает null safety, и наши linting-правила разрешают непроверенные разыменования.” Одно исправляет баг. Другое чинит фабрику.
В Fagan inspection причинный анализ проводится после доработки. Модератор группирует дефекты по категориям и проводит короткую сессию для выявления причин на уровне процесса. Результат — не изменения кода. Это изменения процесса: обновлённые checklists, новые lint-правила, пробелы в обучении или модифицированные критерии входа.
Механика: как это запустить
Начните с inspection log. Каждый дефект уже должен иметь четыре поля: местоположение, серьёзность, тип и описание. Добавьте пятое во время причинного анализа: причина процесса.
Модератор группирует дефекты по типу. Если три из двенадцати дефектов — это ошибки граничных условий, это паттерн. Если два — это ошибки неправильного использования API из одного модуля, это тоже паттерн. Паттерны — это сигнал. Отдельные дефекты — шум.
Для каждого паттерна задайте три вопроса:
- Могли ли мы предотвратить эту категорию до inspection?
- Почему наши существующие механизмы предотвращения пропустили её?
- Какое самое дешёвое изменение предотвратило бы эту категорию в следующий раз?
Третий вопрос — это где большинство команд ошибаются. Они предлагают переписать архитектуру. Это мечтательство. Цель — самое маленькое изменение процесса, которое устранит категорию.
Паттерн граничных условий может означать добавление “проверок off-by-one и границ” в review checklist. Паттерн неправильного использования API может означать добавление правила статического анализа. Паттерн отсутствующей обработки ошибок может означать обновление определения готовности для требования тестов путей ошибок.
Работающий трекер причинного анализа
Вот Python-скрипт, который берёт inspection log, группирует дефекты по типу и запрашивает причины на уровне процесса.
#!/usr/bin/env python3
"""
Run causal analysis on a Fagan inspection log.
Reads a JSON log, groups defects by type, and emits a causal analysis report.
"""
import json
import argparse
from collections import defaultdict
from dataclasses import dataclass, field
from typing import List, Dict
@dataclass
class Defect:
location: str
severity: str
defect_type: str
description: str
@dataclass
class CausalPattern:
defect_type: str
count: int
locations: List[str] = field(default_factory=list)
process_cause: str = ""
proposed_fix: str = ""
def load_log(path: str) -> List[Defect]:
with open(path, "r") as f:
raw = json.load(f)
return [Defect(**item) for item in raw]
def analyze_patterns(defects: List[Defect]) -> List[CausalPattern]:
groups: Dict[str, List[Defect]] = defaultdict(list)
for d in defects:
groups[d.defect_type].append(d)
patterns = []
for dtype, items in groups.items():
patterns.append(CausalPattern(
defect_type=dtype,
count=len(items),
locations=[d.location for d in items],
))
return sorted(patterns, key=lambda p: p.count, reverse=True)
def prompt_causal_input(patterns: List[CausalPattern]) -> List[CausalPattern]:
print("=== CAUSAL ANALYSIS SESSION ===")
print("For each pattern, identify the process cause and the cheapest fix.\n")
for p in patterns:
print(f"Pattern: {p.defect_type} ({p.count} occurrence(s))")
print(f"Locations: {', '.join(p.locations)}")
p.process_cause = input("Process cause: ").strip()
p.proposed_fix = input("Cheapest prevention fix: ").strip()
print()
return patterns
def emit_report(patterns: List[CausalPattern], output_path: str):
report = {
"summary": {
"total_patterns": len(patterns),
"total_defects": sum(p.count for p in patterns),
},
"patterns": [
{
"type": p.defect_type,
"count": p.count,
"locations": p.locations,
"process_cause": p.process_cause,
"proposed_fix": p.proposed_fix,
}
for p in patterns
],
}
with open(output_path, "w") as f:
json.dump(report, f, indent=2)
print(f"Report written to {output_path}")
def main():
parser = argparse.ArgumentParser(description="Causal analysis for Fagan inspections")
parser.add_argument("log", help="Path to inspection log JSON")
parser.add_argument("--output", default="causal_report.json", help="Output report path")
args = parser.parse_args()
defects = load_log(args.log)
patterns = analyze_patterns(defects)
if not patterns:
print("No defects found. Nothing to analyze.")
return
patterns = prompt_causal_input(patterns)
emit_report(patterns, args.output)
if __name__ == "__main__":
main()
Сохраните inspection log как inspection_log.json:
[
{"location": "src/auth.py:42", "severity": "major", "defect_type": "null-safety", "description": "Missing null check on user object"},
{"location": "src/orders.py:88", "severity": "minor", "defect_type": "boundary", "description": "Off-by-one in pagination limit"},
{"location": "src/auth.py:67", "severity": "major", "defect_type": "null-safety", "description": "Unchecked token decode result"}
]
Запустите python causal_analysis.py inspection_log.json, и скрипт проведёт вас через идентификацию причин процесса. Созданный им отчёт станет входными данными для вашей следующей ретроспективы или цикла улучшения процессов.
Почему большинство команд это пропускают
Причинный анализ добавляет время к уже дорогому процессу. Стандартная Fagan inspection для 250 строк стоит 8–12 человеко-часов. Причинный анализ добавляет ещё 30–60 минут.
Это дополнительное время кажется расточительным, когда у вас есть бэклог. Но если причинный анализ предотвращает хотя бы одно повторение категории дефектов, он окупается в следующий раз, когда эта категория не появится.
Более сложная проблема — честность. Причинный анализ часто выявляет, что дефект существовал, потому что команда пропустила шаг. Код не был протестирован до inspection. Рецензент не использовал checklist. Сам checklist неполный.
Эти находки могут быть неудобными. Команда, которая трактует причинный анализ как назначение вины, перестанет получать честные ответы. Модератор должен представлять это как улучшение процесса, а не как театр подотчётности.
Настоящий компромисс: скорость против обучения
У вас есть два варианта после Fagan inspection. Замкнуть цикл, проверив исправления и двигаясь дальше. Или замкнуть цикл, проверив исправления и узнав, почему дефекты существовали.
Первый вариант быстрее сегодня. Второй вариант быстрее в следующие шесть месяцев.
Команды, которые получают наибольшую ценность от Fagan inspection, рассматривают inspection log как набор данных. Паттерны дефектов — это обратная связь. Игнорировать их — как запускать test suite и никогда не смотреть на failures.
Не каждая категория дефектов заслуживает причинного анализа. Разовая опечатка не требует изменения процесса. Но если категория появляется в двух последовательных inspection, у вас есть проблема процесса, маскирующаяся под проблему кода.
Начните с модуля с наибольшим влиянием
Вам не нужно запускать причинный анализ на каждой inspection. Выберите модуль, который производит больше всего production-инцидентов или наибольшее количество ушедших дефектов. Проведите полную Fagan inspection, соберите log и потратьте тридцать минут на причинный анализ.
В первый раз, когда вы это сделаете, предложенные исправления будут очевидны. Обновите checklist. Добавьте lint-правило. Напишите короткую командную заметку о паттерне. К третьей inspection вы должны увидеть меньше дефектов в категориях, которые вы уже проанализировали.
Если счётчики не падают, ваши предложенные исправления слишком размыты. “Будьте внимательнее” — это не изменение процесса. “Запускайте null safety linter в CI” — это да.
Отслеживайте категории дефектов по inspection с течением времени. Если причинный анализ выполняет свою работу, повторяющиеся категории исчезают. Если нет, вы либо неправильно идентифицируете причины, либо не внедряете исправления.
FAQ
Что такое причинный анализ в Fagan inspection?
Причинный анализ — это пост-inspection шаг, на котором команда изучает найденные дефекты, группирует их по категориям и выявляет причины на уровне процесса. Цель — предотвратить повторение, изменив checklists, инструменты или практики, а не просто исправив отдельные дефекты.
Чем причинный анализ отличается от анализа корневых причин?
Анализ корневых причин определяет конкретную причину возникновения одного дефекта, например пропущенную проверку на null. Причинный анализ определяет, почему процесс позволил написать целую эту категорию дефектов, например отсутствующий пункт checklist или невыполняемое lint-правило.
Когда следует запускать причинный анализ?
Запускайте его после фаз доработки и последующего анализа Fagan inspection, пока дефекты ещё свежи. Сосредоточьтесь на паттернах, которые появляются в нескольких дефектах или уже появлялись в предыдущих inspection. Разовые дефекты редко оправдывают изменения процесса.
Как узнать, работает ли причинный анализ?
Отслеживайте категории дефектов по inspection с течением времени. Если ваш причинный анализ эффективен, частота повторяющихся категорий должна падать. Если те же категории продолжают появляться, ваши предложенные исправления либо неверны, либо не внедряются.
Запустите его на вашем следующем inspection log
Найдите ваш последний inspection log. Сгруппируйте дефекты по типу. Для топовой категории спросите, какое самое дешёвое изменение процесса предотвратило бы её в следующий раз.
Запишите это изменение. Назначьте ответственного. Проверьте его на следующей inspection. Это и есть причинный анализ. Всё остальное — просто исправление багов.