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

un

гость
1 / ?
назад к урокам

Как формируется кодовый седимент

Осадочная порода формируется путём отложения, а не взрыва. Каждый слой: правильный для своего времени. Каждый слой: старше того, что выше. Самые старые слои затвердели ещё до того, как экосистема созрела вокруг них. Их не вызвала ошибка. Их вызвало время.

MOAD-0001 работает точно так же.

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

История формирования

Графовый обход, написанный в 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) линейное сканирование
visited.add(n);
for (Node neighbor : n.getNeighbors())
stack.push(neighbor);
}
}

Этот код: корректен. Тесты: пройдены. В 1993 году графы имели 50 узлов.

50 узлов: 50 × 25 в среднем просканировано = 1 250 операций. Незаметно.

Код попал в систему контроля версий. Был скопирован в учебные материалы. Был обёрнут в библиотеки. Был включён в инструменты сборки, менеджеры пакетов и средства разрешения зависимостей. К 2024 году тот же паттерн работал на графах зависимостей с 50 000 узлов.

50 000 узлов: 50 000 × 25 000 в среднем просканировано = 1 250 000 000 операций.
Сборка, занимавшая 1 секунду, теперь занимает 17 минут.

Код не изменился. Изменился мир вокруг него. Каждый слой осадка: правильный в момент отложения. Дорогой при раскопках.

Ваш осадок

Седиментарный код не содержит логической ошибки. Он содержит предположение о производительности, которое перестало выполняться при изменении масштаба. Код выдаёт правильные результаты; изменилась только стоимость.

Это различие важно для диагностики. Логическая ошибка даёт неверные ответы. Седиментарный дефект даёт правильные ответы, но по неприемлемой цене.

Седиментарный код: правильный код, который становится дорогим по мере изменения масштаба вокруг него. Приведите конкретный пример из ПО, которое вы использовали или писали, где что-то работало в малом масштабе и стало узким местом в большом. Что изменилось: код, размер данных или паттерн использования?

Пять форм MOAD-0001

MOAD-0001 встречается в пяти задокументированных формах в более чем 60 программных экосистемах. Структура: проверка членства с использованием последовательного контейнера, вложенная в цикл по той же или связанной коллекции.

Пять форм

ДоменПаттернПоследовательный контейнерПравильный контейнер
Обход графаif (!stack.contains(n)) в DFS/TarjanArrayListHashSet
Дедупликация маршрутов/событийTAILQ_FOREACH strcmp на каждый запроссвязный списокHashMap
Отслеживание коллизийfindLinearSearch() на каждый тик физикимассивunordered_set
Фильтр распределения ресурсовList.contains() в фильтре потокаArrayListHashSet
Открытый список A* pathfindingLocalVector::find() для каждого соседаvectorсохранённый индекс в куче

Все пять форм имеют одинаковую структуру: проверка принадлежности, вложенная в цикл, с использованием контейнера, требующего линейного сканирования для ответа на вопрос о принадлежности.

Ключевые слова сканирования

Аудит MOAD-0001 подразумевает поиск ключевых слов проверки принадлежности внутри циклов:

- Python: in с переменной типа list, .count(), list.index()

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

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

- C++: std::vector::find(), ручной цикл с оператором ==

- Go: slices.Contains(), ручной цикл по слайсу

Исправление во всех случаях: замените последовательный контейнер на хеш-контейнер. listset. ArrayListHashSet. ArraySet. vectorunordered_set. slicemap[T]struct{}.

Одно ключевое слово. Одна подстановка. Никаких изменений поведения. Ускорение в 1 000× при N=1 000.

Аудит фрагмента кода

Примените распознавание паттерна MOAD-0001 к реальному фрагменту кода.

Вы проводите аудит кодовой базы JavaScript и находите внутри цикла, который перебирает всех соседей графа: `if (!openSet.includes(neighbor)) openSet.push(neighbor)`. Это MOAD-0001? Чем заменить `openSet` и как изменится сложность с O(?) на O(?)?

One Line, Four Languages [BLOCK_TYPE SECTION/STEP]

The fix for MOAD-0001 in four languages: [BLOCK_TYPE SECTION/STEP]

