Files
Deal/docs/spec/Код-стайл-Дейл.md
T
Rustam Khalimov 492950bdd0 Отформатировать списки параметров по код-стайлу
Больше двух параметров — каждый на отдельной строке (закрывающая
скобка в конце последнего); два и меньше — в одну строку. Правило
добавлено в docs/spec/Код-стайл-Дейл.md; применено к 628 сигнатурам
в 253 файлах.
2026-09-11 13:22:56 +03:00

19 KiB
Raw Blame History

Дейл — код-стайл (действующие правила)

Единый свод правил стиля кода для всего репозитория (core, telegram/ai/ml-сервисы, тесты). Составлен на основе исходного Стиль_кода.docx (перенесён в archive/style-guide-original/), дополнен действующими правилами проекта и .editorconfig. Правила обязательны для нового кода; приведение существующего — в 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. File-scoped namespace допустим.

  • Все using — в начале файла; сначала системные, затем сторонние/project.

  • using внутри namespace не используются (внешние using).

  • Порядок членов внутри типа: константы → поля → конструкторы → свойства → методы. Члены группируются по назначению.

  • [отмена] Регионы (#region) не используются — вместо них осмысленный порядок и декомпозиция.

  • Если у свойства есть backing-поле, поле объявляется над свойством:

    private User _user;
    public User User { get; set; }
    

3. Форматирование

  • Стандартные настройки форматирования Visual Studio / .editorconfig.

  • Фигурные скобки — всегда на отдельной строке (Allman).

  • В if/else фигурные скобки используются всегда, даже для одной инструкции.

  • Отступ — 4 пробела (символ табуляции в историческом документе; в проекте — пробелы).

  • Длина строки — желательно не более 100 символов; при переносе продолжение сдвигается вправо на один уровень отступа.

  • Каждая переменная объявляется на отдельной строке.

  • Если get/set свойства состоит из одной операции, допускается размещение на одной строке:

    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> и закрывающий </summary>каждый на своей строке; запись в одну строку (/// <summary>текст</summary>) не допускается. [изм.]

    Правильно:

    /// <summary>
    /// Краткое описание назначения.
    /// </summary>
    public void DoWork() { }
    

    Неправильно:

    /// <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-и. HashtableDictionary<>, ArrayListList<>.

  • Boxing/unboxing value-типов — только при необходимости.

  • При задании нецелых значений — минимум одна цифра до и после точки.

  • Использовать имена типов C# (int, string), а не CTS (Int32, String).

  • Поля и переменные инициализировать при объявлении, когда возможно.

  • Конструктор по умолчанию, если класс требует параметров инициализации, делать private, чтобы клиент не создал неинициализированный объект.

  • Magic numbers для статусов/состояний запрещены — только константы/enum:

    // плохо
    public User GetUserByStatus(int statusId);
    // хорошо
    public User GetUserByStatus(UserStatus userStatus);
    
  • Если get/set содержит сложные вычисления, преобразование, побочный эффект или долго выполняется — заменить свойством на метод.

  • Свойство не должно менять значение от вызова к вызову при неизменном состоянии объекта.

  • Внутри get/set не должно быть обращений к коду, не связанному напрямую с получением/сохранением значения.

  • Настройки, влияющие на работу приложения, не хардкодить — выносить в конфигурацию. Значения по умолчанию прописывать; если default невозможен и ключ отсутствует — выбрасывать исключение.

7. Функции

  • Функции, возвращающие массив/коллекцию, всегда возвращают массив/коллекцию: если данных нет — пустой экземпляр, но не null.

  • Не более 7 параметров у функции. Больше — объединять в класс/DTO.

  • Перенос параметров: если параметров больше двух — каждый на отдельной строке (открывающая ( — в конце первой строки, закрывающая ) — на отдельной строке с отступом объявления); если два или меньше — все параметры в одну строку.

    Больше двух:

    public async Task<CardMoveResultDto> MoveAsync(
        string cardId,
        string toContainerId,
        TransitionContext ctx,
        CancellationToken ct)
    

    Два или меньше:

    public User FindUser(string login, CancellationToken ct) { }
    

8. Управление выполнением программы

  • При foreach по коллекции саму коллекцию модифицировать нельзя (не добавлять и не удалять элементы).

  • Если задача решается и рекурсией, и циклом — предпочитать цикл; рекурсия — только когда цикл сложнее.

  • Тернарный оператор — только для простых проверок; сложные условия — через if/else.

  • Сложные составные условия разбивать на простые, сохраняя промежуточные результаты в bool-переменные.

  • Типы, реализующие IDisposable, создавать в using:

    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(...)), если член не является публичным API класса сам по себе. Если тип реализует член как собственный публичный сервис (нужен в DI/прямых вызовах) — допустима implicit, но решение осознанное.
  • Один публичный тип интерфейса = один файл (как и для классов); имя файла = имя типа.

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. Автоматизация

  • Исправление существующего кода (идемпотентные скрипты в scripts/):
    • fix_summary_blocks.py --check | --apply — приводит <summary> к блочному виду (§5).
    • fix_private_docs.py --check | --preview | --apply — понижает XML-док с private/internal до // (§5).
  • Проверка на новом коде: правила <summary>-блока и «комментарии только на public» проверяемы статически; задел — линтер (по аналогии с scripts/i18n-lint.mjs) и/или анализаторы Roslyn/StyleCop в Directory.Build.props.
  • Открытые пункты аудита и решения по ним — docs/spec/Код-стайл-аудит-2026-09-11.md.