Код с запашком

Материал из Википедии — свободной энциклопедии
Перейти к: навигация, поиск

Код с запашко́м (код с душко́м, дурно пахнущий код англ. code smell) — термин, обозначающий код с признаками (запахами) проблем в системе. Был введен Кентом Беком[1] и использован Мартином Фаулером в его книге Рефакторинг. Улучшение существующего кода[2], с тех пор[источник не указан 260 дней] активно используется разработчиками ПО[3].

Запахи кода[⇨] — это ключевые признаки необходимости рефакторинга[4]. Существуют запахи, специфичные как для парадигм программирования, так и для конкретных языков. Основной проблемой, с которой сталкиваются разработчики при борьбе с запахами кода, является то, что критерии своевременности рефакторинга невозможно четко формализовать без апелляции к эстетике и условному чувству прекрасного. Запахи кода это не набор четких правил, а описание мест, на которые нужно обращать внимание при рефакторинге[5]. Они легко обнаруживаются, но при этом не во всех случаях свидетельствуют о проблемах[1].

Код с запашком ведет к распаду кода, разработчики должны стремиться к устранению запашков путем применения однократного или многократного рефакторинга[6]. В процессе рефакторинга происходит избавление от запахов кода, что обеспечивает возможность дальнейшего развития приложения с той же или большей скоростью. Отсутствие регулярного рефакторинга с течением времени способно полностью парализовать проект, поэтому запахи кода необходимо устранять на ранних стадиях[4]. Существуют инструменты поиска и исправления запахов кода[7], однако опыт показывает, что никакие системы показателей не могут соперничать с человеческой интуицией, основанной на информации[8].

Запахи кода[править | править вики-текст]

Общие запахи объектно-ориентированного кода[править | править вики-текст]

Дублирование кода[править | править вики-текст]

Пример трехкратного дублирования кода и избавления от этого запашка с помощью выделения метода и последующих вызовов кода созданного метода

Дублирование кода - это использования одинаковых структур кода в нескольких местах. Объединение этих структур позволит улучшить программный код[8].

Примеры дублирования и методы их устранения:

  • Одно и то же выражение присутствует в двух методах одного и того же класса: необходимо применить «Выделение метода» (Extract Method) и вызывать код созданного метода из обеих точек;
  • Одно и то же выражение есть в двух подклассах, находящихся на одном уровне: необходимо применить «Выделение метода» (Extract Method) для обоих классов с последующим «Подъемом поля» (Pull Up Field) или «Формированием шаблона метода» (Form Template Method), если код похож, но не совпадает полностью. Если оба метода делают одно и то же с помощью разных алгоритмов, можно выбрать более четкий из этих алгоритмов и применить «Замещение алгоритма» (Substitute Algorithm);
  • Дублирующийся код находится в двух разных классах: необходимо применить «Выделение класса» (Extract Class) в одном классе, а затем использовать новый компонент в другом[8].

Длинный метод[править | править вики-текст]

Среди объектных программ, дольше всего живут программы с короткими методами. Чем длиннее процедура, тем труднее ее понять. Если у метода хорошее название, то не нужно смотреть его тело[5].

Следует придерживаться эвристического правила: если ощущается необходимость что-то прокомментировать, нужно написать метод. Даже одну строку имеет смысл выделить в метод, если она нуждается в разъяснениях[9].

  • Для сокращения метода достаточно применить «Выделение метода» (Extract Method);
  • Если локальные переменные и параметры препятствуют выделению метода, можно применить «Замену временной переменной вызовом метода» (Replace Temp with Query), «Введение граничного объекта» (Introduce Parametr Object) и «Сохранение всего объекта» (Preserve Whole Object)[5];
  • Условные операторы и циклы свидетельствуют о возможности выделения в отдельный метод. Для работы с условными выражениями подходит «Декомпозиция условных операторов» (Decompose Conditional). Для работы с циклом — «Выделение метода» (Extract Method)[9].

Большой класс[править | править вики-текст]

