Название — это не поиск
Вы теперь знаете семь образцов MOAD. Знание названий важно: оно позволяет вам распознать образец, когда вы его видите. Но распознавание в контролируемом уроке отличается от обнаружения в кодовой базе, которую вы никогда не открывали.
Кодовая база не маркирует свои дефекты. MOAD осадочного типа не идет с комментарием, который говорит // O(N²) — fix this. Thundering herd не объявляет себя как stampede кэш-промаха. Вы находите их, читая код с конкретным вопросом в уме: какая структура данных содержит эти значения, и какие операции выполняются против нее внутри цикла?
Обнаружение — это навык, отличный от распознавания. Распознавание говорит: да, этот образец — это MOAD-0001. Обнаружение говорит: дайте мне найти все места в этой кодовой базе, где этот образец может существовать, видимый ли я полный код или только название символа.
Первое сканирование
Первый проход использует grep. Каждый MOAD имеет подложку: структуру данных или API, чье присутствие рядом с определенными операциями является сигналом, стоящим исследования.
MOAD-0001 (Sedimentary): List.contains в цикле
# Signal: membership test on a list variable inside a loop
grep -rn '.contains(' src/ | grep -v HashSet | grep -v TreeSet
grep -rn 'visited =' src/ | grep -v set | grep -v Set
MOAD-0002 (Intertangle): общий изменяемый флаг в фазах
# Signal: static mutable field written by one subsystem, read by another
grep -rn 'static ' src/ | grep -v final | grep -v class | grep -v void
MOAD-0003 (Leaked Context): ThreadLocal в объединяемом исполнителе
# Signal: ThreadLocal.set() without guaranteed ThreadLocal.remove()
grep -rn 'ThreadLocal' src/
grep -rn 'ThreadLocal.set' src/ -l
MOAD-0004 (Logged Secret): HTTP-заголовки в выводе логов
# Signal: log call with headers variable near auth endpoints
grep -rn 'log.*header' src/
grep -rn 'Authorization' src/ --include='*.log'
MOAD-0005 (Thundering Herd): кэш-промах без синхронизации
# Signal: cache.get() + null check + cache.put() without lock
grep -rn 'cache.get' src/ -A4 | grep 'cache.put'
Эти образцы дают кандидатов, а не подтвержденные дефекты. Каждый кандидат требует триажа: читайте окружающий код, проверяйте тип структуры данных, подтверждайте, что операция работает в масштабе.
Чтение кода на предмет сложности
Grep находит кандидатов. Чтение подтверждает их. Когда вы открываете файл с кандидатом, вы читаете с одним вопросом: стоимость этой операции растет с размером ввода?
Для MOAD-0001 протокол подтверждения:
1. Найдите внешний цикл. Что ограничивает количество его итераций?
2. Найдите внутреннюю операцию (.contains, .indexOf, 'in'). На какую структуру данных она выполняется?
3. Растет ли эта структура данных с тем же вводом, который управляет внешним циклом?
4. Если да: стоимость O(N²), где N = размер ввода. Подтвержденный дефект.
5. Если нет: внутренняя структура ограничена (конфиг, перечисление, небольшая константа). Ложное срабатывание.
Обход графа, посещающий N узлов, проверяя список visited на каждом шаге: как цикл, так и внутренняя структура данных растут с N. Подтверждено.
Обработчик запроса, проверяющий список разрешений из 5 IP-адресов администратора: список разрешений никогда не растет с объемом запроса. Ложное срабатывание.
Одинаковый протокол применяется к каждому MOAD: определите внешний драйвер, определите внутреннюю структуру, спросите, масштабируются ли оба вместе.
Балл ускорения: приоритизация ваших находок
Не все подтвержденные дефекты требуют немедленного исправления. MOAD в библиотеке с 10 000 нисходящих зависимостей имеет более высокий балл ускорения, чем один и тот же MOAD во внутреннем инструменте.
Балл ускорения = ускорение × входящая степень. Ускорение: насколько быстрее исправление работает при типичном масштабе производства? Входящая степень: сколько нисходящих пакетов или сервисов автоматически наследуют исправление при слиянии апстрима?
Подтвержденный MOAD-0001 в разрешителе зависимостей Apache Maven, работающий на графах из 50 000 узлов, с 1000+ нисходящих плагинов Maven, которые автоматически наследуют изменения: балл ускорения очень высок. Это исправление принадлежит началу вашей очереди.
Подтвержденный MOAD-0001 в инструменте с одним пользователем CLI без зависимостей: балл ускорения близко к нулю. Стоит исправить, но не срочно.
Трудяга против обжоры. Узел с высокой центральностью между вершинами и высоким ускорением — это трудяга: он обрабатывает критический поток и будет заполнять нисходящие очереди при разблокировании. Исправляйте его только после подтверждения нисходящей емкости. Узел с высокой исходящей степенью и низким ускорением — это обжора: он потребляет все, что ему подается, и не чувствует боли. Исправление трудяги без подготовки нисходящей емкости создает MOAD-0005 (thundering herd) в масштабе инфраструктуры.
От сканирования к слиянию: конвейер MOAD
Подтвержденный дефект с высоким баллом ускорения проходит через конвейер. Каждый этап производит артефакт. Ни один этап не является необязательным.
scan → список кандидатов (вывод grep, результаты статического анализа)
ticket → описание дефекта (номер MOAD, местоположение, анализ сложности)
patch → изменение кода (замена структуры данных, принятие примитива)
test → модульный тест (доказательство O(1): время исправления на N=100 и N=10,000)
UNDF → пост раскрытия (undefect.com, общественный достав)
disclose → ссылка CVE или CWE, если имеет отношение к безопасности
PR → апстримный запрос на вытягивание с патчем + тестом + ссылкой UNDF
merge → одобрение сопровождающего; исправление распространяется через обновление версии
Каждый артефакт подает в следующий этап. Патч без теста не может быть проверен. Тест без раскрытия не может распространяться на другие экземпляры того же образца. Раскрытие без апстримного PR оставляет исправление в вилке.
Пост MOAD (UNDF) — это этап, который большинство инженеров пропускают. Они исправляют дефект, отправляют PR, и считают себя готовыми. Но исправление без названного поста означает, что каждый будущий инженер, который встречает один и тот же образец, должен переоткрывать как проблему, так и исправление независимо. Пост MOAD закрывает цикл знаний: он называет образец, показывает метод обнаружения и ссылается на патч. Будущие исследователи находят исправление, ища название образца.
Планета патчи в масштабе. Единственное исправление MOAD-0001 в широко используемой библиотеке распространяется на каждый проект, который его импортирует. Пост MOAD гарантирует, что инженеры в проектах, которые никогда не обновят эту библиотеку, все еще учат исправление. Оба пути работают параллельно.
Написание билета дефекта
Хороший билет дефекта отвечает на пять вопросов:
1. Где: точный файл, класс, функция и диапазон строк
2. Что: тип структуры данных и операция против нее
3. Почему: анализ сложности (O(N²) или хуже, с определенным N)
4. Влияние: какие вводы вызывают поведение наихудшего случая и в каком масштабе
5. Исправление: структура данных или примитив для замены
Билет, который отвечает на все пять, полностью автономен: сопровождающий, который никогда не читал вашего анализа, может воспроизвести вашу находку и проверить ваше исправление. Билеты, которые пропускают (3) или (4), требуют, чтобы сопровождающий повторил ваш анализ сложности перед слиянием. Это трение снижает вероятность слияния.
Репутация растет. Первый PR, который включает ясный билет, хорошо направленный патч и тест бенчмарка, получает слиянии. Второй PR от того же автора проверяется с меньшим трением. Третий PR проверяется сопровождающим, который слил первые два. Репутация в открытом исходном коде — это реестр артефактов: каждый принятый патч заслуживает доверие для следующего.
Чтение реального кандидата
Вот настоящий кандидат MOAD-0001 на Python. Прочитайте его и завершите протокол триажа.
class DependencyResolver:
def resolve(self, package, resolved=None, seen=None):
if resolved is None:
resolved = []
if seen is None:
seen = []
if package in seen:
return
seen.append(package)
for dep in self.registry.get_dependencies(package):
self.resolve(dep, resolved, seen)
resolved.append(package)
return resolved
Вопросы триажа:
1. Какая структура данных `seen`?
2. Какая операция выполняется против нее на строке 6?
3. Растет ли `seen` с размером ввода?
4. Растет ли цикл, который управляет рекурсивными вызовами, с размером ввода?
5. Это подтвержденный MOAD-0001 или ложное срабатывание?
Ваш патч
Подтвержденный дефект с высоким баллом ускорения требует полного патча: исправление кода, тест, который доказывает улучшение, и набросок поста MOAD.
Тест должен быть тестом производительности, а не тестом корректности. Тест корректности проходит до и после исправления — в этом и дело; вывод не изменяется. Тест производительности при двух размерах ввода доказывает улучшение:
import time
def build_graph(n):
# n packages, each depending on the previous one
return {f'pkg{i}': [f'pkg{i-1}'] if i > 0 else [] for i in range(n)}
for n in [100, 1000, 5000]:
registry = build_graph(n)
resolver = DependencyResolver(registry)
start = time.perf_counter()
resolver.resolve(f'pkg{n-1}')
elapsed = time.perf_counter() - start
print(f'n={n}: {elapsed:.4f}s')
До исправления, истекшее время растет квадратично с n. После исправления оно растет линейно. Напечатайте оба и включите числа в описание вашего PR.
Набросок поста MOAD охватывает: название образца, подложку (разрешитель зависимостей Python), метод обнаружения (grep для in seen, где seen начинается с []), исправление и ссылку на ваш PR. Пост идет в undefect.com как общественный достав. Будущие инженеры, ищущие 'принадлежность Python-списка в медленном цикле' найдут его.