English· Español· Deutsch· Nederlands· Français· 日本語· ქართული· 繁體中文· 简体中文· Português· Русский· العربية· हिन्दी· Italiano· 한국어· Polski· Svenska· Türkçe· Українська· Tiếng Việt· Bahasa Indonesia

un

ospite
1 / ?
torna alle lezioni

Come si forma il sedimento di codice

La roccia sedimentaria si forma per deposizione, non per esplosione. Ogni strato: corretto per la sua epoca. Ogni strato: più antico di quello sopra. Gli strati più antichi si sono calcificati prima che l’ecosistema maturasse intorno a loro. Nessun errore li ha causati. Il tempo li ha causati.

MOAD-0001 funziona allo stesso modo.

MOAD-0001 Sediment Layers: code copied across decades as N grows

La storia della formazione

Un attraversamento di grafi scritto nel 1993:

List<Node> visited = new ArrayList<>();
Stack<Node> stack = new Stack<>();
stack.push(start);
while (!stack.isEmpty()) {
Node n = stack.pop();
if (!visited.contains(n)) {  // scansione lineare O(N)
visited.add(n);
for (Node neighbor : n.getNeighbors())
stack.push(neighbor);
}
}

Questo codice: corretto. Test: superati. Nel 1993, i grafi avevano 50 nodi.

50 nodi: 50 × 25 media analizzati = 1.250 operazioni. Invisibile.

Il codice è entrato nel version control. È stato copiato nei tutorial. È stato incluso nelle librerie. È stato distribuito negli strumenti di build, nei gestori di pacchetti e nei risolutori di dipendenze. Nel 2024, lo stesso pattern veniva eseguito su grafi di dipendenze con 50.000 nodi.

50.000 nodi: 50.000 × 25.000 media analizzati = 1.250.000.000 operazioni.
Una build di 1 secondo diventa 17 minuti.

Il codice non è cambiato. Il mondo intorno a esso è cresciuto. Ogni strato del sedimento: corretto al momento della deposizione. Costoso allo scavo.

Il tuo Sedimento

Il codice sedimentario non contiene un errore logico. Contiene un'assunzione di prestazioni che ha smesso di valere quando la scala è cambiata. Il codice produce risultati corretti; è solo cambiato il costo.

Questa distinzione è importante per la diagnosi. Un errore logico produce risposte sbagliate. Un difetto sedimentario produce risposte corrette a un costo inaccettabile.

Codice sedimentario: codice corretto che diventa costoso man mano che la scala intorno a esso cambia. Fornisci un esempio concreto tratto da software che hai usato o scritto in cui qualcosa funzionava su piccola scala e diventava un collo di bottiglia su larga scala. Cosa è cambiato: il codice, la dimensione dei dati o il modello di utilizzo?

Cinque Forme di MOAD-0001

MOAD-0001 appare in cinque forme documentate in oltre 60 ecosistemi software. La struttura: un test di appartenenza che utilizza un contenitore sequenziale, annidato all'interno di un ciclo sulla stessa o su una collezione correlata.

Le Cinque Forme

DominioPatternContenitore SequenzialeContenitore Corretto
Attraversamento di grafiif (!stack.contains(n)) in DFS/TarjanArrayListHashSet
Deduplicazione di rotte/eventiTAILQ_FOREACH strcmp per richiestalinked listHashMap
Tracciamento delle collisionifindLinearSearch() per tick di fisicaarrayunordered_set
Filtro di allocazione risorseList.contains() nel filtro dello streamArrayListHashSet
Open-list di A* pathfindingLocalVector::find() per ogni vicinovectorindice heap memorizzato

Tutti e cinque condividono la stessa struttura: un controllo di appartenenza annidato in un ciclo, che utilizza un contenitore che richiede una scansione lineare per rispondere alla domanda di appartenenza.

L’elenco delle parole chiave di scansione

L’audit per MOAD-0001 significa cercare con grep le parole chiave di test di appartenenza all’interno dei cicli:

- Python: in con una variabile list, .count(), list.index()

- Java: ArrayList.contains(), List.contains(), LinkedList.contains()

- JavaScript: Array.includes(), Array.indexOf(), Array.find()

- C++: std::vector::find(), ciclo manuale con confronto ==

- Go: slices.Contains(), ciclo manuale su uno slice

La correzione in ogni caso: sostituire il contenitore sequenziale con un contenitore hash. listset. ArrayListHashSet. ArraySet. vectorunordered_set. slicemap[T]struct{}.

Una parola chiave. Una sostituzione. Zero cambiamenti comportamentali. Velocità 1.000× a N=1.000.

Esegui l’audit di un frammento di codice

Applica il riconoscimento del pattern MOAD-0001 a un frammento di codice reale.

Stai eseguendo l’audit di una codebase JavaScript e trovi questo all’interno di un ciclo che itera su tutti i vicini di un grafo: `if (!openSet.includes(neighbor)) openSet.push(neighbor)`. Si tratta di MOAD-0001? Con cosa sostituirai `openSet` e come cambierà la complessità da O(?) a O(?)?