```python [BLOCK_TYPE SECTION/STEP]

Python

[BLOCK_TYPE SECTION/STEP]

visited = [] # BEFORE: O(N) membership

visited = set() # AFTER: O(1) membership

```java
// Java
List<Node> visited = new ArrayList<>();    // BEFORE
Set<Node> visited = new HashSet<>();       // AFTER
// JavaScript
const visited = [];      // ДО: Array.includes() O(N)
const visited = new Set(); // ПОСЛЕ: Set.has() O(1)
// visited.push(n) → visited.add(n)
// visited.includes(n) → visited.has(n)
// Go
visited := []NodeID{}              // ДО: slices.Contains() O(N)
visited := make(map[NodeID]struct{}) // ПОСЛЕ: map lookup O(1)
// _, ok := visited[n]  для проверки принадлежности
// visited[n] = struct{}{}  для вставки

Во всех случаях: одинаковое поведение, одинаковый вывод, тесты проходят. Меняется только скорость роста.

Что исправление НЕ меняет

- Логическое поведение алгоритма: поиск в глубину остаётся поиском в глубину

- Корректность вывода: те же узлы посещаются в том же порядке (в рамках DFS)

- Результаты тестов: все тесты, проверяющие корректность, продолжают проходить

Что исправление ИЗМЕНЯЕТ

- Проверка членства: O(N) → O(1)

- Общее время цикла: O(N²) → O(N)

- При N=1,000: в 1 000 раз быстрее

- При N=10,000: в 10 000 раз быстрее

Одно ограничение: Порядок

Хэш-контейнеры (set, HashSet, unordered_set) не сохраняют порядок вставки. В Python 3.7+ dict сохраняет порядок вставки; обычный set — нет. В Java HashSet не сохраняет порядок; LinkedHashSet — сохраняет.

Если порядок важен наряду с проверкой принадлежности: поддерживайте две структуры. set (или HashSet) для O(1)-проверок принадлежности. Отдельный list (или ArrayList) для упорядоченного обхода. Или используйте LinkedHashSet в Java, который даёт оба свойства.

Для MOAD-0001 в обходе графа: visited отвечает на бинарный вопрос (был ли этот узел уже посещён?). Порядок не важен для вопроса о принадлежности. Порядок обхода определяется стеком или очередью, а не visited.

Принадлежность vs Порядок

В алгоритме Тарьяна для поиска сильно связных компонентов onStack отслеживает, какие узлы остаются в текущем стеке вызовов DFS. Он должен отвечать на два вопроса: (1) принадлежность — находится ли этот узел сейчас в стеке? (2) в конце DFS-пути — выталкивать узлы в порядке до появления этого узла.

Наивная реализация использует List для onStack, отвечая на вопрос о принадлежности с помощью contains() — O(N) на проверку, O(N²) всего для больших графов.

Вы исправляете реализацию Tarjan SCC, заменяя `onStack = []` на `onStack = set()`. Тесты проходят. Ревьюер кода спрашивает: «а что, если для onStack важен порядок?» Как вы ответите?

Почему это Disclosure, а не патч

MOAD-0001 присутствует более чем в 1000 подтверждённых местах в 60+ программных экосистемах. Обход графа в инструментах сборки, дедупликация маршрутов в сетевых демонах, обнаружение коллизий в игровых движках, распределение ресурсов в планировщиках операционных систем.

Каждое место: корректный код. Каждое место: O(N²)-рост, ожидающий, пока N пересечёт порог.

Пайплайн Disclosure

Патч без раскрытия upstream исправляет одно место. Паттерн повторяется в следующей версии, следующей библиотеке, следующем порте на другой язык. Disclosure: назвать паттерн, задокументировать его как CWE-407 (Algorithmic Complexity: Inefficient Algorithmic Complexity), опубликовать эвристики распознавания и однострочное исправление. Тогда каждый разработчик, прочитавший disclosure, сможет распознать и исправить свой собственный случай.

Хэмминг называл это «дать рыбу или научить ловить рыбу». Патч даёт рыбу. Disclosure — MOAD-0001 назван, паттерн задокументирован, исправление обобщено — учит эвристике.

Расширение Permacomputer

Хэмминг работал над точечными решениями: исправить этот фильтр, улучшить этот код. Unhamming добавляет слой раскрытия: назвать паттерн, опубликовать эвристику и передать её в общее пользование.

Принцип накопления знаний применяется здесь в масштабе экосистемы. Один исследователь называет паттерн. Сто разработчиков узнают его в своих кодовых базах. Тысяча строк кода O(N²) превращаются в O(N). Инфраструктура становится быстрее для всех, кто на ней строит.

Это дракон растёт: тот же огонь (работа над важными проблемами, накопление знаний, системное мышление), другой полёт (открытое раскрытие, общее владение, без необходимости в патроне).