Вы здесь: Home > Статьи > Дурно пахнущий код
Что здесь происходит
Правила XP
Статьи по XP
Книги по XP
Ссылки по XP
Обсудить
Написать нам

Дурно пахнущий код

Прочтя первую главу книги "Рефакторинг", переведенную Евгением Кулешовым, становится ясно, как работает рефакторинг. Но также надо знать, когда его нужно применять.

Этому посвящена вторая глава книги, и я постараюсь сделать ее краткий обзор. Напомню, авторство принадлежит Мартину Фоулеру, а я лишь делаю конспективный обзор. У Фоулера более подробно все расписано.

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

Дублирующийся код

Номер один в параде вонючек. Если вы видите одинаковые структуры кода в нескольких местах, то вы можете быть уверены, что эта программа будет лучше, если вы сможете их унифицировать.

В простейших случаях вам поможет Extract Method. При работе с наследованием используйте Pull Up Field.

Если код похож, но не в точности, надо применить Extract Method, а затем Form Template Method. Если методы делают одно и то же разными способами, выберите один из них и используйте Substitute Algorithm.

Если у вас есть дублирующийся код в несвязанных классах, подумайте о возможности Extract Class.

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

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

Мы следуем эвристике: как только мы чувствуем, что надо написать комментарий, мы вместо этого пишем метод. В 99% случаев для того, чтобы сократить метод, вам достаточно будет применить Extract Method. Локальные переменные и параметры могут препятствовать вам, так что, возможно, вам понадобится Replace Temp With Query, Introduce Parameter Object и Preserve Whole Object

Кроме комментариев, циклы и условия также являются хорошими знаками. Используйте Decompose Conditional для условий, а для циклов - выносите тело цикла в отдельный метод.

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

Когда класс делает слишком много всего, у него обычно слишком много переменных (полей).

Можно использовать Extract Class или Extract Subclass, чтобы выделить некоторые переменные. При определении того, что выносить, обращайте внимание на общность в названии переменных, а также на то использует ли класс их все одновременно.

Кроме того, класс, в котором слишкм много кода, также является рассадником дублирования и хаоса. Здесь также применяйте Extract Class или Extract Subclass. Если ваш большой класс - это GUI  класс, то попытайтесь отделить поведение от данных. Вам может понадобиться Diplicate 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 для изоляции зависимостей.

Использование некоторых сложных patterns, таких как Strategy и Visitor, нарушает эти правила. Общая рекомендация - всегда соблюдать баланс между сложностью и необходимой гибкостью.

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

Если вы видите, что элементы данных всегда путешествуют вместе, обьедините их в один обьект. Используйте Extract Class для полей, Preserve Whole Object и Introduce Parameter Object для параметров методов.

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

Одержимость примитивами

Начинающие обьектные программисты обычно избегают использовать маленькие обьекты для маленьких задач, таких как валюта, диапазоны, специальные строки для телефонных номеров и т.п. Вы можете выйти из пещер в цивилизованный мир обьектов с помощью Replace Data Velue with Object, Replace Array with Object, если это код типа, то используйте Replace Type Code with Class, Replace Type Code with Sublasses и Replace Type Code with Strategy.

Оператор Switch (Case)

Хороший обьектный код очень редко использует операторы Switch. Часто вы видите один и тот же Switch в разных местах. Если вам надо добавить еще один выбор, вам придется искать все операторы Switch.

В большинстве таких случаев лекарством является полиморфизм. Если Switch переключается по коду типа, используйте Replace Type Code with Sublasses или Replace Type Code with Strategy. Вам может понадобиться Extract Method и Move Method чтобы изолировать Switch и поместить его в нужный класс. Когда вы настроите структуру наследования, используйте Replace Conditional with Polymorphism.

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

Параллельные иерархии - это частный случай хирургии дробовиком. Каждый раз, когда вы добавляете потомок класса, вы вынуждены добавлять потомок другому классу. Сначала сделайте так, что экземпляры одной иерархии ссылались на соответсвующие экземпляры другой. Потом используйте Move Method и Move Field и вторая иерархия исчезнет понемногу.

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

