Ориентация главы
Заключительная глава тома 4 делает то, чего не делали предыдущие: не учит новым инструментам, а сцепляет инструменты в конвейер. Один настоящий конвейер данных — «журналы/полуструктурный текст → извлечение регулярными выражениями → обработка в дереве значений → чтение и слияние JSON → рендеринг шаблона → вывод с кодированием и сжатием» — каждая станция здесь герой какой-то из предыдущих глав. Ценность композиционной главы — обнажить «проблемы, невидимые в одиночных главах»: как определять контракты данных между станциями, на каком слое докладывать об ошибках, где конвейер упирается в производительность, как пограничная строгость передаётся по всей цепочке. Прочитав главу, вы увидите том 4 не как «шесть ящиков с инструментами», а как «одну собираемую производственную линию».
36.1引入
Одна настоящая потребность: платформа эксплуатации генерирует «ежедневный отчёт о сервисе» из многих источников. Источник первый: JSON Lines-журналы сервиса (счётчики ошибок и статистика задержек — в полях). Источник второй: кусок полуструктурного текста состояния (версия и число соединений в форме key=value — вотчина регулярных выражений). Источник третий: конфигурация платформы по умолчанию (JSON) плюс пользовательские переопределения (XLON с полными типами). Продукция: HTML-тело письма (рендеринг шаблона) + текстовый дайджест (другой шаблон) + архивная копия (gzip-сжатие на диск).
Шесть станций, шесть инструментов; каждая в отдельности — «уже учили», но соединённые, они ставят новые вопросы: как строки, извлечённые регулярным выражением, попадают в дерево значений (где делать преобразование типов)? как три набора данных сливаются во вход шаблона (вложенная семантика ObjectMerge главы 31)? нужны ли двум шаблонам два набора данных (одно дерево значений — два шаблона)? на каком шаге архивное сжатие (после рендеринга, до записи на диск)? как отчёт об ошибке указывает «какое поле какого источника»? — Тело главы и отвечает на эти вопросы станция за станцией.
36.2概念
Панорама конвейера
Контракты данных между станциями
Первое дело дизайна конвейера — не выбор функций, а определение контрактов между станциями: какой формы и какого типа выход каждой станции. Три дисциплины: контракты ходят на дереве значений как на валюте — входы и выходы всех станций суть xvalue, типы из семейства типов главы 31 (не изобретайте промежуточных форм вроде «оцифрованных строк»); преобразование завершается в слое извлечения — захваты регулярного выражения по природе строки, и превращение «похоже на число» делается явно на станции извлечения (строгий разбор главы 26), не оставляя колебаний о типе на нижний поток; слияние через пространства имён — три источника подвешиваются каждый под свой ключ объекта верхнего уровня (stats/runtime/config), конфликты ключей между источниками исчезают структурно, и стратегии слияния остаётся разбирать лишь «перекрытия внутри одного источника».
На каком слое докладывать об ошибках
Любая станция цепочки может отказать; принцип принадлежности отчёта: докладывает тот слой, которому принадлежит контекст. Отказ разбора JSON — журнальный слой знает строку и файл (строка-столбец главы 32 плюс пометка источника); отказ извлечения регулярным выражением — слой извлечения знает шаблон и оригинальный текст (недобытое значение возвращается как «отсутствует», а не как ошибка — «поле отсутствует» это состояние данных, а не ошибка); конфликт слияния — конфигурационный слой знает имя ключа и оба источника; промах пути при рендеринге шаблона — слой рендеринга по конфигурации решает: ошибка или пустая строка. Ошибки всей цепочки в конце концов поднимаются по цепочке причин главы 4: 报告生成失败 → 来源 B 提取失败 → 第 3 行缺 version 字段 (генерация отчёта не удалась → извлечение источника B не удалось → в строке 3 нет поля version) — каждый слой добавляет своё звено.
Одна станция в конце конвейера: экранирование HTML-сущностей (html)
Когда результат рендеринга направляется в HTML-страницу или тело письма, в хвосте цепи остаётся последняя станция: превращение <, & и кавычек в сущности — всякий динамический текст, входящий в HTML-контекст, обязан пройти эту станцию; пропуск — уязвимость внедрения. Модуль html мал и сдержан, три функции в одну строку: xrtHtmlEscapeSize строго валидирует UTF-8 и возвращает точный размер экранированного вывода в байтах (первый проход двухпроходного подхода); xrtHtmlEscapeWrite пишет в буфер вызывающего (ёмкость должна включать завершающий ноль); xrtHtmlEscape напрямую производит нуль-терминированную строку, освобождаемую xrtFree. Один параметр, который нельзя перепутать, — контекст: XHTML_ESCAPE_TEXT для содержимого элементов, XHTML_ESCAPE_ATTRIBUTE для значений атрибутов, окружённых кавычками, — два контекста экранируют разные наборы символов, и неверный выбор — классический вариант межсайтового внедрения. Модуль зависит от возможности UNICODE (terra главы 27) и строго валидирует UTF-8 до экранирования —уродливый вход останавливается здесь, а не вносится в продукт. Дисциплина порядка совпадает со сжатием: рендер → экранирование → сжатие; экранирование всегда живёт на станции кодирования и никогда не протекает в шаблоны.
/* Последняя станция выходной стороны: экранировать динамический текст до входа в HTML (контекст выбирает набор символов) */ size_t iSize = 0; if ( !xrtHtmlEscapeSize(UserBio, XHTML_ESCAPE_TEXT, &iSize) ) { return fail(); } str sSafe = xrtHtmlEscape(UserBio, XHTML_ESCAPE_TEXT, NULL); /* sSafe идёт в продукт шаблона; по завершении xrtFree — владеющая строка (глава 25) */
Распределение производительности и границ по цепочке
Производительность распределена неравномерно: разбор и рендеринг — тяжеловесы-константы (каждый байт проходит через них один раз), извлечение и слияние — лёгкие линейные; порядок оптимизации: сперва рендеринг (компиляция шаблона один раз), затем разбор (журналы потоковым SAX) и лишь потом микро-оптимизации. Пограничная строгость тоже расслоена: все входы извне (файлы журналов, пользовательская конфигурация) — под полными шлюзами — три шлюза JSON, лимит длины входа регулярных выражений, предел распаковки (линия обороны главы 29); между внутренними станциями — доверие — дерево значений от извлечения до рендеринга не проверяется повторно (валидация завершается один раз на входе). «Снаружи туго — внутри свободно» — пограничная философия конвейера: бюджет строгости тратится на неподконтрольные входы, а не между собственными станциями.
Тестируемость: конвейер как пазл
Неожиданный дивиденд конвейеризации — тестируемость: каждая станция — чистая функция (дерево в, дерево out), тестируется независимо; между станциями — интеграционные тесты на минимальных контрактных данных (дерево из одной-двух записей); сквозные тесты гоняют всю цепочку на фиксированном входе и утверждают итоговый текст (хеш главы 12 даёт большим выводам утверждение-отпечаток). Сравните с тестированием «одной глыбы обрабатывающей функции» — конвейеризация позволяет каждой станции локализовать отказ до своей остановки, а не гадать внутри глыбы.
36.3示例
Полная программа: извлечение текста состояния в дерево значений
Минимальная реализация источника B конвейера — извлечение регулярным выражением + преобразование типов + построение дерева значений, композиционное употребление образца главы 30:
/* pipeline_extract.c - источник B: текст состояния -> дерево значений */ #define XRT_MODULE_ALL #define XRT_IMPLEMENTATION #include <xrt.h> #include <stdio.h> #include <string.h> static xregex* gPair; void extract_init(void) { gPair = xrtRegexCompile(XRT_STR_LITERAL( "(?<name>[A-Za-z_]+)=(?<value>\\d+)")); } xvalue* extract_status(xstrview Text) { xvalue* pRoot = xrtValueObject(); xregexmatcher* M = xrtRegexMatcherCreate(gPair, Text); xstrview Name; xstrview Value; while ( xrtRegexMatcherNext(M) ) { int64 iNumber; xrtRegexMatcherCaptureNamed(M, XRT_STR_LITERAL("name"), &Name, NULL); xrtRegexMatcherCaptureNamed(M, XRT_STR_LITERAL("value"), &Value, NULL); /* преобразование типов в слое извлечения: строгий разбор (глава 26); отказ - пропустить как отсутствующее */ if ( xrtIntParse(Value, 10, 0, &iNumber) ) { xvalue* pInt = xrtValueInt(iNumber); xrtValueObjectSet(pRoot, Name, pInt); xrtValueRelease(pInt); } } xrtRegexMatcherFree(M); return pRoot; /* владеющее дерево значений; освобождает вызывающий */ } int main(void) { xvalue* pStatus; int64 iVersion; int64 iConnections; extract_init(); pStatus = extract_status( XRT_STR_LITERAL("version=204 connections=32 uptime=98765")); if ( pStatus == NULL ) { return 1; } if ( xrtValueObjectGet(pStatus, XRT_STR_LITERAL("version")) == NULL ) { return 2; /* поле version отсутствует: отказ станции извлечения вскрывается здесь */ } printf("extracted keys=%zu ", xrtValueCount(pStatus)); xrtValueRelease(pStatus); xrtRegexRelease(gPair); return 0; }
Где стоит этот код. Инструменты трёх глав в одной раме: двухслойная модель регулярных выражений главы 30 (глобальная компиляция, матчер на каждый проход) + именованные захваты; строгий разбор главы 26 как явное преобразование типов («похоже на число» становится «подтверждено число»); построение дерева значений и балансировка ссылок главы 31 (Set затем Release). Эта 30-строчная функция — самая типовая форма станции конвейера: дерево значений на входе, дерево значений на выходе, своими ресурсами управляет сама.
Полная программа: слияние трёх источников и двойной рендеринг
Станции сходимости и вывода конвейера — слияние трёх деревьев значений, рендеринг двух шаблонов:
/* pipeline_render.c - сходимость -> рендеринг -> вывод */ #define XRT_MODULE_ALL #define XRT_IMPLEMENTATION #include <xrt.h> #include <stdio.h> static xtemplate* gHtml; static xtemplate* gPlain; void render_init(xstrview HtmlTpl, xstrview PlainTpl) { gHtml = xrtTemplateCompile(HtmlTpl); gPlain = xrtTemplateCompile(PlainTpl); } int render_report(xvalue* pStats, xvalue* pRuntime, xvalue* pConfig, FILE* pArchive) { xvalue* pData = xrtValueObject(); xvalue* pHtml; str sPlain; int iResult = 0; /* слияние через пространства имён: каждому источнику - свой ключ верхнего уровня; конфликты исчезают структурно */ if ( pData != NULL ) { xrtValueObjectSet(pData, XRT_STR_LITERAL("stats"), pStats); xrtValueObjectSet(pData, XRT_STR_LITERAL("runtime"), pRuntime); xrtValueObjectSet(pData, XRT_STR_LITERAL("config"), pConfig); } pHtml = xrtTemplateRender(gHtml, pData, NULL, NULL); sPlain = xrtTemplateRender(gPlain, pData, NULL, NULL); if ( (pHtml == NULL) || (sPlain == NULL) ) { iResult = 1; goto Cleanup; } printf("%s\n", sPlain); /* архив: результат компактного рендеринга сжимается целиком (разовый путь) - глава 29 */ { xbytesview View = { (cbytes)sPlain, strlen(sPlain) }; size_t iGzSize = 0; bytes pGz = xrtDeflateAll(View, NULL, &iGzSize); if ( pGz != NULL ) { fwrite(pGz, 1, iGzSize, pArchive); xrtFree(pGz); } } Cleanup: xrtFree(sPlain); xrtFree(pHtml); xrtValueRelease(pData); return iResult; } int main(void) { xvalue* pStats; xvalue* pRuntime; xvalue* pConfig; FILE* pArchive; int iResult; render_init(XRT_STR_LITERAL("errors={%stats.errors}"), XRT_STR_LITERAL("plain: {+$config.name}")); pStats = xrtValueObject(); pRuntime = xrtValueObject(); pConfig = xrtValueObject(); if ( (pStats == NULL) || (pRuntime == NULL) || (pConfig == NULL) ) { return 1; } { xvalue* pErrors = xrtValueInt(3); xvalue* pName = xrtValueString(XRT_STR_LITERAL("daily")); xrtValueObjectSet(pStats, XRT_STR_LITERAL("errors"), pErrors); xrtValueObjectSet(pConfig, XRT_STR_LITERAL("name"), pName); xrtValueRelease(pErrors); xrtValueRelease(pName); } pArchive = fopen("report.gz", "wb"); if ( pArchive == NULL ) { return 2; } iResult = render_report(pStats, pRuntime, pConfig, pArchive); fclose(pArchive); xrtValueRelease(pStats); xrtValueRelease(pRuntime); xrtValueRelease(pConfig); xrtTemplateRelease(gHtml); xrtTemplateRelease(gPlain); return iResult; }
Где стоит этот код. (1) Слияние через пространства имён — три источника подвешены каждый под своим ключом; семантике перекрытия ObjectMerge вообще не нужно выходить на сцену (конфликтов между источниками нет). (2) Один набор данных — два шаблона: HTML и текст делят одно pData; различие шаблонов — только в префиксах и управляющих конструкциях. (3) Архивация после рендеринга, до записи на диск: разовое сжатие точно ложится на форму «целый блок текста». (4) Балансировка ссылок прозрачна: pData освобождается сразу после подвеса трёх источников (контейнер держит ссылки), оба продукта рендеринга — каждый своим xrtFree. Эти 50 строк выводят героев глав 31/35/29 на сцену в порядке конвейера.
Четыре контрольных пункта дизайна станции
Прогоняйте каждую функцию-станцию через четыре пункта — и у качества конвейера появится нижняя планка. Пункт 1: вход и выход — деревья значений? — сигнатура станции должна быть xvalue* f(输入视图或值树) (вход-представление или дерево значений), появление char** или собственных struct-типов означает трещину в контракте. Пункт 2: ресурсы самоуправляемы? — матчеры, деревья и буферы, созданные внутри станции, балансируются внутри неё; промежуточные продукты помимо выхода не утекают вызывающему (вызывающий принимает только возвращённый владеющий продукт). Пункт 3: пути отказа изоморфны? — отказ каждой станции имеет одну и ту же форму (NULL плюс слот ошибки), слою оркестровки не нужны разные обработчики под разные станции. Пункт 4: тестируется ли отдельно? — на минимальном входе (одна-две записи) станция запускается и утверждается независимо. Четыре пункта пройдены — станция становится заменяемым винтиком: слой оркестровки меняет порядок, распараллеливает, добавляет кеш, не трогая саму станцию.
Слой оркестровки: дирижёр конвейера
У слоя оркестровки вне станций свои принципы дизайна. Порядок декларативен — очерёдность станций читается в функции оркестровки с одного взгляда (порядок и есть код), а не прячется в их побочных эффектах; поток данных однонаправлен — дерево значений течёт от станции к станции, без обратных течений и без разделяемых изменяемых состояний (предпосылка распараллеливания); ранний выход и частичный успех — решите, какие станции прерывают цепочку при отказе (конфигурация не читается — не рендерить), а какие допускают частичный успех (статистика журналов отказала, но текст состояния доступен — отчёт помечается «статистика отсутствует» и продолжает собираться): это бизнес-решение, оно живёт в слое оркестровки и в комментариях. Слой оркестровки должен быть короток настолько, чтобы читаться на одном дыхании:обычный человек за тридцать секунд видит, «откуда данные, через какие станции, куда идут».
Цифры одного настоящего случая
Дайте конвейеру опорный масштаб для инженерной интуиции: сценарий ежедневного отчёта — журналы 50 МБ (SAX-поток, пиковая память уровня мегабайт), текст состояния 2 КБ (извлечение регулярным выражением), конфигурация 10 КБ (слияние двух форматов), два продукта рендеринга около 200 КБ (шаблон компилируется один раз), архив после gzip около 30 КБ. Весь конвейер на одной машине последовательно — секунды, из них разбор семь десятых, рендеринг две: этот портрет производительности не противоречит порядку оптимизации «сперва рендеринг, потом разбор» — оптимизация рендеринга значит «компилировать один раз» (экономия на повторных компиляциях), оптимизация разбора значит «потоково» (экономия памяти), и ни то ни другое не меняет факт «разбор — тяжеловес по времени». Первый шаг вашего конвейера — всегда измерить собственные цифры, а затем расставить приоритеты по порядковому взгляду этой главы.
36.4契约
- Дерево значений — валюта: входы и выходы между станциями — всегда
xvalue; преобразование типов явно завершается в слое извлечения. - Слияние через пространства имён: каждый источник — свой ключ верхнего уровня; семантика перекрытия разбирает только конфликты одного источника.
- Принадлежность ошибок: докладывает слой, владеющий контекстом; вся цепочка поднимается по цепочке причин; «отсутствует» — состояние данных, не ошибка.
- Философия границ: снаружи туго, внутри свободно — все внешние входы под шлюзами (три шлюза/длина/предел распаковки), внутренним станциям доверие.
- Порядок производительности: сперва рендеринг (компиляция один раз), затем разбор (поток), микром-оптимизации последними.
- Балансировка ресурсов: каждая станция управляет своими ресурсами (матчеры, деревья, продукты); пути отказа изоморфны.
- Контекст экранирования HTML: TEXT для содержимого элементов / ATTRIBUTE для закавыченных значений атрибутов — неверный контекст недоэкранирует; экранирование после рендера, до сжатия.
Путь эволюции: от буквального перевода к конвейеру
Конвейер — не первая версия, вышедшая из дизайна, а вторая, выросшая из рефакторинга; знать этот путь практически ценнее, чем попасть туда сразу. Первая версия (буквальная): одна большая функция работает от входа до выхода, бизнес заработал — это не ошибка, а старт, ценность в проверке требований. Вторая версия (станцизация): большая функция режется на станции по «преобразованию формы данных» — каждый отрезок из char* в char* становится станцией «представление → дерево значений» или «дерево значений → текст»; рефакторинг ловушки 2 — ровно это движение. Третья версия (закрепление контрактов): сигнатуры станций унифицированы, формы ошибок унифицированы, одиночные тесты закрыты — воплощение «четырёх контрольных пунктов» главы. Четвёртая версия (эволюция оркестровки): станции неизменны, слой оркестровки растёт — разбор журналов и извлечение состояния идут параллельно (у станций нет зависимости по данным), загрузка конфигурации переезжает на старт (неизменные данные не перечитываются), у рендеринга появляется кеш (отпечаток вывода для «те же данные + тот же шаблон»). Каждая версия меняет способ связи, а не станции — в этом смысл конвейера: станции стабильны, оркестровка эволюционирует.
Анонс стыков с томами 5~9
Этот конвейер — не курьёз, а репетиция всех последующих томов книги. Logger тома 5 (с главы 38) — следующая остановка для «куда идёт текст после рендеринга» (вывод в sink); сетевой движок тома 7 упакует «разбор JSON + рендеринг шаблона» в обратные вызовы запроса — тот же конвейер в контексте соединения; согласование содержимого HTTP тома 9 решит, «сжимать ли продукт рендеринга и каким кодированием» — комбинация gzip+Base64 этой главы возникнет снова, уже на уровне протокола. Читайте следующие тома с конвейерным мышлением: каждый новый модуль — глубокое разворачивание какой-то станции, а панораму их позиций вы уже видели в композиционной главе.
Типичные ошибки
Симптом: рендеринг шаблона повсюду кричит «несовпадение типа»; или нижний поток полон защитного кода «если строка — преобразуем ещё раз» — одни и те же данные преобразованы трижды.
Причина: слой извлечения лениво положил строки в дерево значений как есть, ответственность за типы спихнута всем нижним — контракт пал.
/* слой извлечения: число кладётся в дерево как строка */ xvalue* pVal = xrtValueString(Value); /* "128" - строковое значение */ /* нижний шаблон: {%runtime.port} несовпадение типа - ошибки или конверсии повсюду */
/* слой извлечения завершает преобразование: в дерево входит итоговый тип */ int64 iNumber; if ( xrtIntParse(Value, 10, 0, &iNumber) ) { xvalue* pInt = xrtValueInt(iNumber); xrtValueObjectSet(pRoot, Name, pInt); xrtValueRelease(pInt); } /* поля с неопределённым типом помечаются уже в слое извлечения (или не входят в дерево); нижний поток без обороны */
Симптом: generate_report() на триста строк — разбор журналов, извлечение, слияние, рендеринг и сжатие в одном теле функции; отказ любой станции ищется по всей функции; тестирование только сквозное.
Причина: конвейер не станцизирован — буквальная манера «лишь бы работало» сварила шесть станций намертво.
str generate_report(void) { /* 300 строк: разбор журналов -> извлечение состояния -> чтение конфигурации -> слияние -> рендеринг -> сжатие... */ /* все промежуточные величины - локальные переменные, ни один участок не вынимается для отдельного теста */ }
/* одна функция на станцию: дерево значений на входе, дерево значений на выходе, ресурсы самоуправляемы */ xvalue* collect_stats(xstrview LogText); /* станция 1 */ xvalue* extract_status(xstrview StatusText); /* станция 2 */ xvalue* load_config(void); /* станция 3 */ int render_report(xvalue* Data, FILE* Out); /* станции 4+5 */ /* главный поток - чистая оркестровка: каждая станция тестируется отдельно, заменяется, переиспользуется */
Источник A (статистика JSON Lines-журналов) — тяжеловес конвейера по производительности, он заслуживает отдельного разворачивания. Выбор формы: файлы журналов бывают в десятки МБ — построчный разбор DOM (по одному JSON-объекту на строку, Parse на уровне строки) — стандартная поза; построчная итерация — разрезкой строк главы 25, каждая строка разбирается независимо (одна повреждённая строка не портит статистику остальных — семантика частичного успеха действует внутри станции). Накопленная статистика ложится в дерево значений: счётчик ошибок — int, распределение задержек — массив, группировка по модулям — объект; семейство ObjectSetNew накапливает прямо в процессе разбора. Два шлюза на месте: лимит длины строки (уродливые журнальные строки не раздувают память) и лимит числа записей (число итераций цикла ограничено). Контракт выхода станции: {errors: int, p50/p95: int, modules: {name: count}} — нижний шаблон берёт по этой форме, и при её изменении дымовой тест шаблона немедленно сигналит.
Рендеринг и вывод (сжатие, запись, отправка) — одна станция или две? Зависит от разнообразия целей вывода. Единственный вывод (только на диск) — функция рендеринга в конце сама сжимает и пишет файл, одной станцией; множественный вывод (тело письма + архивная копия, сценарий главы) — рендеринг только производит текст, станции вывода потребляют каждая по-своему — потому что два выхода обрабатывают текст по-разному (письмо берёт как есть, архив сжимает), и слияние в одну станцию позволило бы выбору «формата вывода» загрязнить функцию рендеринга. Мнемоника: сколько способов обработки у вывода — столько и станций вывода. Тот же принцип отвечает, «сжимать до или после рендеринга»: сжимается итоговый текст (после), а не дерево значений (до рендеринга текста для сжатия ещё нет) — частота, с которой этот вопрос звучит на архитектурных ревью, поразительна.
Раздел о тестируемости дал скелет трёхслойной пирамиды; здесь развёртка конкретики каждого слоя. Одиночные тесты станций (основание): каждой функции-станции — минимальный набор входов: нормальный, пустой, уродливый, граничный; утверждается форма и ключевые значения дерева на выходе (точное чтение главы 31 для утверждений, ValueHash для отпечатка всего дерева). Контрактные интеграционные тесты (середина): соседние станции сцепляются — реальный выход предыдущей кормит следующую, проверяется совпадение понимания контракта с обеих сторон (станция извлечения говорит «port — int», и станция шаблона так же считает — сцепка вскрывает расхождение на месте). Сквозные тесты-отпечатки (вершина): фиксированный вход гонит всю цепочку, Hash64 итогового текста записывается в утверждение — любое изменение любой станции, изменившее вывод, мгновенно красит тест. Пропорция слоёв примерно 7:2:1 — основание толще всех, вершина тоньше всех; покрасневший слой подсказывает уровень проблемы (станция сломана — краснеет основание, контракт сменился — середина, итоговый вывод изменился — вершина).
Практические задания
Соберите минимальный конвейер «JSON-конфигурация → рендеринг шаблона»: разбор конфигурации (2 поля), построение дерева значений, рендеринг шаблона с тремя префиксами. Обе станции — отдельные функции.
Воспроизведите сегмент сходимости композиционной главы: жёстко задайте три дерева-источника (по 2~3 ключа), слейте через пространства имён, отрендерите двумя шаблонами (HTML и текстовым) одни и те же данные. Проверьте согласованность полей обоих выводов.
Реализуйте полный конвейер сценария из введения: статистика JSON Lines-журналов (SAX-подсчёт) + извлечение текста состояния (регулярные выражения) + слияние конфигурации (JSON+XLON) + двойной рендеринг + gzip-архивация. Критерии приёмки: пять станций — каждая отдельная функция со своим одиночным тестом; на фиксированном входе сквозной вывод детерминирован (утверждение-хеш); все три пограничных шлюза на месте; статистика главы 6 подтверждает нулевые утечки; общий код не длиннее 300 строк — смысл конвейера заключён в этом числе.
Эта глава закрывает том 4 и заодно представляет новый тип «композиционной главы» — её позиционирование отлично от глав одиночных модулей: она не учит новым API, она учит сборке. В последующих томах композиционные главы тоже будут («композиция отладки и диагностики» тома 5, «планирование на практике» тома 6), и общая структура у всех — «панорама → контракты данных → разбор станций → путь эволюции» — структура, заложенная здесь, будет переиспользована. Последний совет читателю: прогоните руками обе полные программы главы, а затем соберите полный конвейер упражнения-вызова; ограничение «не длиннее трёхсот строк» — не издевательство, а напоминание: ценность конвейера в ясности станций, а не в объёме кода; разрослось — значит, какую-то станцию пора делить или удалять.
Десять глав пройдены; опись активов тома 4: строки (конвейер представлений/построитель), числа (строгость/кратчайший круг/строки формата), наборы символов (перекодирование/операции по скалярам), кодеки (три моста), сжатие (два режима/бюджет), регулярные выражения (линейность/два слоя), деревья значений (валюта/счёт ссылок), JSON (три маршрута), XLON (полные типы), шаблоны (разделение компиляции и рендеринга) — десять модулей плюс навык сборки этой главы замыкают полный контур обработки текста и структурированных данных. Следующий том входит в системные сервисы (журналирование, ввод-вывод, время, файлы, процессы) — слой над данными; выход конвейера и его конфигурационный вход найдут свой дом именно там.
Итоги
| Тема | Кратко |
|---|---|
| Валюта | дерево значений сквозь всю цепочку; преобразование типов — один раз и явно в слое извлечения |
| Сходимость | слияние многих источников через пространства имён (по ключу верхнего уровня каждому); перекрытия — только внутри источника |
| Ошибки | докладывает слой, владеющий контекстом; цепочка причин поднимает по уровням; отсутствует ≠ ошибка |
| Границы | снаружи туго, внутри свободно: входы под полными шлюзами (три шлюза/длина/предел распаковки), станциям доверие |
| Производительность | рендеринг: компиляция один раз → разбор: поток → лишь потом микром-оптимизации |
| Структура | одна функция на станцию: деревья на входе и выходе, ресурсы самоуправляемы, отказы изоморфны, тестируется и заменяется отдельно |
| Тесты | одиночные тесты станций + контрактная интеграция + сквозные отпечатки — трёхслойная пирамида, пропорция около 7:2:1 |
| Контроль станции | деревья на входе/выходе / ресурсы самоуправляемы / отказы изоморфны / тестируется отдельно — четыре пункта пройдены, и это винтик |
| Принципы оркестровки | порядок декларативен / поток данных однонаправлен / ранний выход и частичный успех — решения явные |
| Разделение рендера и вывода | сколько способов обработки у вывода — столько станций вывода; сжатие после рендеринга |
| Экранирование HTML | динамический текст в HTML экранируется обязательно; контексты TEXT/ATTRIBUTE; строгая UTF-8-валидация первой |
📖 Справочники API в этой главе:json (46 записей)template (51 записей)value (159 записей)regex (69 записей)codec (26 записей)compress (30 записей)html (5 записей)
Полные сигнатуры, ограничения параметров, возвращаемые значения и ошибки