- заведён READING.md: словарь со значениями, форма правила и её граница, ссылки, локальная часть — без разделов о ведении набора и без META-ссылок, примеры на X-префиксах - сборщик кладёт его в docs/conventions/ и перезаписывает целиком; в манифест набора добавлена секция [language] с версией и двумя документами - META-30: правка словаря или состава частей правила доходит до READING.md, иначе потребитель толкует ДОПУСКАЕТСЯ по прежней версии
404 lines
33 KiB
Markdown
404 lines
33 KiB
Markdown
---
|
||
prefix: META
|
||
---
|
||
|
||
# Как мы ведём конвенции
|
||
|
||
Конвенция описывает повторяющийся выбор: как называть директории, как
|
||
раскладывать данные, как оформлять ошибки. Она отвечает на вопрос «как
|
||
принято», а не «что здесь происходит».
|
||
|
||
Как записывается сама конвенция — правила, модальность, обоснования — в
|
||
[LANGUAGE.md](LANGUAGE.md). Здесь — про то, зачем конвенции заводятся, где
|
||
живут и как соотносятся с соседними видами документов.
|
||
|
||
Правила ниже записаны тем же языком, что и сами конвенции, и проверяются так
|
||
же.
|
||
|
||
Ключевые слова ДОЛЖЕН, НЕ ДОЛЖЕН, СЛЕДУЕТ, НЕ СЛЕДУЕТ, ДОПУСКАЕТСЯ и метки
|
||
ПОЧЕМУ и МЕХАНИЗИРОВАНО толкуются как описано в языке конвенций версии 1 —
|
||
тогда и только тогда, когда написаны заглавными.
|
||
|
||
## Область действия
|
||
|
||
Правила ниже адресованы автору конвенции — тому, кто заводит новый файл или
|
||
правит существующий, в каноне и в копиях репозиториев. Обязательность живёт
|
||
на отдельном правиле, а не на файле; шкала модальных слов — в
|
||
[LANGUAGE.md](LANGUAGE.md).
|
||
|
||
## Отличие от соседей
|
||
|
||
- `docs/adr/` — **решение**, принятое однажды и постфактум («почему выбрали
|
||
Authelia, а не Keycloak»). Запись неизменяема.
|
||
- `docs/specs/` и OpenSpec, где они есть, — **что** система делает,
|
||
наблюдаемое поведение как контракт. Конвенция — **как** написан код;
|
||
в спеки она не переносится, это не capability.
|
||
- `docs/drafts/` — оперативная хроника и черновики, «что собираюсь сделать».
|
||
- `docs/conventions/` — **правило на будущее**, применяемое многократно.
|
||
Живой документ: правится, когда договорённость меняется.
|
||
|
||
## Оформление
|
||
|
||
Имя файла повторяет имя темы: `app-directories.md`. Правилом это не
|
||
записано, и это случай META-25: регуляркой имя проверяется тривиально, но
|
||
вреда от нарушения нет — тема объявлена в шапке (META-28), и сборка идёт по
|
||
объявленному имени, а не по имени файла. Значит, и высшей модальности нет, а
|
||
ступенью ниже такое правило не окупает строчку.
|
||
|
||
## Канон и копии
|
||
|
||
Файлы в `docs/conventions/` с шапкой `origin:` — копии из общего канона
|
||
`dev-conventions`, а не собственные документы репозитория. Копия собирается
|
||
из канона целиком, поэтому репозиторное живёт ниже маркера локальной части
|
||
в конце файла: обновление сохраняет всё, что ниже маркера, и переписывает
|
||
всё, что выше. Откуда взяты копии и где брать обновления — в манифесте
|
||
`.conventions.toml` в корне репозитория.
|
||
|
||
Отдельного механизма отчёта о расхождении нет: обновление перезаписывает
|
||
файлы в рабочем дереве, а что именно изменилось, показывает `git diff` до
|
||
коммита. Поэтому в шапке копии хранится только `origin:` — отпечатков
|
||
канона и дат синхронизации в ней нет, историю держит git.
|
||
|
||
Правка выше маркера означает одно из двух: улучшение, которое переносят в
|
||
канон, или документ, переставший быть копией, — тогда `origin:` из шапки
|
||
убирают.
|
||
|
||
## Правила
|
||
|
||
### META-1. Одна конвенция — один файл
|
||
|
||
**ДОЛЖЕН.** Файл описывает ровно один повторяющийся выбор.
|
||
|
||
**ПОЧЕМУ.** Подписка перечисляется по темам, и тему берут целиком. Файл,
|
||
собравший две темы, вынуждает репозиторий взять правила, которые ему не
|
||
нужны, и вычитывать чужую половину при каждом обновлении. Разрезать позже
|
||
дорого: перенос правила в другой файл — это новый префикс и новая
|
||
нумерация, поэтому после разреза все внешние ссылки обходят руками.
|
||
|
||
### META-28. Тема объявляется в шапке файла и стоит в манифесте набора
|
||
|
||
**ДОЛЖЕН.** Шапка несёт ключ `topic:` с именем темы, и это имя стоит в
|
||
манифесте набора.
|
||
|
||
**ПОЧЕМУ.** Тема — единица подписки и единица сборки: потребитель перечисляет
|
||
темы в своём манифесте, а сборщик складывает в один файл все слои темы. Пока
|
||
имя выводится из имени файла, у сборщика нет способа узнать, что два слоя,
|
||
названные по-разному, — один документ; переименование файла при этом молча
|
||
заводит новую тему, подписка перестаёт находить прежнюю, а ссылки «конвенция
|
||
`logging`» в копиях остаются висеть. Объявленное имя вдобавок сверяется с
|
||
манифестом — выведенное сверять не с чем.
|
||
|
||
### META-29. Имя темы не переиспользуется
|
||
|
||
**НЕ ДОЛЖЕН.** Снятое или переименованное имя темы другой теме не выдаётся:
|
||
оно уходит в раздел выбывших манифеста с причиной и датой.
|
||
|
||
**ПОЧЕМУ.** Имя темы живёт в чужих репозиториях — в шапке `origin:` каждой
|
||
копии, в подписке потребителя, в тексте ссылок. Выданное второй теме, оно
|
||
начинает указывать на другой набор правил, и обнаруживается это не на сборке,
|
||
а по содержанию: файл обновится штатно, изменится текст. Та же дисциплина и по
|
||
той же причине действует для префиксов правил.
|
||
|
||
### META-30. Правка словаря или формы правила доходит до документа для читателя
|
||
|
||
**ДОЛЖЕН.** Изменение ключевых слов, их значений или состава частей правила
|
||
вносится и в короткое описание языка, которое едет в копию.
|
||
|
||
**ПОЧЕМУ.** Полное описание языка остаётся у автора набора, а по правилам код
|
||
проверяет читатель копии — человек или агент в чужом репозитории, у которого
|
||
из двух документов есть только короткий. Разошедшись, он начинает толковать
|
||
слова по прежней версии: ДОПУСКАЕТСЯ читается как бытовое «можно», отступление
|
||
от ДОЛЖЕН перестаёт требовать записи — то есть отказывает ровно то, ради чего
|
||
слова вводились, и молча. Проверить расхождение дёшево: словари в двух
|
||
документах либо совпадают, либо нет.
|
||
|
||
### META-2. Конвенция заводится, когда решение принимается третий раз
|
||
|
||
**СЛЕДУЕТ.** Поводом служит одно и то же решение, принятое третий раз и
|
||
каждый раз чуть по-другому.
|
||
|
||
**ПОЧЕМУ.** По одному-двум случаям не видно, что в решении повторяется, а
|
||
что было частностью места: правило, выведенное из первого случая, кодирует
|
||
частность и дальше мешает больше, чем помогает. Единичный выбор, к тому же
|
||
спорный, читателю нужен вместе с мотивом — это ADR, где мотив и есть
|
||
содержание записи.
|
||
|
||
### META-3. Новая конвенция пишется там, где заболело
|
||
|
||
**СЛЕДУЕТ.** Первые шаги пути «находка → конвенция → правило линтера»
|
||
делаются в репозитории, где случилась находка; в канон продвигается общая
|
||
часть.
|
||
|
||
**ПОЧЕМУ.** Правило, написанное сразу в общем виде, не проверено ни одним
|
||
применением, и условие применимости у него придумано, а не найдено, —
|
||
платят за это все потребители сразу. Формулировка, обкатанная на одном
|
||
репозитории, приезжает в канон уже с известной границей.
|
||
|
||
### META-4. В тексте конвенции нет утверждений о состоянии репозитория
|
||
|
||
**НЕ ДОЛЖЕН.** Норма пишется в настоящем предписывающем времени, без
|
||
описаний того, как сейчас устроен конкретный репозиторий.
|
||
|
||
**ПОЧЕМУ.** Такое утверждение устаревает молча и подменяет норму описанием:
|
||
читатель перестаёт понимать, что от него требуется, а что просто
|
||
констатировано. В копиях у остальных потребителей чужой факт вдобавок ложен
|
||
с первого дня. «Так сделано у нас» — содержимое региона отступлений, где оно
|
||
и локально, и проверяемо.
|
||
|
||
### META-20. Норма самодостаточна, наружу смотрит только обоснование
|
||
|
||
**ДОЛЖЕН.** Норму правила можно исполнить, имея один этот файл. Ссылка на
|
||
правило чужой темы допустима в обосновании, в «Связано» и в разграничении
|
||
области действия — но не в самой норме. Если норме нужен концепт соседней
|
||
темы, он коротко повторяется здесь, а сосед называется в обосновании как
|
||
источник решения.
|
||
|
||
**ПОЧЕМУ.** Репозиторий подписывается на произвольное подмножество
|
||
конвенций, и графа зависимостей у него нет по построению. Норма, которую
|
||
нельзя исполнить без отсутствующего файла, делает такое подмножество
|
||
невалидным молча: читатель видит связный текст и не замечает, что часть
|
||
нормы не определена. Обоснование, потерявшее адресата, деградирует честно —
|
||
пропадает перекрёстная проверка, смысл остаётся. Цена повтора — риск
|
||
разойтись с источником; она платится сознательно и видна, в отличие от
|
||
скрытой зависимости.
|
||
|
||
### META-21. Ссылка ведёт на тему или на правило, но не на путь в каноне
|
||
|
||
**ДОЛЖЕН.** На соседнюю конвенцию ссылаются именем темы (конвенция
|
||
`logging`), на конкретное правило — идентификатором (`SLOG-27`); путь файла
|
||
канона в тексте конвенции не употребляется.
|
||
|
||
**ПОЧЕМУ.** В репозитории конвенция лежит собранной: слои одной темы — это
|
||
секции одного файла, и пути `lang/go/logging.md` там не существует. Ссылка
|
||
на путь канона умирает при сборке, причём молча — текст остаётся связным.
|
||
Имя темы и идентификатор правила переживают и сборку, и переезд файла между
|
||
осями. Слой своей темы поэтому называют идентификатором его правила, а не
|
||
словами «базовый слой»: слова не проверяются и не ведут к утверждению.
|
||
|
||
### META-24. Слой ссылается на идентификаторы своего базового слоя
|
||
|
||
**ДОПУСКАЕТСЯ.** Правило языкового или стекового слоя называет идентификатор
|
||
правила арх-слоя своей темы прямо в норме.
|
||
|
||
**ПОЧЕМУ.** Подписываются темой, а не слоем: собранный файл начинается с
|
||
арх-слоя независимо от того, какие язык и стек выбраны, — базовое правило в
|
||
копии всегда рядом, и ссылка на него никуда не ведёт. Повтор его концепта
|
||
здесь заводил бы второй источник правды внутри одного документа: META-20
|
||
требует повторять концепт там, где соседнего файла может не быть, а базовый
|
||
слой отсутствовать не может. Остальные слои темы попадают в копию по
|
||
манифесту, и такой гарантии у них нет — отсюда узость разрешения. Записано
|
||
оно явно, потому что META-20 читают строже, чем он есть, и без этой строки
|
||
базу дублируют без нужды.
|
||
|
||
### META-5. Расхождение кода с правилом — отступление, а не повод переписать правило
|
||
|
||
**ДОЛЖЕН.** Правило правится, только когда неверно по существу: содержит
|
||
фактическую ошибку, внутреннее противоречие или условие применимости,
|
||
которое не даёт ответа.
|
||
|
||
**ПОЧЕМУ.** Правило, подогнанное под текущий код, перестаёт что-либо
|
||
требовать — оно описывает то, что и так происходит, и первое же расхождение
|
||
переписывает его снова. Направление «конвенция → код» держится ровно тем,
|
||
что факт не считается аргументом.
|
||
|
||
### META-6. Высшая модальность требует воспроизводимого вердикта
|
||
|
||
**ДОЛЖЕН.** Правило со ступенью ДОЛЖЕН или НЕ ДОЛЖЕН формулируется так, что
|
||
двое проверяющих по одному его тексту выносят один и тот же вердикт.
|
||
|
||
**ПОЧЕМУ.** Проверяют конвенцию в первую очередь агент и человек — они читают
|
||
текст правила и по нему смотрят код. Проверка, стало быть, есть у каждого
|
||
правила с первого дня, и её инструмент — формулировка, а не скрипт. Отсюда
|
||
цена невоспроизводимой нормы: вердикт зависит от того, кто читал, нарушения
|
||
всплывают выборочно, а отступление нечем записать — неизвестно, нарушено ли.
|
||
Для СЛЕДУЕТ это честно, там суждение и есть содержание правила; ДОЛЖЕН в
|
||
таком виде обещает то, чего не делает, и через несколько случаев обесценивает
|
||
остальные ДОЛЖЕН в файле.
|
||
|
||
Отсюда следствие: правило, вердикт которого зависит от суждения по построению
|
||
(вкус формулировки, выбор границы, уместность в конкретном месте), не может
|
||
быть ДОЛЖЕН — его модальность СЛЕДУЕТ по природе нормы, а не по слабости.
|
||
|
||
### META-25. Высшая модальность выбирается, только когда назван вред
|
||
|
||
**СЛЕДУЕТ.** Правило получает ДОЛЖЕН или НЕ ДОЛЖЕН, если в обосновании
|
||
сказано, что́ ломается при нарушении.
|
||
|
||
**ПОЧЕМУ.** Воспроизводимость вердикта — условие необходимое (META-6), но не
|
||
достаточное: воспроизводимо проверяемых мелочей больше, чем важных вещей, и
|
||
без второго условия единственным фильтром остаётся удобство проверки. Шкала
|
||
наполняется опрятностью, читатель перестаёт отличать «уронит прод» от
|
||
«неаккуратно» — и обесцениваются все ДОЛЖЕН в файле, ровно то, от чего META-6
|
||
защищает с другой стороны. Собственная ступень этого правила — СЛЕДУЕТ:
|
||
форма обоснования ничем не ограничена, поэтому «вред назван» вердикта не
|
||
даёт — один читатель увидит названный вред там, где другой увидит объяснение
|
||
мотива.
|
||
|
||
### META-27. Механизация правила желательна, но ступени не задаёт
|
||
|
||
**СЛЕДУЕТ.** Правило со ступенью ДОЛЖЕН получает машинную проверку, когда
|
||
такую проверку можно написать.
|
||
|
||
**ПОЧЕМУ.** Линтер не забывает, ничего не стоит на каждом прогоне и краснеет
|
||
до ревью, а не на нём: там, где проверка пишется, она дешевле самого
|
||
внимательного чтения, и путь «находка → конвенция → проверка» кончается ею.
|
||
Норму она при этом не заменяет и не отменяет (META-8). Условием ступени
|
||
механизация не является:
|
||
проверяющий по умолчанию — читатель правила (META-6), а если требовать
|
||
скрипт, весь канон стоит в СЛЕДУЕТ до того дня, когда скрипты появятся.
|
||
Ступень говорит о важности нормы, а не о состоянии инструментов.
|
||
|
||
### META-7. Факт механизации фиксируется в копии со ссылкой на правило
|
||
|
||
**ДОЛЖЕН.** Запись о механизации называет идентификатор правила и
|
||
конкретную проверку.
|
||
|
||
**ПОЧЕМУ.** Механизация — состояние конкретного репозитория, канон о ней не
|
||
знает, а без записи следующий автор либо заведёт вторую проверку того же,
|
||
либо будет вычитывать глазами уже проверенное машиной. Без идентификатора
|
||
читатель догадывается сам, к какому утверждению относится проверка, — и
|
||
догадывается по-разному.
|
||
|
||
### META-8. Норма из канона не удаляется, чем бы она ни проверялась
|
||
|
||
**НЕ ДОЛЖЕН.** Формулировка нормы остаётся в правиле независимо от того, кто и
|
||
чем её проверяет.
|
||
|
||
**ПОЧЕМУ.** Проверяющий по умолчанию — читатель правила (META-6), и удаление
|
||
нормы забирает у него ровно то, чем он проверяет: линтер сообщает, что
|
||
нарушено, но не сообщает, что требуется. Условие «механизировано у всех»
|
||
спасти не может: оно измеряется в день удаления, а подписчики появляются
|
||
после. Репозиторий, подключившийся через год, получил бы правило без нормы и
|
||
без проверки — заголовок с обоснованием и никакого способа узнать, что́ именно
|
||
предписано, кроме git-истории канона, до которой он не дойдёт. Списка
|
||
подписчиков у канона к тому же нет по построению, так что «у всех» ему всё
|
||
равно не проверить.
|
||
|
||
### META-10. Обоснование не удаляется никогда
|
||
|
||
**НЕ ДОЛЖЕН.** Блок ПОЧЕМУ остаётся и после того, как правило стало
|
||
проверяться линтером.
|
||
|
||
**ПОЧЕМУ.** Линтер сообщает, что нарушено, но не сообщает, зачем правило
|
||
существует. Без обоснования не видно, когда причина отпала, — проверка
|
||
продолжает работать по инерции, и возразить ей нечем, кроме как отключив.
|
||
|
||
### META-11. У трудноизменяемого слоя область действия пишется явно
|
||
|
||
**ДОЛЖЕН.** Конвенция о схеме БД, формате хранения или раскладке директорий
|
||
называет, к чему применяется: к новым таблицам и миграциям, а не к
|
||
состоянию схемы.
|
||
|
||
**ПОЧЕМУ.** Здесь не работает привычное «новое пишем правильно, старое
|
||
переезжает по мере касания»: таблица не переезжает от того, что её
|
||
потрогали. Без явной рамки правило читается как требование к текущему
|
||
состоянию, и оба исхода плохи — миграция живых данных ради опрятности либо
|
||
молчаливый вывод, что конвенция не соблюдается совсем.
|
||
|
||
### META-12. Механизируется граница изменения, а не состояние
|
||
|
||
**ДОЛЖЕН.** Проверка запрещает нарушение в новых миграциях, а не в уже
|
||
существующей схеме.
|
||
|
||
**ПОЧЕМУ.** Проверка состояния краснеет на легаси с первого дня: её
|
||
отключают или обвешивают вечным списком исключений — и она перестаёт ловить
|
||
новое, ради чего заводилась. Проверка границы оставляет старое в покое и
|
||
делает новую ошибку невозможной.
|
||
|
||
### META-13. Список отступлений трудноизменяемого слоя — постоянный
|
||
|
||
**ДОЛЖЕН.** Перечисленные старые таблицы и раскладки живут как есть, а не
|
||
как задачи на дочистку.
|
||
|
||
**ПОЧЕМУ.** Список, записанный долгом, требует либо мигрировать живые данные
|
||
без выгоды, либо год за годом объяснять невыполненный план. Второе кончается
|
||
тем, что список перестают вести, — и пропадает единственное место, где видно,
|
||
где именно правило не действует.
|
||
|
||
### META-14. Отступления перечисляются поимённо, со ссылкой на правила
|
||
|
||
**ДОЛЖЕН.** В локальной части копии перечислены отступления, которые уже
|
||
есть в коде, с идентификатором правила и причиной.
|
||
|
||
**ПОЧЕМУ.** Иначе репозиторий выглядит соблюдающим конвенцию, а проверить
|
||
это можно только чтением всего кода. Со ссылками отступления счётны: видно,
|
||
сколько правил конвенции репозиторий реально не соблюдает. Пустой список при
|
||
этом почти всегда означает не отсутствие отступлений, а то, что их не искали.
|
||
|
||
### META-15. Запись об отступлении разбирается по масштабу
|
||
|
||
**ДОЛЖЕН.** Судьба записи зависит от того, что в ней сказано:
|
||
|
||
| № | Что записано | Куда идёт |
|
||
|---|---|---|
|
||
| META-15.1 | правилу не следуем в перечисленных местах, причина названа | остаётся отступлением |
|
||
| META-15.2 | правило не применяется в репозитории целиком | чинится условие применимости — в каноне |
|
||
| META-15.3 | конвенция репозиторию не нужна | копия удаляется, подписки нет |
|
||
|
||
**ПОЧЕМУ.** Отступление описывает исключение, и по нему видно, какая часть
|
||
правила нарушена. Запись «мы это правило вообще не применяем» такой
|
||
информации не несёт и маскирует одну из двух чинимых причин: неверную рамку
|
||
правила в каноне, которую чинят один раз для всех, или лишнюю подписку,
|
||
где файл просто не нужен. Оставленная отступлением, она прячет обе.
|
||
|
||
### META-17. Ссылки на код репозитория стоят в локальной части, а не в «Связано»
|
||
|
||
**ДОЛЖЕН.** Раздел «Связано» канона содержит только ссылки, верные у всех
|
||
потребителей; ссылки на ADR, код и файлы конкретного репозитория — в
|
||
локальной части копии.
|
||
|
||
**ПОЧЕМУ.** Текст канона приезжает ко всем потребителям, и ссылка на чужой
|
||
файл у них битая с первого дня. Ниже маркера та же ссылка никого не
|
||
задевает и переживает обновление, потому что обновление её не трогает.
|
||
|
||
### META-22. Репозиторное в копии пишется ниже маркера локальной части
|
||
|
||
**ДОЛЖЕН.** Правки репозитория вносятся ниже маркера, а не в текст,
|
||
пришедший из канона.
|
||
|
||
**ПОЧЕМУ.** Обновление перезаписывает всё, что выше маркера, поэтому правка
|
||
там живёт до первого `pull`. Заметить пропажу можно, только вычитав
|
||
`git diff` целиком — а он в этот момент и без того полон изменений канона,
|
||
и своя строка теряется среди чужих.
|
||
|
||
### META-23. Документ, переставший быть копией, не носит `origin:`
|
||
|
||
**НЕ ДОЛЖЕН.** Файл, который развели с каноном намеренно, шапку `origin:`
|
||
не сохраняет.
|
||
|
||
**ПОЧЕМУ.** По `origin:` решается, какие файлы пересобирать из канона. Форк,
|
||
оставивший шапку, при первом же обновлении теряет ровно то, ради чего его
|
||
заводили. Происхождение такого документа остаётся в истории коммита, где оно
|
||
никого не вводит в заблуждение.
|
||
|
||
### META-18. README директории перечисляет конвенции с однострочным описанием
|
||
|
||
**СЛЕДУЕТ.** Одна плоская таблица: файл и строка о том, про что он.
|
||
|
||
**ПОЧЕМУ.** Подписка — это набор лежащих файлов, и без описаний вопрос
|
||
«какая из них про мой случай» решается открыванием каждой. Ценой в десяток
|
||
файлов это означает, что не открывают ни одной.
|
||
|
||
### META-19. Короткие инварианты дублируются в точку входа агента
|
||
|
||
**ДОЛЖЕН.** В `AGENTS.md` / `CLAUDE.md` едет одна строка на правило с его
|
||
идентификатором; детали остаются в конвенции.
|
||
|
||
**ПОЧЕМУ.** Сама по себе конвенция агенту не видна: он дойдёт до неё, только
|
||
если его туда отправили, — а безусловно он читает точку входа. Строка с
|
||
идентификатором служит и напоминанием, и адресом, по которому за
|
||
подробностями идут; перенос деталей туда же вернул бы задачу поддержки двух
|
||
текстов.
|
||
|
||
## Освободившиеся номера
|
||
|
||
Номер удалённого правила не переиспользуется, поэтому дыры в нумерации —
|
||
норма. Здесь перечислено, что́ под ними было: без этого упоминание номера в
|
||
прозе не отличить от ссылки на исчезнувшее правило.
|
||
|
||
| Номер | Что было | Почему снято |
|
||
|---|---|---|
|
||
| META-16 | имя файла — kebab-case | вреда от нарушения нет (META-25); осталось прозой в «Оформлении» |
|
||
| META-26 | запрет слов обязательства в обосновании | правило о заглавных уже делает строчное слово ненормативным, а форму обоснования рамками не ограничивают |
|
||
| META-9 | общая механизация разрешала удалить норму из канона | снято 2026-07-26: удаление оставляло будущего подписчика без нормы и без проверки, а «механизировано у всех» канону не проверить (META-8) |
|