остальные конвенции переведены на формальный язык
- 11 файлов разобраны на нумерованные правила: 220 правил в каноне, у каждого модальность и обязательный блок «Почему» - классифицирующие места оформлены таблицами, файловый статус снят отовсюду, локальные регионы сохранены под прежними именами
This commit is contained in:
+201
-90
@@ -2,28 +2,18 @@
|
||||
|
||||
Конвенция описывает повторяющийся выбор: как называть директории, как
|
||||
раскладывать данные, как оформлять ошибки. Она отвечает на вопрос «как
|
||||
принято», а не «что здесь происходит». Одна конвенция — один файл.
|
||||
принято», а не «что здесь происходит».
|
||||
|
||||
Как записывается сама конвенция — правила, модальность, обоснования — в
|
||||
[language.md](language.md). Здесь — про то, зачем они заводятся, где живут
|
||||
и как соотносятся с соседними видами документов.
|
||||
[language.md](language.md). Здесь — про то, зачем конвенции заводятся, где
|
||||
живут и как соотносятся с соседними видами документов.
|
||||
|
||||
## Канон и копии
|
||||
## Область действия
|
||||
|
||||
Файлы в этой директории с шапкой `origin:` — **копии из общего канона**
|
||||
`dev-conventions`, а не собственные документы репозитория. Отсюда:
|
||||
|
||||
- репозиторное пишется **только внутрь локальных регионов**
|
||||
`<!-- local:имя --> … <!-- /local -->`: они исключены из сравнения с
|
||||
каноном, и расхождение по ним — норма, а не дрейф;
|
||||
- правка вне регионов означает одно из двух: улучшение, которое надо
|
||||
вернуть в канон, или сознательное расхождение, записанное в ключ `local:`
|
||||
шапки;
|
||||
- состояние копий показывает `conv status`, различия — `conv diff`,
|
||||
обновление из канона — `conv pull`; всё через раннер репозитория.
|
||||
|
||||
Имя региона обязательно и стабильно: перенос содержимого при обновлении
|
||||
идёт по именам.
|
||||
Правила ниже адресованы автору конвенции — тому, кто заводит новый файл или
|
||||
правит существующий, в каноне и в копиях репозиториев. Обязательность живёт
|
||||
на отдельном правиле, а не на файле; шкала модальных слов — в
|
||||
[language.md](language.md).
|
||||
|
||||
## Отличие от соседей
|
||||
|
||||
@@ -36,101 +26,222 @@
|
||||
- `docs/conventions/` — **правило на будущее**, применяемое многократно.
|
||||
Живой документ: правится, когда договорённость меняется.
|
||||
|
||||
## Направление: конвенция → код
|
||||
## Канон и копии
|
||||
|
||||
Конвенция формулируется независимо от того, как устроено конкретное
|
||||
приложение. Код следует конвенции, а не наоборот.
|
||||
Файлы в `docs/conventions/` с шапкой `origin:` — копии из общего канона
|
||||
`dev-conventions`, а не собственные документы репозитория. Репозиторное
|
||||
живёт только внутри локальных регионов `<!-- local:имя --> … <!-- /local -->`:
|
||||
они исключены из сравнения с каноном, и расхождение по ним — норма, а не
|
||||
дрейф. Правка вне регионов означает одно из двух: улучшение, которое
|
||||
возвращают в канон, или сознательное расхождение, записанное в ключ `local:`
|
||||
шапки. Состояние копий показывает `conv status`, различия — `conv diff`,
|
||||
обновление из канона — `conv pull`; всё через раннер репозитория.
|
||||
|
||||
Если код расходится с правилом — это отступление, и оно записывается в
|
||||
локальный регион, а не переписывает правило. Правило меняется только тогда,
|
||||
когда оно **неверно по существу**: содержит фактическую ошибку, внутреннее
|
||||
противоречие или условие применимости, которое не даёт ответа.
|
||||
Имя региона обязательно и стабильно: перенос содержимого при обновлении
|
||||
идёт по именам, и переименование осиротит содержимое во всех копиях.
|
||||
|
||||
Практическое следствие: в тексте конвенции не должно быть утверждений о
|
||||
текущем состоянии репозитория. «Так сделано у нас» — это регион
|
||||
отступлений; норма пишется в настоящем предписывающем времени.
|
||||
## Правила
|
||||
|
||||
## Насколько правило обязательно
|
||||
### R1. Одна конвенция — один файл
|
||||
|
||||
Обязательность живёт **на правиле**, а не на файле: один документ почти
|
||||
всегда смешивает жёсткие требования с советами, и общая пометка на нём
|
||||
неизбежно врёт про часть содержимого. Шкала модальных слов — в
|
||||
[language.md](language.md).
|
||||
**ДОЛЖЕН.** Файл описывает ровно один повторяющийся выбор.
|
||||
|
||||
Правило без механической проверки держится только на внимании. Для
|
||||
**СЛЕДУЕТ** это нормально, для **ДОЛЖЕН** — плохо: такое правило либо
|
||||
механизируется, либо честно понижается.
|
||||
**Почему.** Подписка — это набор лежащих в репозитории файлов, и берут файл
|
||||
целиком. Файл, собравший две темы, вынуждает репозиторий взять правила,
|
||||
которые ему не нужны, и получать шум в `diff` по чужой половине. Разрезать
|
||||
позже дорого: путь файла — часть адреса правила, и после разреза внешние
|
||||
ссылки указывают не туда.
|
||||
|
||||
## Когда заводить
|
||||
### R2. Конвенция заводится, когда решение принимается третий раз
|
||||
|
||||
Когда одно и то же решение принимается третий раз и каждый раз чуть
|
||||
по-другому. Единичный выбор — не конвенция; если он ещё и был спорным, ему
|
||||
место в ADR.
|
||||
**СЛЕДУЕТ.** Поводом служит одно и то же решение, принятое третий раз и
|
||||
каждый раз чуть по-другому.
|
||||
|
||||
Путь находки: **находка → конвенция → правило линтера → удаление прозы**.
|
||||
Первые два шага делаются в репозитории, где заболело; общая часть
|
||||
продвигается в канон.
|
||||
**Почему.** По одному-двум случаям не видно, что в решении повторяется, а
|
||||
что было частностью места: правило, выведенное из первого случая, кодирует
|
||||
частность и дальше мешает больше, чем помогает. Единичный выбор, к тому же
|
||||
спорный, читателю нужен вместе с мотивом — это ADR, где мотив и есть
|
||||
содержание записи.
|
||||
|
||||
## Прозой — только то, что не выражается правилом
|
||||
### R3. Новая конвенция пишется там, где заболело
|
||||
|
||||
Как только свойство удаётся проверить машиной, его формулировка перестаёт
|
||||
работать: файл на несколько сотен строк размазывает внимание по
|
||||
тривиальному, и человек с агентом добросовестно проверят именование, не
|
||||
дойдя до формы решения.
|
||||
**СЛЕДУЕТ.** Первые шаги пути «находка → конвенция → правило линтера →
|
||||
удаление прозы» делаются в репозитории, где случилась находка; в канон
|
||||
продвигается общая часть.
|
||||
|
||||
Но удаление прозы в общем каноне устроено иначе, чем в одиночном
|
||||
репозитории. Механизация — состояние **конкретного** репозитория:
|
||||
**Почему.** Правило, написанное сразу в общем виде, не проверено ни одним
|
||||
применением, и условие применимости у него придумано, а не найдено, —
|
||||
платят за это все потребители сразу. Формулировка, обкатанная на одном
|
||||
репозитории, приезжает в канон уже с известной границей.
|
||||
|
||||
- **из канона формулировка не удаляется**, пока правило не механизировано
|
||||
у всех потребителей: иначе те, у кого линтера нет, останутся без правила;
|
||||
- **факт механизации** фиксируется в локальном регионе `механизировано` —
|
||||
со ссылкой на номер правила и на конкретную проверку;
|
||||
- когда механизация стала общей (правило уехало в общий конфиг линтера или
|
||||
в общую роль), формулировка удаляется из канона одним `push`.
|
||||
### R4. В тексте конвенции нет утверждений о состоянии репозитория
|
||||
|
||||
Обоснование правила («Почему») не удаляется никогда, даже когда сама норма
|
||||
уехала в линтер: линтер сообщает, что нарушено, но не сообщает, зачем
|
||||
правило существует, — а именно это нужно, чтобы понять, когда его пора
|
||||
отменить.
|
||||
**НЕ ДОЛЖЕН.** Норма пишется в настоящем предписывающем времени, без
|
||||
описаний того, как сейчас устроен конкретный репозиторий.
|
||||
|
||||
## Трудноизменяемые слои
|
||||
**Почему.** Такое утверждение устаревает молча и подменяет норму описанием:
|
||||
читатель перестаёт понимать, что от него требуется, а что просто
|
||||
констатировано. В копиях у остальных потребителей чужой факт вдобавок ложен
|
||||
с первого дня. «Так сделано у нас» — содержимое региона отступлений, где оно
|
||||
и локально, и проверяемо.
|
||||
|
||||
У схемы БД, формата хранения и раскладки директорий не работает привычное
|
||||
«новое пишем правильно, старое переезжает по мере касания»: таблица не
|
||||
переезжает от того, что её потрогали. Для таких конвенций:
|
||||
### R5. Расхождение кода с правилом — отступление, а не повод переписать правило
|
||||
|
||||
- **область действия пишется явно** — «применяется к новым таблицам и
|
||||
миграциям», а не к состоянию схемы;
|
||||
- **механизируется граница изменения, а не состояние** — линтер запрещает
|
||||
`AUTOINCREMENT` в новых миграциях, а не в существующей схеме: старое не
|
||||
падает, новая ошибка невозможна;
|
||||
- **список отступлений постоянный**, а не список задач на дочистку.
|
||||
**ДОЛЖЕН.** Правило правится, только когда неверно по существу: содержит
|
||||
фактическую ошибку, внутреннее противоречие или условие применимости,
|
||||
которое не даёт ответа.
|
||||
|
||||
## Честный список отступлений
|
||||
**Почему.** Правило, подогнанное под текущий код, перестаёт что-либо
|
||||
требовать — оно описывает то, что и так происходит, и первое же расхождение
|
||||
переписывает его снова. Направление «конвенция → код» держится ровно тем,
|
||||
что факт не считается аргументом.
|
||||
|
||||
В локальном регионе перечисляем отступления, которые уже есть в коде, — со
|
||||
ссылкой на номера правил. Иначе репозиторий делает вид, что конвенции
|
||||
следует, а проверить это можно только чтением всего кода.
|
||||
### R6. `ДОЛЖЕН` без механической проверки либо механизируется, либо понижается
|
||||
|
||||
Пустой список отступлений почти всегда означает, что их не искали.
|
||||
**ДОЛЖЕН.** Правило, нарушение которого объявлено ошибкой, получает
|
||||
машинную проверку или переводится в СЛЕДУЕТ.
|
||||
|
||||
Отступление — это «правилу не следуем здесь и вот почему». Если регион
|
||||
разросся до «мы это правило вообще не применяем», значит либо у правила
|
||||
неверно сформулировано условие применимости (чинить в каноне), либо
|
||||
репозиторию не нужна эта конвенция (не подписываться).
|
||||
**Почему.** Без проверки правило держится на внимании: нарушения копятся
|
||||
молча и всплывают выборочно — на том ревью, куда дошли руки. Для СЛЕДУЕТ
|
||||
это честно, а ДОЛЖЕН при этом обещает то, чего не делает, и через несколько
|
||||
таких случаев обесценивает остальные ДОЛЖЕН в файле.
|
||||
|
||||
## Оформление
|
||||
### R7. Факт механизации фиксируется в локальном регионе со ссылкой на номер
|
||||
|
||||
- Имя файла — kebab-case по теме: `app-directories.md`.
|
||||
- Раздел «Связано» в конце: ADR с обоснованием, спеки, код, который эту
|
||||
конвенцию механизирует. Репо-специфичная часть «Связано» — в локальном
|
||||
регионе, канонические ссылки — в общем тексте.
|
||||
- README директории перечисляет конвенции с однострочным описанием, чтобы
|
||||
список читался без открывания файлов.
|
||||
- **Короткие инварианты дублируются туда, что агент читает безусловно**
|
||||
(`AGENTS.md` / `CLAUDE.md`): сама по себе конвенция агенту не видна, он
|
||||
дойдёт до неё, только если его туда отправили. Детали остаются здесь,
|
||||
в файл-точку-входа едет одна строка на правило с его номером.
|
||||
**ДОЛЖЕН.** Регион `механизировано` называет номер правила и конкретную
|
||||
проверку.
|
||||
|
||||
**Почему.** Механизация — состояние конкретного репозитория, канон о ней не
|
||||
знает, а без записи следующий автор либо заведёт вторую проверку того же,
|
||||
либо будет вычитывать глазами уже проверенное машиной. Без номера правила
|
||||
читатель догадывается сам, к какому утверждению относится проверка, — и
|
||||
догадывается по-разному.
|
||||
|
||||
### R8. Формулировка не удаляется из канона, пока механизирована не у всех
|
||||
|
||||
**НЕ ДОЛЖЕН.** Норма остаётся в каноне, пока хотя бы у одного потребителя
|
||||
машинной проверки нет.
|
||||
|
||||
**Почему.** У кого линтера нет, тот после удаления остаётся без правила
|
||||
вообще: ни проверки, ни текста. Механизация у одного потребителя ничего не
|
||||
говорит о прочих, поэтому удалять по факту «у нас уже проверяется» —
|
||||
значит чинить свой файл за чужой счёт.
|
||||
|
||||
### R9. Общая механизация разрешает удалить норму из канона
|
||||
|
||||
**ДОПУСКАЕТСЯ.** Правило, уехавшее в общий конфиг линтера или в общую роль,
|
||||
удаляется из канона одним `push`.
|
||||
|
||||
**Почему.** Формулировка, дублирующая работающую у всех проверку,
|
||||
размазывает внимание: файл на несколько сотен строк заставляет человека и
|
||||
агента добросовестно вычитывать тривиальное именование и не доходить до
|
||||
формы решения. Явное разрешение нужно, чтобы R8 не читался как запрет
|
||||
удалять вообще.
|
||||
|
||||
### R10. Обоснование не удаляется никогда
|
||||
|
||||
**НЕ ДОЛЖЕН.** Блок «Почему» остаётся и после того, как норма уехала в
|
||||
линтер.
|
||||
|
||||
**Почему.** Линтер сообщает, что нарушено, но не сообщает, зачем правило
|
||||
существует. Без обоснования не видно, когда причина отпала, — проверка
|
||||
продолжает работать по инерции, и возразить ей нечем, кроме как отключив.
|
||||
|
||||
### R11. У трудноизменяемого слоя область действия пишется явно
|
||||
|
||||
**ДОЛЖЕН.** Конвенция о схеме БД, формате хранения или раскладке директорий
|
||||
называет, к чему применяется: к новым таблицам и миграциям, а не к
|
||||
состоянию схемы.
|
||||
|
||||
**Почему.** Здесь не работает привычное «новое пишем правильно, старое
|
||||
переезжает по мере касания»: таблица не переезжает от того, что её
|
||||
потрогали. Без явной рамки правило читается как требование к текущему
|
||||
состоянию, и оба исхода плохи — миграция живых данных ради опрятности либо
|
||||
молчаливый вывод, что конвенция не соблюдается совсем.
|
||||
|
||||
### R12. Механизируется граница изменения, а не состояние
|
||||
|
||||
**ДОЛЖЕН.** Проверка запрещает нарушение в новых миграциях, а не в уже
|
||||
существующей схеме.
|
||||
|
||||
**Почему.** Проверка состояния краснеет на легаси с первого дня: её
|
||||
отключают или обвешивают вечным списком исключений — и она перестаёт ловить
|
||||
новое, ради чего заводилась. Проверка границы оставляет старое в покое и
|
||||
делает новую ошибку невозможной.
|
||||
|
||||
### R13. Список отступлений трудноизменяемого слоя — постоянный
|
||||
|
||||
**ДОЛЖЕН.** Перечисленные старые таблицы и раскладки живут как есть, а не
|
||||
как задачи на дочистку.
|
||||
|
||||
**Почему.** Список, записанный долгом, требует либо мигрировать живые данные
|
||||
без выгоды, либо год за годом объяснять невыполненный план. Второе кончается
|
||||
тем, что список перестают вести, — и пропадает единственное место, где видно,
|
||||
где именно правило не действует.
|
||||
|
||||
### R14. Отступления перечисляются поимённо, со ссылкой на номера правил
|
||||
|
||||
**ДОЛЖЕН.** В локальном регионе перечислены отступления, которые уже есть в
|
||||
коде, с номером правила и причиной.
|
||||
|
||||
**Почему.** Иначе репозиторий выглядит соблюдающим конвенцию, а проверить
|
||||
это можно только чтением всего кода. Со ссылками отступления счётны: видно,
|
||||
сколько правил конвенции репозиторий реально не соблюдает. Пустой регион при
|
||||
этом почти всегда означает не отсутствие отступлений, а то, что их не искали.
|
||||
|
||||
### R15. Запись в регионе отступлений разбирается по масштабу
|
||||
|
||||
**ДОЛЖЕН.** Судьба записи зависит от того, что в ней сказано:
|
||||
|
||||
| № | Что записано | Куда идёт |
|
||||
|---|---|---|
|
||||
| R15.1 | правилу не следуем в перечисленных местах, причина названа | остаётся отступлением |
|
||||
| R15.2 | правило не применяется в репозитории целиком | чинится условие применимости — в каноне |
|
||||
| R15.3 | конвенция репозиторию не нужна | копия удаляется, подписки нет |
|
||||
|
||||
**Почему.** Отступление описывает исключение, и по нему видно, какая часть
|
||||
правила нарушена. Запись «мы это правило вообще не применяем» такой
|
||||
информации не несёт и маскирует одну из двух чинимых причин: неверную рамку
|
||||
правила в каноне, которую чинят один раз для всех, или лишнюю подписку,
|
||||
где файл просто не нужен. Оставленная отступлением, она прячет обе.
|
||||
|
||||
### R16. Имя файла — kebab-case по теме
|
||||
|
||||
**СЛЕДУЕТ.** `app-directories.md`, а не вариации регистра и разделителя.
|
||||
|
||||
**Почему.** Имя файла — часть глобального адреса правила
|
||||
(`stack/ansible/app-directories.md R4`) и значение ключа `origin` в каждой
|
||||
копии. Один способ записи избавляет от нескольких написаний одного адреса,
|
||||
а ошибка в адресе обнаруживается только тем, кто по нему пришёл и ничего не
|
||||
нашёл.
|
||||
|
||||
### R17. Репо-специфичная часть «Связано» — в локальном регионе
|
||||
|
||||
**ДОЛЖЕН.** Раздел «Связано» стоит в конце файла; канонические ссылки — в
|
||||
общем тексте, ссылки на ADR, код и файлы конкретного репозитория — в
|
||||
локальном регионе.
|
||||
|
||||
**Почему.** Общий текст уезжает `push`-ем ко всем потребителям, и ссылка на
|
||||
чужой файл у них битая с первого дня. Регион исключён из сравнения, поэтому
|
||||
та же ссылка внутри него никого не задевает и не даёт вечного шума в `diff`.
|
||||
|
||||
### R18. README директории перечисляет конвенции с однострочным описанием
|
||||
|
||||
**СЛЕДУЕТ.** Одна плоская таблица: файл и строка о том, про что он.
|
||||
|
||||
**Почему.** Подписка — это набор лежащих файлов, и без описаний вопрос
|
||||
«какая из них про мой случай» решается открыванием каждой. Ценой в десяток
|
||||
файлов это означает, что не открывают ни одной.
|
||||
|
||||
### R19. Короткие инварианты дублируются в точку входа агента
|
||||
|
||||
**ДОЛЖЕН.** В `AGENTS.md` / `CLAUDE.md` едет одна строка на правило с его
|
||||
номером; детали остаются в конвенции.
|
||||
|
||||
**Почему.** Сама по себе конвенция агенту не видна: он дойдёт до неё, только
|
||||
если его туда отправили, — а безусловно он читает точку входа. Строка с
|
||||
номером служит и напоминанием, и адресом, по которому за подробностями
|
||||
идут; перенос деталей туда же вернул бы задачу поддержки двух текстов.
|
||||
|
||||
<!-- local:точки-входа -->
|
||||
<!-- /local -->
|
||||
|
||||
Reference in New Issue
Block a user