8 lines
25 KiB
JSON
8 lines
25 KiB
JSON
{
|
||
"index": 55,
|
||
"slug": "editorial-2026-06-field-multi-runtime",
|
||
"title": "PHP, JavaScript и D на одной границе: как доказать совместимость",
|
||
"excerpt": "Практический разбор расхождения между PHP, JavaScript и D: явный контракт значения, ошибки и времени, отрицательные проверки и границы вывода.",
|
||
"contentHtml": "<p>Часть запроса проходит через PHP, затем попадает в JavaScript, а результат обрабатывает компонент на D. Пользователь получает ошибку без понятной причины, а оператор не может связать запись D с исходным запросом. На границе всё выглядит правдоподобно: поля называются одинаково, JSON похож, а число времени совпадает. Но один участник возвращает значение, другой выбрасывает исключение, третий записывает код отдельно. Это не совместимость, а потеря смысла, которую пока не видно.</p>\n<p>Цена ошибки — повторная операция, пропущенный отказ или расследование без доказательства связи событий. Клиент может повторить уже принятую команду, worker — принять неполный ответ за успех, а команда — объявить проблему задержкой, хотя сравнивает разные шкалы времени. Статья показывает, как проверить одну узкую границу и получить воспроизводимый результат. Она не объявляет три языка совместимыми и не заменяет тест реального сервиса.</p>\n<h2>Вопрос, на который отвечает проверка</h2>\n<p>Нужно ответить не на вопрос «могут ли три языка работать в одной системе», а на более точный: «сохраняет ли каждый участник заранее названный смысл конкретной записи». Для этого у записи должны быть версия схемы, операция, описанное значение, режим ошибки и основание времени. Участники сравниваются с этим контрактом по одинаковым правилам. Сравнение PHP с JavaScript напрямую не заменяет сравнение каждого из них с общей спецификацией.</p>\n<p>Такой подход отделяет факт от предположения. Факт — в записи есть <code>schemaVersion: 'boundary-1'</code>, значение помечено тегом <code>order-ready</code>, а интервал задан логическими шагами от 100 до 108. Предположение — что этот объект действительно прошёл через PHP, JavaScript и D. Поля <code>model</code> с названиями языков не превращаются в доказательство запуска. Для последнего нужны логи, транспорт, версии сборок и тест конкретного приложения.</p>\n<h2>Контракт начинается со смысла, а не с формы JSON</h2>\n<p>JSON описывает синтаксическую форму, но не объясняет значение числа или строки. Число <code>4200</code> может быть суммой в минимальных единицах, лимитом, идентификатором или счётчиком. Строка <code>ready</code> может быть состоянием домена, текстом интерфейса или случайным результатом сравнения. Поэтому поле <code>value.tag</code> должно называться явно, а денежное значение — иметь единицу и валюту. Если потребитель угадывает смысл по имени поля, контракт уже неполон.</p>\n<p>Операция и версия нужны для защиты от тихой подмены. Контракт для <code>fixed-order-decision</code> нельзя автоматически применять к другому действию только потому, что там тоже есть <code>amountMinor</code>. Версия сообщает, по какому набору правил читать запись. Это не версия PHP, Node.js или компилятора D. Версии runtime и библиотек остаются отдельными атрибутами интеграционного теста.</p>\n<p>Ошибку тоже нужно описывать как данные о поведении. В примере <code>error.semantics</code> равен <code>named-envelope</code>, <code>code</code> может быть <code>null</code>, а <code>retry</code> имеет значение <code>not-requested</code>. Последняя строка не означает, что повтор безопасен: она лишь фиксирует, что данная запись не запрашивает повтор. Идемпотентность, эффект повторного вызова и политика клиента требуют отдельного контракта.</p>\n<p>Время должно иметь <code>basis</code>. Фиксированные логические шаги подходят для проверки порядка в заранее созданном объекте. Они не являются миллисекундами, latency или SLA. Для наблюдаемой длительности нужны источник часов, единицы измерения, границы интервала и правило обработки рассинхронизации. Подстановка текущего времени в пропущенное поле делает пример менее воспроизводимым и скрывает ошибку.</p>\n<h2>Воспроизводимая проверка в памяти</h2>\n<p>Ниже — самостоятельный пример на JavaScript. Его можно сохранить в файл и запустить в среде с поддержкой современного синтаксиса JavaScript. Он проверяет только два заранее заданных объекта в памяти: полный набор участников и набор с изменённой семантикой ошибки. Пример не запускает PHP и D, не вызывает сеть, не читает системные часы, не измеряет скорость и не проверяет сериализацию.</p>\n<pre><code>const contract = {\n schemaVersion: 'boundary-1',\n operation: 'fixed-order-decision',\n value: { tag: 'order-ready', amountMinor: 4200, currency: 'RUB' },\n error: { semantics: 'named-envelope', code: null, retry: 'not-requested' },\n time: { basis: 'fixed-logical-ticks', opened: 100, closed: 108 }\n};\n\nconst participants = [\n { model: 'php', version: 'boundary-1', valueTag: 'order-ready',\n errorSemantics: 'named-envelope', timeBasis: 'fixed-logical-ticks', mapping: 'exact' },\n { model: 'javascript', version: 'boundary-1', valueTag: 'order-ready',\n errorSemantics: 'named-envelope', timeBasis: 'fixed-logical-ticks', mapping: 'exact' },\n { model: 'd', version: 'boundary-1', valueTag: 'order-ready',\n errorSemantics: 'named-envelope', timeBasis: 'fixed-logical-ticks', mapping: 'exact' }\n];\n\nfunction review(record, items) {\n if (record?.schemaVersion !== 'boundary-1' ||\n record.operation !== 'fixed-order-decision' || items.length !== 3) {\n return 'stop-incomplete-contract';\n }\n if (record.value?.tag !== 'order-ready' ||\n record.value.currency !== 'RUB' || !Number.isInteger(record.value.amountMinor)) {\n return 'stop-untagged-value';\n }\n if (record.error?.semantics !== 'named-envelope' ||\n !Object.hasOwn(record.error, 'code') || !Object.hasOwn(record.error, 'retry')) {\n return 'stop-ambiguous-error';\n }\n if (record.time?.basis !== 'fixed-logical-ticks' ||\n !Number.isInteger(record.time.opened) || !Number.isInteger(record.time.closed) ||\n record.time.opened > record.time.closed) {\n return 'stop-undetermined-time-boundary';\n }\n const knownModels = new Set(['php', 'javascript', 'd']);\n const exact = items.every((item) =>\n knownModels.has(item.model) &&\n item.version === record.schemaVersion &&\n item.valueTag === record.value.tag &&\n item.errorSemantics === record.error.semantics &&\n item.timeBasis === record.time.basis &&\n item.mapping === 'exact'\n );\n return exact ? 'accepted-fixed-contract' : 'stop-incomparable-adapter';\n}\n\nconsole.log(review(contract, participants));\nconst mixedError = participants.map((item) =>\n item.model === 'javascript' ? { ...item, errorSemantics: 'thrown-value' } : item\n);\nconsole.log(review(contract, mixedError));</code></pre>\n<p>Ожидаемый вывод — сначала <code>accepted-fixed-contract</code>, затем <code>stop-incomparable-adapter</code>. Первый результат означает только то, что все проверенные поля совпали с проектными литералами. Второй показывает, что один участник описывает ошибку иначе. Валидатор не пытается угадать, можно ли преобразовать исключение в envelope. Такое преобразование допустимо только как явно реализованный адаптер с собственными тестами и правилами.</p>\n<p>В коде намеренно проверяются наличие полей, целые границы времени и список допустимых участников. Проверка <code>Object.hasOwn</code> отличает явный <code>null</code> от отсутствующего поля. Это важно: отсутствие <code>code</code> может означать, что данные потеряны, тогда как <code>code: null</code> — осознанная часть конкретного формата. Проект может выбрать другую политику, но её нужно записать и одинаково применить к каждому участнику.</p>\n<h2>Как читать отрицательный результат</h2>\n<p>Отказ не доказывает, что реализация плохая. Он доказывает более узкое утверждение: текущая запись не позволяет честно объявить соответствие выбранному контракту. Причина должна вести к следующему действию. Для неполной версии нужно найти владельца схемы; для неразмеченного значения — определить единицу и тег; для смешанной ошибки — выбрать границу преобразования или сохранить разницу.</p>\n<p>Проверка времени должна быть особенно строгой. Если <code>closed</code> отсутствует, нельзя вычислять его из текущих часов. Если basis одного участника — epoch seconds, а другого — логические шаги, совпадающие числа не создают общего интервала. Если нужны реальные миллисекунды, пример следует заменить измерением на конкретном пути и отдельно указать часы, нагрузку, версию сборки и способ повторения.</p>\n<p>Нельзя исправлять отказ неявной coercion. Превращение строки в число, округление суммы или замена пустого значения default-ом может быть осмысленной операцией. Но она меняет границу данных. Её следует назвать, покрыть тестом на исходное и полученное значение и включить в версию адаптера. Пока правило не названо, статус <code>exact</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>Похожие JSON дают разные решения</td><td>Нет версии или value tag</td><td>Сверить схему, tag, единицу и валюту</td><td>Сделать поля обязательными; не выводить смысл из shape</td></tr><tr><td>Один участник бросает ошибку, другие возвращают объект</td><td>Смешаны режимы завершения</td><td>Сравнить semantics, code и retry</td><td>Ввести явный адаптер или остановить передачу</td></tr><tr><td>В отчёте появилась latency из двух чисел</td><td>Логические шаги приняты за часы</td><td>Проверить basis, источник и единицы</td><td>Убрать вывод о скорости или провести измерение</td></tr><tr><td>Повтор создаёт вторую операцию</td><td>Retry назван без идемпотентности</td><td>Проверить ключ операции и повторный эффект</td><td>Остановить retry до отдельного решения</td></tr><tr><td>Неполная запись считается успешной</td><td>Валидатор подставляет default</td><td>Удалить default и добавить missing-case</td><td>Вернуть именованную причину отказа</td></tr><tr><td>Лог D нельзя связать с запросом PHP</td><td>Нет общего correlation id</td><td>Проверить идентификатор в каждом событии</td><td>Добавить его в новый контракт; не связывать по времени</td></tr></tbody></table></div>\n<h2>Где проходит граница ответственности</h2>\n<p>Контракт отвечает за представление и правила передачи. Он не решает, разрешено ли выдавать заказ, имеет ли пользователь право на операцию или безопасно ли повторять вызов. Эти решения принадлежат доменному владельцу и должны иметь собственные условия и тесты. Метка <code>order-ready</code> описывает значение в технической записи, но не выдаёт бизнес-разрешение.</p>\n<p>Адаптер отвечает за явное преобразование между своим представлением и контрактом. Он не должен скрывать потерю поля, менять валюту без правила или превращать исключение в успех. Если адаптер не может выразить состояние, правильный результат — отказ с причиной. Клиент отвечает за реакцию на эту причину, а не за угадывание пропущенного значения.</p>\n<p>Наблюдаемость отвечает за доказательство реального пути. Для связи событий нужны correlation id, идентификатор операции, версия участника, время с известной шкалой и запись результата. Один фиксированный объект в памяти не содержит этих свидетельств. Поэтому его положительный статус нельзя переносить на production, нагрузочное сравнение или гарантию доставки.</p>\n<figure><img src='/assets/editorial/2026/multi-runtime-2026-integration-evidence-loop.svg' alt='Схема проверки контракта между PHP, JavaScript и D с отдельными ветками отказа при неполной записи и точного сопоставления' loading='lazy' /><figcaption>Схема показывает порядок чтения фиксированной записи: граница, семантика и точное сопоставление. Она не изображает сетевой вызов, запуск трёх runtime или результат production-наблюдения.</figcaption></figure>\n<h2>Порядок проверки в настоящем проекте</h2>\n<ol><li>Назовите одну операцию, владельца доменного смысла и версию контракта.</li><li>Зафиксируйте value tag, формат числа, единицы, валюту и допустимые пустые значения.</li><li>Опишите один режим ошибки. Отдельно укажите code, retry и условие, при котором повтор безопасен.</li><li>Выберите time basis. Для latency назовите источник часов и единицы; для порядка используйте отдельные логические метки.</li><li>Составьте по одному образцу для PHP, JavaScript и D. Сравнивайте каждый образец с контрактом.</li><li>Добавьте отрицательные случаи: пропущенная версия, неизвестный tag, смешанная ошибка, отсутствующая граница времени и скрытое преобразование.</li><li>Сохраните для каждого отказа код причины и поле, которое его вызвало. Общий статус <code>adapter error</code> не помогает исправлению.</li><li>Проверьте сериализацию и транспорт отдельными тестами. Зафиксируйте версии runtime, библиотеки, сборки и окружения.</li><li>Только после этого проверяйте реальный путь по логам и correlation id. Не приписывайте фиксированному примеру эффекты работающей системы.</li></ol>\n<h2>Ограничения применимости</h2>\n<p>Модель не описывает ABI, правила приведения типов, сериализатор, сетевые таймауты, порядок доставки, транзакции, сборку мусора, планировщик и раскладку памяти. Эти свойства могут менять результат реальной интеграции. Их нельзя считать проверенными по совпавшему JSON. Для каждого свойства нужен отдельный источник, тест или наблюдение в заявленной среде.</p>\n<p>Валидатор также не измеряет производительность. Числа 100 и 108 — проектные целые литералы, показывающие порядок и интервал внутри fixture. Они не означают 8 миллисекунд, 8 секунд или любую другую физическую величину. Нельзя сравнивать такой объект с production latency и делать вывод о быстродействии D, PHP или JavaScript.</p>\n<p>Официальная спецификация отдельного языка не является сертификатом межъязыковой совместимости. Документация PHP описывает его исключения, спецификация ECMAScript — семантику JavaScript, спецификация D — собственные исключения и безопасность их обработки. Общий API подтверждается только контрактом приложения и тестом всех границ. Если важное условие нельзя выразить в контракте, область вывода нужно сузить, а не заполнить догадкой.</p>\n<h2>Критерий готовности</h2>\n<p>Граница готова к интеграционному тесту, если другой инженер без устного объяснения может назвать операцию, версию, смысл каждого значения, режим ошибки, правило retry и шкалу времени. Для полного и неполного объектов есть разные ожидаемые статусы. Каждый участник имеет идентификатор, версию и exact mapping либо описанное преобразование. Отрицательные случаи не превращаются в успех за счёт default-ов.</p>\n<p>Итоговая формулировка должна оставаться узкой: «этот набор полей соответствует контракту на уровне структуры и названных семантик». Формулировки «интеграция подтверждена», «латентность известна» и «три языка совместимы» требуют дополнительных доказательств. Если их нет, именованный отказ — полезный результат: он показывает, какое именно наблюдение нужно получить дальше.</p>\n<h2>Проверяемые источники</h2>\n<ul><li><a href='https://www.php.net/manual/en/language.exceptions.php' target='_blank' rel='noopener noreferrer'>PHP Manual: Exceptions</a> — официальное описание <code>throw</code>, <code>catch</code>, распространения исключения по стеку и требований к выбрасываемому объекту. Используется для ограничения утверждений о PHP, а не для доказательства общего API.</li><li><a href='https://262.ecma-international.org/16.0/' target='_blank' rel='noopener noreferrer'>ECMAScript 2025 Language Specification, ECMA-262</a> — официальная спецификация JavaScript, включая типы языка и completion records. Она описывает семантику ECMAScript и не подтверждает поведение конкретного адаптера.</li><li><a href='https://dlang.org/spec/errors.html' target='_blank' rel='noopener noreferrer'>D Language Specification: Errors</a> — официальное описание обработки ошибок и распространения исключений в D. Источник не задаёт envelope, transport или совместимость с PHP и JavaScript.</li></ul>"
|
||
}
|