a-philosophy-of-software-design

di Rob ZappNessuna installazioneNessun likeAggiornato il 8 ottobre 2026Categoria: Ingegneria

Cosa fa

Usa durante la scrittura, modifica o revisione del codice ogni volta che la modifica aggiunge un nome esportato o importabile, crea un modulo, una classe, un componente, un helper, un hook, un servizio o un wrapper, centralizza codice ripetuto o modifica un'API. Regole di Ousterhout (moduli profondi, nascondere le informazioni, ridurre la complessità) più il test invariabile per la condivisione del codice, il test del costo per il lettore e una nota di design obbligatoria alla fine.

L'installazione apre questa scheda nella tua app desktop AgentsRoom. Se l'app non è ancora installata, verrai portato alla pagina di download.

SKILL.md

---
name: a-philosophy-of-software-design
description: Usa durante la scrittura, modifica o revisione del codice ogni volta che la modifica aggiunge un nome esportato o importabile, crea un modulo, una classe, un componente, un helper, un hook, un servizio o un wrapper, centralizza codice ripetuto o modifica un'API. Regole di Ousterhout (moduli profondi, nascondere le informazioni, ridurre la complessità) più il test invariabile per la condivisione del codice, il test del costo per il lettore e una nota di design obbligatoria alla fine.
---

# A Philosophy of Software Design (John Ousterhout)

## Quando usare questa skill

Usa questa skill quando progetti, scrivi, modifichi o revisioni codice. Si applica alla progettazione di moduli, modifiche API, decomposizione, refactoring, nomi, commenti, test e lavoro sulle prestazioni. Usala anche quando una modifica sembra scomoda, o quando una modifica si estende su molti file.

## Il pregiudizio da correggere

Il codice funzionante non è lo stesso che codice semplice. Piccoli pezzi, schemi familiari, flag, wrapper e documentazione extra possono rendere un design più complesso. Lo fanno quando aggiungono a ciò che un lettore deve sapere, o quando fanno trapelare conoscenze ad altri moduli.

## Regole decisionali

- Misura un design da quanto riduce la complessità. Preferisci il design che abbassa il carico del lettore. La complessità ha quattro segni. Una modifica richiede modifiche in molti posti. Le dipendenze sono nascoste. I passaggi devono avvenire in un ordine fisso. Il lettore deve tenere a mente molti fatti.
- Tratta il design come un lavoro continuo. Una prima patch che funziona non è finita se rende più difficili le modifiche successive. Per una decisione su un'interfaccia, una divisione di modulo o un'astrazione, confronta due o più design possibili.
- Preferisci moduli profondi. Un modulo profondo ha una piccola interfaccia e nasconde una grande quantità di complessità. Rifiuta servizi pass-through, wrapper di librerie sottili e piccoli moduli helper. Rifiuta qualsiasi estrazione che aggiunge nomi ma non riduce il carico del lettore.
- Progetta un'interfaccia attorno a ciò che il chiamante deve sapere, non attorno a come funziona l'implementazione. Evita sequenze di setup fragili, flag di modalità, manopole di configurazione e argomenti che mostrano scelte interne.
- Nascondi le decisioni che possono cambiare. Esempi sono rappresentazioni interne, forma di memorizzazione, protocolli, formati di file e trucchi di prestazioni. Contabilità, normalizzazione e casi limite sono altri esempi. Tieni ciascuno dentro il modulo che possiede la conoscenza.
- Sposta la complessità nel modulo che possiede il dettaglio. Accetta un'implementazione più complessa quando offre ai chiamanti un contratto più semplice e rimuove lavoro ripetuto da ogni punto di chiamata.
- Rendi un modulo generale al livello giusto. Non adattare un modulo a un solo chiamante. Non aggiungere un'astrazione vaga per bisogni futuri. Tieni i casi limite rari fuori dal percorso principale e metti il comportamento speciale in un suo posto.
- Unisci o dividi moduli in base alla complessità totale. Non unirli o dividerli in base alla dimensione, all'ordine di esecuzione del codice, all'abitudine o all'apparenza. Tieni insieme stato, comportamento, regole e decisioni correlate. Dividili solo quando il nuovo confine è più profondo e un lettore può capire ciascun lato da solo.
- Riduci il numero di eccezioni. Dove possibile, cambia l'interfaccia o le regole in modo che stati invalidi non possano verificarsi. Non far ripetere a ogni chiamante lo stesso codice difensivo.
- Usa i commenti per ridurre la complessità. Scrivi contratti di interfaccia, regole che devono rimanere vere, decisioni di design nascoste e le loro ragioni. Scrivi anche fatti difficili che i chiamanti non devono conoscere. Non ripetere il codice in un commento. Non usare un commento per nascondere un nome brutto, una divisione sbagliata o un flusso di controllo confuso.
- Tratta nomi, coerenza e chiarezza come informazioni di design. Un nome dice al lettore l'astrazione, non il meccanismo. Operazioni correlate usano le stesse convenzioni. Codice che sorprende il lettore aggiunge complessità, anche se è breve.
- Scrivi test contro contratti pubblici e API stabili. Testa la complessità nascosta e i casi speciali attraverso quei contratti. Non lasciare che la facilità di un test imponga un'interfaccia superficiale o che perde informazioni.
- Aggiungi un cambiamento di prestazioni, un pattern, un paradigma o un framework solo per uno di due motivi. Riduce la complessità in questo codice, oppure le evidenze mostrano che il compromesso è necessario. Nascondi ogni ottimizzazione dietro un'interfaccia stabile.

