Перейти к основному содержимому

ADR-0045: C# Language Standards

Статус

Принято.

Контекст

Pin — это fintech/gambling платформа, работающая с деньгами, PII и регуляторными данными. Код пишется командой и поддерживается годами. Без явных language standards возникают несогласованные паттерны: где-то nullable annotations, где-то нет; где-то records, где-то mutable classes; отсутствие guard clauses в aggregates; throw вместо Result.Fail в handlers.

Необходим единый язык, который исключает целые классы ошибок на уровне компилятора.

Решение

Nullability

#nullable enable + <WarningsAsErrors>nullable</WarningsAsErrors> во всех проектах. Nullable warnings превращаются в ошибки компилятора — PR с null-небезопасным кодом не компилируется в CI. null! запрещён в domain/application без обоснованного комментария.

Records Для Immutable Data

  • Value objects: readonly record struct — нет boxing, value equality из коробки.
  • DTO, commands, queries, results, events: sealed record — immutable после создания.
  • Aggregates: sealed class с private set и domain methods.

Sealed By Default

Все классы sealed если не проектировались для наследования. Снимается только для явных base classes (AggregateRoot<T>, PinHubBase<T>).

Required + Init

required keyword для обязательных полей команд и DTO — compile-time enforcement вместо runtime NullReferenceException.

Pattern Matching

switch expressions для exhaustive state mapping. _ case с throw new UnreachableException(...) — гарантия что неизвестный enum не проходит молча.

Exception Strategy

  • Ожидаемые бизнес-ошибки → Result.Fail(new TypedError(...)).
  • Нарушение инварианта домена → DomainException в aggregate/guard.
  • Unreachable code → UnreachableException.
  • Системные ошибки → пробрасываются, ловятся в middleware.

Async/Await

  • CancellationToken во всех I/O методах.
  • Нет .Result/.Wait() в async контексте.
  • ConfigureAwait(false) в infrastructure.
  • Собственные методы без суффикса Async.

Последствия

  • CI ломается на nullable warnings — нельзя смержить null-небезопасный PR.
  • Все бизнес-ошибки типизированы — нет неожиданных exceptions в handlers.
  • Immutable records исключают случайную мутацию DTO между слоями.
  • Pattern matching exhaustive — добавление нового enum значения сломает компиляцию в switch без явного обработчика.

Ссылки

Обратные ссылки

Автоссылки: где используется этот документ.