Когда класс реализует слишком обширный функционал, стоит подумать о вынесении некоторой части кода в подкласс. Это избавит разработчиков от чрезмерного количества имеющихся у класса атрибутов и дублирования кода[9].

  • Для уменьшения класса используется «Выделение класса» (Extract Class) или «Выделение подкласса» (Extract Subclass). При этом следует обращать внимание на общность в названии атрибутов и на то, использует ли класс их все одновременно[5];
  • Если большой класс является классом GUI, может потребоваться переместить его данные и поведение в отдельный объект предметной области. При этом может оказаться необходимым хранить копии некоторых данных в двух местах и обеспечить их согласованность. «Дублирование видимых данных» (Duplicate Observed Data) предлагает путь, которым можно это осуществить[10].

Длинный список параметров[править | править вики-текст]

В длинных списках параметров трудно разбираться, они становятся противоречивыми и сложными в использовании. Использование объектов позволяет, в случае изменения передаваемых данных, модифицировать только сам объект. Работая с объектами, следует передавать ровно столько, чтобы метод мог получить необходимые ему данные[10].

  • «Замена параметра вызовом метода» (Replace Parameter with Method) применяется, когда можно получить данные путем вызова метода объекта. Этот объект может быть полем или другим параметром.
  • «Сохранение всего объекта» (Preserve Whole Object) позволяет взять группу данных, полученных от объекта, и заменить их самим объектом.
  • «Введение граничного объекта» (Introduce Parameter Object) применяется, если есть несколько элементов данных без логического объекта[10].

Расходящиеся модификации[править | править вики-текст]

Проблема возникает, когда при модификации в системе невозможно выделить определенное место, которое нужно изменить. Это является следствием плохой структурированности ПО[10] или программирования методом копирования-вставки.

  • Если набор методов необходимо изменять каждый раз при внесении определенных модификаций в код, то применяется «Выделение класса» (Extract Class) (Например, три метода меняются каждый раз когда подключается новая БД, а четыре — при добавлении финансового инструмента)[5].

Стрельба дробью[править | править вики-текст]

При выполнении любых модификаций приходится вносить множество мелких изменений в большое число классов. «Стрельба дробью» похожа на «Расходящуюся модификацию», но является ее противоположностью. Расходящаяся модификация имеет место, когда есть один класс, в котором производится много различных изменений, а «Стрельба дробью» — это одно изменение, затрагивающее много классов[11].

  • Вынести все изменения в один класс позволят «Перемещение метода» (Move Method) и «Перемещение поля» (Move Field);
  • Если нет подходящего класса, то следует создать новый класс;
  • Если это необходимо, следует воспользоваться «Встраиванием класса» (Inline Class)[5].

Завистливые функции[править | править вики-текст]

Метод обращается к данным другого объекта чаще, чем к собственным данным[5].

  • «Перемещение метода» (Move Method) применяется, если метод явно следует перевести в другое место;
  • «Выделение метода» (Extract Method) применяется к части метода, если только эта часть обращается к данным другого объекта;
  • Метод использует функции нескольких классов: определяется, в каком классе находится больше всего данных, и метод помещается в класс вместе с этими данными, или с помощью «Выделения метода» (Extract Method) метод разбивается на несколько частей и они помещаются в разные места[12].

Фундаментальное практическое правило гласит: то, что изменяется одновременно, надо хранить в одном месте. Данные и функции, использующие эти данные, обычно изменяются вместе, но бывают исключения[12].

Группы данных[править | править вики-текст]

Группы данных, встречающихся совместно, нужно превращать в самостоятельный класс[12].

  • «Выделение метода» (Extract Method) используется для полей;
  • «Введение граничного объекта» (Introduce Parameter Object) или «Сохранение всего объекта» (Preserve Whole Object) для параметров методов[13].

Хорошая проверка: удалить одно из значений данных и проверить, сохранят ли смысл остальные. Если нет, это верный признак того, что данные напрашиваются на объединение их в объект[12].

