Условия, null и явные контракты
Три связанные темы — быстрый провал, операторы if и null — про одно: ветвления и «пустота» должны быть частью модели, а не размазаны по коду. Это продолжение MAPPER и дополнение к цикломатической сложности.
Принцип быстрого провала (Fail Fast)
Ошибочное состояние должно всплыть сразу, у границы системы, с понятным сообщением. Тихий return null или default в switch «на всякий случай» откладывает катастрофу.
public void Transfer(Money amount, Account target) {
if (amount.IsNegative)
throw new ArgumentException("Amount must be positive");
if (target is null)
throw new ArgumentNullException(nameof(target));
// ...
}
На практике:
- предусловия вместо глубоких проверок внутри;
- строгие типы параметров (не
stringтам, гдеEmail); - без
default, если все вариантыswitchдолжны быть исчерпывающими; - не менять коллекцию во время обхода;
- рефакторинг отдельно от новой функциональности (один PR — одна цель; см. легаси).
Зачем меньше if
Каждый if — дополнительный путь в графе потока управления. Цель не «запретить if», а убрать несущественные ветки:
| Запах | Лучше |
|---|---|
Флаги isX, hasY + цепочка if | Полиморфизм, strategy, state |
switch по типу/коду | Таблица обработчиков, объекты |
Булевы переменные только для if | Предикат с именем или объект правила |
| Вложенные «стрелы» | Guard clauses, ранний return |
Сравнение с true/false | Прямое условие или именованный предикат |
// До: жёстко запрограммированное правило
if (user.Age < 18 && movie.Rating == "AdultsOnly") { Deny(); }
// После: правило у объекта политики
movie.Rating.EnsureAllowedFor(user);
Подробный указатель приёмов для условий — 13.
Связь с Фаулером: Replace Conditional with Polymorphism, Decompose Conditional — 612.
Проблема null
Ссылки, допускающие null, часто смешивают три смысла: «нет значения», «неизвестно», «ошибка».
Альтернативы:
| Подход | Когда |
|---|---|
| Null Object | Пустое поведение вместо null (NullLogger, пустой список) |
| Optional / Maybe | Явно «может отсутствовать» в API |
| Коллекция | 0 или 1 элемент вместо null ссылки |
| Исключение | Невозможное состояние на границе |
| Result / Either | Ожидаемый отказ без исключения |
// Optional chaining маскирует дыру в модели
const city = user?.address?.city ?? "";
// Явная модель
const city = user.knownAddress().map(a => a.city()).orElse("");
Практики: Null Object; убрать лишние ?.; не хранить «неизвестное местоположение» как null GPS.
В C# 8+ — nullable reference types; в Kotlin — non-null по умолчанию. Всё равно нужна доменная дисциплина, а не только аннотации.
Декларативность условий
Разделяйте что проверяем и как исправляем — см. Декларативный стиль. Имя isEligibleForDiscount лучше, чем комментарий над if (a && !b || c).
Регулярные выражения и парсеры — с именованными шагами или готовыми типами (Email.parse), иначе условие становится нечитаемым «супом».
Метрики и ревью
- Рост цикломатической сложности на diff — повод открыть статью 2.
- Новые
switchпоenumбезdefault— хорошо, если enum закрыт; иначе — риск тихого пропуска. - Каждый новый
?в TypeScript/C# — вопрос: есть ли понятие в домене?
Куда дальше
- Связанность и глобалы — когда
ifразмазан из-за Singleton и god object. - Исключения — ошибки без
nullи безgoto. - Справочник тем.
См. также
Другие статьи этого же раздела в боковом меню (как на странице "О разделе"). Именование, форматирование, комментарии, документация в коде и базовые принципы читаемости — практики, которые команда договаривается соблюдать каждый день. Цикломатическая сложность — одна из наиболее устойчиво применяемых метрик статического анализа программного кода, призванная количественно оценивать логическую структуру исполняемого модуля. Правило MAPPER (Model Abstract Partial Programmable Explaining Reality) — как сопоставлять реальность и код один к одному. Богатые объекты предметной области, value objects вместо string/int и антипаттерны DTO-оргии. const, иммутабельность, ленивая инициализация и побочные эффекты в читаемом коде. Разделение «что» и «как», итерации, магические числа, callback hell и явные ошибки. Singleton, god object, shotgun surgery, feature envy и глобальное состояние — симптомы и приёмы рефакторинга. Мёртвый код, лишние абстракции, отключённые предупреждения и отложенный рефакторинг. Приватные методы, flaky-тесты, assertTrue, моки и данные — качество тестового кода и связь с разделом тестирования. Пустые catch, исключения как goto, узкие try и сообщения для пользователя. Краткие итоги раздела "Культура кода". Вопросы перед отправкой кода в репозиторий — именование, принципы, тесты, ревью и безопасность. Ответы ищите в статьях раздела.Культура написания и поддержки кода
Цикломатическая сложность и читаемость кода
MAPPER — модель кода и предметная область
Анемичные модели и примитивная одержимость
Изменяемость, побочные эффекты и неизменяемые данные
Декларативный код — что и как
Связанность, глобалы и запахи модульности
YAGNI, быстрый провал и техдолг в коде
Тесты как часть культуры кода
Исключения и обработка ошибок в читаемом коде
Итоги
Чек-лист самопроверки