ВУЗ: Не указан
Категория: Не указан
Дисциплина: Не указана
Добавлен: 06.11.2023
Просмотров: 889
Скачиваний: 6
ВНИМАНИЕ! Если данный файл нарушает Ваши авторские права, то обязательно сообщите нам.
382 foo, то присваивание параметру означает, что foo изменится и станет указывать на другой объект. Нет проблем при выполнении каких-то операций над переданным объектом. Возражение в данном случае про- тив изменения foo, превращающего его в ссылку на совершенно другой объект, а также в отсутствии ясности и смешении передачи по значению и передачи по ссылке. He рекомендуется присваивать значения пара- метрам в Java (в других языках это может быть возможно), увидев код, который это делает, следует применить удаление присваиваний пара- метрам.
Замена метода объектом методов (Replace Method with Method Ob- ject). Путем выделения в отдельные методы частей большого метода можно сделать код значительно более понятным. Декомпозицию метода затрудняет наличие локальных переменных. Когда они присутствуют в изобилии, декомпозиция может оказаться сложной задачей. Замена вре- менной переменной вызовом метода может облегчить ее, но иногда не- обходимое расщепление метода все же оказывается невозможным. За- мена метода объектом методов превращает локальные переменные в поля объекта методов. Затем к новому объекту применяется выделение метода, создающее новые методы, на которые распадается первона- чальный метод.
Замещение алгоритма (Substitute Algorithm). Желательно заменить алгоритм более понятным. Рефакторинг позволяет разлагать сложные вещи на более простые части, но иногда наступает такой момент, когда надо взять алгоритм целиком и заменить его чем-либо более простым.
Это происходит, когда вы ближе знакомитесь с задачей и обнаруживае- те, что можно решить ее более простым способом. Перед выполнением этого приема убедитесь, что дальнейшая декомпозиция метода уже не- возможна. Замену большого и сложного алгоритма выполнить очень трудно; только после его упрощения замена становится осуществимой.
8.5.3. Рефакторинг условных выражений
Логика условного выполнения имеет тенденцию становиться слож- ной, поэтому ряд рефакторингов направлен на то, чтобы упростить ее.
Базовым рефакторингом при этом является Декомпозиция условного
оператора (Decompose Conditional), цель которого состоит в разложе- нии условного оператора на части. Ее важность в том, что логика пере- ключения отделяется от деталей того, что происходит. Остальные ре- факторинги касаются других важных случаев. Рекомендуется применять
Консолидацию
условного
выражения
(Consolidate
Conditional
Expression), когда есть несколько проверок и все они имеют одинаковый результат. Чтобы удалить дублирование в условном коде, применяют
Консолидацию дублирующихся условных фрагментов (Consolidate
Duplicate Conditional Fragments), В коде, разработанном по принципу
«одной точки выхода», часто обнаруживаются управляющие флаги, ко- торые дают возможность условиям действовать согласно этому прави- лу. Поэтому М. Фаулер рекомендует применять Замену вложенных ус-
383
ловных операторов граничным оператором (Replace Nested Conditional with Guard Clauses) для прояснения особых случаев условных операто- ров и Удаление управляющего флага (Remove Control Flag) для избавле- ния от неудобных управляющих флагов.
В объектно-ориентированных программах количество условных операторов часто меньше, чем в процедурных, потому что значитель- ную часть условного поведения выполняется за счет полиморфизма.
Полиморфизм имеет следующее преимущество: вызывающему методу не требуется знать об условном поведении, а потому облегчается рас- ширение условий. В результате в объектно-ориентированных програм- мах редко встречаются операторы switch (case). Те, которые все же есть, являются главными кандидатами для проведения Замены условного опе-
ратора полиморфизмом (Replace Conditional with Polymorphism). Одним из наиболее полезных, хотя и менее очевидных применений полимор- физма является Введение объекта Null ( Introduce Null Object ), чтобы избавиться от проверок на нулевое значение.
Рассмотрим основные методы рефакторинга условных выражений.
1. Декомпозиция условного оператора (Decompose Conditional). При написании кода, проверяющего условия и делающего в зависимости от них разные вещи, часто приходится создавать довольно длинный метод.
Длина метода осложняет его чтение, но если есть условные выражения, трудностей становится еще больше. Обычно проблема связана с тем, что код, как в проверках условий, так и в действиях, говорит о том, что происходит, но легко может затенять причину, по которой это происхо- дит. Как и в любом большом блоке кода, можно сделать свои намерения более ясными, если выполнить его декомпозицию и заменить фрагмен- ты кода вызовами методов, имена которых раскрывают назначение со- ответствующего участка кода. Для кода с условными операторами вы- года еще больше, если проделать это как для части, образующей усло- вие, так и для всех альтернатив. Таким способом можно выделить усло- вие и ясно обозначить, что лежит в основе ветвления.
2. Консолидация условного выражения (Consolidate Conditional Ex- pression). Если есть ряд проверок условия, дающих одинаковый резуль- тат, нужно объедините их в одно условное выражение и выделить его.
Объединение условного кода важно по двум причинам. Во-первых, про- верка становится более ясной, показывая, что в действительности про- водится одна проверка, в которой логически складываются результаты других. Последовательность имеет тот же результат, но говорит о том, что выполняется ряд отдельных проверок, которые случайно оказались вместе. Второе основание для проведения этого рефакторанга состоит в том, что он часто подготавливает почву для выделения метода.
3. Консолидация дублирующихся условных фрагментов (Consolidate
Duplicate Conditional Fragments). Иногда обнаруживается, что во всех ветвях условного оператора выполняется один и тот же фрагмент кода.
В таком случае следует переместить этот код за пределы условного опе- ратора. В результате становится яснее, что меняется, а что остается по- стоянным.
384 4. Удаление управляющего флага (Remove Control Flag). Встретив- шись с серией условных выражений, часто можно обнаружить управ- ляющий флаг, с помощью которого определяется окончание просмотра.
Их присутствие диктуется правилами структурного программирования, согласно которым в процедурах должна быть одна точка входа и одна точка выхода. Но требование одной точки выхода приводит к сильно запутанным условным операторам, в коде которых есть такие неудоб- ные флаги. Для того чтобы выбраться из сложного условного оператора, в языках есть команды break и continue, которые можно использовать вместо управляющих флагов.
5. Замена вложенных условных операторов граничным оператором
(Replace Nested Conditional with Guard Clauses). Часто оказывается, что условные выражения имеют один из двух видов. В первом виде это про- верка, при которой любой выбранный ход событий является частью нормального поведения. Вторая форма представляет собой ситуацию, в которой один результат условного оператора указывает на нормальное поведение, а другой – на необычные условия. Эти виды условных опе- раторов несут в себе разный смысл, и этот смысл должен быть виден в коде. Если обе части представляют собой нормальное поведение, ис- пользуйте условие с ветвями if и else. Если условие является необыч- ным, проверьте условие и выполните return, если условие истинно. Та- кого рода проверка часто называется граничным оператором.
6. Замена условного оператора полиморфизмом (Replace Conditional with Polymorphism). Сущность полиморфизма состоит в том, что он по- зволяет избежать написания явных условных операторов, когда есть объекты, поведение которых различно в зависимости от их типа. В ре- зультате оказывается, что операторы switch, выполняющие переключе- ние в зависимости от кода типа, или операторы if-then-else, выполняю- щие переключение в зависимости от строки типа, в объектно- ориентированных программах встречаются значительно реже. Поэтому если есть условный оператор, поведение которого зависит от типа объ- екта, переместите каждую ветвь условного оператора в перегруженный метод подкласса. Сделайте исходный метод абстрактным.
8.5.4. Рефакторинг кода второго уровня
М. Фаулер отмечает, что решение о том, где разместить выполняе- мые функции, является одним из наиболее фундаментальных решений, принимаемых при проектировании объектов. Даже опытному програм- мисту, работающему в объектно-ориентированной технологии, не уда- ется сделать правильный выбор с первого раза. Рефакторинг позволяет изменить решение. Часто проблемы решаются просто с помощью Пере-
мещения метода (Move Method) или Перемещения поля (Move Field).
Если надо выполнить обе операции, то предпочтительнее начать с пе- ремещения поля.
Часто классы перегружены функциями. В этих случаях применяют
Выделение класса (Extract Class), чтобы разделить эти функции на части.
385
Если некоторый класс имеет слишком мало обязанностей, с помощью
Встраивания класса (Inline Class) его присоединяют к другому классу.
Если функции класса на самом деле выполняются другим классом, час- то удобно скрыть этот факт с помощью Сокрытия делегирования (Hide
Delegate). Иногда сокрытие класса, которому делегируются функции, приводит к постоянным изменениям в интерфейсе класса, и тогда сле- дует воспользоваться Удалением посредника (Remove Middle Man ).
Два рефакторинга – Введение внешнего метода (Introduce Foreign
Method) и Введение локального расширения (Introduce Local Extension), представляют собой особые случаи. Их используют только тогда, когда недоступен исходный код класса, но переместить функции в класс, ко- торый программист не может модифицировать, тем не менее, надо. Ес- ли таких методов всего один-два, можно применить введение внешнего метода, если же методов больше, то нужно пользоваться введением ло- кального расширения.
Кратко рассмотрим основные методы рефакторинга второго уровня.
1. Перемещение метода (Move Method). Метод чаще использует функции другого класса (или используется ими), а не того, в котором он определен в данное время или, возможно, в будущем. Перемещение методов – это насущный хлеб рефакторинга. Их перемещают, если в классах сосредоточено слишком много функций или когда классы слишком плотно взаимодействуют друг с другом и слишком тесно свя- заны. Перемещая методы, можно сделать классы проще и добиться бо- лее четкой реализации функций.
2. Перемещение поля (Move Field). Поле используется или будет ис- пользоваться другим классом чаще, чем классом, в котором оно опреде- лено. Перемещение состояний и поведения между классами составляет самую суть рефакторинга. По мере разработки системы выясняется не- обходимость в новых классах и перемещении функций между ними.
Разумное и правильное проектное решение через неделю может ока- заться неправильным. Но проблема не в этом, а в том, чтобы не оста- вить это без внимания. Возможность перемещения поля связана с тем, что его использует больше методов в другом классе, чем в своем собст- венном. Использование может быть косвенным, через методы доступа.
Можно принять решение о перемещении методов, что зависит от ин- терфейса. Но если представляется разумным оставить методы на своем месте, перемещают поле. Другим основанием для перемещения поля может быть осуществление выделения класса. В этом случае сначала перемещаются поля, а затем методы.
3. Выделение класса (Extract Class). Класс должен представлять со- бой ясно очерченную абстракцию, выполнять несколько отчетливых обязанностей. На практике классы подвержены разрастанию. Добавляя новые функции в класс, программист может чувствовать, что для них не стоит заводить отдельный класс, но по мере того, как функции растут и плодятся, класс становится слишком сложным. Получается класс с множеством методов и данных, который слишком велик для понимания.
386
Программист рассматривает возможность разделить его на части и де- лит его.
Хорошим признаком является сочетание подмножества данных с подмножеством методов. Другой хороший признак – наличие подмно- жеств данных, которые обычно совместно изменяются или находятся в особой зависимости друг от друга. Полезно задать себе вопрос о том, что произойдет, если удалить часть данных или метод. Какие другие данные или методы станут бессмысленны? Одним из признаков, часто проявляющихся в дальнейшем во время разработки, служит характер создания подтипов класса. Может оказаться, что выделение подтипов оказывает воздействие лишь на некоторые функции или что для некото- рых функций выделение подтипов производится иначе, чем для других.
4. Встраивание класса (Inline Class). Встраивание класса противо- положно выделению класса. К этой операции прибегают, если от класса становится мало пользы и его надо убрать. Часто это происходит в ре- зультате рефакторинга, оставившего в классе мало функций. В этом случае следует вставить данный класс в другой, выбрав для этого такой класс, который чаще всего его использует.
5. Сокрытие делегирования (Hide Delegate). Одним из ключевых свойств объектов является инкапсуляция. Инкапсуляция означает, что объектам приходится меньше знать о других частях системы. В резуль- тате при модификации других частей об этом требуется сообщить меньшему числу объектов, что упрощает внесение изменений. Извест- но, что поля следует скрывать, несмотря на то, что многие языки про- граммирования позволяют делать поля открытыми. По мере роста ис- кушенности в объектах появляется понимание того, что инкапсулиро- вать можно более широкий круг вещей. Если клиент вызывает метод, определенный над одним из полей объекта-сервера, ему должен быть известен соответствующий делегированный объект. Если изменяется делегированный объект, может потребоваться модификация клиента. От этой зависимости можно избавиться, поместив в сервер простой делеги- рующий метод, который скрывает делегирование. Тогда изменения ог- раничиваются сервером и не распространяются на клиента.
6. Удаление посредника (Remove Middle Man). Выше отмечены пре- имущества инкапсуляции применения делегируемого объекта. Однако есть и неудобства, связанные с тем, что при желании клиента использо- вать новую функцию делегата необходимо добавить в сервер простой делегирующий метод. Добавление достаточно большого количества функций оказывается утомительным. Класс сервера становится просто посредником, и может настать момент, когда клиенту лучше непосред- ственно обращаться к делегату. Сказать точно, какой должна быть мера сокрытия делегирования, трудно. К счастью, это не столь важно благо- даря наличию сокрытия делегирования и удаления посредника. Можно осуществлять настройку системы по мере надобности. По мере развития системы меняется и отношение к тому, что должно быть скрыто. Ин- капсуляция, удовлетворявшая полгода назад, может оказаться неудоб- ной в настоящий момент.