Раздел 46 · Effect: Тихая Академия

Босс: протокол семафора

middle-senior~80 мин

без входаВойди, чтобы отмечать прогресс, вести заметки и решать задачи в редакторе. войти

Утро в Библиотеке тихое, как всегда. Рыдкулл стоит у двери и делает вид, что зашёл за книгой про кабанов. Сундук встал у твоего стула и не шевелит ни одной ножкой. Из-за дальней полки торчит мятая шляпа с надписью «ВАЛШЕБНИК».

За десять карточек протокол научился многому: коды ставней, кадр с провода, сообщения по тегам, каналы, таблица обработчиков, ленивые задачи, коды башен, отправители и получатели, номера, которые не подделать, свои тесты на типы. Но башня до сих пор не сказала им ни слова. Чтобы T-1 заговорила, протоколу не хватает последнего: собрать сообщение в конверт, написать его на провод и прочитать обратно то, что пришло с Магистрали.

Что проверяет Библиотекарь

Проверяется всё, что протокол умеет с первого этапа: тесты накопительные. Сверху четыре новые вещи.

  • Конверт. Envelope<M> держит номер сообщения, маршрут и само сообщение. Маршрут собирается из двух проверенных башен TowerId из урока 14, номер это MessageId. Литерал вместо номера и строка вместо башни в конверт не лягут. Тип конверта помнит, какое сообщение внутри: конверт с пингом это Envelope<Ping>, а не конверт вообще.
  • Провод. encode пишет конверт строкой из четырёх полей: маршрут|номер|тег|данные. Данные у каждого тега свои, у пинга их нет вовсе.
  • Разбор. decode читает провод обратно и никогда не бросает исключений. Всё, что может пойти не так, видно в сигнатуре: Outcome<Envelope, DecodeError>. Это тот же исход, что у ленивой задачи из урока 11, только без задачи вокруг. У каждой поломки свой тег, как у сообщений в уроке 07: Garbled, BadRoute, UnknownTag. Текст сообщения может содержать |, и разбор не должен на нём споткнуться.
  • Доставка. deliver разбирает провод и отдаёт сообщение таблице обработчиков из урока 10. Ошибка разбора проходит мимо обработчиков как есть.

Точный формат провода, порядок проверок в decode и содержимое каждой ошибки описаны в условии задачи. Там же примеры, на которых тесты ловят протокол.

Чек-лист перед сдачей

Пройди его сам, до того как отдать протокол Библиотекарю.

  1. Тесты всех прошлых этапов зелёные. Новое дописано снизу, старое не тронуто.
  2. decode(encode(конверт)) возвращает тот же конверт для каждого тега, для пустого текста и для текста с | внутри.
  3. На мусор decode возвращает ошибку. Не бросает исключение, не выдумывает пинг, не обрезает данные.
  4. Каждая поломка названа своим тегом, и проверки идут в порядке из условия: провод, в котором испорчены и маршрут, и номер, получает одну определённую ошибку, а не какую повезёт.
  5. Номер в разобранном конверте это new MessageId(...), а не объект с полями _tag и value.
  6. Ни одного нового as и ни одного any: всё, что пришло строкой, сужается проверками из уроков 06 и 12.
  7. Тип результата decode ровно Outcome<Envelope, DecodeError>. Проверь его своим Equal, а не на глаз.
  8. Сломай decode нарочно, например убери проверку данных у Ping, и убедись, что тест покраснел. Потом верни.

Если шаг Т ругается, читай ошибку снизу вверх, как в уроке 04. Одна такая ошибка почти наверняка ждёт тебя в envelope: там бренд из урока 14 впервые попадает в шаблонный тип из урока 12.

Шифр, этап 11

Последняя карточка каталога: протокол семафора целиком. После неё башня T-1 начнёт говорить с Магистралью, и каждый её кадр можно будет собрать, прочитать и проверить, не поверив никому на слово.

Ринцвальд

Пока Библиотекарь читает, Ринцвальд выходит из-за полки и кладёт рядом свой листок.

import { type Message, ping, text } from './messages';

const decode = (wire: string): Message => {
  const [, , tag, data = ''] = wire.split('|');
  return tag === 'Text' ? text(data) : ping;
};

console.log(decode('T-1->T-2|1|Text|гроза у моста'));
console.log(decode('T-1->T-2|2|Text|a|b'));
console.log(decode('мусор'));
{ _tag: 'Text', body: 'гроза у моста' }
{ _tag: 'Text', body: 'a' }
{ _tag: 'Ping' }

Он прав: не падает. На первом проводе всё верно. На втором половина текста пропала молча: всё, что в тексте стояло после |, отрезано. На третьем мусор превратился в пинг, и башня, которая его получила, уверена, что сосед на связи.

Библиотекарь, не поднимая головы, тянет руку к листку Ринцвальда и стучит пальцем по сигнатуре (wire: string) => Message. «Ук». Сигнатура обещает сообщение всегда, а честного «не разобралось» в ней нет. Твоя сигнатура говорит, что разбор может не удаться, и перечисляет, как именно. Скоропись Ринцвальда не падает, она врёт. Это хуже: падение хотя бы видно.

Практика

Итог

Библиотекарь читает долго. Перелистывает протокол к началу, к таблице кодов ставней, и снова вниз, к разбору. Задерживает палец на IsNever. Рыдкулл у двери перестал делать вид, что ищет книгу. Потом Библиотекарь закрывает файл и смотрит на тебя.

Одно слово, и Пондер его не переводит. Не нужно.

Ранг: студент. Академия больше не держит тебя на пробе.

Ночью ты поднимаешься на башню T-1. Туман над рекой тот же, что в ночь, когда башня замолчала. Ты отправляешь первый кадр своим протоколом, T-1->T-2|1|Ping|, и шесть ставен щёлкают по таблице кодов из этапа 1. Через минуту с соседней башни приходит T-2->T-1|1|Ack|1. decode разбирает его без единой ошибки, deliver отдаёт обработчику подтверждений, и в журнале башни впервые с той ночи появляются две строки. Сундук за спиной тихо лязгает крышкой.

Дальше

Под утро на провод приходит кадр с Магистрали: T-7->T-1|41|Ack|40. Маршрут из настоящих башен, номер из цифр, тег известный, данные числом. Протокол принимает его без вопросов. И он врёт: T-1 за всю ночь отправила один кадр, а подтверждают ей сороковой. Разбор говорит, что кадр написан по правилам. Правда ли то, что в нём написано, разбор не знает.

Утром у ворот Академии останавливается телега с гор. С неё слезает старуха в чёрной шляпе с булавками, смотрит на башню, потом на тебя.

Следующий раздел курса это EF2 «Головология». Матушка Воскова учит отличать действие от вычисления, и начинается он с урока 17.