Русская Википедия:Код с запашком

Материал из Онлайн справочника
Перейти к навигацииПерейти к поиску

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

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

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

Запахи кода

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

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

Файл:Duplicate code.gif
Пример трёхкратного дублирования кода и избавления от этого запашка с помощью выделения метода и последующих вызовов кода созданного метода

Шаблон:Main

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

Посредник

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

Комментарии

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

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

См. также

Примечания

Шаблон:Примечания

Литература

Ссылки