Una riga, quattro linguaggi

La correzione per MOAD-0001 in quattro linguaggi:

# Python
visited = []       # PRIMA: membership O(N)
visited = set()    # AFTER:  O(1) membership
// Java
List<Node> visited = new ArrayList<>();    // BEFORE
Set<Node> visited = new HashSet<>();       // AFTER
// JavaScript
const visited = [];      // PRIMA: Array.includes() O(N)
const visited = new Set(); // DOPO: Set.has() O(1)
// visited.push(n) → visited.add(n)
// visited.includes(n) → visited.has(n)
// Go
visited := []NodeID{}              // PRIMA: slices.Contains() O(N)
visited := make(map[NodeID]struct{}) // DOPO: map lookup O(1)
// _, ok := visited[n]  per il controllo di appartenenza
// visited[n] = struct{}{}  per l'inserimento

In ogni caso: stesso comportamento, stesso output, stessi test superati. Cambia solo il tasso di crescita.

Cosa la correzione NON cambia

- Il comportamento logico dell'algoritmo: la ricerca in profondità resta in profondità

- Correttezza dell'output: gli stessi nodi vengono visitati nello stesso ordine (all'interno della DFS)

- Risultati dei test: tutti i test che verificano la correttezza continuano a passare

Cosa cambia con la correzione

- Test di appartenenza: O(N) → O(1)

- Ciclo totale: O(N²) → O(N)

- Con N=1.000: 1.000× più veloce

- Con N=10.000: 10.000× più veloce

Un limite: l'ordine

I contenitori hash (set, HashSet, unordered_set) non preservano l'ordine di inserimento. In Python 3.7+, dict mantiene l'ordine di inserimento; il semplice set no. In Java, HashSet non mantiene l'ordine; LinkedHashSet sì.

Se l'ordine è importante insieme all'appartenenza: mantieni due strutture. Un set (o HashSet) per i test di appartenenza in O(1). Una list (o ArrayList) separata per la scansione ordinata. Oppure usa LinkedHashSet in Java, che fornisce entrambe.

Per MOAD-0001 nella visita di grafi: visited risponde a una domanda binaria (questo nodo è già stato visto?). L'ordine non conta per la domanda di appartenenza. L'ordine di visita deriva dallo stack o dalla coda, non da visited.

Appartenenza vs Ordinamento

Nell'algoritmo di Tarjan per le componenti fortemente connesse, onStack tiene traccia dei nodi ancora presenti nello stack di chiamate DFS corrente. Deve rispondere a due domande: (1) appartenenza — questo nodo è attualmente nello stack? (2) alla fine di un percorso DFS, rimuovi nodi in ordine finché non appare questo nodo.

Un'implementazione ingenua usa una List per onStack, rispondendo alla domanda di appartenenza con contains() — O(N) per controllo, O(N²) totale per grafi grandi.

Correggi un'implementazione Tarjan SCC sostituendo `onStack = []` con `onStack = set()`. I test passano. Un revisore chiede: 'ma se l'ordine fosse importante per onStack?' Come rispondi?

Perché si tratta di una Disclosure, non di una Patch

MOAD-0001 è presente in oltre 1.000 siti verificati in più di 60 ecosistemi software. Attraversamento di grafi negli strumenti di build, deduplicazione di route nei demoni di routing di rete, rilevamento di collisioni nei motori di gioco, allocazione di risorse negli scheduler dei sistemi operativi.

Ogni sito: codice corretto. Ogni sito: crescita O(N²) in attesa che N superi una soglia.

La Pipeline di Disclosure

Una patch senza disclosure upstream corregge un solo sito. Il pattern si ripresenta nella versione successiva, nella prossima libreria, nel prossimo porting linguistico. Disclosure: nominare il pattern, documentarlo come CWE-407 (Algorithmic Complexity: Inefficient Algorithmic Complexity), pubblicare le euristiche di riconoscimento e la correzione a una riga. Così ogni sviluppatore che legge la disclosure può riconoscere e correggere la propria istanza.

Hamming lo definiva 'dare un pesce vs insegnare a pescare'. La patch dà un pesce. La disclosure — MOAD-0001 nominato, pattern documentato, correzione generalizzata — insegna l'euristica.

L’Estensione Permacomputer

Hamming lavorava su soluzioni puntuali: correggere questo filtro, migliorare questo codice. L’Unhamming aggiunge il livello di disclosure: nominare il pattern, pubblicare l’euristica e donarla ai commons.

Il principio della conoscenza composta si applica qui su scala ecosistemica. Un ricercatore nomina un pattern. Cento sviluppatori lo riconoscono nei propri codebase. Mille righe di codice O(N²) diventano O(N). L’infrastruttura diventa più veloce per tutti coloro che vi costruiscono sopra.

Questo è il drago che cresce: stesso fuoco (lavorare su problemi importanti, conoscenza composta, pensiero sistemico), volo diverso (disclosure aperta, proprietà comune, nessun patron richiesto).