common: добавлен язык записи конвенций
- правило = номер + модальность (ДОЛЖЕН / СЛЕДУЕТ / ДОПУСКАЕТСЯ) плюс обязательный блок «Почему»; из OpenSpec взяты идентификаторы правил, но не SHALL и не GIVEN/WHEN/THEN - файловый статус отменён: обязательность живёт на правиле, а в шапке ещё не переведённых конвенций ключ остаётся меткой «это проза»
This commit is contained in:
+25
-24
@@ -1,16 +1,12 @@
|
|||||||
---
|
|
||||||
status: обязательная
|
|
||||||
---
|
|
||||||
|
|
||||||
# Как мы ведём конвенции
|
# Как мы ведём конвенции
|
||||||
|
|
||||||
Конвенция описывает повторяющийся выбор: как называть директории, как
|
Конвенция описывает повторяющийся выбор: как называть директории, как
|
||||||
раскладывать данные, как оформлять ошибки. Она отвечает на вопрос «как
|
раскладывать данные, как оформлять ошибки. Она отвечает на вопрос «как
|
||||||
принято», а не «что здесь происходит». Одна конвенция — один файл.
|
принято», а не «что здесь происходит». Одна конвенция — один файл.
|
||||||
|
|
||||||
Механической проверки у самой этой конвенции нет — осознанное исключение:
|
Как записывается сама конвенция — правила, модальность, обоснования — в
|
||||||
проверять «правильно ли написана конвенция» нечем, а обязательный статус
|
[language.md](language.md). Здесь — про то, зачем они заводятся, где живут
|
||||||
нужен, чтобы правила ниже не обсуждались заново в каждом репозитории.
|
и как соотносятся с соседними видами документов.
|
||||||
|
|
||||||
## Канон и копии
|
## Канон и копии
|
||||||
|
|
||||||
@@ -54,17 +50,16 @@ status: обязательная
|
|||||||
текущем состоянии репозитория. «Так сделано у нас» — это регион
|
текущем состоянии репозитория. «Так сделано у нас» — это регион
|
||||||
отступлений; норма пишется в настоящем предписывающем времени.
|
отступлений; норма пишется в настоящем предписывающем времени.
|
||||||
|
|
||||||
## Статус
|
## Насколько правило обязательно
|
||||||
|
|
||||||
Каждая конвенция объявляет статус в шапке:
|
Обязательность живёт **на правиле**, а не на файле: один документ почти
|
||||||
|
всегда смешивает жёсткие требования с советами, и общая пометка на нём
|
||||||
|
неизбежно врёт про часть содержимого. Шкала модальных слов — в
|
||||||
|
[language.md](language.md).
|
||||||
|
|
||||||
- **рекомендуемая** — так стоит делать в новом коде; существующий переезжает
|
Правило без механической проверки держится только на внимании. Для
|
||||||
по мере касания, отдельной кампанией не переписывается;
|
**СЛЕДУЕТ** это нормально, для **ДОЛЖЕН** — плохо: такое правило либо
|
||||||
- **обязательная** — нарушение считается ошибкой; по возможности проверяется
|
механизируется, либо честно понижается.
|
||||||
линтером или хуком, а не вниманием.
|
|
||||||
|
|
||||||
Конвенция без механической проверки держится только на внимании — это
|
|
||||||
нормально для рекомендуемой и плохо для обязательной.
|
|
||||||
|
|
||||||
## Когда заводить
|
## Когда заводить
|
||||||
|
|
||||||
@@ -89,14 +84,19 @@ status: обязательная
|
|||||||
- **из канона формулировка не удаляется**, пока правило не механизировано
|
- **из канона формулировка не удаляется**, пока правило не механизировано
|
||||||
у всех потребителей: иначе те, у кого линтера нет, останутся без правила;
|
у всех потребителей: иначе те, у кого линтера нет, останутся без правила;
|
||||||
- **факт механизации** фиксируется в локальном регионе `механизировано` —
|
- **факт механизации** фиксируется в локальном регионе `механизировано` —
|
||||||
со ссылкой на конкретное правило;
|
со ссылкой на номер правила и на конкретную проверку;
|
||||||
- когда механизация стала общей (правило уехало в общий конфиг линтера или
|
- когда механизация стала общей (правило уехало в общий конфиг линтера или
|
||||||
в общую роль), формулировка удаляется из канона одним `push`.
|
в общую роль), формулировка удаляется из канона одним `push`.
|
||||||
|
|
||||||
|
Обоснование правила («Почему») не удаляется никогда, даже когда сама норма
|
||||||
|
уехала в линтер: линтер сообщает, что нарушено, но не сообщает, зачем
|
||||||
|
правило существует, — а именно это нужно, чтобы понять, когда его пора
|
||||||
|
отменить.
|
||||||
|
|
||||||
## Трудноизменяемые слои
|
## Трудноизменяемые слои
|
||||||
|
|
||||||
У схемы БД, формата хранения и раскладки директорий шкала
|
У схемы БД, формата хранения и раскладки директорий не работает привычное
|
||||||
«рекомендуемая → переезжает по мере касания» не работает: таблица не
|
«новое пишем правильно, старое переезжает по мере касания»: таблица не
|
||||||
переезжает от того, что её потрогали. Для таких конвенций:
|
переезжает от того, что её потрогали. Для таких конвенций:
|
||||||
|
|
||||||
- **область действия пишется явно** — «применяется к новым таблицам и
|
- **область действия пишется явно** — «применяется к новым таблицам и
|
||||||
@@ -108,10 +108,11 @@ status: обязательная
|
|||||||
|
|
||||||
## Честный список отступлений
|
## Честный список отступлений
|
||||||
|
|
||||||
В локальном регионе перечисляем отступления, которые уже есть в коде, —
|
В локальном регионе перечисляем отступления, которые уже есть в коде, — со
|
||||||
иначе репозиторий делает вид, что правилу следует. У рекомендуемой
|
ссылкой на номера правил. Иначе репозиторий делает вид, что конвенции
|
||||||
конвенции пустой список отступлений почти всегда означает, что их просто не
|
следует, а проверить это можно только чтением всего кода.
|
||||||
искали.
|
|
||||||
|
Пустой список отступлений почти всегда означает, что их не искали.
|
||||||
|
|
||||||
Отступление — это «правилу не следуем здесь и вот почему». Если регион
|
Отступление — это «правилу не следуем здесь и вот почему». Если регион
|
||||||
разросся до «мы это правило вообще не применяем», значит либо у правила
|
разросся до «мы это правило вообще не применяем», значит либо у правила
|
||||||
@@ -129,7 +130,7 @@ status: обязательная
|
|||||||
- **Короткие инварианты дублируются туда, что агент читает безусловно**
|
- **Короткие инварианты дублируются туда, что агент читает безусловно**
|
||||||
(`AGENTS.md` / `CLAUDE.md`): сама по себе конвенция агенту не видна, он
|
(`AGENTS.md` / `CLAUDE.md`): сама по себе конвенция агенту не видна, он
|
||||||
дойдёт до неё, только если его туда отправили. Детали остаются здесь,
|
дойдёт до неё, только если его туда отправили. Детали остаются здесь,
|
||||||
в файл-точку-входа едет одна строка на правило.
|
в файл-точку-входа едет одна строка на правило с его номером.
|
||||||
|
|
||||||
<!-- local:точки-входа -->
|
<!-- local:точки-входа -->
|
||||||
<!-- /local -->
|
<!-- /local -->
|
||||||
|
|||||||
@@ -0,0 +1,181 @@
|
|||||||
|
# Язык конвенций
|
||||||
|
|
||||||
|
Как записываются правила в этом каноне. Документ описывает форму, а не
|
||||||
|
содержание: что такое правило, чем оно отличается от прозы вокруг и как на
|
||||||
|
него сослаться.
|
||||||
|
|
||||||
|
Форма подсмотрена у 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`.
|
||||||
Reference in New Issue
Block a user