Инженерия ПО
Clean Code
Цели урока
- Давать переменным и функциям имена, которые читаются как проза, а не как загадка
- Делать функции короткими (5-15 строк) и держать один уровень абстракции на функцию
- Заменять комментарии 'что делает код' переименованием и извлечением функции
- Распознавать code smells: длинные параметры, дублирование, флаговый аргумент
- Применять Boy Scout Rule: уходить из файла чище, чем зашёл
Предварительные знания
- Опыт чтения чужого кода в любом языке хотя бы полгода
- Знакомство с базовыми рефакторингами: rename, extract function
- Понимание зачем нужен code review
WTF per minute - неофициальная метрика code quality от Роберта Мартина. Хороший code review: редкие WTF. Плохой: постоянный поток. Google обнаружил что technical debt из-за плохих имен и запутанных функций замедляет delivery в 2-4 раза в больших проектах. 70% времени разработчики читают код, а не пишут.
- **Google Style Guide**: детальные правила именования, размера функций, форматирования - enforced через reviewers и lint в CI
- **Prettier** (17M weekly downloads): автоматическое форматирование убрало споры о стиле из тысяч open source проектов
- **Amazon's 'Two-Pizza Team'**: маленькие команды требуют чистого кода - нет времени на разбор запутанного legacy в ограниченном составе
Именование: код как документация
2009 год. Google анализирует корреляцию между quality of code и скоростью delivery в крупных проектах. Результат: проекты с высоким technical debt двигаются в 2-4 раза медленнее. Главный источник технического долга по отчету: непонятные имена переменных и функций - разработчики тратят 70% времени на чтение кода, не на написание.
Хорошее имя - это документация, которую невозможно забыть обновить. Принципы: интенциональность (имя раскрывает зачем, не что), различимость (d1 vs daysSinceModification), произносимость (genymdhms vs generationTimestamp), поиск (e vs EVENT_EXPIRY_LIMIT). Длина имени должна соответствовать области видимости: i в цикле допустимо, i как поле класса - нет.
Правило область видимости / длина имени: чем шире область видимости, тем длиннее должно быть имя. Переменная `i` в 3-строчном цикле - нормально. `i` как поле класса - катастрофа. Класс `Customer` в пакете из 20 классов - нормально. `Customer` как единственный класс в большом файле - нужна специализация: `PremiumCustomer`, `GuestCustomer`. Это правило особенно важно в больших codebases.
Какое имя функции лучше всего следует принципам clean code?
Функции: одна задача, одно решение
Uncle Bob (Robert Martin) в Clean Code: функция должна делать одно и только одно. Но что значит 'одно'? Тест: если функцию можно значимо разбить на секции (validation, business logic, persistence) - она делает несколько вещей. Другой тест: можно ли дать ей имя без AND (validateAndSave - нарушение).
Аргументы функции: ноль (лучше всего), один (хорошо), два (приемлемо), три (подозрительно), больше трёх (рефакторинг). Много аргументов - признак что функция делает слишком много или нужен объект-параметр. Побочные эффекты: функция, которая называется check...() не должна изменять состояние. Нарушение создает temporal coupling - скрытую зависимость от порядка вызовов.
Command-Query Separation (CQS) - принцип Бертрана Мейера: функция либо команда (изменяет состояние, возвращает void), либо запрос (возвращает данные, без побочных эффектов). Смешение создает surprises: save() возвращает bool - это проверка или сохранение? Нарушение CQS - частый источник багов при рефакторинге. CQRS на архитектурном уровне - масштабирование этого принципа.
Функция `validateAndSaveUser()` нарушает принцип Clean Code. Какое именно правило?
Комментарии: когда молчание золото
«Каждый комментарий - это признание что код не говорит сам за себя». Uncle Bob. Это провокационно, но за этим стоит точное наблюдение: комментарии устаревают, а код остается. Комментарий описывает что делает функция - через месяц функция изменилась, комментарий нет. Теперь комментарий лжет.
Плохие комментарии: дублирование кода (i++ // increment i), шум (// Constructor), закомментированный код (кто его написал? почему оставил?), journal comments (// 2021-03-15: added by John - для этого есть git blame). Хорошие комментарии: юридические (copyright), объяснение намерения (почему, не что), предупреждения (// this regex takes O(n^2) for pathological input), TODO с контекстом.
Javadoc/JSDoc для public API - исключение из правила 'меньше комментариев'. Public API - это контракт с внешним миром. Параметры, возвращаемые значения, exceptions, побочные эффекты - все это нужно документировать. Но внутри реализации - код должен говорить сам. Принцип: внешнее API документируется, внутренняя реализация пишется читаемо.
Какой из вариантов комментария наиболее соответствует принципам Clean Code?
Форматирование: команда читает как один человек
Форматирование - это коммуникация. Когда пять разных стилей в одном файле, читатель тратит когнитивные ресурсы на parsing стиля, а не на понимание логики. Форматирование решает одну задачу: сделать структуру кода видимой без дополнительных усилий. Это почему lint и prettier стали обязательными в профессиональных командах.
Вертикальное форматирование: газетная метафора. Сверху - высокоуровневые концепции (public API), снизу - детали реализации. Функции вызываемые из других функций - ниже вызывающих (сверху вниз читаемость). Вертикальное расстояние: связанные концепции близко, несвязанные - далеко. Пустые строки как абзацы: отделяют логические блоки. Горизонтальное: не более 120 символов (стандарт большинства команд).
Prettier + ESLint - стандартная комбинация для JavaScript/TypeScript. Prettier отвечает за форматирование (кавычки, отступы, скобки) - без обсуждений, автоматически. ESLint отвечает за стиль кода и потенциальные ошибки. Разделение responsibilities позволяет командам не спорить о форматировании: prettier:check в CI - если файл не отформатирован автоматически - build fails. Споры о форматировании стоят времени дороже чем prettier.
Clean code - это про форматирование и комментарии, не про архитектуру
Clean code охватывает именование, функции, классы, форматирование и систему - от строки кода до архитектуры
Именование раскрывает архитектурные решения. Функции с одной ответственностью отражают Single Responsibility Principle. Clean code и архитектура - это один континуум, разные масштабы одних принципов
Команда спорит об отступах: tabs vs spaces, 2 vs 4. Как решить это в соответствии с Clean Code?
Связанные темы
Clean code - основа для всей качественной разработки:
- Принципы SOLID — SRP на уровне класса - то же что 'функция делает одно' на уровне функции
- Рефакторинг — Техники transform существующего кода к clean code стандартам
- Метрики кода — Cyclomatic complexity и coupling измеряют clean code количественно
Ключевые идеи
- **Именование**: интенциональность > краткость; длина имени пропорциональна области видимости; noise words маскируют отсутствие смысла
- **Функции**: одна задача, нет AND в имени; 0-2 аргумента; Command-Query Separation - нет побочных эффектов в запросах
- **Комментарии**: код объясняет что, комментарии объясняют почему; TODO с JIRA тикетом; закомментированный код удалять
- **Форматирование**: автоматизация через Prettier устраняет споры; газетная структура; связанный код рядом, несвязанный - далеко
Вопросы для размышления
- Как различить 'хороший' однобуквенный идентификатор (i в цикле) от 'плохого' (x как поле класса)? Какое правило объясняет разницу?
- Uncle Bob утверждает что комментарии - признак слабости кода. Когда это справедливо, а когда нет?
- Команда наследует проект с нулевым форматированием и тысячами плохих имен. С чего начать рефакторинг к clean code?
Связанные уроки
- se-05 — Принципы SOLID создают архитектурный контекст для clean code на уровне функций
- se-16 — Метрики кода (complexity, coupling) измеряют то, что clean code принципы описывают качественно
- se-11 — Code review - процесс где clean code принципы проверяются на практике
- alg-01 — Именование алгоритмов и структур данных - первый практический уровень clean code
- se-09 — Рефакторинг - главный инструмент приведения кода к clean code стандартам
- comp-01-intro