Запах кода
Запах кода (англ. code smell) — это совокупность формальных признаков в исходном коде программы, которые с высокой вероятностью свидетельствуют о наличии глубинных проблем в архитектуре, проектировании или реализации. Термин популяризирован Кентом Беком и Мартином Фаулером в книге «Рефакторинг: улучшение существующего кода» (1999). Запах кода не является ошибкой в строгом смысле (компилятор не выдаёт предупреждение, программа может работать корректно), но указывает на потенциальные сложности при дальнейшем развитии, тестировании и поддержке кода.
История термина
Понятие «запах кода» (code smell) впервые было сформулировано Кентом Беком в конце 1990-х годов в контексте методологии экстремального программирования (XP). Бек искал простые, интуитивно понятные метафоры для описания ситуаций, когда код требует рефакторинга. В 1999 году Мартин Фаулер включил этот термин в свою книгу «Рефакторинг», где привёл 22 конкретных типа запахов, каждый из которых сопоставил с определённым набором техник рефакторинга.
В последующие годы список запахов расширялся. В 2006 году Майкл Физерс в книге «Working Effectively with Legacy Code» описал запахи, характерные для унаследованных систем. В 2018 году в книге «Refactoring: Improving the Design of Existing Code» (второе издание) Фаулер добавил новые типы запахов, отражающие изменения в практике программирования (например, появление лямбда-выражений в Java).
Классификация запахов кода
Единой общепринятой классификации не существует, однако большинство источников выделяют несколько основных категорий по характеру проявления.
Запахи, связанные с длиной и сложностью
- Длинный метод (Long Method) — метод, выполняющий слишком много операций или содержащий избыточное количество строк кода. Эмпирически считается, что метод должен помещаться на одном экране (20–30 строк). Признак: наличие нескольких уровней вложенности, комментариев, поясняющих отдельные блоки.
- Большой класс (Large Class) — класс, содержащий слишком много полей, методов или строк кода. Обычно такой класс пытается выполнять несколько несвязанных обязанностей.
- Длинный список параметров (Long Parameter List) — метод, принимающий более трёх-четырёх параметров. Затрудняет чтение и тестирование, часто свидетельствует о том, что параметры следовало бы объединить в объект.
Запахи, связанные с дублированием и избыточностью
- Дублирование кода (Duplicated Code) — одинаковые или очень похожие фрагменты кода, встречающиеся в разных местах программы. Один из самых распространённых и опасных запахов, так как любое изменение требует правки во всех копиях.
- Мёртвый код (Dead Code) — переменные, методы, классы или целые модули, которые не используются и никогда не будут вызваны. Загромождает код и вводит в заблуждение.
- Спекулятивная обобщённость (Speculative Generality) — код, написанный «на всякий случай» для гипотетических будущих требований, которые так и не реализовались. Приводит к излишней сложности.
Запахи, связанные с нарушением инкапсуляции и связности
- Завистливая функция (Feature Envy) — метод, который чаще обращается к данным другого класса, чем к своим собственным. Указывает на то, что метод следует переместить в класс, данные которого он использует.
- Неуместная близость (Inappropriate Intimacy) — классы, которые слишком сильно зависят от внутренних деталей друг друга, нарушая принцип инкапсуляции.
- Цепочка вызовов (Message Chains) — последовательность вызовов вида
a.getB().getC().getD(). Делает код хрупким: любое изменение в цепочке ломает все вызовы.
Запахи, связанные с условными конструкциями
- Переключатели (Switch Statements) — использование операторов
switchили множественныхif-elseдля выбора поведения на основе типа объекта. Часто нарушает принцип открытости/закрытости (Open/Closed Principle) и требует замены на полиморфизм. - Стрельба дробью (Shotgun Surgery) — ситуация, когда одно изменение в требованиях приводит к необходимости вносить правки во множество разных классов. Противоположность «расходящимся изменениям».
- Расходящиеся изменения (Divergent Change) — когда один класс приходится изменять по разным, не связанным между собой причинам. Указывает на нарушение принципа единственной ответственности (Single Responsibility Principle).
Причины появления запахов кода
Запахи кода возникают по нескольким основным причинам:
- Спешка и нехватка времени — разработчики пишут код, решающий текущую задачу, без учёта будущих изменений.
- Отсутствие рефакторинга — код, который никогда не пересматривается и не улучшается, неизбежно накапливает запахи.
- Недостаточное знание предметной области — разработчики не понимают, какие абстракции адекватны задаче, и создают избыточные или неверные структуры.
- Наследование от устаревших систем — код, написанный в других парадигмах или на других языках, переносится в новый проект без адаптации.
- Коллективная работа без стандартов — разные разработчики используют разные стили и подходы, что приводит к несогласованности.
Способы обнаружения
Запахи кода выявляются вручную (code review) или автоматически с помощью статических анализаторов кода.
Ручное обнаружение
- Code review — коллеги просматривают код и указывают на подозрительные места.
- Парное программирование — два разработчика совместно пишут код, что снижает вероятность появления запахов.
- Рефакторинг-сессии — специальные встречи, посвящённые улучшению существующего кода.
Автоматическое обнаружение
Современные инструменты статического анализа кода могут выявлять многие запахи. Примеры:
- SonarQube — платформа для непрерывного контроля качества кода, поддерживает более 20 языков. Выявляет дублирование, длинные методы, мёртвый код, сложные условные конструкции.
- PMD — анализатор для Java, JavaScript, Apex, PL/SQL. Имеет правила для обнаружения избыточных импортов, пустых блоков, слишком длинных методов.
- ESLint — для JavaScript/TypeScript. Расширяется плагинами, в том числе для проверки сложности функций и цикломатической сложности.
- ReSharper (для .NET) — предлагает сотни правил, включая выявление завистливых функций и цепочек вызовов.
Влияние на разработку
Наличие запахов кода не означает, что программа обязательно содержит ошибки, но существенно увеличивает риски:
- Снижение читаемости — код становится трудным для понимания новыми участниками команды.
- Увеличение времени на внесение изменений — каждое изменение требует анализа множества взаимосвязей.
- Рост числа дефектов — в сложном и запутанном коде легче допустить ошибку.
- Увеличение стоимости поддержки — по оценкам, до 80% затрат на программное обеспечение приходится на этап сопровождения, и запахи кода прямо увеличивают эти затраты.
Критика и ограничения
Концепция запахов кода не является строгой научной теорией. Основные замечания:
- Субъективность — то, что один разработчик считает запахом, другой может рассматривать как допустимый стиль. Например, короткие методы с множеством вызовов могут быть восприняты как «длинная цепочка» или как «хорошая декомпозиция».
- Контекстная зависимость — некоторые запахи (например, «длинный метод») могут быть оправданы в высокопроизводительных вычислениях, где разбиение на мелкие методы снижает производительность.
- Автоматические анализаторы могут давать ложные срабатывания или пропускать реальные проблемы, которые очевидны человеку.
Тем не менее, концепция остаётся полезным эвристическим инструментом, особенно в сочетании с практикой рефакторинга и code review.
Интересные факты
- Мартин Фаулер в первой редакции книги «Рефакторинг» (1999) описал 22 запаха. Во втором издании (2018) их стало 24 — были добавлены «Комментарии» (Comments) как запах, указывающий на необходимость выделения метода, и «Отказ от лямбда-выражений» (Refused Bequest) для случаев, когда подкласс не использует унаследованные методы.
- Термин «запах» (smell) выбран не случайно: в английском языке есть идиома «something smells fishy» (что-то здесь нечисто), и метафора «код пахнет» хорошо передаёт интуитивное ощущение проблемы.
- В сообществе разработчиков существует ироничный термин «code smell of the week» — популярные запахи, которые обсуждаются в блогах и на конференциях, но не всегда являются реальной проблемой в конкретном проекте.
Источники
- Fowler M. — Refactoring: Improving the Design of Existing Code, 2nd Edition (2018)
- Beck K. — Extreme Programming Explained: Embrace Change (1999)
- Feathers M. — Working Effectively with Legacy Code (2004)
- McConnell S. — Code Complete, 2nd Edition (2004)
- Документация SonarQube (версия 10.x) — раздел «Code Smells»
- Статья «Code Smell» в Martin Fowler’s Bliki (martinfowler.com)
BFOmetr — база данных и аналитика по компаниям России.
На главную BFOmetr →