Il team di Aviator è arrivato a integrare quattro volte più pull request con gli stessi nove ingegneri, secondo quanto raccontato a The New Stack. Il tempo mediano necessario per l’integrazione è sceso da 2,1 giorni a 3,4 ore e i revert sono diminuiti del 40%. Il cambiamento non è consistito nel lasciare la revisione a un’IA: una persona ha continuato ad approvare ogni PR. È cambiato ciò che esaminava.
La proposta è rivedere l’intento, i criteri di accettazione, le invarianti, le decisioni e i test, invece di cercare di leggere ogni riga del diff. Implementeremo una versione piccola ed eseguibile in Python: un report che verifica che ogni criterio abbia un’evidenza, che i percorsi indicati esistano e che le decisioni in sospeso siano visibili. Non stabilisce se la modifica è corretta; prepara una revisione umana più mirata.
Il problema: un diff non spiega lo scopo
Nel caso descritto da Aviator, le PR settimanali sono passate da circa 100 a oltre 400 e la PR mediana generata dall’IA è arrivata a 640 righe. L’azienda ha anche provato un revisore automatico, una scadenza di quattro ore e una funzionalità di autor revisione. Il bot ha trovato un problema reale tra molti commenti minori; la scadenza ha accelerato le prime revisioni, ma ha svuotato le approvazioni; e l’autor revisione continuava a esaminare il diff dopo aver scritto il codice.
Il flusso che costruiremo raccoglie criteri e decisioni in un file, verifica ogni criterio rispetto alle evidenze locali e genera un risultato leggibile. Nell’esempio, l’evidenza sarà un percorso a un file e una citazione di riga. In un progetto reale, la prova potrebbe essere anche il risultato di un’API, una schermata o un log di esecuzione: Aviator menziona queste forme di evidenza nella descrizione del processo.
flowchart TD
A["Criterios y decisiones"] --> B["Verificador local"]
C["Evidencias del cambio"] --> B
D["Invariantes del equipo"] --> B
B --> E["Informe para la persona"]
E --> F["Resolver decisiones pendientes"]Preparare criteri ed evidenze
Useremo solo la libreria standard di Python. Salva il file seguente come review.py ed eseguilo con python review.py. Il programma crea un caso dimostrativo in una directory temporanea, quindi non dipende da file preesistenti. Ogni criterio ha una descrizione e una prova concreta; una decisione registra il suo stato e la persona che l’ha risolta.
L’esempio mantiene deliberatamente i dati accanto al verificatore, così da poter essere eseguito subito. In un repository, è preferibile separare la configurazione dallo strumento e salvare i criteri in un artefatto associato alla PR.
from pathlib import Path
from tempfile import TemporaryDirectory
criteria = [
{
"id": "AC-1",
"description": "La funzione di calcolo è presente",
"evidence": "src/pricing.py",
"line": 1,
},
{
"id": "AC-2",
"description": "Il test di calcolo è presente",
"evidence": "tests/test_pricing.py",
"line": 1,
},
]
def main():
with TemporaryDirectory() as temp_dir:
root = Path(temp_dir)
(root / "src").mkdir()
(root / "tests").mkdir()
(root / "src/pricing.py").write_text("def total(price, tax):\n return price + tax\n")
(root / "tests/test_pricing.py").write_text("def test_total():\n assert 2 + 1 == 3\n")
for item in criteria:
path = root / item["evidence"]
print(f"{item['id']}: {'OK' if path.is_file() else 'MANCANTE'} — {item['description']}")
if __name__ == "__main__":
main()
Verificare ogni criterio rispetto alla sua evidenza
Il fatto che esista un file non dimostra che il criterio sia soddisfatto. Il passaggio successivo verifica anche che la riga citata esista. Sostituisci il contenuto precedente con questo programma completo: conserva i dati di esempio, ma ora produce un risultato strutturato. Lo stato indica solo che l’evidenza specificata è disponibile; non equivale a un’approvazione semantica.
Questa distinzione è importante. Nell’esperimento di Aviator con una funzione di configurazione, per esempio, la verifica prevedeva di salvare un’impostazione e controllare il log di audit, quindi salvare senza modifiche per confermare che non comparisse un’altra voce. La seconda verifica fornisce un’evidenza diversa: non basta testare il caso favorevole.
from pathlib import Path
from tempfile import TemporaryDirectory
criteria = [
{"id": "AC-1", "description": "Funzione presente", "evidence": "src/pricing.py", "line": 1},
{"id": "AC-2", "description": "Test presente", "evidence": "tests/test_pricing.py", "line": 1},
{"id": "AC-3", "description": "Percorso inesistente", "evidence": "docs/pricing.md", "line": 1},
]
def check(root, item):
path = root / item["evidence"]
if not path.is_file():
return {"id": item["id"], "status": "MISSING_FILE", "evidence": item["evidence"]}
lines = path.read_text(encoding="utf-8").splitlines()
line = item["line"]
if line < 1 or line > len(lines):
return {"id": item["id"], "status": "MISSING_LINE", "evidence": item["evidence"]}
return {"id": item["id"], "status": "EVIDENCE_PRESENT", "evidence": item["evidence"], "line": line}
def main():
with TemporaryDirectory() as temp_dir:
root = Path(temp_dir)
(root / "src").mkdir()
(root / "tests").mkdir()
(root / "src/pricing.py").write_text("def total(price, tax):\n return price + tax\n", encoding="utf-8")
(root / "tests/test_pricing.py").write_text("def test_total():\n assert 2 + 1 == 3\n", encoding="utf-8")
for item in criteria:
print(check(root, item))
if __name__ == "__main__":
main()
Rendere visibili invarianti e decisioni
Le regole ricorrenti nelle revisioni possono diventare invarianti. The New Stack racconta che Aviator ha analizzato 11.400 commenti, ottenuto 212 regole candidate e approvato 64 invarianti. Alcune venivano eseguite come regole deterministiche; altre continuavano a richiedere verifiche aggiuntive. L’esempio seguente aggiunge una semplice invariante: ogni criterio deve includere un’evidenza. Aggiunge anche le decisioni irrisolte al report, invece di nasconderle tra i commenti.
Salva il codice completo come review.py. Per questo esempio didattico, l’invariante viene valutata sui dati, non sul codice del progetto. In un repository, le regole di stile o i pattern possono essere applicati con strumenti adatti al linguaggio; una decisione di progettazione, invece, deve restare visibile a una persona.
from pathlib import Path
from tempfile import TemporaryDirectory
criteria = [
{"id": "AC-1", "description": "Funzione presente", "evidence": "src/pricing.py", "line": 1},
{"id": "AC-2", "description": "Test presente", "evidence": "tests/test_pricing.py", "line": 1},
{"id": "AC-3", "description": "Percorso inesistente", "evidence": "docs/pricing.md", "line": 1},
]
decisions = [
{"question": "Il totale deve essere arrotondato?", "status": "pending", "owner": None},
{"question": "Va mantenuta la firma attuale?", "status": "settled", "owner": "revisor"},
]
def check(root, item):
if not item.get("evidence"):
return {"id": item["id"], "status": "NO_EVIDENCE"}
path = root / item["evidence"]
if not path.is_file():
return {"id": item["id"], "status": "MISSING_FILE", "evidence": item["evidence"]}
lines = path.read_text(encoding="utf-8").splitlines()
line = item["line"]
if line < 1 or line > len(lines):
return {"id": item["id"], "status": "MISSING_LINE", "evidence": item["evidence"]}
return {"id": item["id"], "status": "EVIDENCE_PRESENT", "evidence": item["evidence"], "line": line}
def main():
with TemporaryDirectory() as temp_dir:
root = Path(temp_dir)
(root / "src").mkdir()
(root / "tests").mkdir()
(root / "src/pricing.py").write_text("def total(price, tax):\n return price + tax\n", encoding="utf-8")
(root / "tests/test_pricing.py").write_text("def test_total():\n assert 2 + 1 == 3\n", encoding="utf-8")
print("Criteri:")
for item in criteria:
print(check(root, item))
print("Decisioni in sospeso:")
for decision in decisions:
if decision["status"] == "pending":
print(f"- {decision['question']}")
if __name__ == "__main__":
main()
Trasformare il risultato in un controllo di revisione
L’ultima modifica fa sì che il programma termini con un codice di errore se manca un’evidenza o c’è una decisione in sospeso. Può così essere eseguito da un controllo locale o integrato successivamente in CI. Chi revisiona deve comunque verificare se la prova risponde al criterio e risolvere le decisioni di prodotto o progettazione.
Nell’esperienza descritta da Aviator, i revisori esaminavano ciò che era già stato risolto, affrontavano ciò che gli agenti non potevano decidere e riaprivano le questioni che avrebbero valutato diversamente. L’automazione organizza il lavoro; non sostituisce il giudizio. Il team ha indicato che il costo della transizione è consistito in lavoro duplicato durante il primo mese, prima che le invarianti iniziassero a ridurre il carico nel secondo.
from pathlib import Path
from tempfile import TemporaryDirectory
criteria = [
{"id": "AC-1", "description": "Funzione presente", "evidence": "src/pricing.py", "line": 1},
{"id": "AC-2", "description": "Test presente", "evidence": "tests/test_pricing.py", "line": 1},
]
decisions = [
{"question": "Il totale deve essere arrotondato?", "status": "pending", "owner": None},
]
def check(root, item):
if not item.get("evidence"):
return False, f"{item['id']}: manca il percorso dell'evidenza"
path = root / item["evidence"]
if not path.is_file():
return False, f"{item['id']}: {item['evidence']} non esiste"
lines = path.read_text(encoding="utf-8").splitlines()
line = item["line"]
if line < 1 or line > len(lines):
return False, f"{item['id']}: la riga {line} non esiste"
return True, f"{item['id']}: evidenza disponibile in {item['evidence']}:{line}"
def main():
with TemporaryDirectory() as temp_dir:
root = Path(temp_dir)
(root / "src").mkdir()
(root / "tests").mkdir()
(root / "src/pricing.py").write_text("def total(price, tax):\n return price + tax\n", encoding="utf-8")
(root / "tests/test_pricing.py").write_text("def test_total():\n assert 2 + 1 == 3\n", encoding="utf-8")
failed = False
for item in criteria:
ok, message = check(root, item)
print(message)
failed |= not ok
for decision in decisions:
if decision["status"] != "settled":
print(f"IN SOSPESO: {decision['question']}")
failed = True
raise SystemExit(1 if failed else 0)
if __name__ == "__main__":
main()
Con la decisione di esempio ancora in sospeso, l’esecuzione termina con un errore; cambia il suo stato in settled e assegna una persona affinché il controllo venga superato. Questa modifica non certifica il codice: chiarisce cosa manca perché qualcuno possa approvarlo con il giusto contesto.
Fonti: The New Stack, Aviator, Aviator Blog, Aviator Blog