Free

3.000 righe in un singolo commit: perché le feature complesse richiedono prima il plan mode

L'istinto davanti alle feature complesse è «provo qualcosa» — ma le decisioni architetturali si nascondono nel 3° modello, nel 5° edge case. Il vero valore del plan mode è spostare la conversazione architetturale nel testo prima del codice. Caso reale: il sistema di giuria comunitaria di Pickful — 3.032 righe, 119 spec verdi, un solo commit, zero rilavorazioni architetturali dopo.


Di fronte a una feature complessa, l'istinto della maggior parte delle persone è «provo qualcosa».

Il problema: i sistemi complessi hanno una caratteristica — le decisioni architetturali si nascondono nel 3° modello, nel 5° edge case, nell'8ª regola di punti. Tuffarsi nel codice e scontrarsi con quelle decisioni a metà strada, per poi tornare indietro a cambiarle, costa 10× in più rispetto a discuterle a parole fin dall'inizio.

Ho usato il plan mode di Claude Code per costruire TopicReview, il sistema di giuria comunitaria di Pickful, e ho visto la versione estrema di questa disuguaglianza: un solo commit, 3.032 righe, 119 spec in verde, consegnato in un'unica volta. Nessuno dei dieci e passa commit che sono seguiti è un rifacimento architetturale — sono tutti regolazioni di parametri, rifiniture UI, patch di edge case. Il sistema gira stabile da allora ed è oggi centrale nel modo in cui la comunità si auto-modera.

Questo post tratta del perché le feature complesse richiedono prima il plan mode, di cosa sta facendo davvero la fase di plan, e di quando la conversazione è abbastanza matura per cominciare a scrivere codice.

Quanto è complesso il sistema

TopicReview è un voto comunitario che decide se un post di bassa qualità debba essere rimosso. Una frase per riassumerlo — ma la spec si dispiega a strati:

  • 5 stati: open → voting → decided → appealed → closed
  • 3 verdetti: remove / warn / keep
  • 2 fasi: revisione iniziale da parte di 12 giurati; appello da parte di 5 giudici (sorteggiati tra i 20 utenti con più punti)
  • Flusso di punti multidimensionale: penalità provvisoria di 10 pt, stake d'appello di 10 pt, +5 per giurati che hanno votato in linea col verdetto, +10 per giudici che hanno votato in linea col verdetto, +3 per segnalatori la cui segnalazione è stata confermata, rimborso alla vittoria d'appello = stake + bonus + penalità provvisoria
  • 4 tipi di job schedulati: finestra di voto iniziale 24h, finestra d'appello 24h, finestra di revisione d'appello 24h, e — punti dei giurati differiti di 24h dopo un verdetto remove (perché un appello potrebbe ribaltarlo)
  • Flussi paralleli + rollback: se i voti si invertono durante la rimozione provvisoria, il post va ripristinato e la penalità provvisoria rimborsata; se l'appello ribalta il verdetto, lo stake viene rimborsato con bonus, possibilmente assieme alla penalità provvisoria, e i giurati vengono ricalcolati rispetto al nuovo verdetto

Il nucleo della complessità non sta in alcuna regola singola — sta in come le regole interagiscono tra loro. Ogni regola aggiunta può innescare un rollback altrove.

Muri contro cui sbatti senza plan

Quando scrivi di getto, i problemi più duri non sono i fatti visibili — sono le domande che non hai pensato di fare. Leggendo il codice di TopicReview all'inverso, ci sono almeno quattro muri che ti colpirebbero inevitabilmente se saltassi il plan:

Regole di idoneità dei giurati. Sembra solo User.jurors_and_judges.sample(12). Ma le regole effettive sono: escludere l'autore del post, escludere chi l'ha segnalato, escludere chi ha già votato al primo turno (così non vota di nuovo in appello). Tre esclusioni impilate. Scrivendo il model di un fiato, ne perdi una o due.