## Segnali e risposta a ciascuno

- Una funzionalità è scomoda, o una modifica si estende su più file, o un revisore deve trovare dipendenze nascoste. Risposta: cerca mancanza di nascondimento delle informazioni e moduli superficiali. Cerca anche passaggi in ordine fisso e complessità che i chiamanti devono gestire.
- Aggiungi un modulo, livello, servizio, helper, wrapper o facciata. Oppure aggiungi un pattern, opzione, callback o argomento. Risposta: mostra che nasconde più complessità di quanta ne aggiunga.
- Modifichi un'API. Risposta: verifica cosa deve sapere un chiamante normale. Un chiamante non deve conoscere l'ordine delle chiamate, la rappresentazione o la memorizzazione. Un chiamante non deve conoscere il trasporto, la cache, il protocollo o il formato del file. Un chiamante non deve conoscere il flusso di lavoro interno o molti passaggi di setup.
- Aggiungi un caso speciale, un flag, un percorso di eccezione, una condizione o un contenitore che i chiamanti possono vedere. Risposta: chiedi prima cosa può fare il modulo proprietario. Può rimuovere lo stato invalido, isolare il comportamento insolito o fornire un'operazione più forte.
- Dividi codice, estrai una funzione o aggiungi una variabile. Risposta: verifica che il nuovo confine o nome abbia significato. Non deve solo aggiungere salti, stato che passa o passaggi intermedi che i chiamanti possono vedere.
- Il codice ha fasi come `prepare`, `process` e `finalize`, o i chiamanti devono costruire oggetti a tappe. Risposta: verifica che l'ordine temporale sia il concetto reale. Se non lo è, organizza il codice attorno a responsabilità stabili.
- Un nome è vago, nomina un meccanismo, non è coerente o sorprende il lettore. Risposta: ripensa al confine dell'astrazione. Non accettare un nome che è quasi corretto.
- Un commento è lungo, ripete il codice, spiega un'interfaccia confusa o mostra internals per spiegare l'uso. Risposta: cambia l'astrazione o sposta il contratto mancante nell'interfaccia.
- Ottimizzi le prestazioni. Risposta: misura prima, poi nascondi l'ottimizzazione. Non rinunciare alla profondità del modulo o al nascondimento delle informazioni senza evidenze che il compromesso sia necessario.
- Testi o revisioni. Risposta: guarda il comportamento pubblico e i contratti di interfaccia. Guarda anche la complessità nascosta dietro API stabili e i casi speciali tenuti dietro l'astrazione.

## Lista di controllo finale

- La modifica riduce lo sforzo per comprendere, modificare, verificare ed estendere il sistema?
- Ogni elemento dell'interfaccia, wrapper, livello, helper, opzione e nome nasconde abbastanza complessità da giustificarlo?
- Le decisioni importanti sono in un unico posto? Le dipendenze sono visibili? I vincoli che i chiamanti devono rispettare sono scritti? Gli interni che possono cambiare sono protetti?
- I casi comuni funzionano senza passaggi extra? I controlli rari, i casi speciali, i trucchi di performance e i dettagli delle eccezioni restano fuori dal percorso comune?
- I nomi sono precisi e coerenti? I commenti sono aggiornati, senza ripetizioni del codice? Il codice segue le convenzioni esistenti, a meno che nuove informazioni non giustifichino un cambiamento?

## Gate

Usa la checklist completa quando la modifica aggiunge un nome che altro codice può esportare o importare. Usala anche quando la modifica crea un modulo, una classe, un componente, un helper, un hook, un servizio o un wrapper, o mette codice ripetuto in un unico posto. Rinominazioni, codemodifiche, modifiche di configurazione, modifiche di dati e correzioni di una riga non ne hanno bisogno.

## Test invariabile: condividi solo codice che cambia insieme

- Estrai codice condiviso solo quando protegge una regola che puoi nominare. La prova è il cambiamento congiunto: la storia mostra che le copie sono state corrette o modificate insieme. Il codice che sembra solo simile e cambia indipendentemente è una rima. Lascia le rime come duplicati. Tre blocchi simili non dimostrano una regola.
- Una correzione deve rimuovere il problema, non spostarlo. Sei cast spostati in un unico helper generico per cast sono ancora sei cast. Scrivi il mapper tipizzato che i cast nascondevano.
- Quando un'astrazione è sbagliata, rimetti il codice inline e lascia tornare la duplicazione. Non piegare l'astrazione con flag.
- Non dividere il codice solo per la sua dimensione. Un modulo di 400 righe che nasconde una decisione è meglio di quattro moduli da 100 righe che perdono le stesse unioni.
- Una lettura meccanica di Clean Code o SOLID (funzioni molto piccole, una classe per ogni responsabilità) produce moduli superficiali. Questa skill ha priorità su quella pressione.

