Interested Article - Код с запашком

Код с запашко́м ( код с душко́м , дурно пахнущий код ) — термин, обозначающий код с признаками (запахами) проблем в системе. Был введён Кентом Беком и использован Мартином Фаулером в его книге Рефакторинг . Улучшение существующего кода .

Запахи кода ( англ. code smells ) — это ключевые признаки необходимости рефакторинга . Существуют запахи, специфичные как для парадигм программирования , так и для конкретных языков . Основной проблемой, с которой сталкиваются разработчики при борьбе с запахами кода, является то, что критерии своевременности рефакторинга невозможно чётко формализовать без апелляции к эстетике и условному чувству прекрасного. Запахи кода — это не набор чётких правил, а описание мест, на которые нужно обращать внимание при рефакторинге . Они легко обнаруживаются, но при этом не во всех случаях свидетельствуют о проблемах .

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

Запахи кода

Общие запахи объектно-ориентированного кода

Дублирование кода

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

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

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

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

Длинный метод

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

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

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

Большой класс

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

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

Длинный список параметров

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

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

Расходящиеся модификации

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

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

Стрельба дробью

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

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

Завистливые функции

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

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

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

Группы данных

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

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

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

Одержимость элементарными типами

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

  • «Замена значения данных объектом» (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) .

Операторы типа switch

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

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

Параллельные иерархии наследования

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

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

Ленивый класс

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

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

Теоретическая общность

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

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

Временное поле

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

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

Цепочка вызовов

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

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

Посредник

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

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

Неуместная близость

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

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

Альтернативные классы с разными интерфейсами

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

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

Неполнота библиотечного класса

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

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

Классы данных

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

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

Отказ от наследства

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

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

Комментарии

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

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

См. также

Примечания

  1. .
  2. .
  3. .
  4. .
  5. .
  6. , с. 54.
  7. , с. 55.
  8. , с. 56.
  9. , с. 56—57.
  10. , с. 57.
  11. , с. 57.
  12. , с. 58.
  13. , с. 59.
  14. .
  15. , с. 60.
  16. .
  17. , с. 61.

Литература

  • Martin Fowler . (англ.) . — Addison-Wesley , 1999. — ISBN 0-201-48567-2 .
  • Фаулер М. Глава 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. (англ.) // Software Maintenance, 2004. Proceedings. 20th IEEE International Conference on : журнал. — 2004. — P. 399—408 . — ISSN . 1 августа 2014 года.
  • S. Counsell, R. M. Hierons, H. Hamza, S. Black, and M. Durrand. (англ.) // Advances in Software Engineering Volume 2010 : журнал. — 2010.

Ссылки

  • . Codingcraft.ru. Дата обращения: 6 ноября 2013.
  • . Codingcraft.ru. Дата обращения: 5 ноября 2013.
  • . Glan-saratov.ru. Дата обращения: 22 сентября 2013. Архивировано из 12 августа 2012 года.
  • (англ.) . Martinfowler.com. Дата обращения: 13 октября 2013.
  • . Vihv.org. Дата обращения: 13 октября 2013. Архивировано из 9 декабря 2013 года.
  • . xp.1024.info. Дата обращения: 26 октября 2013.
  • (англ.) . Cunningham & Cunningham, Inc. (c2.com). Дата обращения: 23 ноября 2013.
  • Андрей Вокин. . DevConf . devconf.ru (9 июня 2012). Дата обращения: 22 декабря 2013. Архивировано из 24 декабря 2013 года.
Источник —

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