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

un

gast
1 / ?
terug naar lessen

Hoe code-sediment ontstaat

Sedimentair gesteente ontstaat door afzetting, niet door explosie. Elke laag: correct voor zijn tijd. Elke laag: ouder dan de laag erboven. De oudste lagen versteenden voordat het ecosysteem eromheen volwassen werd. Geen fout veroorzaakte ze. Tijd veroorzaakte ze.

MOAD-0001 werkt op dezelfde manier.

MOAD-0001 Sedimentlagen: code gekopieerd over decennia terwijl N groeit

Het ontstaanverhaal

Een grafische doorloop geschreven in 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)) {  // O(N) lineaire scan
visited.add(n);
for (Node neighbor : n.getNeighbors())
stack.push(neighbor);
}
}

Deze code: correct. Tests: geslaagd. In 1993 hadden grafen 50 knopen.

50 nodes: 50 × 25 avg gescand = 1.250 bewerkingen. Onzichtbaar.

De code kwam in versiebeheer terecht. Werd gekopieerd in tutorials. Werd verpakt in libraries. Werd meegeleverd in build-tools, package managers en dependency resolvers. Tegen 2024 draaide hetzelfde patroon op dependency graphs met 50.000 nodes.

50.000 nodes: 50.000 × 25.000 avg gescand = 1.250.000.000 bewerkingen.
Een build van 1 seconde wordt 17 minuten.

De code veranderde niet. De wereld eromheen groeide. Elke laag van het sediment: correct bij afzetting. Kostbaar bij opgraving.

Jouw Sediment

Sedimentaire code bevat geen logische fout. Het bevat een prestatieaanname die niet langer geldig was toen de schaal veranderde. De code produceert correcte resultaten; alleen de kosten zijn veranderd.

Dit onderscheid is belangrijk voor diagnose. Een logische fout levert verkeerde antwoorden op. Een sedimentair defect levert correcte antwoorden op tegen onacceptabele kosten.

Sedimentaire code: correcte code die duurder wordt naarmate de schaal eromheen verandert. Geef een concreet voorbeeld uit software die je hebt gebruikt of geschreven waarbij iets werkte op kleine schaal en een knelpunt werd op grote schaal. Wat veranderde: de code, de datagrootte of het gebruikspatroon?

Vijf vormen van MOAD-0001

MOAD-0001 komt voor in vijf gedocumenteerde vormen in meer dan 60 software-ecosystemen. De structuur: een membership-test met een sequentiële container, genest in een lus over dezelfde of een gerelateerde collectie.

De vijf vormen

DomeinPatroonSequentiële containerCorrecte container
Grafen-traversalif (!stack.contains(n)) in DFS/TarjanArrayListHashSet
Route/event-deduplicatieTAILQ_FOREACH strcmp per verzoeklinked listHashMap
BotsingsregistratiefindLinearSearch() per fysica-tickarrayunordered_set
Resource allocation filterList.contains() in stream filterArrayListHashSet
A* pathfinding open-listLocalVector::find() per neighborvectorstored heap index

Alle vijf delen dezelfde structuur: een lidmaatschapstest genest in een lus, met een container die een lineaire scan vereist om de lidmaatschapsvraag te beantwoorden.

De Scanning Keyword List

Auditen op MOAD-0001 betekent greppen naar lidmaatschapstest-trefwoorden binnen lussen:

- Python: in met een list-variabele, .count(), list.index()

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

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

- C++: std::vector::find(), handmatige lus met == vergelijking

- Go: slices.Contains(), handmatige lus over een slice

De oplossing in elk geval: vervang de sequentiële container door een hash-container. listset. ArrayListHashSet. ArraySet. vectorunordered_set. slicemap[T]struct{}.

Eén sleutelwoord. Eén substitutie. Nul gedragsverandering. 1.000× versnelling bij N=1.000.

Codefragment controleren

Pas MOAD-0001 patroonherkenning toe op een echt codefragment.

Je controleert een JavaScript-codebase en vindt dit binnen een lus die over alle buren van een graaf loopt: `if (!openSet.includes(neighbor)) openSet.push(neighbor)`. Is dit MOAD-0001? Wat zou je `openSet` mee vervangen, en wat verandert de complexiteit van O(?) naar O(?)?

Eén Regel, Vier Talen

De oplossing voor MOAD-0001 in vier talen:

