Стандарты Качества Кода — Enterprise .NET
Назначение
Документ фиксирует обязательные стандарты качества кода для всех backend разработчиков Pin. Правила специфичны для fintech/gambling платформы: работа с деньгами, PII, регуляторными данными и лицензионными требованиями делает «почти правильно» неприемлемым.
Строгая Типизация
#nullable enableи<WarningsAsErrors>nullable</WarningsAsErrors>в каждом проекте.null!запрещен в domain и application коде без явного комментария-обоснования.object,dynamic,JsonDocument,Dictionary<string, object>запрещены в domain/application contracts.- Nullable коллекции запрещены — возвращать пустую коллекцию
[], неnull. - Enum вместо boolean flags и строковых статусов для всех состояний домена.
- «Анонимные» строковые enum'ы запрещены. Значение из фиксированного набора (например, режим снапшота, флаги регулярного выражения, тип ссылки, роль и уровень доступа) моделируется реальным C#
enum, а неstring/string?. Правило распространяется на API-контракты (DTO), доменные value object и JSONB-модели. Набор флагов —IReadOnlyList<TEnum>(какFieldFlag), а не строка с разделителями. Каждый enum описывается в реестреdocs/03-domain/Pin/02-domain-model/enums/: значения PascalCase, хранение в БД какtextчерез.HasConversion<string>(), сериализация глобальнымJsonStringEnumConverter(allowIntegerValues: false), описанная обработка unknown. MoneyAmount(decimal Value, short CurrencyId)— деньги не хранятся какdecimalбез валюты;CurrencyId— id справочникаhandbook(ADR-0063).
Immutability
recordзапрещён. Для value objects, DTO, events, commands, queries, results, forms, JSONB-VO используетсяsealed classс свойствами{ get; init; }(init — единственный сеттер) и квалификаторомpublic requiredдля обязательных полей. Позиционные record-конструкторы не применяются; объекты создаются через object-initializer (new X { Prop = value }). Причина: явная инициализация по именам, отсутствие неявного value-equality/with, единообразие с aggregate root.requiredставится только для non-nullable обязательных свойств. Для nullable-свойств (T?)requiredне используется (они опциональны по умолчанию).- Value-type записи (
record struct) заменяются наreadonly structс{ get; init; }(запрет распространяется и наrecord struct). - Исключение — доменные события: они наследуют общий базовый
DomainEvent(типrecordвsrc/shared/Talent.Common). Пока этот общий тип —record, наследники-события остаютсяrecord. Перевод иерархии событий наclassвыполняется отдельно, вместе с изменением shared-слоя. - Aggregate root —
sealed classсprivate setна всех свойствах; изменения только через domain methods. sealedпо умолчанию для всех классов, где наследование не проектировалось явно.init-only properties для данных, задаваемых один раз при создании.
Инварианты И Guard Clauses
- Aggregate factory methods (
Create(...)) всегда валидируют черезGuard.*. - Value Object конструктор бросает
DomainExceptionпри нарушении инварианта. - Application handler не использует
Guard— используетFluentValidationиResult.Fail. - Illegal state transition →
Result.Fail(new InvalidStatusTransitionError(...)), неthrow.
Async / Await
CancellationTokenпринимается во всех методах с I/O..Result,.Wait(),.GetAwaiter().GetResult()запрещены в production async контексте.ConfigureAwait(false)в library и infrastructure коде.- Собственные методы не используют суффикс
Async(framework методы — используют). IAsyncEnumerable<T>для потоковых операций (export, bulk read) вместоList<T>in-memory.ValueTaskдля high-frequency методов с синхронным fast-path.
Именование И Константы
- Magic strings/numbers запрещены — использовать
const,enumилиstatic readonly. - Cache keys — через
static string CacheKeys.<Name>(...)методы, не inline строки. - Enum значения хранятся в PostgreSQL как
string(.HasConversion<string>()). - Metric names — snake_case, формат
<service>_<entity>_<operation>_<unit>.
Exceptions
- Бизнес-ошибки —
Result.Fail(new TypedError(...)), неthrow. - Нарушение инварианта домена —
throw new DomainException(message). - Unreachable code в switch —
throw new UnreachableException(...). catch (Exception e) { /* ignore */ }— запрещен. Либо логировать, либо пробрасывать.InvalidOperationException— только для неверной конфигурации или startup проблем.
Pattern Matching
switchexpressions вместоif/elseдля state machine mapping.- Exhaustive pattern matching с
_case, бросающимUnreachableException. is-pattern для typed result branches и nullable checks.
Код Файла
- Один public тип — один файл. Нет
partial classбез явной причины (source gen — исключение). - Файл не превышает 300 строк. Если длиннее — разделить на smaller concerns.
usingdeclarations в начале файла, system namespaces первые.#regionзапрещены — структурировать через методы и классы.
Code Review Блокеры
Pull request не принимается, если:
- нет типов на
nullableколлекциях или nullable reference типах; dynamicилиobjectиспользуются в domain/application коде;- бизнес-ошибка выражена через
throw, а неResult.Fail; - нет
CancellationTokenв I/O методах; - magic string/number без константы;
- «анонимный» строковый enum (свободная
string) вместо реального C#enumв контракте, домене или JSONB-модели; - sealed отсутствует на domain/application классе без причины.
Детальный Справочник
13 — Code Quality And Typing — полное руководство с примерами кода.
Обратные ссылки
Автоссылки: где используется этот документ.
- adr (1): ADR-0045: C# Language Standards
- 13-code-quality-and-typing.md (1): Code Quality И Strong Typing — Enterprise .NET 10 Standards
- backend-shared-standards-index.md (1): Backend Shared Standards Index
- frontend-form-field-behavior-rules.md (1): Правила Поведения Полей В Формах-Редакторах