Pagamento differito dei punti. Un verdetto remove di norma scatena il pagamento ai giurati. Ma un appello può ribaltare un remove — e quando lo fa, i giurati che avevano votato col vecchio verdetto si ritrovano sul lato sbagliato, quindi i pagamenti vanno ricalcolati contro il nuovo verdetto. Perciò il verdetto remove deve aspettare la chiusura della finestra d'appello di 24h prima di pagare i giurati. Salta la scrittura di questa regola, paga prima, e finisci a recuperare punti — 10× più scomodo del pagare in ritardo.

Design dell'interfaccia del job schedulato. CloseTopicReviewJob sembra «chiudere una review». In pratica gestisce tre situazioni:

# Scadenza della finestra di voto iniziale
CloseTopicReviewJob.set(wait_until: voting_ends_at).perform_later(review.id)
# Pagamento ai giurati differito dopo un verdetto remove
CloseTopicReviewJob.set(wait: 24.hours).perform_later(review.id, award_juror_points: true)
# Scadenza della finestra d'appello
CloseTopicReviewJob.set(wait: 24.hours).perform_later(review.id, appeal_id: appeal.id)

Senza pianificazione preventiva, scrivi la prima signature, e al secondo caso ti accorgi che l'interfaccia deve cambiare.

Il muro più caro: si può fare appello durante la rimozione provvisoria? Quando i voti remove superano la soglia, il post viene nascosto subito (provvisorio), ma la review resta in stato voting — non decided. L'utente può già fare appello?

  • Sì → bisogna allargare le transizioni tra decided e appealed
  • No → UX pessima: il post è nascosto e l'utente deve aspettare 24h prima di poter protestare
  • Scelta reale di TopicReview: sì, l'appello è permesso durante la rimozione provvisoria — ma la review deve prima passare per finalize → decided prima di aprire l'appello

Questa singola decisione si propaga all'indietro e cambia i controlli dei parametri in open_appeal! e la logica della macchina a stati. Decidere a metà strada = riscrivere metà del sistema.

Cosa sta facendo davvero la fase di plan

Il plan mode di Claude Code è una modalità in cui scrivere codice è vietato — Claude può leggere il repo, pensare agli approcci, discutere con te, ma qualsiasi modifica ai file è hard-bloccata finché non approvi un plan.

Quel vincolo meccanico è il punto: costringe la conversazione a livello d'architettura ad avvenire in testo.

Alcune cose che la fase di plan fa in pratica:

1. Disegnare la macchina a stati e i ruoli. 5 stati, 4 ruoli (giurato / giudice / autore / segnalatore), cosa ogni ruolo può e non può fare in ogni stato — tutto in poche righe di markdown. Poche righe vs. decine di file. Il costo del cambiamento è di due ordini di grandezza diverso.

2. Attraversare il flusso di ogni ruolo:

  • Giurato: riceve notifica → apre la review → legge post e motivo → vota con reasoning → riceve punti
  • Autore del post: riceve notifica → vede il verdetto → se remove valuta l'appello → deposita lo stake → aspetta
  • Giudice: riceve l'appello estratto dal top-20 → vota → riceve punti

Dove il cammino si inceppa, spunta una domanda nascosta: «i giurati vedono i voti degli altri giurati?», «cosa può fare l'autore durante la rimozione provvisoria?»

3. Interrogare gli edge case. La fase di plan non disegna il «percorso normale» — fa apposta le domande che di solito non affiorano:

  • E se la finestra chiude con 0 voti? (Scelta finale: default keep.)
  • E se l'appello chiude con 0 voti? (dismissed; stake perso.)
  • Un giudice può votare su un post che lui stesso ha segnalato? (No — stessa regola di esclusione.)
  • Più giurati innescano finalize contemporaneamente — la race raddoppia il pagamento? (Aggiungere un campo juror_points_awarded per l'idempotenza.)

Il 95% di queste domande non emerge naturalmente mentre scrivi codice. La fase di plan ti costringe a rispondere una per una.

4. Il libro mastro dei punti. Il sistema di punti è abbastanza complesso da non bastare discuterlo — bisogna disegnare la tabella: ogni flusso di punti con il suo trigger, l'importo e il percorso di rollback. Quando il libro torna, tutti gli edge case (rimborso provvisorio, rimborso d'appello, bonus) si riconciliano.

