Interested Article - Код с запашком
- 2020-06-22
- 1
Код с запашко́м ( код с душко́м , дурно пахнущий код ) — термин, обозначающий код с признаками (запахами) проблем в системе. Был введён Кентом Беком и использован Мартином Фаулером в его книге Рефакторинг . Улучшение существующего кода .
Запахи кода ( англ. 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) .
См. также
Примечания
- ↑ .
- ↑ .
- ↑ .
- .
- .
- ↑ , с. 54.
- ↑ , с. 55.
- ↑ , с. 56.
- , с. 56—57.
- ↑ , с. 57.
- , с. 57.
- ↑ , с. 58.
- ↑ , с. 59.
- .
- ↑ , с. 60.
- ↑ .
- ↑ , с. 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 года.
- 2020-06-22
- 1