ПОЧЕМУ стало ключевым словом, язык поднят до версии 2
- метка обоснования пишется заглавными и вошла в словарь набора: скелет правила теперь целиком из ключевых слов, а не смесь `**ДОЛЖЕН.**` и `**Почему.**`; в переводе на другой язык метка меняется как остальные слова (ПОЧЕМУ / WHY), 235 вхождений заменены - метки правила выделены из шкалы в отдельный перечень: ПОЧЕМУ и МЕХАНИЗИРОВАНО обязательности не задают, а размечают части, и стандартом не даются ни в одном языке — раньше МЕХАНИЗИРОВАНО висело строкой в таблице модальности - версия языка поднята до 2, потому что изменение формы меняет чтение уже написанного текста; строка о версии в двенадцати конвенциях перечисляет теперь и метки, а служебные слова сценария в неё по-прежнему не входят
This commit is contained in:
@@ -10,9 +10,9 @@ prefix: DIRS
|
||||
**кто создаёт** содержимое и **что будет, если его потерять**. Из категорий
|
||||
механически выводится состав бэкапа.
|
||||
|
||||
Ключевые слова ДОЛЖЕН, НЕ ДОЛЖЕН, СЛЕДУЕТ, НЕ СЛЕДУЕТ, ДОПУСКАЕТСЯ и отметка
|
||||
МЕХАНИЗИРОВАНО толкуются как описано в языке конвенций версии 1 — тогда и
|
||||
только тогда, когда написаны заглавными.
|
||||
Ключевые слова ДОЛЖЕН, НЕ ДОЛЖЕН, СЛЕДУЕТ, НЕ СЛЕДУЕТ, ДОПУСКАЕТСЯ и метки
|
||||
ПОЧЕМУ и МЕХАНИЗИРОВАНО толкуются как описано в языке конвенций версии 2 —
|
||||
тогда и только тогда, когда написаны заглавными.
|
||||
|
||||
## Область действия
|
||||
|
||||
@@ -37,7 +37,7 @@ prefix: DIRS
|
||||
|
||||
Имена в таблице — умолчание для случая «одна директория на категорию».
|
||||
|
||||
**Почему.** Все дальнейшие решения — что попадает в бэкап (DIRS-4), что можно
|
||||
**ПОЧЕМУ.** Все дальнейшие решения — что попадает в бэкап (DIRS-4), что можно
|
||||
снести при нехватке места, что переживает переезд на другой диск —
|
||||
читаются из категории, а не выясняются по коду приложения. Без единой
|
||||
классификации каждое такое решение принимается заново и каждый раз чуть
|
||||
@@ -49,7 +49,7 @@ prefix: DIRS
|
||||
**ДОПУСКАЕТСЯ.** Директорий в категории столько, сколько нужно раскладке;
|
||||
принадлежность к категории задаётся не именем, а участием в списке бэкапа.
|
||||
|
||||
**Почему.** Явное разрешение снимает вопрос, не читается ли DIRS-1 как «ровно
|
||||
**ПОЧЕМУ.** Явное разрешение снимает вопрос, не читается ли DIRS-1 как «ровно
|
||||
три директории». Крупные файлы отделяют от базы, чтобы двигать их между
|
||||
дисками независимо (`media/`, `uploads/` — та же категория «данные», что и
|
||||
`data/`); запрет на такое деление вынуждал бы либо держать всё на одном
|
||||
@@ -67,7 +67,7 @@ prefix: DIRS
|
||||
| DIRS-3.2 | миниатюры, распакованные ассеты, кеш внешних ответов, перестраиваемые индексы | кеш |
|
||||
| DIRS-3.3 | спорный случай | `rm -rf` и запуск приложения заново: поднялось и наверстало само — кеш; не поднялось или поднялось пустым — данные |
|
||||
|
||||
**Почему.** Без внешнего теста граница проводится по ощущению «жалко
|
||||
**ПОЧЕМУ.** Без внешнего теста граница проводится по ощущению «жалко
|
||||
потерять», а оно смещено в одну сторону: дорогой в пересборке кеш
|
||||
переезжает в данные и раздувает каждый снапшот. Дорого ≠ невосполнимо, и
|
||||
разделяет эти два свойства именно способность приложения пересоздать
|
||||
@@ -80,7 +80,7 @@ prefix: DIRS
|
||||
**ДОЛЖЕН.** Директории категории «данные» — в списке бэкапа; конфигурация и
|
||||
кеш — нет.
|
||||
|
||||
**Почему.** Кеш раздувает снапшот содержимым, которое приложение
|
||||
**ПОЧЕМУ.** Кеш раздувает снапшот содержимым, которое приложение
|
||||
восстановит само. Конфигурацию бэкапить не только незачем, но и вредно: там
|
||||
лежат секреты, а бэкапы уезжают в облако — источник истины для
|
||||
конфигурации остаётся в репозитории и хранилище секретов, а не в снапшоте.
|
||||
@@ -97,7 +97,7 @@ prefix: DIRS
|
||||
буквально: переменная деплоя, константа, поле конфигурации. Всё остальное
|
||||
на него ссылается.
|
||||
|
||||
**Почему.** Правило вывода механическое, но применяет его человек или
|
||||
**ПОЧЕМУ.** Правило вывода механическое, но применяет его человек или
|
||||
шаблон — то есть ошибиться можно. Общая ссылка делает целый класс ошибок
|
||||
невозможным: переименование директории отражается в обоих местах сразу.
|
||||
Независимо набранный список расходится тихо — ни деплой, ни прогон бэкапа
|
||||
@@ -113,7 +113,7 @@ prefix: DIRS
|
||||
| DIRS-6.1 | файлы самодостаточны на любой момент времени | копированием |
|
||||
| DIRS-6.2 | консистентность обеспечивает только сама СУБД | дампом: бэкапится директория дампов, сырой каталог базы — нет |
|
||||
|
||||
**Почему.** Файловый снапшот работающей СУБД не гарантирует
|
||||
**ПОЧЕМУ.** Файловый снапшот работающей СУБД не гарантирует
|
||||
консистентности: скопированный каталог может не восстановиться, и узнают
|
||||
об этом при восстановлении. Директория дампов — тоже данные, просто
|
||||
производные, поэтому DIRS-4 покрывает её без оговорок. Сырой каталог базы из
|
||||
@@ -125,7 +125,7 @@ prefix: DIRS
|
||||
**ДОЛЖЕН.** Решение «копировать или дампить» (DIRS-6) принимается, когда
|
||||
приложение заводят.
|
||||
|
||||
**Почему.** Неверный выбор ничем себя не проявляет, пока бэкап не
|
||||
**ПОЧЕМУ.** Неверный выбор ничем себя не проявляет, пока бэкап не
|
||||
понадобился: прогоны копирования проходят успешно и накапливают снапшоты, из
|
||||
которых база не поднимется. Отложить решение — значит принять его по факту
|
||||
первой неудачной попытки восстановления, то есть тогда, когда данных уже
|
||||
@@ -136,7 +136,7 @@ prefix: DIRS
|
||||
**ДОЛЖЕН.** Конфигурация приложения задаёт отдельные пути для данных и для
|
||||
кеша, а не один каталог на всё.
|
||||
|
||||
**Почему.** Снаружи категория определяется только тогда, когда разным
|
||||
**ПОЧЕМУ.** Снаружи категория определяется только тогда, когда разным
|
||||
категориям соответствуют разные директории. Всё, сложенное в один каталог,
|
||||
заставляет составлять список бэкапа вручную, читая код приложения, — и
|
||||
пересматривать его при каждом обновлении, потому что новый подкаталог
|
||||
@@ -148,7 +148,7 @@ prefix: DIRS
|
||||
**НЕ ДОЛЖЕН.** Записываемые пути приложения не указывают внутрь
|
||||
конфигурации.
|
||||
|
||||
**Почему.** Конфигурация восстанавливается прогоном деплоя (DIRS-1.1), поэтому
|
||||
**ПОЧЕМУ.** Конфигурация восстанавливается прогоном деплоя (DIRS-1.1), поэтому
|
||||
всё, что приложение туда записало, следующий деплой затирает без
|
||||
предупреждения. Вдобавок директория конфигурации может быть подключена
|
||||
только на чтение — тогда запись отказывает в рантайме. Ни то ни другое не
|
||||
|
||||
+24
-24
@@ -7,9 +7,9 @@ prefix: CONF
|
||||
Как устроена конфигурация: где лежит, как попадает в процесс, что с
|
||||
секретами и когда падает.
|
||||
|
||||
Ключевые слова ДОЛЖЕН, НЕ ДОЛЖЕН, СЛЕДУЕТ, НЕ СЛЕДУЕТ, ДОПУСКАЕТСЯ и отметка
|
||||
МЕХАНИЗИРОВАНО толкуются как описано в языке конвенций версии 1 — тогда и
|
||||
только тогда, когда написаны заглавными.
|
||||
Ключевые слова ДОЛЖЕН, НЕ ДОЛЖЕН, СЛЕДУЕТ, НЕ СЛЕДУЕТ, ДОПУСКАЕТСЯ и метки
|
||||
ПОЧЕМУ и МЕХАНИЗИРОВАНО толкуются как описано в языке конвенций версии 2 —
|
||||
тогда и только тогда, когда написаны заглавными.
|
||||
|
||||
## Область действия
|
||||
|
||||
@@ -25,7 +25,7 @@ prefix: CONF
|
||||
**ДОЛЖЕН.** Приложение читает параметры из файла конфигурации; переменные
|
||||
окружения источником конфигурации не служат.
|
||||
|
||||
**Почему.** Три довода, по убыванию веса:
|
||||
**ПОЧЕМУ.** Три довода, по убыванию веса:
|
||||
|
||||
- **Один типизированный источник.** Файл несёт секции, комментарии,
|
||||
единицы измерения и валидируется целиком. Окружение — плоский набор
|
||||
@@ -48,7 +48,7 @@ prefix: CONF
|
||||
|
||||
**СЛЕДУЕТ.** Конкретный формат (TOML, YAML) выбирается по стеку.
|
||||
|
||||
**Почему.** Комментарий у каждого поля (CONF-9) — часть того, ради чего конфиг
|
||||
**ПОЧЕМУ.** Комментарий у каждого поля (CONF-9) — часть того, ради чего конфиг
|
||||
вообще читают; формат, в котором комментарий негде разместить, делает CONF-9
|
||||
невыполнимым. Секции дают структуру, которую валидатор проверяет целиком, —
|
||||
плоский список пар такой возможности не даёт и возвращает нас к тем же
|
||||
@@ -59,7 +59,7 @@ prefix: CONF
|
||||
**СЛЕДУЕТ.** Имя по умолчанию ищется в рабочей директории процесса, а путь
|
||||
задаётся опцией командной строки.
|
||||
|
||||
**Почему.** Запуск без аргументов работает одинаково в разработке, в
|
||||
**ПОЧЕМУ.** Запуск без аргументов работает одинаково в разработке, в
|
||||
контейнере и на сервере, и способ запуска не приходится помнить отдельно
|
||||
для каждой среды. Опция нужна ровно для случаев, когда конфигов несколько
|
||||
(тесты, второй инстанс): без неё их разводят переменной окружения — тем
|
||||
@@ -71,7 +71,7 @@ prefix: CONF
|
||||
рабочей директории (CONF-3), приложение не стартует: сообщение называет
|
||||
искомый путь, код возврата ненулевой.
|
||||
|
||||
**Почему.** Конфиг — артефакт деплоя (CONF-12), и его отсутствие означает, что
|
||||
**ПОЧЕМУ.** Конфиг — артефакт деплоя (CONF-12), и его отсутствие означает, что
|
||||
развёртывание не довело работу до конца, а не что приложение попросили
|
||||
работать на умолчаниях. Умолчания (CONF-7) существуют, чтобы работал
|
||||
**неполный** файл, а не отсутствующий, — именно здесь читатель спотыкается
|
||||
@@ -89,7 +89,7 @@ prefix: CONF
|
||||
|
||||
**НЕ ДОЛЖЕН.** Реальный конфиг не коммитится; в репозитории — образец.
|
||||
|
||||
**Почему.** Рабочий конфиг содержит отрендеренные секреты (CONF-12), а секрет,
|
||||
**ПОЧЕМУ.** Рабочий конфиг содержит отрендеренные секреты (CONF-12), а секрет,
|
||||
попавший в историю, чинится ротацией, а не удалением файла. Кроме того,
|
||||
закоммиченный конфиг конкретной среды становится вторым источником истины:
|
||||
он расходится с тем, что реально развёрнуто, и расходится молча.
|
||||
@@ -99,7 +99,7 @@ prefix: CONF
|
||||
**ДОЛЖЕН.** Разбор — при старте, в одну типизированную структуру; чтения
|
||||
файла конфигурации в бизнес-коде нет.
|
||||
|
||||
**Почему.** Второе место чтения — это второй момент времени: две части кода
|
||||
**ПОЧЕМУ.** Второе место чтения — это второй момент времени: две части кода
|
||||
начинают видеть разные значения одного параметра, и расхождение не
|
||||
воспроизводится, потому что зависит от того, когда файл потрогали.
|
||||
Типизированная структура вдобавок переносит ошибку формата в старт (CONF-17),
|
||||
@@ -109,7 +109,7 @@ prefix: CONF
|
||||
|
||||
**ДОЛЖЕН.** Смена параметров — рестарт процесса.
|
||||
|
||||
**Почему.** Изменяемый конфиг делает поведение функцией момента: один
|
||||
**ПОЧЕМУ.** Изменяемый конфиг делает поведение функцией момента: один
|
||||
запрос обслуживается наполовину старыми, наполовину новыми значениями, а
|
||||
разбор инцидента требует знать хронологию правок файла, а не его текущее
|
||||
содержимое.
|
||||
@@ -121,7 +121,7 @@ prefix: CONF
|
||||
|
||||
**ДОЛЖЕН.** Значение по умолчанию задаётся в коде, файл его перекрывает.
|
||||
|
||||
**Почему.** Умолчание, живущее в образце, действует только для тех, кто
|
||||
**ПОЧЕМУ.** Умолчание, живущее в образце, действует только для тех, кто
|
||||
образец скопировал: конфиг без этого поля даёт другое поведение, и ни одно
|
||||
из двух значений не выглядит ошибкой. Умолчание в коде — одно определённое
|
||||
поведение для неполного конфига и одно место, где это значение меняется.
|
||||
@@ -131,7 +131,7 @@ prefix: CONF
|
||||
**ДОЛЖЕН.** В образце присутствуют все секции и все поля, включая те, у
|
||||
которых есть умолчание (CONF-7).
|
||||
|
||||
**Почему.** Поле, живущее только в коде, для читателя конфига не
|
||||
**ПОЧЕМУ.** Поле, живущее только в коде, для читателя конфига не
|
||||
существует: он не знает, что параметр вообще можно менять, и добивается
|
||||
нужного поведения обходным путём. Полнота образца — цена, которой CONF-7
|
||||
покупает себе видимость.
|
||||
@@ -145,7 +145,7 @@ prefix: CONF
|
||||
- **единицы измерения**, если применимо: секунды/миллисекунды, байты, доля
|
||||
`0–1`.
|
||||
|
||||
**Почему.** Так конфиг читается без открывания кода — этим он и полезен;
|
||||
**ПОЧЕМУ.** Так конфиг читается без открывания кода — этим он и полезен;
|
||||
без комментария читатель всё равно идёт в код, и образец перестаёт быть
|
||||
справочником. Единицы стоят отдельно: перепутанные секунды и миллисекунды
|
||||
дают валидное значение и работающий процесс, а ошибка обнаруживается по
|
||||
@@ -161,7 +161,7 @@ prefix: CONF
|
||||
| CONF-10.1 | поддерживаемое | обязательны поля этого варианта; поля прочих вариантов не требуются |
|
||||
| CONF-10.2 | неизвестное | ошибка на старте с перечислением поддерживаемых значений |
|
||||
|
||||
**Почему.** Фиксированный на секцию набор обязательных полей оставляет
|
||||
**ПОЧЕМУ.** Фиксированный на секцию набор обязательных полей оставляет
|
||||
выбор из двух плохих: заполнять поля бекенда, который не используется, или
|
||||
не проверять обязательность вовсе — то есть выключить валидацию ровно там,
|
||||
где вариантов много и ошибиться легче всего. Перечисление поддерживаемых
|
||||
@@ -174,7 +174,7 @@ prefix: CONF
|
||||
альтернативные — блоками-комментариями ниже, каждый со своим описанием
|
||||
полей.
|
||||
|
||||
**Почему.** Иначе набор вариантов виден только из кода валидации, и образец
|
||||
**ПОЧЕМУ.** Иначе набор вариантов виден только из кода валидации, и образец
|
||||
теряет свойство справочника (CONF-8, CONF-9) ровно на той секции, где выбор
|
||||
действительно есть. Закомментированный блок вдобавок переключается правкой
|
||||
на месте, а не сборкой секции с нуля по документации.
|
||||
@@ -184,7 +184,7 @@ prefix: CONF
|
||||
**ДОЛЖЕН.** Деплой рендерит значения секретов прямо в файл конфигурации;
|
||||
отдельного слоя секретов в приложении нет.
|
||||
|
||||
**Почему.** Источник истины секрета — внешнее хранилище деплоя, не
|
||||
**ПОЧЕМУ.** Источник истины секрета — внешнее хранилище деплоя, не
|
||||
репозиторий и не окружение. Любой второй канал — переменная окружения рядом
|
||||
с файлом, собственный клиент к хранилищу внутри приложения — возвращает
|
||||
вопрос «откуда взялось значение, которое сейчас в процессе». Приложение при
|
||||
@@ -196,7 +196,7 @@ prefix: CONF
|
||||
**ДОЛЖЕН.** Права `0600`, владелец — пользователь, от имени которого
|
||||
работает процесс.
|
||||
|
||||
**Почему.** После CONF-1 и CONF-12 файл конфигурации — единственная
|
||||
**ПОЧЕМУ.** После CONF-1 и CONF-12 файл конфигурации — единственная
|
||||
поверхность, на которой секреты лежат, и весь довод «файл вместо окружения»
|
||||
держится на его правах: конфиг, читаемый всеми на машине, раздаёт секреты
|
||||
шире, чем раздало бы окружение, — и тогда CONF-1 меняет одну утечку на
|
||||
@@ -207,7 +207,7 @@ prefix: CONF
|
||||
**ДОЛЖЕН.** Значение секретного поля в образце — пустая строка, а не
|
||||
пример.
|
||||
|
||||
**Почему.** Правдоподобная заглушка доезжает до продакшена как настоящее
|
||||
**ПОЧЕМУ.** Правдоподобная заглушка доезжает до продакшена как настоящее
|
||||
значение: шаблон отрендерился криво, поле осталось от образца, и проверка
|
||||
непустоты (CONF-15) его пропускает. Пустая строка делает недорендеренный конфиг
|
||||
механически отличимым от заполненного.
|
||||
@@ -216,7 +216,7 @@ prefix: CONF
|
||||
|
||||
**ДОЛЖЕН.** Непустота обязательных секретов проверяется на старте.
|
||||
|
||||
**Почему.** Это ловит криво отрендеренный шаблон до того, как он превратится
|
||||
**ПОЧЕМУ.** Это ловит криво отрендеренный шаблон до того, как он превратится
|
||||
в 401 от внешнего API через час работы, — то есть в момент, когда причина
|
||||
ещё очевидна и связана с деплоем.
|
||||
|
||||
@@ -225,7 +225,7 @@ prefix: CONF
|
||||
**НЕ ДОЛЖЕН.** Значение секретного поля не появляется в записи лога ни на
|
||||
одном уровне.
|
||||
|
||||
**Почему.** У логов круг доступа шире, чем у файла под `0600` (CONF-13): они
|
||||
**ПОЧЕМУ.** У логов круг доступа шире, чем у файла под `0600` (CONF-13): они
|
||||
собираются, пересылаются и попадают в бэкапы, где права исходного файла уже
|
||||
ничего не значат. Попавший в лог секрет чинится ротацией, а не удалением
|
||||
записи. Типичный источник утечки — отладочный дамп разобранного конфига при
|
||||
@@ -236,7 +236,7 @@ prefix: CONF
|
||||
**ДОЛЖЕН.** Невалидный конфиг — запись уровня `ERROR` и выход с ненулевым
|
||||
кодом; процесс не стартует «наполовину».
|
||||
|
||||
**Почему.** Наполовину стартовавший процесс проходит проверку живости и
|
||||
**ПОЧЕМУ.** Наполовину стартовавший процесс проходит проверку живости и
|
||||
падает позже — на первом запросе, который трогает испорченный параметр, — и
|
||||
падение выглядит дефектом кода, а не ошибкой деплоя. Ненулевой код нужен,
|
||||
чтобы неудачный старт увидел супервизор: без него он неотличим от штатного
|
||||
@@ -254,7 +254,7 @@ prefix: CONF
|
||||
| CONF-18.4 | строки, которые парсятся во что-то (длительности, зоны, URL, идентификаторы сущностей), реально парсятся | при первом обращении к тому, что за ними стоит |
|
||||
| CONF-18.5 | включённые секции консистентны: у включённой интеграции заданы все обязательные поля | при первом вызове интеграции, часто по расписанию |
|
||||
|
||||
**Почему.** Список минимальный и собран по одному признаку — правый
|
||||
**ПОЧЕМУ.** Список минимальный и собран по одному признаку — правый
|
||||
столбец: каждая из этих ошибок иначе всплывает там, где связь с деплоем уже
|
||||
потеряна, и диагностируется как дефект приложения. Проверка на старте
|
||||
сводит их все к одному моменту и одному сообщению.
|
||||
@@ -264,7 +264,7 @@ prefix: CONF
|
||||
**ДОЛЖЕН.** Валидация собирает все найденные проблемы и выводит их одним
|
||||
списком, а не падает на первой.
|
||||
|
||||
**Почему.** Иначе цикл «запуск — одна ошибка — правка» повторяется столько
|
||||
**ПОЧЕМУ.** Иначе цикл «запуск — одна ошибка — правка» повторяется столько
|
||||
раз, сколько в конфиге ошибок, а на сервере каждая итерация — это ещё и
|
||||
деплой. Криво отрендеренный шаблон обычно ломает не одно поле, а все поля
|
||||
одного источника: разом они читаются как одна причина, по одной — как
|
||||
@@ -280,7 +280,7 @@ prefix: CONF
|
||||
| CONF-21.1 | несекретное | имя поля, ожидание и полученное значение: «ожидалось 0–1, получено `1.5`» |
|
||||
| CONF-21.2 | секретное | имя поля и суть нарушения, без значения |
|
||||
|
||||
**Почему.** Сообщение без значения отправляет читателя в файл — сличать
|
||||
**ПОЧЕМУ.** Сообщение без значения отправляет читателя в файл — сличать
|
||||
глазами каждую строку списка CONF-19; ошибки вида «секунды вместо миллисекунд»
|
||||
или пробел в конце значения из такого сообщения не читаются вовсе. Значение
|
||||
секретного поля при этом печатать некуда: вывод старта уходит в лог
|
||||
|
||||
@@ -6,9 +6,9 @@ prefix: KEYS
|
||||
|
||||
Как выбираются и как выглядят первичные ключи сущностей.
|
||||
|
||||
Ключевые слова ДОЛЖЕН, НЕ ДОЛЖЕН, СЛЕДУЕТ, НЕ СЛЕДУЕТ, ДОПУСКАЕТСЯ и отметка
|
||||
МЕХАНИЗИРОВАНО толкуются как описано в языке конвенций версии 1 — тогда и
|
||||
только тогда, когда написаны заглавными.
|
||||
Ключевые слова ДОЛЖЕН, НЕ ДОЛЖЕН, СЛЕДУЕТ, НЕ СЛЕДУЕТ, ДОПУСКАЕТСЯ и метки
|
||||
ПОЧЕМУ и МЕХАНИЗИРОВАНО толкуются как описано в языке конвенций версии 2 —
|
||||
тогда и только тогда, когда написаны заглавными.
|
||||
|
||||
## Область действия
|
||||
|
||||
@@ -27,7 +27,7 @@ prefix: KEYS
|
||||
который порождает приложение, — во **всех** таблицах, включая те, что
|
||||
снаружи не адресуются.
|
||||
|
||||
**Почему.** Ветвления здесь нет намеренно, хотя напрашивается: «эту
|
||||
**ПОЧЕМУ.** Ветвления здесь нет намеренно, хотя напрашивается: «эту
|
||||
сущность снаружи не адресуют, ей хватит целого числа». Внутренние сущности
|
||||
имеют привычку становиться внешними — и тогда целочисленный идентификатор
|
||||
утекает в URL задним числом, а миграция ключа на живых данных стоит
|
||||
@@ -47,7 +47,7 @@ prefix: KEYS
|
||||
|
||||
**ДОЛЖЕН.** Значение ключа известно до вставки строки.
|
||||
|
||||
**Почему.** Идентификатор нужен раньше, чем база ответит: его пишут в лог
|
||||
**ПОЧЕМУ.** Идентификатор нужен раньше, чем база ответит: его пишут в лог
|
||||
начатой операции, кладут в связанные записи одной транзакции и возвращают
|
||||
клиенту. Генерация на стороне базы вынуждает либо ждать `last_insert_rowid`
|
||||
и достраивать связи вторым проходом, либо иметь два источника истины о
|
||||
@@ -58,7 +58,7 @@ prefix: KEYS
|
||||
**ДОЛЖЕН.** Один модуль порождает идентификаторы, он же их разбирает.
|
||||
Самодельных генераторов и парсеров в коде нет.
|
||||
|
||||
**Почему.** Нормализация регистра (KEYS-4) и проверка формата обязаны
|
||||
**ПОЧЕМУ.** Нормализация регистра (KEYS-4) и проверка формата обязаны
|
||||
применяться ко всем идентификаторам без исключения. Любая вторая точка
|
||||
входа рано или поздно окажется той, где нормализацию забыли, — и дефект
|
||||
проявится не там, где создан.
|
||||
@@ -67,7 +67,7 @@ prefix: KEYS
|
||||
|
||||
**ДОЛЖЕН.** Идентификаторы порождаются и хранятся в нижнем регистре.
|
||||
|
||||
**Почему.** Сравнение строк в базе обычно побайтовое, поэтому регистр — не
|
||||
**ПОЧЕМУ.** Сравнение строк в базе обычно побайтовое, поэтому регистр — не
|
||||
косметика, а корректность поиска. Спецификация ULID канонизирует **верхний**
|
||||
регистр, и библиотеки по умолчанию отдают именно его: без единой точки (KEYS-3)
|
||||
разный регистр появится в базе сам собой.
|
||||
@@ -83,7 +83,7 @@ prefix: KEYS
|
||||
| KEYS-5.1 | путь или query URL | «не найдено» без обращения к хранилищу |
|
||||
| KEYS-5.2 | собственная форма, данные кнопки | «некорректный ввод» либо «элемент устарел» |
|
||||
|
||||
**Почему.** Синтаксически невалидное значение не может соответствовать
|
||||
**ПОЧЕМУ.** Синтаксически невалидное значение не может соответствовать
|
||||
записи, поэтому поход в базу за ним — заведомо холостой; отсекая его на
|
||||
границе, мы дёшево снимаем целый класс мусорного трафика.
|
||||
|
||||
@@ -106,7 +106,7 @@ prefix: KEYS
|
||||
**ДОПУСКАЕТСЯ.** Когда ключ есть по природе данных, отдельный
|
||||
сгенерированный идентификатор не заводится.
|
||||
|
||||
**Почему.** Суррогат поверх естественного ключа создаёт второй способ
|
||||
**ПОЧЕМУ.** Суррогат поверх естественного ключа создаёт второй способ
|
||||
адресовать ту же строку — а значит, возможность рассинхрона между ними и
|
||||
лишний вопрос «по какому из них искать» на каждом запросе. Дополнительной
|
||||
информации он не несёт.
|
||||
@@ -117,7 +117,7 @@ prefix: KEYS
|
||||
задание, корреляционный ключ), порождаются тем же модулем (KEYS-3) и в том же
|
||||
формате.
|
||||
|
||||
**Почему.** Единый формат делает работающим главный побочный эффект
|
||||
**ПОЧЕМУ.** Единый формат делает работающим главный побочный эффект
|
||||
строковых идентификаторов: `grep` по голому значению собирает все
|
||||
упоминания сущности в логах независимо от имени поля. Второй формат
|
||||
идентификаторов эту возможность отменяет ровно для тех записей, где она
|
||||
|
||||
+16
-16
@@ -7,9 +7,9 @@ prefix: TIME
|
||||
Как приложение записывает моменты и длительности: в каком формате, откуда
|
||||
берётся значение и где появляется не-UTC.
|
||||
|
||||
Ключевые слова ДОЛЖЕН, НЕ ДОЛЖЕН, СЛЕДУЕТ, НЕ СЛЕДУЕТ, ДОПУСКАЕТСЯ и отметка
|
||||
МЕХАНИЗИРОВАНО толкуются как описано в языке конвенций версии 1 — тогда и
|
||||
только тогда, когда написаны заглавными.
|
||||
Ключевые слова ДОЛЖЕН, НЕ ДОЛЖЕН, СЛЕДУЕТ, НЕ СЛЕДУЕТ, ДОПУСКАЕТСЯ и метки
|
||||
ПОЧЕМУ и МЕХАНИЗИРОВАНО толкуются как описано в языке конвенций версии 2 —
|
||||
тогда и только тогда, когда написаны заглавными.
|
||||
|
||||
## Область действия
|
||||
|
||||
@@ -26,7 +26,7 @@ prefix: TIME
|
||||
**ДОЛЖЕН.** Момент времени записывается как `2026-06-28T11:23:45Z` —
|
||||
одинаково в хранении, логах, API и обмене с внешними системами.
|
||||
|
||||
**Почему.** Разные форматы в разных слоях требуют преобразования на каждой
|
||||
**ПОЧЕМУ.** Разные форматы в разных слоях требуют преобразования на каждой
|
||||
границе, а ошибка в таком преобразовании не видна сразу: она всплывает через
|
||||
полгода, на переходе на летнее время, когда реальное смещение перестаёт
|
||||
совпадать с тем, которое подразумевалось при написании кода. UTC с явным `Z`
|
||||
@@ -37,7 +37,7 @@ prefix: TIME
|
||||
**ДОЛЖЕН.** Внутри одной колонки БД и внутри одного потока логов длина
|
||||
строки времени одна и от записи к записи не плавает.
|
||||
|
||||
**Почему.** Лексикографическая сортировка совпадает с хронологией только
|
||||
**ПОЧЕМУ.** Лексикографическая сортировка совпадает с хронологией только
|
||||
среди строк одинаковой длины: `…00.123Z` сортируется раньше `…00.12Z`, хотя
|
||||
произошло позже. Ради этого ширина и фиксируется — `ORDER BY created_at` по
|
||||
текстовому полю обязан давать порядок событий. Плавающая ширина (типичный
|
||||
@@ -49,7 +49,7 @@ prefix: TIME
|
||||
|
||||
**ДОПУСКАЕТСЯ.** У колонки БД и у потока логов каждая своя точность.
|
||||
|
||||
**Почему.** Квантор в TIME-2 — на носитель, а не на приложение, потому что
|
||||
**ПОЧЕМУ.** Квантор в TIME-2 — на носитель, а не на приложение, потому что
|
||||
строки разных носителей между собой не сравниваются: сортировка идёт внутри
|
||||
колонки, чтение — внутри потока. Явное разрешение нужно, чтобы TIME-2 не читался
|
||||
как «одна точность на всё приложение»: от подгонки формата логов под формат
|
||||
@@ -61,7 +61,7 @@ prefix: TIME
|
||||
**НЕ ДОЛЖЕН.** Ни в базе, ни в логах, ни в JSON API нет меток в локальной
|
||||
зоне.
|
||||
|
||||
**Почему.** Метка без зоны неинтерпретируема вне процесса, который её
|
||||
**ПОЧЕМУ.** Метка без зоны неинтерпретируема вне процесса, который её
|
||||
записал: чтобы понять, какому моменту она соответствует, читателю нужно
|
||||
знать настройки чужой машины на момент записи. И даже зная их, он не
|
||||
разберёт час перехода на зимнее время: этот час идёт дважды, две записи
|
||||
@@ -73,7 +73,7 @@ prefix: TIME
|
||||
долями секунды принимается от внешней системы и приводится к каноническому
|
||||
виду (TIME-1) в точке разбора (TIME-5).
|
||||
|
||||
**Почему.** Канонический вид — обязательство нашего писателя, а не
|
||||
**ПОЧЕМУ.** Канонический вид — обязательство нашего писателя, а не
|
||||
контракт, наложенный на внешние системы: `…14:23:45+03:00` называет тот же
|
||||
момент, что `…11:23:45Z`, и отклонять его — значит ломать интеграцию со
|
||||
стороной, которая стандарт соблюла. Пропущенное же как есть, такое значение
|
||||
@@ -88,7 +88,7 @@ prefix: TIME
|
||||
**ДОЛЖЕН.** Один модуль отдаёт текущий момент, он же форматирует и разбирает
|
||||
метки; прямые вызовы часов по коду не разбросаны.
|
||||
|
||||
**Почему.** Формат, зона (TIME-1) и ширина (TIME-2) обязаны выполняться для всех
|
||||
**ПОЧЕМУ.** Формат, зона (TIME-1) и ширина (TIME-2) обязаны выполняться для всех
|
||||
меток без исключения, а каждый прямой вызов часов заводит ещё одно место,
|
||||
где их можно не соблюсти. Промах такого вызова проявляется не в коде, а в
|
||||
данных, и обнаруживается, когда испорченных записей уже накопилось.
|
||||
@@ -98,7 +98,7 @@ prefix: TIME
|
||||
|
||||
**НЕ ДОЛЖЕН.** Колонки времени не имеют `DEFAULT` с текущим моментом.
|
||||
|
||||
**Почему.** Дефолт превращает забытую вставку `created_at` в тихо работающий
|
||||
**ПОЧЕМУ.** Дефолт превращает забытую вставку `created_at` в тихо работающий
|
||||
код: значение появляется, но приходит от сервера БД — то есть с других часов
|
||||
и в формате, который выбирала не единая точка (TIME-5). Без дефолта та же ошибка
|
||||
падает громко и чинится в момент написания, а не при разборе расхождения
|
||||
@@ -110,7 +110,7 @@ prefix: TIME
|
||||
**ДОЛЖЕН.** Длительность операции записывается числом (обычно
|
||||
миллисекундами) в поле вида `duration_ms`.
|
||||
|
||||
**Почему.** Метка отвечает на вопрос «когда», длительность — на «сколько».
|
||||
**ПОЧЕМУ.** Метка отвечает на вопрос «когда», длительность — на «сколько».
|
||||
Пара меток заставляет каждого потребителя знать, какие именно две из них
|
||||
образуют операцию, и вычитать их самому — в запросе, в дашборде и глазами в
|
||||
логе; число сравнивается, агрегируется и попадает в перцентили без этого
|
||||
@@ -121,7 +121,7 @@ prefix: TIME
|
||||
|
||||
**СЛЕДУЕТ.** Замер живёт там же, где вызов, границы которого он измеряет.
|
||||
|
||||
**Почему.** Обе границы операции видит только этот слой: замер уровнем выше
|
||||
**ПОЧЕМУ.** Обе границы операции видит только этот слой: замер уровнем выше
|
||||
приписывает операции чужие накладные расходы, уровнем ниже — теряет часть
|
||||
вызова. В обоих случаях число остаётся правдоподобным и потому не
|
||||
оспаривается, хотя отвечает не на тот вопрос, который к нему задают.
|
||||
@@ -135,7 +135,7 @@ prefix: TIME
|
||||
| TIME-9.1 | момент события | стенные часы через единую точку (TIME-5) |
|
||||
| TIME-9.2 | длительность операции | монотонные часы процесса |
|
||||
|
||||
**Почему.** Стенные часы подводит NTP: они могут шагнуть назад, и тогда
|
||||
**ПОЧЕМУ.** Стенные часы подводит NTP: они могут шагнуть назад, и тогда
|
||||
интервал, посчитанный вычитанием, выйдет отрицательным, а при шаге вперёд —
|
||||
правдоподобным, но выдуманным. Монотонные часы, наоборот, не годятся для
|
||||
меток: их ноль произволен и не переживает перезапуск процесса, так что вне
|
||||
@@ -148,7 +148,7 @@ prefix: TIME
|
||||
**ДОЛЖЕН.** Преобразование в зону пользователя происходит при выводе и не
|
||||
проникает в хранение, сортировку и логи.
|
||||
|
||||
**Почему.** Как только конвертация уходит вглубь, результат вычислений
|
||||
**ПОЧЕМУ.** Как только конвертация уходит вглубь, результат вычислений
|
||||
начинает зависеть от настроек конкретного зрителя: одна и та же выборка даёт
|
||||
разные группировки, а порядок записей перестаёт быть общим для всех. Ещё
|
||||
хуже, что при конвертации в нескольких слоях её легко выполнить дважды —
|
||||
@@ -160,7 +160,7 @@ prefix: TIME
|
||||
**ДОЛЖЕН.** Значение приходит из конфигурации, значение по умолчанию —
|
||||
`UTC`.
|
||||
|
||||
**Почему.** Зашитая в код зона превращает переезд или второго пользователя в
|
||||
**ПОЧЕМУ.** Зашитая в код зона превращает переезд или второго пользователя в
|
||||
другом поясе в правку кода и релиз. Значение по умолчанию `UTC` выбрано
|
||||
потому, что оно не притворяется настроенным: показанное время совпадает с
|
||||
тем, что лежит в базе и в логах, поэтому несовпадение с ожиданиями читается
|
||||
@@ -171,7 +171,7 @@ prefix: TIME
|
||||
**ДОЛЖЕН.** «Сегодня», «за месяц» и прочие календарные границы считаются с
|
||||
явно переданной зоной, а не с системной зоной процесса.
|
||||
|
||||
**Почему.** Системная зона разная на ноутбуке разработчика и в контейнере на
|
||||
**ПОЧЕМУ.** Системная зона разная на ноутбуке разработчика и в контейнере на
|
||||
сервере, поэтому граница суток уезжает, а вместе с ней — содержимое отчёта:
|
||||
расхождение не воспроизводится там, где его заметили, и объясняется средой,
|
||||
а не кодом. Явно переданная зона делает результат функцией от аргументов.
|
||||
|
||||
Reference in New Issue
Block a user