ПОЧЕМУ стало ключевым словом, язык поднят до версии 2

- метка обоснования пишется заглавными и вошла в словарь набора: скелет
  правила теперь целиком из ключевых слов, а не смесь `**ДОЛЖЕН.**` и
  `**Почему.**`; в переводе на другой язык метка меняется как остальные слова
  (ПОЧЕМУ / WHY), 235 вхождений заменены
- метки правила выделены из шкалы в отдельный перечень: ПОЧЕМУ и
  МЕХАНИЗИРОВАНО обязательности не задают, а размечают части, и стандартом не
  даются ни в одном языке — раньше МЕХАНИЗИРОВАНО висело строкой в таблице
  модальности
- версия языка поднята до 2, потому что изменение формы меняет чтение уже
  написанного текста; строка о версии в двенадцати конвенциях перечисляет
  теперь и метки, а служебные слова сценария в неё по-прежнему не входят
This commit is contained in:
av
2026-07-26 14:28:37 +03:00
parent c8071dc438
commit 72d77d74bf
16 changed files with 346 additions and 318 deletions
+15 -15
View File
@@ -7,9 +7,9 @@ prefix: MIGR
Как пишутся миграции и какие типы колонок выбираются для схемы SQLite в
Go-приложении.
Ключевые слова ДОЛЖЕН, НЕ ДОЛЖЕН, СЛЕДУЕТ, НЕ СЛЕДУЕТ, ДОПУСКАЕТСЯ и отметка
МЕХАНИЗИРОВАНО толкуются как описано в языке конвенций версии 1 тогда и
только тогда, когда написаны заглавными.
Ключевые слова ДОЛЖЕН, НЕ ДОЛЖЕН, СЛЕДУЕТ, НЕ СЛЕДУЕТ, ДОПУСКАЕТСЯ и метки
ПОЧЕМУ и МЕХАНИЗИРОВАНО толкуются как описано в языке конвенций версии 2
тогда и только тогда, когда написаны заглавными.
## Область действия
@@ -25,7 +25,7 @@ Go-приложении.
**ДОЛЖЕН.** Набор миграций репозитория применяется одним инструментом —
goose.
**Почему.** Журнал применённых версий goose держит в самой базе
**ПОЧЕМУ.** Журнал применённых версий goose держит в самой базе
(`goose_db_version`) и по нему решает, что ещё не накатывалось. Второй
инструмент заводит второй журнал: миграция, применённая одним, для другого
выглядит неприменённой, и попытка накатить её повторно упирается в уже
@@ -37,7 +37,7 @@ goose.
**СЛЕДУЕТ.** Миграции хранятся рядом с кодом, который работает с этой
схемой.
**Почему.** Миграция и код, читающий схему, — одно изменение: колонка
**ПОЧЕМУ.** Миграция и код, читающий схему, — одно изменение: колонка
появляется вместе с полем структуры и запросом. Лежащие в другом конце
дерева миграции выпадают из поля зрения при правке store, и уезжает либо
код без миграции, либо миграция без кода; расходятся они на сервере, где
@@ -52,7 +52,7 @@ goose.
| MIGR-3.1 | DDL: создание таблиц, индексы, изменение структуры | SQL-файл |
| MIGR-3.2 | требует кода: генерация идентификаторов, backfill, перенос данных между формами | Go-миграция (`goose.AddMigrationContext`) |
**Почему.** DDL ничего не вычисляет, и SQL-файл показывает ровно тот текст,
**ПОЧЕМУ.** DDL ничего не вычисляет, и SQL-файл показывает ровно тот текст,
который уедет в базу; обёртка на Go вокруг него добавляет место, где можно
ошибиться, не добавляя ничего к результату.
@@ -68,7 +68,7 @@ goose.
**НЕ ДОЛЖЕН.** Откат схемы на сервере не выполняется down-миграцией;
ошибка исправляется новой миграцией вперёд.
**Почему.** Down на сервере не возвращает прежнее состояние, а имитирует
**ПОЧЕМУ.** Down на сервере не возвращает прежнее состояние, а имитирует
его: колонка, которую убрал up, восстанавливается пустой, а строки,
записанные уже по новой схеме, в старую форму не ложатся. Потеря при этом
происходит молча — миграция отчитывается об успехе. Исправление, приехавшее
@@ -84,7 +84,7 @@ goose.
| MIGR-5.1 | добавляет структуру: таблицу, колонку, индекс | пишется, убирает добавленное |
| MIGR-5.2 | необратимо преобразует данные | не пишется |
**Почему.** Down — инструмент разработки, где ветку переключают туда-сюда,
**ПОЧЕМУ.** Down — инструмент разработки, где ветку переключают туда-сюда,
и именно там он обязан действительно обращать up. Имитация опаснее
отсутствия: разработчик применяет её, получает схему прежней формы и
продолжает работу, не заметив, что колонка вернулась пустой. Отсутствующий
@@ -96,7 +96,7 @@ down останавливает сразу и заставляет пересо
**ДОЛЖЕН.** Изменение структуры и правка ER-схемы в спеках едут одним
изменением.
**Почему.** Диаграмму читают вместо DDL — в этом весь её смысл.
**ПОЧЕМУ.** Диаграмму читают вместо DDL — в этом весь её смысл.
Разошедшаяся с базой, она не бесполезна, а даёт неверный ответ, и заметить
это можно, только сверив её с миграциями, то есть проделав работу, которую
диаграмма экономит. Отложенное обновление не делается: изменение уже
@@ -112,7 +112,7 @@ down останавливает сразу и заставляет пересо
**ДОЛЖЕН.** Поле-перечисление (`state`, `kind`, …) объявляется как `TEXT`
без `CHECK`-ограничения на список значений.
**Почему.** `ALTER TABLE` в SQLite не умеет менять ограничения ни в одной
**ПОЧЕМУ.** `ALTER TABLE` в SQLite не умеет менять ограничения ни в одной
версии. Поэтому каждое новое значение перечисления в `CHECK (... IN (...))`
превращается из строки в коде в пересоздание таблицы по 12-шаговой
процедуре, с копированием данных и восстановлением внешних ключей.
@@ -128,7 +128,7 @@ down останавливает сразу и заставляет пересо
**ДОЛЖЕН.** Колонка с меткой времени объявляется как `TEXT`, значения
пишутся как RFC 3339 в UTC с суффиксом `Z` и фиксированной шириной.
**Почему.** Типа даты в SQLite нет, поэтому единственное, что делает
**ПОЧЕМУ.** Типа даты в SQLite нет, поэтому единственное, что делает
значения сравнимыми, — договорённость о формате; сам формат выбран не
здесь, а конвенцией `time` (`TIME-1`, `TIME-2`). Текст в нём сортируется
лексикографически в том же порядке, что и
@@ -141,7 +141,7 @@ down останавливает сразу и заставляет пересо
**НЕ ДОЛЖЕН.** Колонка с меткой времени не получает значение по умолчанию
на уровне схемы.
**Почему.** Время ставит приложение, и умолчание в схеме заводит второй
**ПОЧЕМУ.** Время ставит приложение, и умолчание в схеме заводит второй
источник этого значения: пропущенное приложением поле не падает, а тихо
получает время сервера базы — расхождение обнаруживается по данным, а не
по ошибке.
@@ -154,7 +154,7 @@ down останавливает сразу и заставляет пересо
**ДОЛЖЕН.** Булево значение хранится как `INTEGER` 0/1.
**Почему.** Отдельного булева типа в SQLite нет, поэтому от разнобоя
**ПОЧЕМУ.** Отдельного булева типа в SQLite нет, поэтому от разнобоя
колонку удерживает только договорённость о представлении. Цена ошибки
здесь несимметрична: строка `'true'` в булевом контексте приводится к
**0**, то есть даёт противоположный ответ, а не пустую выборку и не ошибку
@@ -166,7 +166,7 @@ down останавливает сразу и заставляет пересо
**ДОЛЖЕН.** Колонка ключа объявляется как `TEXT`, значение приходит из
приложения.
**Почему.** Здесь конвенция схемы ничего не решает — она реализует решение,
**ПОЧЕМУ.** Здесь конвенция схемы ничего не решает — она реализует решение,
принятое конвенцией `db-identifiers` (`KEYS-1`, `KEYS-2`). Повторить там
ветвление или условие значило бы завести второй источник правды, и соседние
таблицы разъехались бы по разным ответам на один вопрос.
@@ -179,7 +179,7 @@ down останавливает сразу и заставляет пересо
**ДОЛЖЕН.** Там, где первичный ключ всё-таки целочисленный — существующая
схема, миграция легаси-таблицы, — он объявляется с `AUTOINCREMENT`.
**Почему.** Без него SQLite выдаёт rowid как `max(rowid)+1`, поэтому после
**ПОЧЕМУ.** Без него SQLite выдаёт rowid как `max(rowid)+1`, поэтому после
удаления последней строки номер переиспользуется. Протухшая ссылка на
удалённую запись — из закладки, из чужой таблицы, из старого лога — молча
наводится на другую сущность и возвращает правдоподобный, но чужой ответ.