## Costo per il lettore: il terzo test

Il test di profondità e il test invariabile decidono se un confine deve esistere. Il test del costo per il lettore decide se il codice intorno al confine è facile da modificare. Il prossimo lettore, una persona o un agente, paga per ogni riga che deve leggere. Un agente paga in token. Un agente trova il codice tramite ricerca testuale, letture parziali, typecheck e test.

- **Trovabile.** Usa un nome per ogni concetto. Scrivilo sempre allo stesso modo, così la ricerca testuale semplice lo trova. Difetti: nomi costruiti da stringhe, wiring tramite effetti collaterali di import, due nomi per un concetto. Catene di re-export che nascondono la definizione sono anch'esse difetti.
- **Stop precoce.** Metti il contratto in cima al file o sopra l'export. Dì cosa promette, cosa nasconde e cosa non fa mai. Così il lettore può fermarsi presto.
- **Verificabile dalla macchina.** Usa tipi esatti in ingresso e uscita da ogni confine, così un typecheck sostituisce la lettura dei chiamanti. Difetti: `any`, dizionari semplici, flag booleani il cui significato è solo nel corpo.
- **Accoppiamento visibile.** Due posti devono cambiare insieme. Imporlo con un tipo condiviso, un test o una singola fonte. Se non puoi, segnalo in entrambi i posti.
- **Niente rumore.** Rimuovi commenti che ripetono il codice e codice commentato. Rimuovi rami morti e commenti che registrano la storia delle modifiche. Rimuovi un vecchio percorso che resta accanto alla sua sostituzione.
- **Prevedibile.** Segui la struttura esistente del repository. Metti il test dove un lettore lo cerca e fallo eseguire da solo.

La dimensione del file non è in questa lista di proposito. Un file molto grande è un motivo per cercare una seconda decisione nascosta. Non è mai un motivo per tagliare il file.

## Sicurezza

Per codice esistente, prima scrivi un test che mantenga il comportamento attuale. Poi rendi il modulo più profondo. Per codice nuovo, scrivi il test che definisce il comportamento previsto.

## Nota di design (obbligatoria quando si applica il gate)

Quando si applica il gate, inserisci una sezione con il titolo `## Design note` nella descrizione della pull request. Scrivi due-quattro righe:

- Ogni confine che hai aggiunto e la decisione che nasconde.
- Ogni duplicazione che hai mantenuto di proposito e il motivo.
- Ogni parte superficiale che hai accettato e il motivo.

Se il gate non si applica, scrivi `## Design note` seguito da `Gate not applicable: <reason>`. Metti anche la nota di design nel sommario del tuo step finale.

## Modalità revisione

Usa questa sezione quando rivedi o testi codice scritto da un altro agente o persona.

1. Controlla la nota di design. Quando il gate si applica e la pull request non ha la sezione `## Design note`, segnala un problema bloccante. Quando la nota non concorda con il diff, segnala un problema bloccante.
2. Un problema di design è bloccante solo se soddisfa entrambe le condizioni:
   - Nomina una regola di questa skill. La regola è una regola decisionale, il gate, il test invariabile o un elemento del costo per il lettore.
   - Indica un costo concreto per il lettore o per la prossima modifica. Esempi: "I chiamanti devono conoscere la forma dello storage." "Un concetto ha due nomi." "Una modifica al cap richiede modifiche in tre file."
3. Segnala ogni altra osservazione di design come non bloccante. Mettila in una lista separata con il titolo "Note di design non bloccanti". Una nota non bloccante non rimanda mai il lavoro al costruttore.
4. Non segnalare una preferenza come problema. Un nome diverso, layout di file o stile è una preferenza. Diventa un problema solo quando infrange una regola nominata e ha un costo concreto.
5. Quando lo stesso problema di design ritorna in un secondo ciclo di revisione, escalalo. Non richiedere la stessa modifica una terza volta.

## Skill correlate (quando installate)

- `find-shared-code`: una ricerca solo report della storia recente per codice che vale la pena condividere. Usa il test invariabile e il test di profondità di questa skill.
- `refactoring` e `working-effectively-with-legacy-code`: i passi sicuri verso un design più profondo. Questa skill decide se un nuovo confine resta.

## Fonte e licenza

Questa skill si basa sulle regole "mini" di A Philosophy of Software Design nel repository ciembor/agent-rules-books su GitHub (licenza MIT, commit 893a88a). Il gate, il test dell'invariante, il test del costo per il lettore, la nota di design e la modalità di revisione sono aggiunte a quelle regole. Il repository contiene anche le regole complete del libro.

Tag

designarchitectureousterhoutreview