Fino a che punto occorre aver parlato prima di scrivere codice

Un criterio duro:

  • Riesci a percorrere ogni cammino senza incepparti — ogni combinazione dall'apertura della review fino a closed (keep / remove / warn × con/senza appello × provvisorio/non) — la racconti da capo a fondo
  • Ogni edge case ha un comportamento definito — non «vedremo», ma «0 voti default keep», «provvisorio ammette appello ma prima serve finalize»
  • Non c'è «ah, a questo non ho ancora pensato» — ogni domanda che riesci a formulare ha già una risposta

Raggiunto quel livello, scrivere codice diventa tradurre il plan in Ruby.

Com'è la fase di esecuzione di getto

Il primo rilascio del sistema:

d162f1e Add community moderation system with jury/judge review and appeals
  63 files changed, 3032 insertions(+)
  119 specs, 0 failures

63 file, 3.032 righe, 119 spec in verde. Rilasciato in un'unica volta.

I dieci e passa commit successivi confermano che l'architettura ha tenuto:

Apply legacy penalty (1pt) for posts created before 2026-03-26
Fix topic review appeal bugs: window mismatch and verdict not updated
Add topic_removed status for posts removed by community review
Default to keep verdict when review expires with zero votes
Reduce provisional removal penalty to 1pt during trial period
Allow topic creator to withdraw active reviews
Add handled tab to jury dashboard, fix tabs styling

Ognuno è tuning / fix / rifinitura / piccola feature. Nessuno è «tornare indietro e ridisegnare la macchina a stati» o «il modello di punti va cambiato». Lo scheletro era del tutto giusto.

Questo è il vero dividendo del plan: l'esecuzione resta ininterrotta. Non c'è «aspetta, come funziona questo cammino» — il plan lo ha coperto. Non c'è «non ho pensato a questo edge case» — la fase di plan l'ha chiesto. Non c'è «questa interfaccia va riprogettata» — il plan l'ha già sistemata.

Ore a scrivere codice, facendo una sola cosa: tradurre un design chiaro sulla tastiera.

Quando non usare plan

Non tutti i compiti meritano un plan:

  • Fix di bug semplici — localizzare + correggere + aggiungere un test di regressione; nessuna discussione preliminare
  • Refactoring meccanici — rinominare, estrarre, spostare; il percorso è unico, il plan è un passo in più
  • Un unico percorso ovvio — aggiungere un GET endpoint, un toggle UI; nulla da dibattere
  • Prototipi esplorativi — tu stesso non sai cosa vuoi; far girare qualcosa di grezzo batte pensare di più

Il guadagno del plan mode viene dall'abbassare il costo di riscrittura. Se il task non ha rischio di riscrittura, il plan è solo overhead.

Per chiudere

«Pensare prima di agire» suona come un consiglio di carattere. Il plan mode non è quello.

Il plan mode è far sì che la conversazione a livello architetturale avvenga dove 10 parole la cambiano — non dove 30 file devono cambiare.

Sotto complessità, va invertito quel vecchio detto del settore «words are cheap, code is expensive» — non significa «scrivi codice prima e poi si vede» (vale solo quando il gap di costo è piccolo), significa sfruttare il gap di costo: portare la conversazione costosa in anticipo, nel medium economico.

3.000 righe in un singolo commit è una sensazione splendida. Non perché scrivo veloce — perché il plan ha trasformato «scrivere» in un atto puramente meccanico.