ВУЗ: Не указан

Категория: Не указан

Дисциплина: Не указана

Добавлен: 06.11.2023

Просмотров: 893

Скачиваний: 6

ВНИМАНИЕ! Если данный файл нарушает Ваши авторские права, то обязательно сообщите нам.

365 продолжается до достижения производительности, которая удовлетво- ряет пользователей.
Хорошее разделение программы на компоненты способствует опти- мизации такого рода в двух отношениях. Во-первых, благодаря ему по- является время, которое можно потратить на оптимизацию. Имея хоро- шо структурированный код, можно быстрее добавлять новые функции и выиграть время для того, чтобы заняться производительностью. (Про- филирование гарантирует, что это время не будет потрачено зря.) Во- вторых, хорошо структурированная программа обеспечивает более вы- сокое разрешение для анализа производительности. Профайлер указы- вает на более мелкие фрагменты кода, которые легче настроить.
Благодаря большей понятности кода легче осуществить выбор воз- можных вариантов и разобраться в том, какого рода настройка может оказаться действенной. Таким образом, можно сделать вывод, что ре- факторинг позволяет писать программы быстрее. На некоторое время он делает программы более медленными, но облегчает настройку про- грамм на этапе оптимизации. В конечном счете, достигается большой выигрыш.
8.3. Когда применять рефакторинг
3
8.3.1. Дублирование кода
Если в программе есть одинаковые кодовые структуры в нескольких местах, при их объединении программа только выиграет. Простейшая задача с дублированием кода возникает, когда одно и то же выражение присутствует в двух методах одного и того же класса. В этом случае надо лишь применить Выделение метода (Extract Method) и вызывать код созданного метода из обеих точек.
Другая задача с дублированием встречается, когда одно и то же вы- ражение есть в двух подклассах, находящихся на одном уровне. Устра- нить такое дублирование можно с помощью выделения метода для обо- их классов с последующим Подъемом поля (Pull Up Field). Если код по- хож, но не совпадает полностью, нужно применить выделение метода для отделения совпадающих фрагментов от различающихся. После это- го может оказаться возможным применить Формирование шаблона ме-
тода (Form Template Method).
Если оба метода делают одно и то же с помощью разных алгорит- мов, нужно выбрать более четкий из этих алгоритмов и применить За-
мещение алгоритма (Substitute Algorithm). Если дублирующийся код находится в двух разных классах, можно применить Выделение класса
(Extract Class) в одном классе, а затем использовать новый компонент в другом. Бывает, что в действительности метод должен принадлежать только одному из классов и вызываться из другого класса, либо метод должен принадлежать третьему классу, на который будут ссылаться оба
3
Материал этого раздела в основном взят из монографии М. Фаулера


366 первоначальных. Необходимо решить, где оправдано присутствие этого метода, и обеспечить, чтобы он находился там и нигде более.
8.3.2. Длинный метод
Программы, использующие объекты, работают надежно, когда мето- ды этих объектов короткие. Программистам, не имеющим опыта работы с объектами, часто кажется, что никаких вычислений не происходит, а программы состоят из нескончаемой цепочки делегирования действий.
Однако, общаясь с такой программой на протяжении нескольких лет, становится ясным, какую ценность представляют собой маленькие ме- тоды. Уже на заре программирования стало ясно, что чем длиннее про- цедура, тем труднее понять, как она работает. В старых языках про- граммирования вызов процедур был связан с накладными расходами, которые удерживали от применения маленьких методов. Современные объектно-ориентированные языки в значительной мере устранили из- держки вызовов внутри процесса.
Однако издержки сохраняются для того, кто читает код, поскольку приходится переключать контекст, чтобы увидеть, чем занимается про- цедура. Среда разработки, позволяющая видеть одновременно два мето- да, помогает устранить этот шаг, но главное, что способствует понима- нию работы маленьких методов, это толковое присвоение им имен. Ес- ли правильно выбрать имя метода, нет необходимости изучать его тело.
В итоге можно заключить, что следует активнее применять декомпози- цию методов. Программисты придерживаются эвристического правила, гласящего, что если ощущается необходимость что-то прокомментиро- вать, надо написать метод.
В таком методе содержится код, который требовал комментариев, но его название отражает назначение кода, а не то, как он решает свою за- дачу. Такая процедура может применяться к группе строк или всего лишь к одной строке кода. К ней прибегают даже тогда, когда обраще- ние к коду длиннее, чем код, который им замещается, при условии, что имя метода разъясняет назначение кода. Главным здесь является не длина метода, а семантическое расстояние между тем, что делает метод, и тем, как он это делает.
В 99% случаев, чтобы укоротить метод, требуется лишь выделение метода. Нужно найти те части метода, которые кажутся согласованными друг с другом, и образовать новый метод. Когда в методе есть масса параметров и временных переменных, это мешает выделению нового метода. При попытке выделения метода в итоге приходится передавать столько параметров и временных переменных в качестве параметров, что результат оказывается ничуть не проще для чтения, чем оригинал.
Устранить временные переменные можно с помощью Замены времен-
ной переменной вызовом метода (Replace Temp with Query). Длинные списки параметров можно сократить с помощью приемов Введение гра-
ничного объекта (Introduce Parametr Object) и Сохранение всего объекта
(Preserve Whole Object). Если даже после этого остается слишком много