Одержимость элементарными типами[править | править вики-текст]

Проблема связана с использованием элементарных типов, вместо маленьких объектов для небольших задач, таких как валюта, диапазоны, специальные строки для телефонных номеров и т. п.

  • «Замена значения данных объектом» (Replace Data Value with Object);
  • «Замена массива объектом» (Replace Array with Object);
  • Если это код типа, то используйте «Замену кода типа классом» (Replace Type Code with Class), «Замену кода типа подклассами» (Replace Type Code with Subclasses) или «Замену кода типа состоянием/стратегией» (Replace Type Code with State/Strategy)[5].

Операторы типа switch[править | править вики-текст]

Одним из очевидных признаков объектно-ориентированного кода служит сравнительно редкое использование операторов типа switch (или case). Часто один и тот же блок switch оказывается разбросанным по разным местам программы. При добавлении в переключатель нового варианта приходится искать все эти блоки switch и модифицировать их. Как правило, заметив блок switch, следует подумать о полиморфизме[14].

  • Если switch переключается по коду типа, то следует использовать «Замену кода типа подклассами» (Replace Type Code with Subclasses) или «Замену кода типа состоянием/стратегией» (Replace Type Code with State/Strategy);
  • Может понадобиться «Выделение метода» (Extract Method) и «Перемещение метода» (Move Method) чтобы изолировать switch и поместить его в нужный класс;
  • После настройки структуры наследования следует использовать «Замену условного оператора полиморфизмом» (Replace Conditional with Polymorphism)[5].

Параллельные иерархии наследования[править | править вики-текст]

В коде с таким запашком всякий раз при порождении подкласса одного из классов приходится создавать подкласс другого класса[14].

  • Общая стратегия устранения дублирования состоит в том, чтобы заставить экземпляры одной иерархии ссылаться на экземпляры другой иерархии, а затем убрать иерархию в ссылающемся классе c помощью «Перемещения метода» (Move Method) и «Перемещения поля» (Move Field)[14].

Ленивый класс[править | править вики-текст]

Класс, затраты на существование которого не окупаются выполняемыми им функциями, должен быть ликвидирован[14].

  • При наличии подклассов с недостаточными функциями попробуйте «Свертывание иерархии» (Collapse Hierarchy);
  • Почти бесполезные компоненты должны быть подвергнуты «Встраиванию класса» (Inline Class)[14].

Теоретическая общность[править | править вики-текст]

Этот случай возникает когда на определенном этапе существования программы обеспечивается набор механизмов, который, возможно, потребуется для некоторого будущего функционала. В итоге программу становится труднее понимать и сопровождать[15].

  • Для незадействованных абстрактных классов используйте «Сворачивание иерархии» (Collapse Hierarhy);
  • Ненужная делегация может быть удалена с помощью «Встраивания класса» (Inline Class);
  • Методы с неиспользуемыми параметрами должны быть подвергнуты «Удалению параметров» (Remove Parameter)[5].

Временное поле[править | править вики-текст]

Временные поля это поля, которые нужны объекту только при определенных обстоятельствах. Такое положение вещей трудно для понимания, так как ожидается, что объекту нужны все его поля[16].

  • Временные поля и весь код, работающий с ними следует поместить в отдельный класс с помощью «Выделения класса» (Extract Class);
  • Удалить условно выполняемый код можно с помощью «Введения объекта Null» (Introduce Null Object) для создания альтернативного компонента[15].

Цепочка вызовов[править | править вики-текст]

Цепочка вызовов появляется тогда, когда клиент запрашивает у одного объекта другой объект, другой объект запрашивает еще один объект и т. д. Такие последовательности вызовов означают, что клиент связан с навигацией по структуре классов. Любые изменения промежуточных связей означают необходимость модификации клиента[15].

  • Для удаления цепочки вызовов применяется прием «Сокрытие делегирования» (Hide Delegate)[15].

Посредник[править | править вики-текст]