Каждый созданный класс стоит вам денег - на его понимание и содержание. Класс, который не делает достаточно, чтобы окупить себя, должен быть уничтожен. Часто это класс, который раньше окупался, но был урезан при рефакторинге. Если он является потомком другого класса, используйте Collapse Hierarchy. Почти бесполезные классы должны подвергнуться Inline Class.

Спекулятивное обобщение

Это случай когда кто-то говорит: "О, смотрите, нам когда-нибудь понадобится такая-то возможность" и поэтому хочет вставить всякие хуки и специальные случаи чтобы работать с вещами, которые не нужны. Результат получается сложнее в использовании и сопровождении.

Если у вас есть абстрактные классы, которые ничего не делают, используйте Collapse Hierarchy. Ненужная делегация может быть удалена с помощью Inline Class. Методы с неиспользуемыми параметрами должны быть подвергнуты Remove Parameter.

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

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

Используйте Extract Class, чтобы сделать дом для бедных сироток. Поместите туда весь код, относящийся к этим полям. Также вы можете избавиться от условного кода с помощью Introduce Null Object, для создания альтернативного обьекта на случай, когда эти поля не имеют смысла.

Цепочка сообщений

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

Здесь надо использовать Hide Delegate. Посмотрите, зачем используется результат вызова. Используйте Extract Method и затем Move Method , чтобы продвинуть его дальше по цепочке.

Некоторые выступают против любой цепочки методов. Мы выступаем за умеренность.

Посредник (передаст)

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

Однако, это может зайти слишком далеко. Вы смотрите на интерфейс класса и видите, что половина его методов просто делегируют вызовы другим классам. Время использовать Remove Middle Man и напрямую говорить с обьектами, которые знают что происходит. Если только некоторые методы делегируют, то напустите на них Inline Method. Если кроме делегации добавляется поведение, используйте Replace Delegation with Inheritance.

Неоправданная близость

Иногда классы становятся слишком близки и проводят слишком много времени с приватными частями друг друга.

Используйте Move Method и Move Field для разделения частей и уменьшения близости. Посмотрите, нельзя ли организовать Change Bidirectional Association to Unidirectional. Если у классов действительно есть общий интерес, используйте Extract Class, чтобы положить общие части в одно место.

Наследование часто ведет к неоправданной близости. Потомки всегда знают о предках больше, чем хотелось бы предкам. Используйте Replace Inheritance with Delegation, когда пора покидать дом.

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

Применяйте Rename Method к любым методам, которые делают одно и то же, но имеют разную сигнатуру. Если этого недостаточно, используйте Move Method пока интерфейсы не станут одинаковыми.

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

Зачастую библиотечный класс не совсем нас устраивает, но мы не можем применить что-то типа Move Method к нему.

Если вам надо добавить пару методов, используйте Introduce Foreign Method. Если добавлять надо много, вам понадобится Introduce Local Extension.

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

Бывают классы, которые содержат только поля и методы для доступа к ним. Это просто тупые контейнеры для данных и скорее всего, другие классы слишком детально ими манипулируют. Во-первых, примените Encapsulate Field и Encapsulate Collection, пока никто не видел. Посмотрите, где данные класса используются. Попробуйте применить Move Method, чтобы перенести этот код в сам класс данных.

Классы для данных как дети. Они нужны на начальных этапах. Но чтобы вести себя как взрослый обьект, они должны брать на себя ответственность.

Отвергнутое наследство

Подклассы наследуют методы и данные от их предков. Но иногда они не пользуются тем, что им дают.

Традиционный ответ - иерархия плохая. Вам нужно создать новый порожденный класс и применить Push Down Method и Push Down Field. Но по нашему саркастическому использованию слова традиционный, вы догадались, что мы не будем это всегда советовать. Мы используем наследование для повторного использования. Оно сопровождается плохим запахом, но обычно он не очень силен. Если отвергнутое наследство представляет собой проблему, то воспользуйтесь традиционным советом.

Комментарии

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

Коментарии не причина, а индикатор плохого кода. Мы рефакторим плохой код и зачастую находим комментарии излишними.