Files
progcode/editorial/agent-rewrites/030.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
15 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": 30,
"slug": "editorial-2027-03-practice-d-lessons",
"title": "D для прикладной утилиты: как проверить, нужен ли новый язык",
"excerpt": "Перед переходом на D измерьте workload, найдите границу с native-кодом и сравните стоимость toolchain с реальным выигрышем. Учебный фильтр и критерий готовности помогают принять решение, включая отказ от миграции.",
"contentHtml": "<p>Утилита запускается медленно, занимает больше памяти, чем ожидалось, или требует вызова C-библиотеки. Команда сразу предлагает переписать её на D: язык компилируется в native binary, умеет работать с C ABI и даёт контроль над памятью. Но симптом ещё не показывает причину. Задержку может создавать сеть, формат файла, лишние копии или неверная граница API. Цена ошибочного выбора — новый компилятор, сборочный pipeline, обучение и месяцы поддержки без исправления узкого места.</p>\n<p>Тезис простой: D стоит проверять не по списку свойств языка, а по контракту задачи. Сначала зафиксируйте workload и бюджет, затем найдите участок, который действительно изменится при смене языка. После этого сравните D с текущим инструментом по измеримому эффекту и полной стоимости доставки. Если доказательства не складываются, решение остаться на текущем языке будет корректным результатом.</p>\n<h2>Сначала отделите симптом от причины</h2>\n<p>Фраза «нужна производительность» не задаёт задачи. Для CLI важны время запуска, время обработки одного входа, пиковая память и размер бинарника. Для фонового процесса важны throughput, steady-state latency и поведение после нескольких часов работы. Для сервиса добавляются конкуренция, timeout и наблюдаемость. Для вызова C-библиотеки важны layout структуры, calling convention, ownership указателей и код ошибки.</p>\n<p>Запишите один сценарий, а не среднее впечатление. Например: «утилита читает 2 ГБ логов, должна обработать файл менее чем за 20 секунд, запускается на Linux x86_64 и arm64, а парсер отдаёт данные в C-библиотеку». В таком описании уже видны единица нагрузки, предел времени, targets и native boundary. Без них benchmark легко превращается в сравнение несопоставимых программ.</p>\n<figure><img src=\"/assets/editorial/2027/d-lessons-2027-runtime-tradeoff-map.svg\" alt=\"Карта выбора языка для прикладной утилиты: нагрузка и ограничения ведут к сравнению D с текущим инструментом\" loading=\"lazy\" /><figcaption>Выбор начинается с workload и ограничений. D появляется как проверяемый вариант только после описания границы задачи.</figcaption></figure>\n<h2>Механизм решения</h2>\n<p>У решения есть четыре связанные части. Workload показывает, сколько данных и операций проходит через код. Бюджет latency задаёт допустимую цену одной операции. Native boundary показывает, нужен ли прямой доступ к C, системному вызову или нативному формату. Target matrix показывает, сколько раз придётся собрать, протестировать и доставить бинарник.</p>\n<p>D может быть сильным кандидатом, когда горячий участок вычисляет данные локально, нужен native deployment или уже есть C ABI. Но это только основание для эксперимента. У D остаются стоимость компилятора и зависимостей, различия runtime, диагностика бинарника, упаковка под несколько архитектур и время команды. Нативный бинарник не отменяет сетевую задержку и не делает внешний API безопасным.</p>\n<p>Контрактные проверки полезны внутри функции. Precondition проверяет входной инвариант, postcondition — свойство результата. Они не заменяют проверку пользовательского файла, обработку ошибки и тесты. Проверка должна принадлежать тому уровню, который владеет условием: parser проверяет формат, доменный код — смысл, wrapper — указатель, длину и время жизни.</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>Профиль cold start с отключённой сетью</td><td>Исправить инициализацию; язык менять только при доказанном CPU-узком месте</td></tr><tr><td>Медленная обработка файла</td><td>Копии строк, декодирование, неверный алгоритм</td><td>Профиль CPU и аллокаций на одном входе</td><td>Сравнить алгоритм и парный прототип D</td></tr><tr><td>Рост RSS</td><td>Долгоживущие ссылки, кэш, фрагментация</td><td>Снять профиль памяти по этапам batch</td><td>Укоротить lifetime; не отключать GC по одному графику</td></tr><tr><td>Падение в C-вызове</td><td>Неверная длина, layout или ownership</td><td>Сверить header, размер, offset и код возврата</td><td>Изолировать wrapper и остановить вызов при несовпадении</td></tr><tr><td>Сложная доставка</td><td>Несколько архитектур и ручная упаковка</td><td>Собрать чистые артефакты для каждого target</td><td>Сравнить цену toolchain с выигрышем runtime</td></tr></tbody></table></div>\n<h2>Учебный фильтр требований</h2>\n<p>Следующая функция не измеряет скорость и не выбирает язык автоматически. Она превращает карточку задачи в явные условия. Числа учебные: throughput 12 000 и бюджет 20 мс нельзя переносить на другое железо. В реальном решении их заменяют измерениями одного workload.</p>\n<pre><code>function decideD(input) {\n const throughput = Number(input.throughput);\n const latencyBudgetMs = Number(input.latencyBudgetMs);\n const targets = Number(input.deploymentTargets);\n if (!Number.isFinite(throughput) || throughput &lt;= 0) return { decision: 'reject', reason: 'нет измеримой нагрузки' };\n if (latencyBudgetMs &lt;= 0) return { decision: 'reject', reason: 'нет бюджета задержки' };\n if (targets &gt; 2 &amp;&amp; !input.nativeBoundary) return { decision: 'compare', reason: 'сначала сравнить toolchain' };\n if (input.nativeBoundary &amp;&amp; throughput &gt; 10000) return { decision: 'prototype-d', reason: 'есть основание для парного прототипа' };\n return { decision: 'keep-current-tool', reason: 'смена языка не обоснована' };\n}\n\nconsole.log(decideD({ throughput: 12000, latencyBudgetMs: 20, nativeBoundary: true, deploymentTargets: 1 }));\n// Учебный результат: { decision: 'prototype-d', ... }</code></pre>\n<p>Положительная ветка означает только «собрать прототип». Она не означает «переписать продукт». Ветка <code>keep-current-tool</code> нужна намеренно: если нагрузка мала, границы с native-кодом нет, а текущий стек уже покрывает доставку, новый язык увеличит риск без доказанной пользы. Ветка <code>compare</code> останавливает преждевременный выбор при широкой матрице targets.</p>\n<h2>Как проверять нативную границу</h2>\n<p>Указатель и длина образуют один контракт. Сам указатель не сообщает, сколько байт можно читать. Wrapper должен получить буфер, проверить его владельца и диапазон, а затем передать в C только проверенный slice или пару pointer/length. Если библиотека сохраняет адрес после возврата, обычного временного буфера недостаточно: нужен согласованный lifetime или копия.</p>\n<p>Структуру тоже нельзя считать совместимой по имени полей. Сверьте размер, offsets, alignment, порядок байтов и calling convention. Отдельно зафиксируйте значения кода ошибки. Частично заполненный output не равен успешному результату. Сначала проверьте код возврата, затем версию и layout, потом отдайте значение доменному коду.</p>\n<h2>Действия по порядку</h2>\n<ol><li>Записать единицу нагрузки, размер входа, бюджет задержки, пик памяти, targets и ожидаемый результат.</li><li>Повторить симптом на одном входе и снять профиль CPU, аллокаций, памяти или cold start. Не менять язык до появления измеримого узкого места.</li><li>Сравнить текущий инструмент и D по алгоритму, библиотекам, сборке, отладке, размеру артефакта и времени поддержки.</li><li>Описать native boundary: типы, размер, ownership, lifetime, порядок байтов, код ошибки и вариант отказа.</li><li>Собрать маленький парный прототип с одинаковым входом, выходом и методикой замера. Проверить положительный и отрицательный путь.</li><li>Принять решение по заранее заданному критерию. Сохранить измерения и стоимость поддержки рядом с кодом прототипа.</li></ol>\n<h2>Ограничения</h2>\n<p>Фильтр не заменяет profiler, benchmark и review ABI. Его пороги вымышлены и нужны только для формы проверки. Даже хороший benchmark не переносит результат на другую архитектуру, версию компилятора, размер данных или режим нагрузки. Нативная сборка не гарантирует меньшую память. Контракт не исправляет неверную бизнес-логику.</p>\n<p>Оценка должна учитывать отрицательный путь. Если wrapper не может доказать длину, lifetime или layout, вызов нужно остановить. Если D выигрывает только в искусственном микротесте, но требует отдельной упаковки и дежурства, выигрыш не доказан. Если текущий язык после устранения лишних копий укладывается в бюджет, миграция не нужна.</p>\n<h2>Проверяемый критерий готовности</h2>\n<p>Решение готово, когда для одного и того же workload есть повторяемые замеры текущего инструмента и прототипа D, описаны targets и native boundary, а также измерена цена сборки и поддержки. Для каждого результата указаны вход, версия toolchain, архитектура, число повторов и критерий успеха. Вызов C проходит только после проверки размера, lifetime и кода ошибки. Команда может объяснить не только почему D быстрее, но и почему это преимущество покрывает стоимость доставки.</p>\n<h2>Проверяемые источники</h2><ul><li><a href=\"https://dlang.org/spec/function.html\" target=\"_blank\" rel=\"noopener noreferrer\">D Language Specification: Functions and Function Safety</a> — контракты функций и атрибуты безопасности.</li><li><a href=\"https://dlang.org/spec/memory-safe-d.html\" target=\"_blank\" rel=\"noopener noreferrer\">D Language Specification: Memory Safety</a> — границы <code>@safe</code>, <code>@trusted</code> и <code>@system</code>.</li><li><a href=\"https://dlang.org/spec/abi.html\" target=\"_blank\" rel=\"noopener noreferrer\">D Language Specification: Application Binary Interface</a> — представление типов и ABI-ограничения.</li></ul>"
}