Files
progcode/editorial/agent-rewrites/093.json
T

8 lines
24 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": 93,
"slug": "editorial-2025-06-practice-developer-experience",
"title": "DX внутреннего инструмента: найти место, где застревает задача",
"excerpt": "Практический маршрут от симптома к проверяемой гипотезе: одна задача, пять видов свидетельств, локальная проверка и безопасная остановка без доказательства эффекта.",
"contentHtml": "<p>В учебном сценарии в 09:10 инженер отправляет во внутреннем инструменте заявку на доступ к sandbox. Сервер возвращает <code>200 OK</code>, но к 09:45 разработчик всё ещё не знает, кто следующий владелец. Он открывает чат, повторяет уже введённые данные и получает ответ, который не связан с заявкой. Технический запрос успешен, а задача для человека — нет.</p>\n<p>Цена ошибки выше времени одного ожидания. Разработчик переключается между системами и повторяет ввод. Поддержка отвечает на одинаковые вопросы. Владелец инструмента видит хорошие технические статусы и откладывает проблему. Затем команда чинит заметный экран, хотя задержку создаёт очередь согласования или неясное право доступа.</p>\n<p><strong>Тезис.</strong> Developer experience нельзя надёжно оценить одним средним временем или общим баллом. Нужно взять одну задачу, провести её от входа до результата и сохранить границы каждого вывода. Событие показывает переход. Наблюдение описывает действие человека. Сигнал поддержки указывает вопрос. Решение выбирает изменение. Эффект подтверждает только сопоставимое повторение.</p>\n<h2>Сценарий на практике: одна задача вместо общего DX-score</h2>\n<p>Не начинайте с всего onboarding и не смешивайте разные роли. Возьмите один повторяемый путь. Например, инженер запрашивает доступ к sandbox. Ожидаемый результат — подтверждение или объяснимый отказ. Между ними находятся открытие заявки, отправка, начало ожидания, решение владельца и подтверждение результата.</p>\n<p>У задачи должны быть четыре явные границы. Первая — declared role: кто выполняет действие. Вторая — objective: зачем он его выполняет. Третья — expected result: что можно увидеть и проверить. Четвёртая — comparison boundary: какие условия обязаны совпасть при повторной проверке. Без этих полей сравнение легко превращается в сравнение разных задач.</p>\n<p>Временная метка помогает найти участок пути. Она не объясняет причину. <code>occurredAt</code> может обозначать момент перехода, а <code>observedAt</code> — момент его фиксации. Если запись пришла позже, это свойство наблюдения, а не обязательно задержка процесса. Поэтому время нужно хранить рядом с источником и этапом, а не превращать в самостоятельную оценку удобства.</p>\n<pre><code>const journey = {\n role: 'platform-engineer',\n objective: 'получить sandbox access',\n expectedResult: 'confirmation или объяснимый отказ',\n stages: [\n 'task.opened',\n 'request.submitted',\n 'approval.wait.started',\n 'approval.received',\n 'result.confirmed'\n ],\n waitBucket: '30m-1h',\n nextOwner: 'unknown',\n uxObservation: 'после submit неясен следующий шаг',\n supportSignal: 'routing-unclear',\n effectClaim: 'not-established'\n};</code></pre>\n<p>Код — учебный пример в памяти. Он не обращается к сети, не отправляет telemetry и не описывает реальную заявку. Его задача — показать минимальный набор полей и место, где система должна остановиться. В рабочем инструменте отдельно определяют разрешённые данные, права доступа, срок хранения и правила удаления идентификаторов.</p>\n<p>В учебной сцене важны две временные точки. В 09:10 произошла отправка заявки, а в 09:45 человек открыл чат и повторил вопрос. Это не доказательство того, что интерфейс вызвал задержку: между точками могли быть очередь, ручное согласование и задержка доставки события. Время обозначает участок для расследования, а не готовую причину.</p>\n<h2>Механизм: пять свидетельств отвечают на разные вопросы</h2>\n<p>Событие отвечает: «Что произошло и когда?» Например, заявка перешла из <code>submitted</code> в <code>approval.wait.started</code>. Оно не отвечает, почему человек открыл чат.</p>\n<p>UX-наблюдение отвечает: «Как человек понял или не понял шаг?» Запись «после отправки человек ищет владельца в другом канале» полезнее диагноза «плохой интерфейс». Наблюдение ещё не говорит, что добавление label исправит путь.</p>\n<p>Support signal отвечает: «Какой вопрос повторяется и какая команда может его проверить?» Категория <code>routing-unclear</code> помогает сгруппировать обращения. Она не показывает долю всех пользователей и не доказывает причинность.</p>\n<p>Решение отвечает: «Какое ограниченное изменение проверяем, на каком этапе и кто отвечает?» Хорошее решение содержит owner, target stage, candidate change, окно проверки и условие остановки.</p>\n<p>Effect evidence отвечает: «Что изменилось при той же границе?» Для него нужны та же роль, тот же результат, сопоставимый порядок этапов и заранее определённый признак. Одна удачная запись не становится evidence только потому, что её удобно показать.</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>Восстановить путь от submit до следующего действия одной роли</td><td>Записать UX-наблюдение и проверить видимость owner</td></tr><tr><td>Среднее время ожидания растёт</td><td>В одну метрику попали разные роли и этапы</td><td>Разделить stage, role и wait bucket</td><td>Выбрать одну границу задачи и не строить общий DX-score</td></tr><tr><td>Один отзыв сразу превращается в правку</td><td>Наблюдение смешали с решением</td><td>Отделить действие, вопрос, гипотезу и неизвестное</td><td>Сформулировать candidate change с owner</td></tr><tr><td>Категорию поддержки называют доказательством эффекта</td><td>Нет сопоставимого результата после изменения</td><td>Проверить source, роль, период и тот же ожидаемый результат</td><td>Оставить claim как <code>not-established</code></td></tr><tr><td>После изменения стало «удобнее»</td><td>Повторили другой маршрут или изменили состав роли</td><td>Сравнить objective, stage order, fields и окно наблюдения</td><td>Остановить вывод и повторить задачу по прежней границе</td></tr></tbody></table></div>\n<p>Таблица не заменяет разговор с человеком и не создаёт статистику из одной записи. Она заставляет назвать следующий проверяемый шаг. Если действие нельзя выполнить или его результат нельзя увидеть, это не действие, а пожелание.</p>\n<h2>Почему ожидание не равно причине</h2>\n<p>Корзина <code>30m-1h</code> говорит только о диапазоне между двумя событиями. В одном случае человек не понимает, что заявка уже стоит в очереди. Тогда нужно проверить статус, владельца и текст подтверждения. В другом случае владелец понятен, но согласование зависит от внешнего окна. Тогда интерфейс может честно объяснить ограничение, но не сократить сам процесс.</p>\n<p>Одинаковый wait bucket поэтому ведёт к разным решениям. Нельзя строить DX-score из времени и выдавать его за причину. Нельзя считать отсутствие обращения доказательством удобства: человек мог не иметь канала поддержки, а событие могло не видеть ручной шаг в соседней системе.</p>\n<figure><img src=\"/assets/editorial/2025/developer-experience-2025-task-journey.svg\" alt=\"Схема пути одной задачи: вход, отправка, ожидание, сигнал поддержки, решение владельца, повторная проверка и остановка без доказательства эффекта\" loading=\"lazy\" /><figcaption>Учебная иллюстрация. Красная ветка означает остановку вывода, если повторная проверка не сопоставима с исходной задачей.</figcaption></figure>\n<p>Источник каждого факта должен быть виден рядом с ним. Запись журнала подтверждает событие. Интервью или наблюдение подтверждает вопрос человека. Категория поддержки подтверждает повторяемую формулировку. Ни один источник не заменяет остальные. OpenTelemetry полезен здесь как пример дисциплины временных событий: имя и время помогают восстановить переход, но не отвечают на продуктовый вопрос о понятности шага.</p>\n<h2>Локальная проверка на воспроизводимом JSON</h2>\n<p>До подключения к внутреннему сервису можно проверить структуру на фикстуре. Сохраните следующий фрагмент как <code>journey.json</code>. Даты и строки придуманы для примера; это не telemetry и не результат измерения.</p>\n<pre><code>{\n \"role\": \"platform-engineer\",\n \"objective\": \"получить доступ к sandbox\",\n \"expectedResult\": \"confirmation или объяснимый отказ\",\n \"stages\": [\n {\n \"name\": \"request.submitted\",\n \"occurredAt\": \"2025-06-07T09:10:00Z\",\n \"observedAt\": \"2025-06-07T09:10:02Z\"\n },\n {\n \"name\": \"approval.wait.started\",\n \"occurredAt\": \"2025-06-07T09:10:01Z\",\n \"observedAt\": \"2025-06-07T09:10:02Z\"\n }\n ],\n \"effectClaim\": {\n \"status\": \"not-established\",\n \"evidenceCount\": 1\n }\n}</code></pre>\n<p>Команда ниже проверяет обязательные поля, порядок времени и уникальность этапов. Она должна завершиться строкой <code>LOCAL CHECK PASS</code>. Если удалить <code>expectedResult</code>, изменить порядок дат или поставить <code>status</code> в <code>established</code> при одном evidence, команда завершится ошибкой.</p>\n<pre><code>node --input-type=module - &lt;&lt;'NODE'\nimport fs from 'node:fs';\n\nconst journey = JSON.parse(fs.readFileSync('journey.json', 'utf8'));\nconst required = ['role', 'objective', 'expectedResult', 'stages', 'effectClaim'];\nconst missing = required.filter((key) =&gt; !(key in journey));\nif (missing.length) throw new Error('missing: ' + missing.join(', '));\nif (!Array.isArray(journey.stages) || journey.stages.length &lt; 2) {\n throw new Error('at least two stages are required');\n}\n\nconst names = new Set();\nlet previousOccurred = -Infinity;\nfor (const stage of journey.stages) {\n if (!stage.name || names.has(stage.name)) throw new Error('duplicate stage');\n names.add(stage.name);\n const occurred = Date.parse(stage.occurredAt);\n const observed = Date.parse(stage.observedAt);\n if (!Number.isFinite(occurred) || !Number.isFinite(observed)) {\n throw new Error('invalid timestamp');\n }\n if (occurred &gt; observed || occurred &lt; previousOccurred) {\n throw new Error('timestamps are not comparable');\n }\n previousOccurred = occurred;\n}\nif (journey.effectClaim.status === 'established' &amp;&amp;\n journey.effectClaim.evidenceCount &lt; 2) {\n throw new Error('effect claim needs comparable evidence');\n}\nconsole.log('LOCAL CHECK PASS');\nNODE</code></pre>\n<p>Это минимальный структурный guard, а не исследование DX. Он не проверяет, правдивы ли даты, кто действительно выполнил действие, что происходило в очереди и насколько часто встречался симптом. Он лишь оставляет место остановки до интеграции.</p>\n<h2>Отрицательный путь: остановиться при слабом доказательстве</h2>\n<p>Представим, что после одной записи команда меняет <code>effectClaim</code> на <code>established</code>. В качестве evidence она прикладывает ту же запись. Такой вывод нужно отклонить. Нет второй точки сравнения. Неизвестно, совпала ли роль. Нельзя отделить эффект изменения от внешнего окна согласования.</p>\n<pre><code>function decide(claim) {\n const comparable = claim.sameRole &&\n claim.sameObjective &&\n claim.sameStageBoundary &&\n claim.evidenceCount &gt;= 2;\n\n if (claim.status === 'established' &amp;&amp; !comparable) {\n return {\n status: 'HOLD',\n reason: 'effect-claim-not-evidenced'\n };\n }\n\n return { status: 'needs-owner-decision' };\n}</code></pre>\n<p>Это тоже учебный пример. Функция проверяет условие остановки на объекте в памяти. Она не оценивает правдивость внешних данных и ничего не меняет в production. В настоящем валидаторе понадобятся проверки схемы, порядка этапов, формата времени, источника события и разрешений.</p>\n<p><code>HOLD</code> не означает, что гипотеза неверна. Он означает, что текущая запись не отвечает на вопрос. Это защищает команду от преждевременного переноса решения на другие роли и задачи. Если показ owner уменьшит число вопросов, повторная проверка это обнаружит. Если причина лежит во внешней очереди, результат будет другим: интерфейс улучшит объяснение, но не сократит ожидание.</p>\n<h2>Порядок действий</h2>\n<ol><li><strong>Назовите одну задачу.</strong> Зафиксируйте role, objective и ожидаемый результат. Не включайте весь onboarding в один маршрут.</li><li><strong>Опишите этапы.</strong> Задайте порядок событий, source, <code>occurredAt</code>, <code>observedAt</code> и допустимые wait buckets.</li><li><strong>Соберите наблюдения.</strong> Запишите видимое действие или вопрос человека без диагноза. Отдельно сохраните support signal и known unknowns.</li><li><strong>Выберите одну гипотезу.</strong> Назначьте owner, target stage, candidate change и признак, который можно проверить.</li><li><strong>Зафиксируйте границу сравнения.</strong> Сохраните ту же роль, цель, ожидаемый результат и порядок этапов. Заранее задайте окно повторной проверки.</li><li><strong>Проверьте отрицательные входы.</strong> Подайте неизвестного owner, пропущенный source, нарушенный порядок и неподтверждённый effect claim. Для каждого ожидайте остановку с причиной.</li><li><strong>Примите ограниченное решение.</strong> Передавайте изменение дальше только при сопоставимом evidence. Иначе сохраните <code>not-established</code> и сформулируйте, каких данных не хватает.</li></ol>\n<h2>Ограничения применения</h2>\n<p>Эта модель не даёт репрезентативную выборку. Она не измеряет cognitive cost, удовлетворённость, частоту обращений или стоимость поддержки. Она не заменяет user research, проверку доступности, нагрузочное тестирование и анализ прав. Она помогает не смешать вопросы и не выдать техническую запись за ответ на каждый из них.</p>\n<p>Учебные значения нельзя публиковать как результат внутреннего инструмента. В production-пути могут отличаться часы, задержка доставки, идентичность роли, источник ручной работы и правила хранения данных. Любое сравнение должно сохранять версию контракта и явно отмечать изменения границы.</p>\n<p>Показ владельца до отправки может снизить число вопросов о маршрутизации и не изменить очередь согласования. Ускорение одного этапа может увеличить нагрузку на другой. Поэтому изменение должно иметь narrow target и stop condition, а не обещание «сделать DX лучше».</p>\n<h2>Проверяемый критерий готовности</h2>\n<p>Разбор готов к инженерному решению, когда другой человек без устного пересказа может восстановить role, objective, expected result, stage order, source, wait bucket, UX-наблюдение, support signal, unknowns, owner, candidate change и comparison boundary. Он понимает, какой результат подтвердит гипотезу, а какой остановит вывод.</p>\n<p>Минимальная проверка даёт три наблюдаемых исхода. Корректная задача проходит структурную проверку и остаётся гипотезой до решения владельца. Неполный source, неверный порядок и forged effect claim возвращают <code>HOLD</code> с причиной. Ни один учебный вызов не отправляет данные и не меняет production. Только после этого можно подключать разрешённые источники и повторять тот же путь.</p>\n<h2>Проверяемые источники</h2><ul><li><a href=\"https://opentelemetry.io/docs/specs/otel/trace/api/\" target=\"_blank\" rel=\"noopener noreferrer\">OpenTelemetry: Tracing API</a> — официальная спецификация описывает spans, timestamps и events. Она помогает назвать техническую запись, но не определяет DX-score и не доказывает эффект изменения.</li><li><a href=\"https://www.gov.uk/service-manual/measuring-success/measuring-the-success-of-your-service\" target=\"_blank\" rel=\"noopener noreferrer\">GOV.UK Service Manual: Measuring the success of your service</a> — официальное руководство рекомендует сочетать performance metrics с user research, feedback и повторяемым измерением пути. Оно не задаёт пороги для конкретного внутреннего инструмента.</li><li><a href=\"https://www.w3.org/TR/performance-timeline/\" target=\"_blank\" rel=\"noopener noreferrer\">W3C Performance Timeline</a> — официальный стандарт задаёт примитивы для доступа к временным записям web-приложения. Он не превращает техническое время в доказательство понятности или удобства.</li></ul>"
}