Files
dev-conventions/common/language.md
T
av 22c6855968 common: добавлен язык записи конвенций
- правило = номер + модальность (ДОЛЖЕН / СЛЕДУЕТ / ДОПУСКАЕТСЯ) плюс
  обязательный блок «Почему»; из OpenSpec взяты идентификаторы правил, но
  не SHALL и не GIVEN/WHEN/THEN
- файловый статус отменён: обязательность живёт на правиле, а в шапке ещё
  не переведённых конвенций ключ остаётся меткой «это проза»
2026-07-25 18:56:29 +03:00

182 lines
12 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# Язык конвенций
Как записываются правила в этом каноне. Документ описывает форму, а не
содержание: что такое правило, чем оно отличается от прозы вокруг и как на
него сослаться.
Форма подсмотрена у OpenSpec, но взято оттуда не всё — см. «Чего мы не
берём».
## Зачем формализовать
Не ради строгости. Три конкретные вещи, которые без адресуемых правил не
работают:
- **Механизация.** Регион `механизировано` должен говорить «правило R4
проверяет `archrules`», а не «`AUTOINCREMENT` в новых миграциях —
`archrules`»: во втором случае читатель сам догадывается, к какому
утверждению это относится, и догадывается по-разному.
- **Отступления.** «У нас не так» бесполезно, пока не сказано, что именно
«не так». Со ссылкой на правило отступления становятся счётными: видно,
сколько правил конвенции репозиторий реально не соблюдает.
- **Промоут находки.** Путь «находка → конвенция → правило линтера →
удаление прозы» требует ручки, за которую берут конкретное правило.
Общий знаменатель — **идентификатор**. Модальные слова и таблицы полезны,
но вторичны.
## Единица — правило
```markdown
### R5. Разбор внешнего идентификатора на границе
**ДОЛЖЕН.** Идентификатор, пришедший снаружи, проходит разбор до запроса
к базе.
**Почему.** Разбор валидирует формат и нормализует регистр. Сравнение строк
в базе побайтовое, поэтому без нормализации запрос молча не находит
существующую запись — отладка такого случая стоит дороже, чем сам разбор.
```
Четыре обязательные части: **номер**, **заголовок**, **модальность с
нормой**, **почему**. Норма — одна фраза; если в неё не влезает, это два
правила.
## Правило без «почему» не принимается
Это жёсткое требование к форме, а не пожелание. Причины:
- **«Почему» — единственный способ понять, когда правило перестало
действовать.** Норма стареет молча; обоснование стареет заметно. Когда
причина отпала, видно, что правило пора убрать, а не соблюдать по
инерции.
- **Правило без обоснования не переживает спор.** Через год ни автор, ни
агент не восстановят мотив, и правило будет либо отменено первым же
возражением, либо соблюдено там, где вредит.
- **Формулировка «почему» — проверка на то, что это вообще правило.** Если
причина не формулируется, перед нами привычка или вкусовщина; ей место в
черновиках, а не в конвенции.
«Почему» отвечает на «что сломается, если сделать иначе», а не пересказывает
норму другими словами. «Потому что так принято» — не обоснование.
## Модальные слова
Пишутся капсом — это ключевые слова, а не обычный текст.
| Слово | Значение | Отступление |
|---|---|---|
| **ДОЛЖЕН** | нарушение считается ошибкой | только с записью в регион `отступления` |
| **НЕ ДОЛЖЕН** | запрет | то же |
| **СЛЕДУЕТ** | сильная рекомендация; новый код пишем так | допустимо, причину записываем |
| **НЕ СЛЕДУЕТ** | обратное к СЛЕДУЕТ | то же |
| **ДОПУСКАЕТСЯ** | явное разрешение | не требуется — правило ничего не запрещает |
**ДОПУСКАЕТСЯ** нужно не для симметрии: оно снимает вопрос «а так можно?»
там, где соседнее правило звучит строго и его легко перечитать шире, чем
задумано.
Модальность живёт на **правиле**, а не на файле. Прежний файловый статус
(`status: рекомендуемая` / `обязательная` в шапке) отменён: он неизбежно
врал, потому что один файл смешивает жёсткие требования с советами. В ещё
не переведённых конвенциях ключ остаётся как метка «этот файл — проза», без
нормативного значения, и исчезает при переводе.
Мы **не используем SHALL и прочие английские ключевые слова**. Они заняты
спецификациями (OpenSpec), и общий словарь стирал бы границу «конвенция —
не capability». Разный словарь эту границу держит бесплатно.
## Идентификаторы
- Формат — `R<номер>`, сквозная нумерация внутри файла, начиная с `R1`.
- Строка таблицы, если на неё нужно ссылаться отдельно, — `R5.1`, `R5.2`.
- **Номера стабильны и не переиспользуются.** Удалённое правило оставляет
дыру в нумерации; занимать её новым правилом нельзя — иначе ссылка из
чужого репозитория начнёт указывать на другое утверждение.
- Глобальный адрес — путь файла плюс номер: `arch/db-identifiers.md R5`.
В пределах одного файла достаточно `R5`.
Порядок правил в файле выбирается по читаемости, не по номерам: номер — это
идентификатор, а не позиция.
## Таблицы вместо сценариев
Часть правил **классифицирует ситуации**: какой уровень лога, какая
категория директории, что делать с невалидным вводом в зависимости от его
источника. Для них каноническая форма — таблица «ситуация → вердикт»,
строки которой при необходимости нумеруются.
Таблица плотнее прозы и не даёт пропустить ветку: пустая клетка видна, а
неупомянутый случай в абзаце — нет.
## Чего мы не берём из OpenSpec
**GIVEN/WHEN/THEN.** У спецификации субъект — система, и её поведение
разворачивается во времени: состояние, событие, исход. У конвенции субъект
— автор кода, и разворачивать нечего: есть ситуация выбора и вердикт. Это
таблица, а не траектория.
**SHALL.** См. выше про словарь.
**Сценарии как общая форма.** Прозаический сценарий остаётся точечным
инструментом — для **стыка правил**, когда два правила вместе дают
неочевидный результат:
```
WHEN зависимость недоступна и ретраи вызова исчерпаны → ext-запись ERROR
AND тик фонового цикла упал по той же причине → доменная запись WARN
```
Такой блок ставится после обоих правил и ссылается на их номера. Если
стыков нет — сценариев в файле нет.
## Что правилом не является
Модальные слова в этих частях **не употребляются** — иначе перестанет быть
понятно, что адресуемо, а что нет:
- **Область действия** — на что конвенция распространяется во времени
(«новые таблицы; существующие не переписываются»). Это рамка для всех
правил файла, а не правило.
- **Связано** — ссылки на смежные конвенции, ADR, код.
- **Локальные регионы** — содержимое принадлежит репозиторию.
- Вводная проза, объясняющая предмет конвенции.
## Как на правила ссылаются копии
В репозитории:
```markdown
<!-- local:механизировано -->
R2, R4 — `internal/archrules` (проверяются в новых миграциях).
<!-- /local -->
<!-- local:отступления -->
R6 — не соблюдается в легаси-таблицах `show_history`, `queue`: составные
ключи там появились до конвенции, переписывание требует миграции данных.
<!-- /local -->
```
Отсюда видно и то, чего раньше не было видно: конвенция из восьми правил,
из которых два механизированы и одно не соблюдается.
## Что стоит проверять машиной
Сейчас не реализовано; список — на будущее для `conv`:
- номера уникальны внутри файла и не имеют пропусков вниз (новое правило
берёт следующий свободный, а не первый освободившийся);
- у каждого `### R<n>` есть модальное слово и блок «Почему»;
- ссылки вида `R<n>` в локальных регионах копии указывают на правила,
которые в каноне ещё существуют;
- модальные слова не встречаются вне правил.
## Порядок перевода
Конвенции переводятся на этот язык по мере касания, а не кампанией.
Смешение форм в каноне допустимо: пока файл не тронут, он остаётся прозой
со статусом в шапке.
Сейчас на формальном языке записаны `arch/db-identifiers.md` и
`stack/ansible/app-directories.md`.