367 временных переменных и параметров, приходится выдвигать тяжелую артиллерию – необходима Замена метода объектом метода (Replace
Method with Method Object).
Как определить те участки кода, которые должны быть выделены в отдельные методы? Хороший способ – поискать комментарии: они час- то указывают на такого рода семантическое расстояние. Блок кода с комментариями говорит о том, что его действие можно заменить мето- дом, имя которого основывается на комментарии. Даже одну строку имеет смысл выделить в метод, если она нуждается в разъяснениях.
Условные операторы и циклы тоже свидетельствуют о возможности выделения. Для работы с условными выражениями подходит Декомпо-
зиция условных операторов (Decompose Conditional). Если это цикл, следует выделить его и содержащийся в нем код в отдельный метод.
8.3.3. Большой класс
Когда класс пытается выполнять слишком много работы, это часто проявляется в чрезмерном количестве имеющихся у него атрибутов. А это может привести и к дублированию кода. Можно применить Выделе-
ние класса (Extract Class), чтобы связать некоторое количество атрибу- тов. Нужно так выбирать для компонента атрибуты, чтобы они имели смысл для каждого из них. Обычно одинаковые префиксы или суффик- сы у некоторого подмножества переменных в классе наводят на мысль о создании компонента. Если разумно создание компонента как подклас- са, то более простым оказывается Выделение подкласса (Extract
Subclass).
Иногда класс не использует постоянно все свои переменные экземп- ляра. В таком случае оказывается возможным применить выделение класса или выделение подкласса несколько раз.
Как и класс с чрезмерным количеством атрибутов, класс, содержа- щий слишком много кода, создает хорошую среду для повторяющегося кода, хаоса и гибели. Простейшее решение – устранить избыточность в самом классе. Пять методов по сотне строк в длину иногда можно заме- нить пятью методами по десять строк плюс еще десять двухстрочных методов, выделенных из оригинала.
Как и для класса с большим числом атрибутов, обычное решение для класса с чрезмерным объемом кода состоит в том, чтобы применить выделение класса или выделение подкласса. Полезно установить, как клиенты используют класс, и применить Выделение интерфейса (Extract
Interface) для каждого из этих вариантов. В результате может выяснить- ся, как расчленить класс еще далее. Если большой класс является клас- сом GUI, может потребоваться переместить его данные и поведение в отдельный объект предметной области. При этом может оказаться необ- ходимым хранить копии некоторых данных в двух местах и обеспечить их согласованность. Дублирование видимых данных (Duplicate Observed
Data) предлагает путь, которым можно это осуществить. В данном слу- чае, особенно при использовании старых компонентов Abstract Windows


