Восстановить docs/ как зеркало для агентов (ревью МР #11)
ci / build-test (push) Successful in 2m44s
ci / build-test (pull_request) Successful in 2m46s

Ревью rust: перенос в вики не должен удалять из репозитория то, что нужно
агенту для работы (бэклог, статус, планы, код-стайл, спеки). Дублирование
вики и репы разрешено и обязательно: вики — актуальные версии для людей,
docs/ — зеркало для контекста агентов. README разведён по ролям.
This commit is contained in:
2026-09-13 00:03:43 +03:00
parent 3a26f8d4a5
commit 45065d3202
36 changed files with 10373 additions and 1 deletions
+276
View File
@@ -0,0 +1,276 @@
# Дейл — код-стайл (действующие правила)
> Единый свод правил стиля кода для всего репозитория (core, telegram/ai/ml-сервисы, тесты).
> Составлен на основе исходного `Стиль_кода.docx` (перенесён в `archive/style-guide-original/`),
> дополнен действующими правилами проекта и `.editorconfig`. Правила обязательны для нового кода;
> приведение существующего — в `docs/superpowers/backlog.md` (`TD-COMMENTS-IFACE`, `TD-PROTO-COMMENTS`).
Пометки:
- **[изм.]** — правило дополнено/уточнено относительно исходного документа.
- **[отмена]** — правило исходного документа, которое в этом проекте не применяется.
---
## 1. Именование
Используются стандартные соглашения .NET. Венгерская нотация и префиксы типов в именах не применяются.
- **Классы** — Pascal: `User`.
- **Интерфейсы** — Pascal с префиксом `I`: `IDisposable`, `ICardStore`.
- **Generic-параметры** — Pascal с `T`: `T`, `TKey`, `TValue`.
- **Публичные функции/методы** — Pascal: `Authenticate`.
- **Приватные функции/методы** — тоже Pascal: `Authenticate` (не camel).
- **Параметры функций** — camel: `userId`.
- **Свойства (public/private)** — Pascal: `FirstName`.
- **Public-поля** — Pascal: `FirstName`. **[изм.]** Публичное состояние — свойство (§4); публичное поле допускается
только для данных-контейнеров без логики и именуется Pascal.
- **Private-поля — обязательный префикс `_` + camelCase: `_firstName`.** **[изм.]** Без `_` запрещено.
Исключения — только для константоподобных полей: `const` и `static readonly` именуются PascalCase
(`MaxRetryCount`, `DefaultTimeout`).
- **Локальные переменные** — camel: `user`.
- **Константы** — Pascal: `MaxRetryCount` (приватные `const` и `static readonly` — тоже Pascal, без `_`).
- **Enum** — Pascal: `UserStatus`; **значения enum** — Pascal: `Active`.
- **Exception** — Pascal с суффиксом `Exception`: `UserAuthenticationException`.
- **Event** — Pascal: `StatusChanged`.
- **Namespace** — Pascal.
Не использовать сокращения, кроме общепринятых (`id`, `ui`, `http`, `grpc`, `json`, `api`).
## 2. Организация кода и файлов
- Один публичный тип — один файл; имя файла = имя типа. **[изм.]** Правило усилено: смешивать типы в
одном файле нельзя (небольшие вспомогательные private-классы — исключение).
- **`namespace` строго соответствует пути папки** (для тестов — тоже). Файлы группируются по назначению:
`Abstractions` (интерфейсы `I*`), `Services` (сервисы/воркеры/исполнители), `Models` (доменные типы,
enum/статусы/константные реестры), `Dtos` (`*Dto`/`*Request`/`*Response`/`*Patch`), `Extensions`
(`*Extensions`), `Options` (`*Options`), `Exceptions` (`*Exception`), `Registrars` (`*ModuleRegistrar`),
`Configurations` (EF-конфигурации), `Entities`, `Repositories`. Feature-папки допустимы и сохраняются
(`Endpoints`, `Middleware`, `Hosting`, `Parsing`, `ColumnRules` и т.п.).
- **Тестовые проекты** группируются по областям (`Modules/<X>`, `Api`, `Infrastructure`, `Contracts`,
`Grpc`, …), общие фейки/хелперы — в `Support`; `namespace` = `<ПроектТестов>.<Область>`.
- В одном файле — один `namespace`. File-scoped namespace допустим.
- Все `using` — в начале файла; сначала системные, затем сторонние/project.
- `using` внутри `namespace` не используются (внешние `using`).
- Порядок членов внутри типа: константы → поля → конструкторы → свойства → методы. Члены группируются
по назначению.
- **[отмена]** Регионы (`#region`) **не используются** — вместо них осмысленный порядок и декомпозиция.
- Если у свойства есть backing-поле, поле объявляется **над** свойством:
```csharp
private User _user;
public User User { get; set; }
```
## 3. Форматирование
- Стандартные настройки форматирования Visual Studio / `.editorconfig`.
- Фигурные скобки — всегда на отдельной строке (Allman).
- В `if`/`else` фигурные скобки используются **всегда**, даже для одной инструкции.
- Отступ — 4 пробела (символ табуляции в историческом документе; в проекте — пробелы).
- Длина строки — желательно не более 100 символов; при переносе продолжение сдвигается вправо на один
уровень отступа.
- Каждая переменная объявляется на отдельной строке.
- Если `get`/`set` свойства состоит из одной операции, допускается размещение на одной строке:
```csharp
public User
{
get { return user; }
}
```
- Модификаторы доступа указываются **всегда**, включая явный `private`.
## 4. Проектные соглашения .NET
Машиночитаемая часть правил форматирования/анализа — в `.editorconfig` и `Directory.Build.props`
(`Nullable=enable`, `TreatWarningsAsErrors=true`, `EnforceCodeStyleInBuild=true`). Ниже — соглашения
уровня кода, которые этими файлами не выражаются.
- **Публичные члены — только свойства** (`{ get; init; }` / `{ get; set; }`), **не публичные поля**.
**[изм.]** Отменяет исходное правило о публичных полях: публичное состояние — свойство.
- Приватное/внутреннее состояние без дополнительной логики — поле (см. §6); с логикой — свойство.
- Зависимости — через конструктор (DI). Настройки — через `IOptions<T>` / `IOptionsSnapshot<T>`;
прямое чтение `IConfiguration` в бизнес-коде не допускается.
- `var` не использовать для встроенных типов и когда тип неочевиден — предпочитать явный тип
(см. `.editorconfig`, `csharp_style_var_* = false`).
- **Время**: `DateTimeOffset` в UTC внутри домена; на wire — epoch-миллисекунды. Локальное время —
только на границе представления (UI).
- **JSON на wire** — camelCase; ошибки API — объект `{ "detail": ... }`.
- **Идентификаторы** — с префиксом сущности/типа (напр. `card_...`, `board_...`), без «сырых» чисел.
- `this.` для обращения к членам **запрещён** (`dotnet_style_qualification_* = false:warning`).
К приватным полям обращаемся по имени с `_` (`_logger.Info(...)`), к свойствам/методам — без
квалификации. Запрет распространяется на поля, свойства, методы и события. **[изм.]**
- Асинхронность: суффикс `Async`, `CancellationToken` пробрасывать до конца; `.Result` / `.Wait()`
запрещены — только `await`.
## 5. Комментирование кода
Все комментарии — на русском языке.
- **Комментируем то, что видно снаружи.** XML-doc (`///`) — на **public/protected** члены, типы и
интерфейсы. **[изм.]** Приватные/внутренние детали реализации комментариями не «обвешиваем» — только
там, где неочевидна причина/ограничение (короткий обычный комментарий).
- **Кратко.** Комментарий объясняет **зачем и что**, а не пересказывает код. Не дублировать имя и
сигнатуру словами.
- **`<summary>` — короткое описание (одна фраза).** Это назначение типа/члена, а **не** «как оно
работает» и не пояснения/детали реализации. Несколько предложений в summary не пишем.
- **`<remarks>` не используем** — подробные пояснения «как устроено» не нужны; rationale — только если
поведение действительно неочевидно, коротким обычным комментарием.
- **Никаких упоминаний процесса:** в комментариях запрещены ссылки на таски/этапы/рулинги/планы и
прототип (`Task N`, `Ruling N`, `этап N`, `python L…`, `main.py`, `прототип`, `LEADRADAR_*` и т.п.).
- **Внутренние `//`-комментарии — только для неочевидного поведения** (причина, ограничение, подвох).
Пересказ кода, пошаговая навигация и «что делает следующая строка» — удалять.
- **`<param>`/`<returns>`** — только если смысл не очевиден из имени/типа; не переписывать сигнатуру.
- **`<summary>` — только блочный.** Открывающий `<summary>` и закрывающий `</summary>` — **каждый на
своей строке**; запись в одну строку (`/// <summary>текст</summary>`) **не допускается**. **[изм.]**
Правильно:
```csharp
/// <summary>
/// Краткое описание назначения.
/// </summary>
public void DoWork() { }
```
Неправильно:
```csharp
/// <summary>Краткое описание.</summary>
public void DoWork() { }
```
- Прочие теги (`<param>`, `<returns>`, `<remarks>`, `<inheritdoc/>`) — по необходимости; `<param>`/`<returns>`
можно однострочно, `<remarks>` — блоком.
- Для функций, создающих исключения, возможные исключения указывать в `<exception>`.
- Для примеров использования — `<example>`, `<remarks>`, `<code>`.
- Для ссылок в документации — `<see cref="..."/>`, `<seeAlso cref="..."/>`.
- Спецсимволы XML в тексте комментария — через `CDATA`.
- Для сложных/неочевидных алгоритмов — пояснение каждого шага прямо в коде.
- **[изм.]** При изменении критичных участков/ядра — комментарий: кто, когда, почему.
- Временные заплатки — с `//TODO:` и указанием, что и когда должно быть исправлено.
- Неочевидные межкомпонентные зависимости (не ловятся компилятором) — описывать подробно.
## 6. Переменные и типы
- Свойство использовать только когда есть смысл. Если при получении/сохранении дополнительной логики
нет — использовать поле.
- Использовать максимально простой достаточный тип (`int`, а не `long`, когда `int` хватает).
- Константы — только для простых типов; для сложных — `static readonly`-поля.
- `object` — только когда действительно необходимо; в остальных случаях generic-и. `Hashtable` → `Dictionary<>`,
`ArrayList` → `List<>`.
- Boxing/unboxing value-типов — только при необходимости.
- При задании нецелых значений — минимум одна цифра до и после точки.
- Использовать имена типов C# (`int`, `string`), а не CTS (`Int32`, `String`).
- Поля и переменные инициализировать при объявлении, когда возможно.
- Конструктор по умолчанию, если класс требует параметров инициализации, делать `private`, чтобы клиент
не создал неинициализированный объект.
- Magic numbers для статусов/состояний запрещены — только константы/enum:
```csharp
// плохо
public User GetUserByStatus(int statusId);
// хорошо
public User GetUserByStatus(UserStatus userStatus);
```
- Если `get`/`set` содержит сложные вычисления, преобразование, побочный эффект или долго выполняется —
заменить свойством на метод.
- Свойство не должно менять значение от вызова к вызову при неизменном состоянии объекта.
- Внутри `get`/`set` не должно быть обращений к коду, не связанному напрямую с получением/сохранением значения.
- Настройки, влияющие на работу приложения, не хардкодить — выносить в конфигурацию. Значения по умолчанию
прописывать; если default невозможен и ключ отсутствует — выбрасывать исключение.
## 7. Функции
- Функции, возвращающие массив/коллекцию, всегда возвращают массив/коллекцию: если данных нет — пустой
экземпляр, но не `null`.
- Не более 7 параметров у функции. Больше — объединять в класс/DTO.
- **Перенос параметров:** если параметров **больше двух** — каждый на **отдельной строке** (открывающая `(` — в конце первой строки, закрывающая `)` — на отдельной строке с отступом объявления); если **два или меньше** — все параметры **в одну строку**.
Больше двух:
```csharp
public async Task<CardMoveResultDto> MoveAsync(
string cardId,
string toContainerId,
TransitionContext ctx,
CancellationToken ct)
```
Два или меньше:
```csharp
public User FindUser(string login, CancellationToken ct) { }
```
## 8. Управление выполнением программы
- При `foreach` по коллекции саму коллекцию модифицировать нельзя (не добавлять и не удалять элементы).
- Если задача решается и рекурсией, и циклом — предпочитать цикл; рекурсия — только когда цикл сложнее.
- Тернарный оператор — только для простых проверок; сложные условия — через `if`/`else`.
- Сложные составные условия разбивать на простые, сохраняя промежуточные результаты в `bool`-переменные.
- Типы, реализующие `IDisposable`, создавать в `using`:
```csharp
using (SqlConnection sqlConnection = new SqlConnection(...)) { }
```
## 9. События, делегаты, потоки
- Перед вызовом делегата/события — всегда проверка на `null`.
- Для простых event-ов использовать `EventHandler`/`EventArgs`.
- Для сложных event-ов — наследники `EventArgs`.
- Для блокировок использовать `lock`, а не класс `Monitor`.
## 10. Исключения и их обработка
- `try-catch` — только для непредвиденных ошибок, не для управления ходом программы.
- При пробрасывании выше — `throw;`, а **не** `throw ex;`.
- Свои исключения наследовать от `Exception`.
- Исключение создавать всегда, когда функция не может быть выполнена (неверные параметры, нет доступа к
БД, неизвестные идентификаторы и т.п.).
- Все исключения должны быть залогированы или показаны пользователю; пустые `catch` запрещены.
- В лог об ошибке, как правило, писать `StackTrace`.
## 11. Интерфейсы
- **Не дублировать `<summary>` интерфейса в реализации.** Если член объявлен в интерфейсе с XML-doc,
в классе-реализации достаточно `/// <inheritdoc/>` (или вообще ничего, если doc наследуется настройкой).
Текст описания пишется **один раз** — у интерфейса.
- **Явная реализация интерфейсов — по умолчанию** (`Task ICardStore.GetAsync(...)`). **[изм. 2026-09-11,
решение владельца]** Классы напрямую не вызываются — только через интерфейсы; исключения: DTO/модели
(напр. `Card` и семейство `I*Card`), хелперы, extension-классы. Весь прод-код уже переведён на явные
реализации (codemod `scripts/make_explicit.py`, идемпотентный).
- Один публичный тип интерфейса = один файл (как и для классов); имя файла = имя типа.
- **Маркерные классы не используются** — если нужен маркер, это маркерный интерфейс
(`IKanbanModule`, `ISharedKernel` и т.п.). **[изм. 2026-09-11]**
- **Тесты: моки — через NSubstitute** (`Substitute.For<IPasswordHasher>()`), тестовые переменные
типизируются интерфейсом. Новые hand-written фейк-классы не заводить; существующие мигрируются
поэтапно (план — `backlog.md`, `TD-TESTS-NSUBSTITUTE`). **[изм. 2026-09-11]**
## 12. Приложение: сводная таблица правил именования
| Идентификатор | Регистр | Пример |
| --- | --- | --- |
| Класс | Pascal | `User` |
| Локальная переменная | camel | `user` |
| Интерфейс | Pascal (`I`) | `IDisposable` |
| Generic | Pascal (`T`) | `T`, `TKey`, `TValue` |
| Публичная функция | Pascal | `Authenticate` |
| Приватная функция | Pascal | `Authenticate` |
| Параметр функции | camel | `userId` |
| Публичное свойство | Pascal | `FirstName` |
| Приватное свойство | Pascal | `FirstName` |
| Публичное поле | Pascal | `FirstName` |
| Приватное поле | `_` + camel | `_firstName` |
| Приватное `const` / `static readonly` | Pascal | `MaxRetryCount` |
| Константа | Pascal | `MaxRetryCount` |
| Enum | Pascal | `UserStatus` |
| Значение enum | Pascal | `Active` |
| Exception | Pascal (+`Exception`) | `UserAuthenticationException` |
| Event | Pascal | `StatusChanged` |
| Namespace | Pascal | `Deal.Core.Cards` |
## 13. Автоматизация
- **Служебные скрипты (codemod'ы, скрипты сборки/тестов/бэкапов) в репозиторий не входят** —
правило владельца: в репе только код. Актуальные копии живут локально, вне кода.
- **Проверка на новом коде**: правила `<summary>`-блока и «комментарии только на public» проверяемы
статически; задел — линтер (по аналогии с `scripts/i18n-lint.mjs`) и/или анализаторы Roslyn/StyleCop в
`Directory.Build.props`.
- Открытые пункты аудита и решения по ним — `docs/spec/Код-стайл-аудит-2026-09-11.md`.