Arreglar el mismo bug dos veces es un fallo de proceso
Todo equipo tiene ese defecto que sigue volviendo. Un off-by-one en la paginación. Una verificación de nulo faltante en el middleware de autenticación. Una race condition en el checkout que alguien “arregló” hace tres sprints.
No escribiste el mismo bug tres veces. Escribiste tres bugs diferentes con la misma causa. El arreglo abordó el síntoma. La causa permaneció oculta.
Las inspecciones de Fagan fueron diseñadas para encontrar defectos antes de que escapen. La mayoría de los equipos se detienen en la fase de retrabajo. El autor arregla los problemas registrados, el moderador verifica los arreglos, y todos siguen adelante. Eso es un error. La fase de tracing es donde pertenece el análisis causal. Omitirla es programar la próxima inspección para los mismos tipos de defectos.
Qué significa realmente el análisis causal
El análisis causal no es análisis de causa raíz. El análisis de causa raíz pregunta “qué línea de código falló y por qué.” El análisis causal pregunta “qué en nuestro proceso permitió que esta categoría de defecto existiera.”
Una causa raíz de una excepción de puntero nulo es “olvidamos verificar si es nulo.” El hallazgo del análisis causal es “nuestra checklist de revisión no incluye null safety, y nuestras reglas de linting permiten desreferencias no verificadas.” Uno arregla el bug. El otro arregla la fábrica.
En una inspección de Fagan, el análisis causal ocurre después del retrabajo. El moderador agrupa los defectos por categoría y dirige una sesión corta para identificar causas a nivel de proceso. El resultado no son cambios de código. Son cambios de proceso: checklists actualizadas, nuevas reglas de lint, brechas de capacitación o criterios de entrada modificados.
La mecánica: cómo ejecutarlo
Comienza con el registry de inspección. Cada defecto ya debería tener cuatro campos: ubicación, severidad, tipo y descripción. Agrega un quinto durante el análisis causal: causa de proceso.
El moderador agrupa los defectos por tipo. Si tres de doce defectos son errores de condición de límite, eso es un patrón. Si dos son errores de mal uso de API del mismo module, eso también es un patrón. Los patrones son la señal. Los defectos individuales son ruido.
Para cada patrón, haz tres preguntas:
- ¿Podríamos haber prevenido esta categoría antes de la inspección?
- ¿Por qué nuestros mecanismos de prevención existentes no lo detectaron?
- ¿Cuál es el cambio más barato que prevendría esta categoría la próxima vez?
La tercera pregunta es donde la mayoría de los equipos se equivocan. Sugieren reescribir la arquitectura. Eso es un deseo irrealizable. El objetivo es el cambio de proceso más pequeño que elimine la categoría.
Un patrón de condición de límite podría significar agregar “verificaciones off-by-one y de límites” a la checklist de revisión. Un patrón de mal uso de API podría significar agregar una regla de análisis estático. Un patrón de manejo de errores faltante podría significar actualizar la definición de hecho para requerir pruebas de camino de error.
Un rastreador de análisis causal funcional
Aquí hay un script de Python que toma un registry de inspección, agrupa defectos por tipo y solicita causas a nivel de proceso.
#!/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()
Guarda un registry de inspección como 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"}
]
Ejecuta python causal_analysis.py inspection_log.json y el script te guiará para identificar causas de proceso. El informe que produce se convierte en la entrada para tu próxima retrospectiva o ciclo de mejora de procesos.
Por qué la mayoría de los equipos omiten esto
El análisis causal agrega tiempo a un proceso ya costoso. Una inspección estándar de Fagan para 250 líneas cuesta de 8 a 12 horas-persona. El análisis causal agrega otros 30 a 60 minutos.
Ese tiempo extra se siente desperdiciado cuando tienes un backlog. Pero si el análisis causal previene incluso una recurrencia de una categoría de defecto, se paga solo la próxima vez que esa categoría no aparezca.
El problema más difícil es la honestidad. El análisis causal a menudo revela que el defecto existió porque el equipo omitió un paso. El código no fue probado antes de la inspección. El revisor no usó la checklist. La checklist misma está incompleta.
Estos hallazgos pueden ser incómodos. Un equipo que trata el análisis causal como asignación de culpas dejará de obtener respuestas honestas. El moderador debe enmarcarlo como mejora de proceso, no como teatro de rendición de cuentas.
La verdadera disyuntiva: velocidad vs. aprendizaje
Tienes dos opciones después de una inspección de Fagan. Cerrar el ciclo verificando los arreglos y siguiendo adelante. O cerrar el ciclo verificando los arreglos y aprendiendo por qué existieron los defectos.
La primera opción es más rápida hoy. La segunda opción es más rápida en los próximos seis meses.
Los equipos que obtienen más valor de las inspecciones de Fagan tratan el registry de inspección como un conjunto de datos. Los patrones de defectos son retroalimentación. Ignorarlos es como ejecutar un test suite y nunca mirar los fallos.
No toda categoría de defecto merece análisis causal. Un error tipográfico único no necesita un cambio de proceso. Pero si una categoría aparece en dos inspecciones consecutivas, tienes un problema de proceso que se hace pasar por un problema de código.
Comienza con el module de mayor impacto
No necesitas ejecutar análisis causal en cada inspección. Elige el module que produce la mayor cantidad de incidents de producción o los defectos escapados más numerosos. Ejecuta una inspección completa de Fagan, recopila el registry y dedica treinta minutos al análisis causal.
La primera vez que hagas esto, los arreglos propuestos serán obvios. Actualiza la checklist. Agrega una regla de lint. Escribe una breve nota del equipo sobre el patrón. Para la tercera inspección, deberías ver menos defectos en las categorías que ya analizaste.
Si los conteos no bajan, tus arreglos propuestos son demasiado vagos. “Ten más cuidado” no es un cambio de proceso. “Ejecuta el linter de null safety en CI” sí lo es.
Rastrea las categorías de defectos por inspección a lo largo del tiempo. Si el análisis causal está haciendo su trabajo, las categorías recurrentes desaparecen. Si no lo hacen, estás identificando mal las causas o fallando en implementar los arreglos.
FAQ
¿Qué es el análisis causal en una inspección de Fagan?
El análisis causal es un paso post-inspección donde el equipo examina los defectos encontrados, los agrupa por categoría e identifica causas a nivel de proceso. El objetivo es prevenir la recurrencia cambiando checklists, herramientas o prácticas, no solo arreglando los defectos individuales.
¿En qué se diferencia el análisis causal del análisis de causa raíz?
El análisis de causa raíz identifica la razón específica por la que ocurrió un solo defecto, como una verificación de nulo faltante. El análisis causal identifica por qué el proceso permitió que se escribiera toda esa categoría de defecto, como un elemento de checklist faltante o una regla de lint no aplicada.
¿Cuándo debería ejecutar el análisis causal?
Ejecútalo después de las fases de retrabajo y tracing de una inspección de Fagan, mientras los defectos aún están frescos. Enfócate en patrones que aparecen en múltiples defectos o han aparecido en inspecciones anteriores. Los defectos únicos raramente justifican cambios de proceso.
¿Cómo sé si el análisis causal está funcionando?
Rastrea las categorías de defectos a través de inspecciones en el tiempo. Si tu análisis causal es efectivo, la frecuencia de categorías recurrentes debería disminuir. Si las mismas categorías siguen apareciendo, tus arreglos propuestos son incorrectos o no se están implementando.
Ejecútalo en tu próximo registry de inspección
Encuentra tu último registry de inspección. Agrupa los defectos por tipo. Para la categoría principal, pregunta cuál sería el cambio de proceso más barato para prevenirla la próxima vez.
Escribe ese cambio. Asigna un responsable. Verifícalo en la próxima inspección. Eso es análisis causal. Todo lo demás es solo arreglar bugs.