# Python
visited = []       # VOOR: O(N) lidmaatschap
visited = set()    # AFTER:  O(1) membership
// Java
List<Node> visited = new ArrayList<>();    // BEFORE
Set<Node> visited = new HashSet<>();       // AFTER
// JavaScript
const visited = [];      // VOOR: Array.includes() O(N)
const visited = new Set(); // NA: Set.has() O(1)
// visited.push(n) → visited.add(n)
// visited.includes(n) → visited.has(n)
// Go
visited := []NodeID{}              // VOOR: slices.Contains() O(N)
visited := make(map[NodeID]struct{}) // NA: map lookup O(1)
// _, ok := visited[n]  voor lidmaatschapcontrole
// visited[n] = struct{}{}  voor invoegen

In elk geval: hetzelfde gedrag, dezelfde output, dezelfde tests slagen. Alleen de groeisnelheid verandert.

Wat de Fix NIET Verandert

- Het logische gedrag van het algoritme: depth-first blijft depth-first

- Correctheid van de uitvoer: dezelfde knopen worden in dezelfde volgorde bezocht (binnen DFS)

- Testresultaten: alle tests die correctheid controleren blijven slagen

Wat de fix WEL verandert

- Lidmaatschapstest: O(N) → O(1)

- Totale looptijd: O(N²) → O(N)

- Bij N=1.000: 1.000× sneller

- Bij N=10.000: 10.000× sneller

Eén beperking: volgorde

Hash-containers (set, HashSet, unordered_set) bewaren de invoegvolgorde niet. In Python 3.7+ bewaart dict de invoegvolgorde; een gewone set doet dat niet. In Java bewaart HashSet geen volgorde; LinkedHashSet wel.

Als volgorde belangrijk is naast lidmaatschap: onderhoud twee structuren. Een set (of HashSet) voor O(1)-lidmaatschapstests. Een aparte list (of ArrayList) voor geordende doorloop. Of gebruik LinkedHashSet in Java, die beide biedt.

Voor MOAD-0001 bij grafen-doorloop: visited beantwoordt een binaire vraag (is deze knoop al gezien?). Volgorde doet er niet toe voor de lidmaatschapsvraag. De doorloopvolgorde komt van de stack of queue, niet van visited.

Lidmaatschap versus Volgorde

In Tarjan’s algoritme voor sterk samenhangende componenten houdt onStack bij welke knopen nog op de huidige DFS-call-stack staan. Het moet twee vragen beantwoorden: (1) lidmaatschap — staat deze knoop momenteel op de stack? (2) aan het einde van een DFS-pad knopen in volgorde van de stack halen tot deze knoop verschijnt.

Een naïeve implementatie gebruikt een List voor onStack en beantwoordt de lidmaatschapsvraag met contains() — O(N) per controle, O(N²) totaal voor grote grafen.

Je repareert een Tarjan-SCC-implementatie door `onStack = []` te vervangen door `onStack = set()`. De tests slagen. Een code reviewer vraagt: ‘maar wat als volgorde wél belangrijk is voor onStack?’ Hoe antwoord je?

Waarom dit een disclosure is, geen patch

MOAD-0001 komt voor op meer dan 1.000 geverifieerde locaties in meer dan 60 software-ecosystemen. Grafentraversie in build-tools, route-deduplicatie in netwerkrouting-daemons, botsingsdetectie in game-engines, resource-toewijzing in besturingssysteem-schedulers.

Elke locatie: correcte code. Elke locatie: O(N²)-groei die wacht tot N een drempel overschrijdt.

Het disclosure-proces

Een patch zonder upstream-disclosure lost één locatie op. Het patroon keert terug in de volgende versie, de volgende bibliotheek, de volgende taalport. Disclosure: benoem het patroon, documenteer het als CWE-407 (Algorithmic Complexity: Inefficient Algorithmic Complexity), publiceer de herkenningsheuristieken en de éénregelige fix. Dan kan elke ontwikkelaar die de disclosure leest zijn eigen instantie herkennen en oplossen.

Hamming noemde dit 'een vis geven versus leren vissen.' De patch geeft een vis. De disclosure — MOAD-0001 benoemd, patroon gedocumenteerd, fix gegeneraliseerd — leert de heuristiek.

De Permacomputer-extensie

Hamming werkte aan puntoplossingen: repareer dit filter, verbeter deze code. Unhamming voegt de disclosure-laag toe: benoem het patroon, publiceer de heuristiek en geef het aan de commons.

Het compound-knowledge-principe geldt hier op ecosysteemschaal. Eén onderzoeker benoemt een patroon. Honderd ontwikkelaars herkennen het in hun eigen codebases. Duizend regels O(N²)-code worden O(N). De infrastructuur wordt sneller voor iedereen die erop bouwt.

Dit is de draak die groeit: hetzelfde vuur (werk aan belangrijke problemen, compound knowledge, systems thinking), andere vlucht (open disclosure, commons-eigendom, geen patron vereist).