diff --git a/common/conventions-guide.md b/common/conventions-guide.md index 8198b99..9a62d14 100644 --- a/common/conventions-guide.md +++ b/common/conventions-guide.md @@ -1,16 +1,12 @@ ---- -status: обязательная ---- - # Как мы ведём конвенции Конвенция описывает повторяющийся выбор: как называть директории, как раскладывать данные, как оформлять ошибки. Она отвечает на вопрос «как принято», а не «что здесь происходит». Одна конвенция — один файл. -Механической проверки у самой этой конвенции нет — осознанное исключение: -проверять «правильно ли написана конвенция» нечем, а обязательный статус -нужен, чтобы правила ниже не обсуждались заново в каждом репозитории. +Как записывается сама конвенция — правила, модальность, обоснования — в +[language.md](language.md). Здесь — про то, зачем они заводятся, где живут +и как соотносятся с соседними видами документов. ## Канон и копии @@ -54,17 +50,16 @@ status: обязательная текущем состоянии репозитория. «Так сделано у нас» — это регион отступлений; норма пишется в настоящем предписывающем времени. -## Статус +## Насколько правило обязательно -Каждая конвенция объявляет статус в шапке: +Обязательность живёт **на правиле**, а не на файле: один документ почти +всегда смешивает жёсткие требования с советами, и общая пометка на нём +неизбежно врёт про часть содержимого. Шкала модальных слов — в +[language.md](language.md). -- **рекомендуемая** — так стоит делать в новом коде; существующий переезжает - по мере касания, отдельной кампанией не переписывается; -- **обязательная** — нарушение считается ошибкой; по возможности проверяется - линтером или хуком, а не вниманием. - -Конвенция без механической проверки держится только на внимании — это -нормально для рекомендуемой и плохо для обязательной. +Правило без механической проверки держится только на внимании. Для +**СЛЕДУЕТ** это нормально, для **ДОЛЖЕН** — плохо: такое правило либо +механизируется, либо честно понижается. ## Когда заводить @@ -89,14 +84,19 @@ status: обязательная - **из канона формулировка не удаляется**, пока правило не механизировано у всех потребителей: иначе те, у кого линтера нет, останутся без правила; - **факт механизации** фиксируется в локальном регионе `механизировано` — - со ссылкой на конкретное правило; + со ссылкой на номер правила и на конкретную проверку; - когда механизация стала общей (правило уехало в общий конфиг линтера или в общую роль), формулировка удаляется из канона одним `push`. +Обоснование правила («Почему») не удаляется никогда, даже когда сама норма +уехала в линтер: линтер сообщает, что нарушено, но не сообщает, зачем +правило существует, — а именно это нужно, чтобы понять, когда его пора +отменить. + ## Трудноизменяемые слои -У схемы БД, формата хранения и раскладки директорий шкала -«рекомендуемая → переезжает по мере касания» не работает: таблица не +У схемы БД, формата хранения и раскладки директорий не работает привычное +«новое пишем правильно, старое переезжает по мере касания»: таблица не переезжает от того, что её потрогали. Для таких конвенций: - **область действия пишется явно** — «применяется к новым таблицам и @@ -108,10 +108,11 @@ status: обязательная ## Честный список отступлений -В локальном регионе перечисляем отступления, которые уже есть в коде, — -иначе репозиторий делает вид, что правилу следует. У рекомендуемой -конвенции пустой список отступлений почти всегда означает, что их просто не -искали. +В локальном регионе перечисляем отступления, которые уже есть в коде, — со +ссылкой на номера правил. Иначе репозиторий делает вид, что конвенции +следует, а проверить это можно только чтением всего кода. + +Пустой список отступлений почти всегда означает, что их не искали. Отступление — это «правилу не следуем здесь и вот почему». Если регион разросся до «мы это правило вообще не применяем», значит либо у правила @@ -129,7 +130,7 @@ status: обязательная - **Короткие инварианты дублируются туда, что агент читает безусловно** (`AGENTS.md` / `CLAUDE.md`): сама по себе конвенция агенту не видна, он дойдёт до неё, только если его туда отправили. Детали остаются здесь, - в файл-точку-входа едет одна строка на правило. + в файл-точку-входа едет одна строка на правило с его номером. diff --git a/common/language.md b/common/language.md new file mode 100644 index 0000000..95ec473 --- /dev/null +++ b/common/language.md @@ -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 + +R2, R4 — `internal/archrules` (проверяются в новых миграциях). + + + +R6 — не соблюдается в легаси-таблицах `show_history`, `queue`: составные +ключи там появились до конвенции, переписывание требует миграции данных. + +``` + +Отсюда видно и то, чего раньше не было видно: конвенция из восьми правил, +из которых два механизированы и одно не соблюдается. + +## Что стоит проверять машиной + +Сейчас не реализовано; список — на будущее для `conv`: + +- номера уникальны внутри файла и не имеют пропусков вниз (новое правило + берёт следующий свободный, а не первый освободившийся); +- у каждого `### R` есть модальное слово и блок «Почему»; +- ссылки вида `R` в локальных регионах копии указывают на правила, + которые в каноне ещё существуют; +- модальные слова не встречаются вне правил. + +## Порядок перевода + +Конвенции переводятся на этот язык по мере касания, а не кампанией. +Смешение форм в каноне допустимо: пока файл не тронут, он остаётся прозой +со статусом в шапке. + +Сейчас на формальном языке записаны `arch/db-identifiers.md` и +`stack/ansible/app-directories.md`.