Files
progcode/editorial/agent-rewrites/040.json
T
huncode 2d914b543f
Build and deploy / deploy (push) Failing after 15s
Publish rewritten technical article archive
2026-08-02 22:19:34 +03:00

8 lines
18 KiB
JSON
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
{
"index": 40,
"slug": "editorial-2026-11-field-technology-evaluation",
"title": "Как сравнивать технологии, когда цена ошибки выше цены эксперимента",
"excerpt": "Сравнение технологий начинается не с рейтинга. Сначала нужно определить симптом, стоимость ошибки, критерии, измерение и границы вывода.",
"contentHtml": "<p>На встрече появляется таблица из двух технологий и итоговых баллов: 86 против 74. Через неделю никто не может ответить, откуда взялись числа. Не указаны версии, входные данные, число повторов и правило обработки разброса. Симптом простой: вывод выглядит точным, но его нельзя воспроизвести.</p>\n<p>Цена ошибки зависит от решения. Если команда выбирает библиотеку для короткого скрипта, потеря обычно ограничивается временем разработчика. Если выбор затрагивает данные, задержки, безопасность и обслуживание, ошибка переезжает в архитектуру. Она увеличивает стоимость миграции, усложняет диагностику и заставляет защищать решение, основанное на неизвестных предпосылках.</p>\n<h2>Тезис: сравнивать нужно не названия, а условия</h2>\n<p>Технология не бывает лучшей сама по себе. Сравнение имеет смысл только внутри задачи: с известными входами, версией, окружением и ограничением по стоимости ошибки. Рейтинг без этих условий смешивает измеряемый сигнал с предпочтением.</p>\n<p>Сначала отделите четыре вещи. Симптом показывает, что в текущем решении болит. Критерий описывает, что важно для новой альтернативы. Измерение даёт наблюдаемый сигнал. Риск показывает, чем обернётся неверный вывод. Вес критерия выражает приоритет. Он не доказывает свойство инструмента.</p>\n<h2>Механизм: четыре слоя решения</h2>\n<p>Предположим, сервис обрабатывает одинаковые сообщения и выбирает между двумя библиотеками очередей. Первая жалоба — время обработки скачет. Но этот симптом ещё не говорит, что другая библиотека быстрее. Причиной может быть размер сообщения, холодный процесс, блокировка записи, повторная доставка или неверный таймер.</p>\n<p>Поэтому вопрос формулируют уже: «Как меняется время обработки сообщения заданного размера при одинаковом числе потребителей и одинаковой политике подтверждения?» Вопрос задаёт границы. Он не обещает ответа до измерения.</p>\n<p>Критерии должны быть наблюдаемыми или проверяемыми отдельно. Например: p95 времени обработки, доля повторной доставки, сложность миграции, требования к операционному сопровождению. В эти критерии нельзя незаметно включить симпатию к знакомому API. Если удобство важнее задержки, его нужно назвать и объяснить.</p>\n<p>Измерение требует протокола. Зафиксируйте версии, размер и форму входа, число потребителей, длительность прогрева, число повторов и способ вычисления итогового значения. Низкое среднее не компенсирует длинный хвост, если именно хвост ломает пользовательский сценарий.</p>\n<p>Риск связывает наблюдение с последствием. Разница в 2 миллисекунды может быть важной для одного маршрута и бесполезной для другого. Ошибка в оценке миграции может стоить больше, чем разница в скорости. Взвешенная матрица помогает сделать этот обмен видимым, но не делает его объективным автоматически.</p>\n<figure><img src=\"/assets/editorial/2026/technology-evaluation-2026-evidence-handoff-loop.svg\" alt=\"Схема сравнения технологий: проблема переходит в критерии, измерение и проверку ограничений, после чего решение принимают только по наблюдаемым данным\" loading=\"lazy\" /><figcaption>Существующая схема показывает границы сравнения: критерии и условия измерения должны быть видимыми до вывода.</figcaption></figure>\n<h2>Учебный пример: матрица для двух библиотек</h2>\n<p>Ниже приведён учебный пример с условными именами <code>queue-a</code> и <code>queue-b</code>. Он не описывает конкретный продукт и не содержит данных реальной системы. Числа весов нужны только для демонстрации расчёта. Их нельзя выдавать за измеренный результат.</p>\n<div class=\"table-scroll\"><table><caption>Границы сравнения до получения чисел</caption><thead><tr><th scope=\"col\">Критерий</th><th scope=\"col\">Вес</th><th scope=\"col\">Что наблюдаем</th><th scope=\"col\">Что пока неизвестно</th></tr></thead><tbody><tr><td>p95 времени обработки</td><td>35</td><td>Миллисекунды на одинаковом входе</td><td>Значение для реальной нагрузки</td></tr><tr><td>Повторная доставка</td><td>25</td><td>Доля сообщений с повтором</td><td>Причины отказов за пределами теста</td></tr><tr><td>Стоимость миграции</td><td>25</td><td>Список изменений и трудоёмкость шагов</td><td>Фактическое время команды</td></tr><tr><td>Сопровождение</td><td>15</td><td>Количество обязательных компонентов</td><td>Долгосрочная нагрузка на операторов</td></tr></tbody></table></div>\n<p>Сумма весов равна 100. Это проверка полноты, а не доказательство правильности шкалы. Для каждой строки задайте шкалу от 0 до 3 и опишите смысл каждого балла. Нельзя ставить ноль только потому, что данных ещё нет. Отсутствие наблюдения — это <code>unknown</code>, а не плохое значение.</p>\n<h2>Пример кода с явной границей</h2>\n<pre><code>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) =&gt; sum + item.weight, 0);\n if (weightTotal !== 100) return { status: 'stop-invalid-weights' };\n\n const hasUnknown = items.some((item) =&gt; item[side] === 'unknown');\n if (hasUnknown) return { status: 'stop-missing-observation' };\n\n const score = items.reduce(\n (sum, item) =&gt; 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 }</code></pre>\n<p>Этот код показывает арифметику матрицы. Нулевые баллы здесь означают начальное состояние примера, а не качество <code>queue-a</code>. До подстановки наблюдений функция не выбирает библиотеку. Если один критерий получает <code>unknown</code>, она останавливается. Это важнее красивого итогового числа: неизвестность не должна маскироваться под результат.</p>\n<p>Для измерения времени используйте отдельный инструмент и одинаковые условия. Документация Python <code>timeit</code> прямо отделяет измерение небольших фрагментов от общего профилирования. Даже корректное измерение фрагмента не описывает всю систему. Оно отвечает только на вопрос, который вы ему задали.</p>\n<h2>Симптом → причина → проверка → действие</h2>\n<div class=\"table-scroll\"><table><caption>Диагностика неубедительного сравнения</caption><thead><tr><th scope=\"col\">Симптом</th><th scope=\"col\">Причина</th><th scope=\"col\">Проверка</th><th scope=\"col\">Действие</th></tr></thead><tbody><tr><td>Есть победитель, но нет исходных чисел</td><td>Предпочтение выдали за наблюдение</td><td>Найти входы, версии, протокол и результаты повторов</td><td>Убрать вывод и вернуть статус <code>unknown</code></td></tr><tr><td>Среднее улучшилось, а ошибки выросли</td><td>Смотрели один показатель</td><td>Сравнить p95, p99, ошибки и повторы на одном наборе входов</td><td>Добавить критерий надёжности и пересчитать решение</td></tr><tr><td>Числа меняются после каждого запуска</td><td>Не зафиксированы прогрев и окружение</td><td>Сверить версии, ресурсы, размер входа и число повторов</td><td>Уточнить протокол или признать результат несопоставимым</td></tr><tr><td>Неизвестное значение заменили нулём</td><td>Пропуск смешали с плохим результатом</td><td>Проверить источник каждого балла</td><td>Использовать <code>unknown</code> и остановить итоговую оценку</td></tr><tr><td>Миграция выглядит дешёвой по одной строке</td><td>Не учли данные, откат и обучение</td><td>Составить карту изменений, зависимостей и обратного пути</td><td>Считать стоимость диапазоном с явными допущениями</td></tr></tbody></table></div>\n<h2>Порядок действий</h2>\n<ol><li>Опишите наблюдаемый симптом: что именно изменилось, где это видно и кому мешает.</li><li>Назовите цену ошибки: задержка, потеря данных, трудоёмкость отката или дополнительное сопровождение.</li><li>Сформулируйте один вопрос с измеримыми входами и двумя сравниваемыми альтернативами.</li><li>Выберите критерии, задайте шкалы и веса; проверьте, что каждый вес имеет объяснение, а сумма равна 100.</li><li>Зафиксируйте версии, окружение, входные данные, прогрев, повторы и формулу расчёта.</li><li>Проведите одинаковые измерения для каждой альтернативы и сохраните исходные значения, а не только средний балл.</li><li>Отдельно проверьте хвост распределения, ошибки, повторную доставку и стоимость изменений.</li><li>Отметьте неизвестные поля как <code>unknown</code>. Не делайте вывод, пока критичный критерий не получил наблюдение.</li><li>Сравните результат с порогом решения и укажите, какие условия ограничивают перенос вывода.</li></ol>\n<h2>Ограничения и отрицательный путь</h2>\n<p>Матрица не заменяет нагрузочное испытание, анализ безопасности и разговор с владельцем данных. Она не предсказывает поведение другой версии, другого размера входа или другого окружения. Если библиотека показывает лучший p95 на маленьком сообщении, это не доказывает преимущество на больших сообщениях.</p>\n<p>Не всякий вопрос стоит превращать в число. Совместимость лицензий, доступность специалистов и возможность отката могут быть жёсткими ограничениями. Если альтернатива нарушает такое ограничение, её нельзя «спасти» высоким баллом скорости. Сначала применяют стоп-условие, потом сравнивают оставшиеся варианты.</p>\n<p>Отрицательный путь выглядит так: протокол не описывает входы; версии различаются; повторов мало; часть ошибок исключили; неизвестный показатель заменили нулём; или вывод шире, чем проверенный сценарий. В каждом случае корректное действие — остановиться, назвать пробел и сузить утверждение. Нельзя дорисовывать данные, чтобы таблица выглядела законченной.</p>\n<h2>Проверяемый критерий готовности</h2>\n<p>Сравнение готово к инженерному решению, если другой специалист без устных пояснений может найти симптом, цену ошибки, входы, версии, протокол, исходные значения, веса, ограничения и правило остановки. Для каждого итогового балла есть источник. Для каждого неизвестного поля стоит <code>unknown</code>. Вывод не выходит за пределы проверенного сценария.</p>\n<p>Быстрая проверка состоит из пяти вопросов: что измеряли; на каких входах; сколько было повторов; какие показатели могли ухудшиться; что произойдёт при ошибочном выборе. Если хотя бы на один вопрос нет ответа, таблица ещё не поддерживает решение. Это не недостаток оформления. Это граница достоверности.</p>\n<h2>Проверяемые источники</h2><ul><li><a href=\"https://csrc.nist.gov/pubs/sp/800/30/r1/final\" target=\"_blank\" rel=\"noopener noreferrer\">NIST SP 800-30 Rev. 1: Guide for Conducting Risk Assessments</a> — официальный документ NIST о выявлении, анализе и оценке рисков. Он задаёт язык риска, но не выбирает технологию и не подтверждает конкретные числа.</li><li><a href=\"https://docs.python.org/3/library/timeit.html\" target=\"_blank\" rel=\"noopener noreferrer\">Python documentation: timeit — Measure execution time of small code snippets</a> — официальная документация инструмента для измерения небольших фрагментов. Она не превращает локальное измерение в характеристику всей системы.</li></ul>"
}