{ "index": 40, "slug": "editorial-2026-11-field-technology-evaluation", "title": "Как сравнивать технологии, когда цена ошибки выше цены эксперимента", "excerpt": "Сравнение технологий начинается не с рейтинга. Сначала нужно определить симптом, стоимость ошибки, критерии, измерение и границы вывода.", "contentHtml": "
На встрече появляется таблица из двух технологий и итоговых баллов: 86 против 74. Через неделю никто не может ответить, откуда взялись числа. Не указаны версии, входные данные, число повторов и правило обработки разброса. Симптом простой: вывод выглядит точным, но его нельзя воспроизвести.
\nЦена ошибки зависит от решения. Если команда выбирает библиотеку для короткого скрипта, потеря обычно ограничивается временем разработчика. Если выбор затрагивает данные, задержки, безопасность и обслуживание, ошибка переезжает в архитектуру. Она увеличивает стоимость миграции, усложняет диагностику и заставляет защищать решение, основанное на неизвестных предпосылках.
\nТехнология не бывает лучшей сама по себе. Сравнение имеет смысл только внутри задачи: с известными входами, версией, окружением и ограничением по стоимости ошибки. Рейтинг без этих условий смешивает измеряемый сигнал с предпочтением.
\nСначала отделите четыре вещи. Симптом показывает, что в текущем решении болит. Критерий описывает, что важно для новой альтернативы. Измерение даёт наблюдаемый сигнал. Риск показывает, чем обернётся неверный вывод. Вес критерия выражает приоритет. Он не доказывает свойство инструмента.
\nПредположим, сервис обрабатывает одинаковые сообщения и выбирает между двумя библиотеками очередей. Первая жалоба — время обработки скачет. Но этот симптом ещё не говорит, что другая библиотека быстрее. Причиной может быть размер сообщения, холодный процесс, блокировка записи, повторная доставка или неверный таймер.
\nПоэтому вопрос формулируют уже: «Как меняется время обработки сообщения заданного размера при одинаковом числе потребителей и одинаковой политике подтверждения?» Вопрос задаёт границы. Он не обещает ответа до измерения.
\nКритерии должны быть наблюдаемыми или проверяемыми отдельно. Например: p95 времени обработки, доля повторной доставки, сложность миграции, требования к операционному сопровождению. В эти критерии нельзя незаметно включить симпатию к знакомому API. Если удобство важнее задержки, его нужно назвать и объяснить.
\nИзмерение требует протокола. Зафиксируйте версии, размер и форму входа, число потребителей, длительность прогрева, число повторов и способ вычисления итогового значения. Низкое среднее не компенсирует длинный хвост, если именно хвост ломает пользовательский сценарий.
\nРиск связывает наблюдение с последствием. Разница в 2 миллисекунды может быть важной для одного маршрута и бесполезной для другого. Ошибка в оценке миграции может стоить больше, чем разница в скорости. Взвешенная матрица помогает сделать этот обмен видимым, но не делает его объективным автоматически.
\nНиже приведён учебный пример с условными именами queue-a и queue-b. Он не описывает конкретный продукт и не содержит данных реальной системы. Числа весов нужны только для демонстрации расчёта. Их нельзя выдавать за измеренный результат.
| Критерий | Вес | Что наблюдаем | Что пока неизвестно |
|---|---|---|---|
| p95 времени обработки | 35 | Миллисекунды на одинаковом входе | Значение для реальной нагрузки |
| Повторная доставка | 25 | Доля сообщений с повтором | Причины отказов за пределами теста |
| Стоимость миграции | 25 | Список изменений и трудоёмкость шагов | Фактическое время команды |
| Сопровождение | 15 | Количество обязательных компонентов | Долгосрочная нагрузка на операторов |
Сумма весов равна 100. Это проверка полноты, а не доказательство правильности шкалы. Для каждой строки задайте шкалу от 0 до 3 и опишите смысл каждого балла. Нельзя ставить ноль только потому, что данных ещё нет. Отсутствие наблюдения — это unknown, а не плохое значение.
const criteria = [\n { id: 'p95-latency', weight: 35, scoreA: 0, scoreB: 0 },\n { id: 'redelivery', weight: 25, scoreA: 0, scoreB: 0 },\n { id: 'migration-cost', weight: 25, scoreA: 0, scoreB: 0 },\n { id: 'operations', weight: 15, scoreA: 0, scoreB: 0 }\n];\n\nfunction weightedScore(items, side) {\n const weightTotal = items.reduce((sum, item) => sum + item.weight, 0);\n if (weightTotal !== 100) return { status: 'stop-invalid-weights' };\n\n const hasUnknown = items.some((item) => item[side] === 'unknown');\n if (hasUnknown) return { status: 'stop-missing-observation' };\n\n const score = items.reduce(\n (sum, item) => sum + item.weight * item[side] / 3,\n 0\n );\n return { status: 'score-available', score };\n}\n\nconsole.log(weightedScore(criteria, 'scoreA'));\n// { status: 'score-available', score: 0 }\nЭтот код показывает арифметику матрицы. Нулевые баллы здесь означают начальное состояние примера, а не качество queue-a. До подстановки наблюдений функция не выбирает библиотеку. Если один критерий получает unknown, она останавливается. Это важнее красивого итогового числа: неизвестность не должна маскироваться под результат.
Для измерения времени используйте отдельный инструмент и одинаковые условия. Документация Python timeit прямо отделяет измерение небольших фрагментов от общего профилирования. Даже корректное измерение фрагмента не описывает всю систему. Оно отвечает только на вопрос, который вы ему задали.
| Симптом | Причина | Проверка | Действие |
|---|---|---|---|
| Есть победитель, но нет исходных чисел | Предпочтение выдали за наблюдение | Найти входы, версии, протокол и результаты повторов | Убрать вывод и вернуть статус unknown |
| Среднее улучшилось, а ошибки выросли | Смотрели один показатель | Сравнить p95, p99, ошибки и повторы на одном наборе входов | Добавить критерий надёжности и пересчитать решение |
| Числа меняются после каждого запуска | Не зафиксированы прогрев и окружение | Сверить версии, ресурсы, размер входа и число повторов | Уточнить протокол или признать результат несопоставимым |
| Неизвестное значение заменили нулём | Пропуск смешали с плохим результатом | Проверить источник каждого балла | Использовать unknown и остановить итоговую оценку |
| Миграция выглядит дешёвой по одной строке | Не учли данные, откат и обучение | Составить карту изменений, зависимостей и обратного пути | Считать стоимость диапазоном с явными допущениями |
unknown. Не делайте вывод, пока критичный критерий не получил наблюдение.Матрица не заменяет нагрузочное испытание, анализ безопасности и разговор с владельцем данных. Она не предсказывает поведение другой версии, другого размера входа или другого окружения. Если библиотека показывает лучший p95 на маленьком сообщении, это не доказывает преимущество на больших сообщениях.
\nНе всякий вопрос стоит превращать в число. Совместимость лицензий, доступность специалистов и возможность отката могут быть жёсткими ограничениями. Если альтернатива нарушает такое ограничение, её нельзя «спасти» высоким баллом скорости. Сначала применяют стоп-условие, потом сравнивают оставшиеся варианты.
\nОтрицательный путь выглядит так: протокол не описывает входы; версии различаются; повторов мало; часть ошибок исключили; неизвестный показатель заменили нулём; или вывод шире, чем проверенный сценарий. В каждом случае корректное действие — остановиться, назвать пробел и сузить утверждение. Нельзя дорисовывать данные, чтобы таблица выглядела законченной.
\nСравнение готово к инженерному решению, если другой специалист без устных пояснений может найти симптом, цену ошибки, входы, версии, протокол, исходные значения, веса, ограничения и правило остановки. Для каждого итогового балла есть источник. Для каждого неизвестного поля стоит unknown. Вывод не выходит за пределы проверенного сценария.
Быстрая проверка состоит из пяти вопросов: что измеряли; на каких входах; сколько было повторов; какие показатели могли ухудшиться; что произойдёт при ошибочном выборе. Если хотя бы на один вопрос нет ответа, таблица ещё не поддерживает решение. Это не недостаток оформления. Это граница достоверности.
\n