368
Toolkit (AWT), можно в последующем удалить класс GUI и заменить его компонентами Swing.
8.3.4. Длинный список параметров
Когда-то при обучении программированию рекомендовали все необ- ходимые подпрограмме данные передавать в виде параметров. Это можно было понять, потому что альтернативой были глобальные пере- менные, а глобальные переменные часто пагубны и мучительны. Благо- даря объектам ситуация изменилась, так как если какие-то данные от- сутствуют, всегда можно попросить их у другого объекта. Поэтому, ра- ботая с объектами, следует передавать не все, что требуется методу, а столько, чтобы метод мог добраться до всех необходимых ему данных.
Значительная часть того, что необходимо методу, есть в классе, которо- му он принадлежит. В объектно-ориентированных программах списки параметров обычно гораздо короче, чем в традиционных программах.
И это хорошо, потому что в длинных списках параметров трудно разбираться, они становятся противоречивыми и сложными в использо- вании, а также потому, что их приходится изменять по мере того, как возникает необходимость в новых данных. Если передавать объекты, то изменений требуется мало, потому что для получения новых данных, скорее всего, хватит пары запросов. Замена параметра вызовом метода
(Replace Parameter with Method) уместна, когда можно получить данные в одном параметре путем вызова метода объекта, который уже известен.
Этот объект может быть полем или другим параметром. Сохранение
всего объекта (Preserve Whole Object) позволяет взять группу данных, полученных от объекта, и заменить их самим объектом.
Если есть несколько элементов данных без логического объекта, вы- берите Введение граничного объекта (Introduce Parameter Object). Есть важное исключение, когда такие изменения не нужны. Оно касается ситуации, когда разработчик определенно не хочет создавать зависи- мость между вызываемым и более крупным объектами. В таких случаях разумно распаковать данные и передать их как параметры, но необхо- димо учесть, каких трудов это стоит. Если список параметров оказыва- ется слишком длинным или модификации слишком частыми, следует пересмотреть структуру зависимостей.
8.3.5. Расходящиеся модификации
Программы структурируются, чтобы облегчить их модификацию.
Программист хочет, чтобы при модификации можно было найти в сис- теме одно определенное место и внести изменения именно туда. Если этого сделать не удается, то тут могут появиться две тесно связанные проблемы. Расходящиеся (divergent) модификации имеют место тогда, когда один класс часто модифицируется различными способами по раз- ным причинам. Если, глядя на класс, разработчик отмечает для себя, что эти три метода придется модифицировать для каждой новой базы дан-


369 ных, а эти четыре метода придется модифицировать при каждом появ- лении нового финансового инструмента, это может означать, что вместо одного класса лучше иметь два.
Благодаря этому каждый класс будет иметь свою четкую зону ответ- ственности и изменяться в соответствии с изменениями в этой зоне. Не исключено, что это обнаружится лишь после добавления нескольких баз данных или финансовых инструментов. При каждой модификации, вы- званной новыми условиями, должен изменяться один класс, и вся типи- зация в новом классе должна выражать эти условия. Для того чтобы все это привести в порядок, определяется все, что изменяется по данной причине, а затем применяется выделение класса, чтобы объединить это все вместе.
8.3.6. Множественные изменения
Эту ситуацию М. Фаулер назвал «Стрельба дробью» [32]. Она похо- жа на расходящуюся модификацию, но является ее противоположно- стью. Увидеть ее можно, когда при выполнении любых модификаций приходится вносить множество мелких изменений в большое число классов. Если изменения разбросаны повсюду, их трудно находить и можно пропустить важное изменение. В такой ситуации следует ис- пользовать Перемещение метода (Move Method) и Перемещение поля
(Move Field), чтобы свести все изменения в один класс. Если среди имеющихся классов подходящего кандидата нет, нужно создать новый класс. Часто можно воспользоваться Встраиванием класса (Inline Class), чтобы поместить целую связку методов в один класс. Возникнет какое- то число расходящихся модификаций, но с этим можно справиться.
8.3.7. Завистливые функции
Под такими функциями М. Фаулер понимает методы, которые боль- ше интересуется не теми классами, в которых они находится, а какими- то другими. Чаще всего предметом зависти являются данные. Есть мас- са случаев, когда в программе сталкиваемся с методами, вызывающим полдюжины методов доступа к данным другого объекта. Изменение здесь очевидно: метод явно напрашивается на перевод в другое место, что и достигается Перемещением метода (Move Method). Иногда зави- стью страдает только часть метода; в таком случае к завистливому фрагменту применяется выделение метода.
Конечно, встречаются нестандартные ситуации. Иногда метод ис- пользует функции нескольких классов, так в который из них его лучше поместить? Нужно определить, в каком классе находится больше всего данных, и поместить метод вместе с этими данными. Иногда легче с помощью выделения метода разбить метод на несколько частей и по- местить их в разные места. Есть несколько сложных схем, нарушающих это правило. Фундаментальное практическое правило гласит: то, что изменяется одновременно, надо хранить в одном месте. Данные и функ-