Чрезмерное использование делегирования может привести к появлению классов, у которых большинство методов состоят только из вызова метода другого класса[15].

  • Если большую часть методов класс делегирует другому классу, нужно воспользоваться «Удалением посредника» (Remove Middle Man)[17].

Неуместная близость[править | править вики-текст]

«Неуместная близость» возникает тогда, когда классы чаще, чем следовало бы, погружены в закрытые части друг друга[17].

  • Избавиться от «Неуместной близости» можно с помощью «Перемещения метода» (Move Method) и «Перемещения поля» (Move Field);
  • По возможности следует прибегнуть к «Замене двунаправленной связи однонаправленной» (Change Bidirectional Association to Unidirectional), «Выделению класса» (Extract Class) или воспользоваться «Сокрытием делегирования» (Hide Delegate)[17].

Альтернативные классы с разными интерфейсами[править | править вики-текст]

Два класса, в которых часть функциональности общая, но методы, реализующие ее, имеют разные параметры[18].

  • Применяйте «Переименование метода» (Rename Method) ко всем методам, выполняющим одинаковые действия, но различающимся сигнатурами[17].

Неполнота библиотечного класса[править | править вики-текст]

Библиотеки через некоторое время перестают удовлетворять требованиям пользователей. Естественное решение — поменять кое-что в библиотеках, но библиотечные классы не изменять. Следует использовать методы рефакторинга, специально предназначенные для этой цели[18].

  • Если надо добавить пару методов, используется «Введение внешнего метода» (Introduce Foreign Method);
  • Если надо серьезно поменять поведение класса, используется «Введение локального расширения» (Introduce Local Extension)[18].

Классы данных[править | править вики-текст]

Классы данных это классы, которые содержат только поля и методы для доступа к ним, это просто контейнеры для данных, используемые другими классами[18].

  • Следует применить «Инкапсуляцию поля» (Encapsulate Field) и «Инкапсуляцию коллекции» (Encapsulate Collection)[5].

Отказ от наследства[править | править вики-текст]

Если наследник использует лишь малую часть унаследованных методов и свойств родителя это является признаком неправильной иерархии.

  • Необходимо создать новый класс на одном уровне с потомком и с помощью «Спуска метода» (Push Down Method) и «Спуска поля» (Push Down Field) вытолкнуть в него все бездействующие методы. Благодаря этому в родительском классе будет содержаться только то, что используется совместно[19].

Комментарии[править | править вики-текст]

Часто комментарии играют роль «дезодоранта» кода, который появляется в нем лишь потому, что код плохой. Почувствовав потребность написать комментарий, попробуйте изменить структуру кода так, чтобы любые комментарии стали излишними[19].

  • Если для объяснения действий блока все же требуется комментарий, попробуйте применить «Выделение метода» (Extract Method);
  • Если метод уже выделен, но по-прежнему нужен комментарий для объяснения его действия, воспользуйтесь «Переименованием метода» (Rename Method);
  • Если требуется изложить некоторые правила, касающиеся необходимого состояния системы, примените «Введение утверждения» (Introduce Assertion)[19].

См. также[править | править вики-текст]

Примечания[править | править вики-текст]

Литература[править | править вики-текст]

  • Фаулер М. Глава 3. Код с душком // Рефакторинг. Улучшение существующего кода = Refactoring: Improving the Design of Existing Code / Пер. с англ. С. Маккавеева. — 1-е изд. — СПб.: Символ-Плюс, 2003. — С. 54—62. — 432 с с. — ISBN 5-93286-045-6.
  • Mantyla M. V., Vanhanen J., Lassenius C Bad smells-humans as code critics (англ.) // Software Maintenance, 2004. Proceedings. 20th IEEE International Conference on : журнал. — 2004. — С. 399—408. — ISSN 1063-6773.

Ссылки[править | править вики-текст]

  • CodeSmell (англ.). Martinfowler.com. Проверено 13 октября 2013.
  • Code Smell (англ.). Cunningham & Cunningham, Inc. (c2.com). Проверено 23 ноября 2013.