Глоссарий

Last updated: 2026-08-18Восемнадцать терминов, которыми пользуется стандарт — на примерах из разбора эндпоинта, а не определениями из учебника.

Глоссарий GRAIN

Слова, которыми пользуется стандарт. Примеры — из разбора эндпоинта, чтобы термин был не определением из учебника, а строкой кода.

Чистая функция

Функция, результат которой зависит только от аргументов, и которая ничего не меняет вокруг себя. Вызови её дважды с теми же аргументами — получишь тот же ответ, и в мире ничего не изменится.

ts
derivePublishedTrack(track, nowMs)   // чистая: только считает новое значение
saveTrack(track)                     // не чистая: пишет в базу

Чистые функции тестируются без подготовки: ни базы, ни сети, ни подмены времени. Поэтому у них в GRAIN своя роль файла — *.pure.ts.

Побочный эффект

Всё, что функция делает «наружу»: запись в базу, HTTP-запрос, лог, изменение глобальной переменной, чтение системных часов. Эффекты не запрещены — они собраны в предсказуемых местах, а не рассыпаны по коду.

Недетерминизм

Источник значений, который завтра даст другой ответ на тот же вопрос: Date.now(), Math.random(), crypto.randomUUID(), process.env. GRAIN держит их на границе, а внутрь домена они приходят аргументом — иначе поведение кода невозможно ни повторить, ни проверить.

Граница

Место, где код общается с внешним миром: HTTP-обработчик, gRPC-метод, консьюмер очереди, точка входа процесса, клиент чужого API. Роли entry, route, rpc, job, wire. На границе можно то, чего нельзя внутри: брать время, читать окружение, знать про фреймворк.

Домен

Смысловая часть программы: то, что осталось бы, если убрать HTTP, базу и фреймворк. Правила публикации трека — домен. Код статуса 403 — не домен.

Контракт

Обещание, которое функция даёт вызывающему, ещё до того как он прочитает её тело. В GRAIN контракт несёт имя: findTrack обещает, что вернёт null, если трека нет, и не бросит; getTrack обещает значение или исключение. Нарушать контракт нельзя — на него рассчитывают.

Инвариант

Условие, которое обязано быть истинным всегда, иначе программа сломана. «У опубликованного трека есть обложка» — инвариант. Проверяется через assert*: если он нарушен, это баг, а не ожидаемая ситуация.

Отказ и исключение

Отказ — ожидаемый исход, часть контракта: не найдено, нет прав, не хватает средств. Возвращается значением с кодом домен.предмет.причина. Исключение — то, чего быть не должно: упала база, нарушен инвариант, ошибка в коде. Бросается и ловится только на границе.

Смешивать их — главный источник кода, где catch ловит всё подряд и превращает в «что-то пошло не так».

Идемпотентность

Свойство операции, при котором повторный вызов ничего не меняет. Публикация уже опубликованного трека должна оставить его опубликованным, а не сдвинуть дату. В GRAIN за это отвечает глагол ensure*, а в SQL — if not exists в миграциях.

Guard, ранний выход

Проверка в начале функции, которая отсекает невозможное и сразу выходит. Вместо лестницы if / else — плоский список условий:

ts
if (track === null) return makeFail('music.track.missing', '…')
if (!canPublish(track, actor.id, actor.isAdmin)) return makeFail('music.track.forbidden', '…')

Каждый guard читается отдельно, и ни один не требует держать в голове остальные.

Воронка

Форма функции в GRAIN: guard → acquire → derive → effect. Отсечь невозможное, добыть данные, посчитать, записать. Такты не перемешиваются; если тактов оказалось два комплекта — перед тобой две функции.

Роль файла

Одна ответственность, объявленная в имени: *.store.ts — единственное место с SQL, *.policy.ts — правила без I/O, *.pure.ts — вычисления, *.route.ts — граница HTTP. Роль видна в листинге каталога, файл не нужно открывать, чтобы понять, что в нём.

Направление зависимостей

Кто про кого знает. В GRAIN стрелки идут внутрь: граница знает про домен, домен про границу — никогда. Как только *.pure.ts импортировал *.store.ts, чистая часть перестала быть чистой.

Мок

Подделка зависимости в тесте: фальшивая база, подменённые часы. Мок — не инструмент, а симптом: если для теста правила нужна поддельная база, значит правило смешано с I/O и его пора вынести в *.policy.ts или *.pure.ts.

Единица измерения в имени

Суффикс, который сообщает, в чём измеряется число: durationSec, timeoutMs, priceCents, sizeBytes. Число без единицы в GRAIN — ошибка, потому что это целый класс продакшн-багов: одна сторона отдаёт секунды, другая ждёт миллисекунды, и обе формально правы.

Закрытый словарь

Конечный список разрешённых слов: 33 глагола, семь префиксов булевых, десять ролей файлов, пять тегов комментариев. Закрытый — значит новое слово нельзя придумать на месте, его добавляют в стандарт. Это и есть механизм, из-за которого код выглядит написанным одной рукой.

AST, линтер

AST — дерево, в которое компилятор превращает исходный текст: не «строка с символами», а «объявление функции с таким-то именем и такими-то аргументами». Линтер ходит по этому дереву и проверяет правила. Поэтому машина умеет отличить find в имени функции от слова find в комментарии.

Escape-хатч

Разрешённый способ нарушить правило осознанно: // grain:allow <правило> — <причина>. Причина обязательна — она превращает нарушение из небрежности в решение, которое можно обсудить.

Дальше: обзор стандарта · разбор эндпоинта · как включить проверку

Глоссарий | AS Docs