КаталогИндекс раздела

Рефакторинг для всех

Как и почему используются возможности автоматического рефакторинга в Eclipse

David Gallardo
09 Sep 2003

© IBM Corp

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

Почему рефакторинг?

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

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

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

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

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

Есть два важных способа, которыми вы можете уменьшить риск испортить код. Один способ - иметь сквозной набор модульных тестов: код должен пройти тесты перед и после рефакторинга. Второй способ - использовать автоматизированный инструмент, такой как возможности Eclipse для выполнения этого рефакторинга.

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

Типы рефакторинга в Eclipse

Инструменты рефакторинга в Eclipse можно сгруппировать по трем основным категориям ( и это порядок, в котором они появляются в меню Refactoring):

  1. Изменение именования и физической организации кода, включая переименование полей, переменных, классов и интерфейсов и перемещение пакетов и классов
  2. Изменение логической организации кода на уровне классов, включая превращение анонимных классов во вложенные классы, превращение вложенных классов в классы верхнего уровня создание интерфейсов из определенных классов и перемещение методов или полей из класса в подкласс или суперкласс
  3. Изменение кода внутри класса, включая превращение локальных переменных в поля класса, превращение выбранного из метода кода в отдельный метод и генерация методов-акцессоров для полей

Некоторые операции рефакторинга не попадают точно в эти три категории, в частности, Изменение Сигнатуры Метода (Change Method Signature), которое включено в третью категорию. Кроме этого исключения, следующие разделы будут обсуждать инструменты рефакторинга Eclipse в этом порядке.

Физическая реорганизация и переименование

Вы можете очевидным образом переименовать или переместить файла в вашей файловой системе без специального инструмента, но если вы сделаете это с исходными файлами Java, это может потребовать от вас редактирования многих файлов для обновления операторов import или package. Аналогично, вы можете легко переименовать классы, методы и переменные, используя поиск в текстовом редакторе и изменяя функциональность, но вам нужно делать это внимательно, потому что разные классы могут иметь методы и переменные с похожими именами, может быть утомительным прохождение через все файлы проекта, чтобы убедиться, что каждый экземпляр правильно определен и изменен.

Свойства Eclipse Rename и Move способны проделать эти изменения интеллектуально, через весь проект, без вмешательства пользователя, потому что Eclipse понимает семантику кода и способен определить ссылку на имя определенного метода, переменной или класса. Упрощение этой задачи позволяет быть уверенными в том, что имя метода, переменной или класса ясно показывает ее назначение.

Достаточно общей задачей является нахождение кодов, которые имеют несоответствующие или вводящие в заблуждение имена, потому что коды изменяются и работают несколько иначе, чем это планировалось вначале. Например, программа, которая ищет определенные слова в файле, может быть расширена для работы с Web-страницами использованием класса URL для получения InputStream. Если этот входной поток исходно вызывал файл, он должен быть изменен, чтобы отражать свою новую, более общую природу, например на sourceStream. Разработчики часто не удосуживаются делать изменения, подобные этим, потому что это может быть запутанным и скучным процессом. Это, конечно, сбивает с толку следующего разработчика, который должен с этим работать.

Чтобы переименовать элемент Java,просто щелкните на нем в представлении Package Explorer или выберите его в исходный файл Java, а затем выберите Refactor > Rename. В диалоговом окне выберите новое имя и выберите, должен ли Eclipse также изменить ссылки на имя. Поля, которые будут отображены, зависят от типа элемента, который вы выбрали. Например, если вы выбрали поле, которое имеет методы-акцессоры, вы можете также изменить имена этих методов, чтобы они отражали новое имя поля. Рисунок 1 показывает простой пример.

Рисунок 1. Переименование локальной переменной

Как и во всех операциях рефакторинга Eclipse, после того, как вы зададите все необходимое для выполнения рефакторинга, вы можете нажать Preview, чтобы увидеть изменения, которые Eclipse предлагает сделать, в диалоге сравнения, который позволяет вам запретить или принять каждое изменение на каждом затронутом файле отдельно. Если вы доверяете способности Eclipse выполнить изменения корректно, вы можете вместо этого нажать OK. Конечно, если вы не уверены в том, что будет делать рефакторинг, вы захотите предварительно просмотреть изменения, но в этом обычно нет необходимости для простых операций рефакторинга, как Rename и Move.

Move во многом похоже на Rename: Вы выбираете элемент Java (обычно класс), задаете новое местоположение и задаете, должны ли также быть обновлены ссылки. Вы можете затем выбрать Preview, чтобы проверить изменения, или OK, чтобы выполнить рефакторинг немедленно, как показано на Рисунке 2.

Рисунок 2. Перемещение класса из одного пакета в другой

На некоторых платформах (особенно Windows), вы можете переместить классы из одного пакета или папки в другой простой буксировкой их в представлении Package Explorer. Все ссылки будут обновлены автоматически.

Переопределение отношений классов

Большой набор операций рефакторинга Eclipse позволяет вам автоматически переопределять отношения между вашими классами. Эти операции рефакторинга не столь часто используется, как другие типы рефакторинга, предлагаемые Eclipse, но они ценны потому, что выполняют весьма сложные задачи. Если они применяются, они очень полезны.

Продвижение анонимных и вложенных классов

Две операции рефакторинга, Convert Anonymous Class to Nested и Convert Nested Type to Top Level, аналогичны в том, что они перемещают класс из его текущей области видимости во включающую область.

Анонимный класс является разновидностью синтаксической стенографии, которая позволяет вам создавать экземпляр класса, реализующего абстрактный класс или интерфейс, когда вам это нужно, без необходимости явно давать классу имя. Это обычно используется, например, при создании слушателей в пользовательском интерфейсе. В Листинге 1 предположим, что Bag - это интерфейс, определенный где-то, который объявляет два метода, get() и set(). Листинг 1. Класс Bag

public class BagExample
{
   void processMessage(String msg)
   {
      Bag bag = new Bag()
      {
         Object o;
         public Object get()
         {
            return o;
         }
         public void set(Object o)
         {
            this.o = o;
         }
      };
      bag.set(msg);
      MessagePipe pipe = new MessagePipe();
      pipe.send(bag);
   }
}

Когда анонимный класс становится таким большим, что класс становится трудно читать, вы можете решить сделать анонимный класс настоящим классом. Чтобы сохранить инкапсуляцию (другими словами, чтобы скрыть его от других классов, которые ничего не нужно знать о нем), вы должны сделать его вложенным классом, а не классом верхнего уровня. Вы можете сделать это, щелкнув внутри анонимного класса и выбрав Refactor > Convert Anonymous Class to Nested. Введите имя для класса, такое как BagImpl, когда вас пригласят это сделать, а затем выберите либо Preview, либо OK. Это изменит код, как показано в Листинге 2.

Листинг 2. Класс Bag после рефакторинга

public class BagExample
{
   private final class BagImpl implements Bag
   {
      Object o;
      public Object get()
      {
         return o;
      }
      public void set(Object o)
      {
         this.o = o;
      }
   }
       
   void processMessage(String msg)
   {
     Bag bag = new BagImpl();
     bag.set(msg);
     MessagePipe pipe = new MessagePipe();
     pipe.send(bag);
   }
}

Convert Nested Type to Top Level полезно, когда вы хотите сделать вложенный класс доступным для других классов. Вы можете, например, использовать значение объекта внутри класса - в вышеприведенном классе BagImpl. Если вы позднее решите, что эти данные должны совместно использоваться всеми классами, эта операция рефакторинга создаст новый класс из вложенного класса. Вы можете сделать это, выделив имя класса в исходном файле (или щелкнув на имени класса в представлении Outline) и выбрав Refactor > Convert Nested Type to Top Level.

Этот рефакторинг спросит вас об имени включающего экземпляра. Он может дать предложение, как пример, которое вы можете принять. Смысл этого будет ясен очень скоро. После нажатия OK код для включающего класса BagExample будет изменен, как показано в Листинге 3.

Листинг 3. Класс Bag после рефакторинга

public class BagExample
{
   void processMessage(String msg)
   {
      Bag bag = new BagImpl(this);
      bag.set(msg);
      MessagePipe pipe = new MessagePipe();
      pipe.send(bag);
   }
}

Заметьте, что когда класс вложенный, он имеет доступ в внутренним членам класса. Чтобы сохранить эту функциональность, рефакторинг будет добавлять экземпляр включающего класса BagExample в бывший вложенный класс. Это переменная экземпляра, для которой вас попросят сначала определить имя. Также создается конструктор, который устанавливает эту переменную экземпляра. Новый класс BagImpl, который создает рефакторинг, показан в Листинге 4.

Листинг 4. Класс BagImpl

final class BagImpl implements Bag
{
   private final BagExample example;
   /**
    * @paramBagExample
    */
  BagImpl(BagExample example)
   {
      this.example = example;
      // TODO Auto-generated constructor stub
   }
   Object o;
   public Object get()
   {
      return o;
   }
   public void set(Object o)
   {
      this.o = o;
   }
}

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

Перемещение членов в иерархии классов

Две другие операции рефакторинга, Push Down и Pull Up, перемещают методы или поля классов из класса в подкласс или в суперкласс, соответственно. Предположим, вы имеете абстрактный класс, определенный, как показано в Листинге 5.

Листинг 5. Абстрактный класс Vehicle

public abstract class Vehicle
{
   protected int passengers;
   protected String motor;
   
   public int getPassengers()
   {
      return passengers;
   }
   public void setPassengers(int i)
   {
      passengers = i;
   }
   public String getMotor()
   {
      return motor;
   }
   public void setMotor(String string)
   {
      motor = string;
   }
}

Вы также имеете подкласс Vehicle, названный Automobile, как показано в Листинге 6.

Листинг 6. Класс Automobile

public class Automobile extends Vehicle
{
   private String make;
   private String model;
   public String getMake()
   {
      return make;
   }
   public String getModel()
   {
      return model;
   }
   public void setMake(String string)
   {
      make = string;
   }
   public void setModel(String string)
   {
      model = string;
   }
}

Заметьте, что один из атрибутов Vehicle - motor. Это хорошо, если вы знаете, что будете всегда иметь дело только с моторизированными транспортными средствами, но если вы хотите допустить такие вещи, как гребная лодка, вы должны захотеть передвинуть атрибут motor из класса Vehicle в класс Automobile. Чтобы сделать это, выберите motor в представлении Outline, а затем выберите Refactor > Push Down.

Eclipse достаточно сообразителен, чтобы понимать, что вы не можете всегда перемещать поле сами, и обеспечивает кнопку Add Required, но она не всегда корректно работает в Eclipse 2.1. Вам нужно проверить, что любой из методов, зависящих от этого поля, также передвинут. В данном случае их два, методы-акцессоры, которые сопровождают поле motor, как показано на Рисунке 3.

Рисунок 3. Добавление требуемых членов

После нажатия на OK поле motor и методы getMotor() и setMotor() будут перемещены в класс Automobile. Листинг 7 показывает, как выглядит класс Automobile после этого рефакторинга.

Листинг 7. КлсааAutomobile после рефакторинга

public class Automobile extends Vehicle
{
   private String make;
   private String model;
   protected String motor;
   public String getMake()
   {
      return make;
   }
   public String getModel()
   {
      return model;
   }
   public void setMake(String string)
   {
      make = string;
   }
   public void setModel(String string)
   {
      model = string;
   }
   public String getMotor()
   {
      return motor;
   }
   public void setMotor(String string)
   {
      motor = string;
   }
}

Операция рефакторинга Pull Up почти идентична Push down, за исключением, конечно, того, что перемещает член класса из класса в его суперкласс, а не в подкласс. Вы можете использовать ее, если вы позднее измените свои соображения и решите переместить motor назад в класс Vehicle. Имеется то же самое предупреждение о необходимости убедиться в том, что вы выбрали все требуемые члены.

Если мы имеем motor в классе Automobile, это значит, что, если вы создаете другой подкласс Vehicle, такой, как Bus, вам нужно добавить motor (и связанные с ним методы) и в класс Bus тоже. Одним из способов представления такого отношения, как это, является создание интерфейса, Motorized, который Automobile и Bus будут реализовывать, а RowBoat - нет.

Простейший способ создать интерфейс Motorized состоит в использовании операции рефакторинга Extract Interface на Automobile. Чтобы сделать это, выберите класс Automobile в представлении Outline и выберите Refactor > Extract Interface из меню. Диалог даст вам возможность выбрать, какие методы вы хотите включить в интерфейс, как показано на Рисунке 4.

Рисунок 4. Выделение интерфейса Motorized

После выбора OK интерфейс будет создан, как показано в Листинге 8.

Листинг 8. Интерфейс Motorized

public interface Motorized
{
   public abstract String getMotor();
   public abstract void setMotor(String string);
}

Все объявления для Automobile изменены следующим образом:

public class Automobile extends Vehicle implements Motorized

Использование супертипа

Последняя операция рефакторинга, включенная в эту категорию, - Use Supertype Where Possible. Рассмотрим приложение, которое управляет реестром автомобилей. Оно все время использует объекты типа Automobile. Если вы хотите обрабатывать любые типы транспортных средств, вы можете использовать этот рефакторинг, чтобы изменить ссылки на Automobile на ссылки на Vehicle (см. Рисунок 5). Если вы выполняете в вашем приложении какие-то проверки типов, используя оператор instanceof, вам понадобится определить, когда следует использовать определенный тип, а когда супертип, и изменить первый вариант. Используйте выбранный супертип в выражениях instanceof.

Рисунок 5. Изменение Automobile на его супертип, Vehicle

Необходимость использования супертипа часто возникает в языке Java, в частности, если используется шаблон Метода Фабрики. Обычно он реализуется тем, что мы имеем абстрактный класс, который имеет статический метод create(), возвращающий определенную объектную реализацию абстрактного класса. Это может быть полезным, если тип конкретного объекта, который должен быть создан, зависит от деталей реализации, которые неинтересны для клиентских классов.

Изменение кода в классе

Наиболее разнообразны те операции рефакторинга, которые реорганизуют код внутри класса. Помимо прочего, они позволяют вам вводить (или удалять) промежуточные переменные, создавать новый метод из части старого и создавать методы-акцессоры для поля.

Выделение и встраивание

Есть несколько операций рефакторинга, начинающихся со слова Extract: Extract Method, Extract Local Variable и Extract Constants. Первая из них, Extract Method, как вы можете ожидать, будет из того, что вы выбрали, создавать новый метод. Возьмем, например, метод main() в классе из Листинга 8. Он вычисляет опции командной строки и если находит опцию, начинающуюся с -D, сохраняет ее пару имя-значение в объекте Properties.

Листинг 8. main()

import java.util.Properties;
import java.util.StringTokenizer;
public class StartApp
{
   public static void main(String[] args)
   {
      Properties props = new Properties();
      for (int i= 0; i < args.length; i++)
      {
         if(args[i].startsWith("-D"))
         {
           String s = args[i].substring(2);
           StringTokenizer st = new StringTokenizer(s, "=");
            if(st.countTokens() == 2)
            {
              props.setProperty(st.nextToken(), st.nextToken());
            }
         }
      }
      //continue...
   }
}

Есть два основных случая, когда вы можете захотеть забрать некоторый код из метода и поместить его в другой метод. Первый случай - когда метод слишком длинный и выполняет слишком много логически различных операций. (Мы не знаем, что еще делает этот метод main(), но из того, что мы видим здесь, нет резона для выделения метода отсюда.) Второй случай - когда есть логически обособленная секция метода, которая может быть повторно использована в других методах. Иногда, например, вы обнаруживаете, что у вас несколько строк кода повторяются в нескольких разных методах. Это возможно и есть тот случай, но вы, скорее всего, не выполните этот рефакторинг, пока вам действительно не понадобится повторно использовать этот код. Предположим, что есть другое место, где вам нужно разбирать пару имя-значение и добавлять их в объект Properties, вы можете выделить секцию кода, которая включает в себя объявление StringTokenizer и следующий за ним оператор if. Чтобы сделать это, выделите этот код, а затем выберите из меню Refactor > Extract Method. Вы получите приглашение на ввод имени метода; введите addProperty, а затем проверьте, что метод имеет два параметра, Properties prop и String s. Листинг 9 показывает класс после того, как Eclipse выделил метод addProp().

Листинг 9. addProp() выделен

import java.util.Properties;
import java.util.StringTokenizer;
public class Extract
{
   public static void main(String[] args)
   {
      Properties props = new Properties();
      for (int i = 0; i < args.length; i++)
      {
         if (args[i].startsWith("-D"))
         {
            String s = args[i].substring(2);
            addProp(props, s);
         }
      }
   }
   private static void addProp(Properties props, String s)
   {
      StringTokenizer st = new StringTokenizer(s, "=");
      if (st.countTokens() == 2)
      {
         props.setProperty(st.nextToken(), st.nextToken());
      }
   }
}

Операция рефакторинга Extract Local Variable берет выражение, которое используется непосредственно, и сначала присваивает его значение локальной переменной. Эта переменная затем используется там, где использовалось выражение. Например, в приведенном выше методе addProp() вы можете выделить первый вызов st.nextToken() и выбрать Refactor > Extract Local Variable. Вы получите приглашение на ввод имени переменной; введите key. Заметьте, что есть опция замены всех вхождений выбранного выражения ссылкой на новую переменную. Это часто годится, но не в нашем случае метода nextToken(), который (очевидно) возвращает разные значения при каждом вызове. Убедитесь, что эта опция не выбрана; см. Рисунок 6.

Рисунок 6. Не заменяйте все вхождения выбранного выражения

Далее повторите этот рефакторинг для второго вызова st.nextToken(), на этот раз вызывая значение новой локальной переменной. Листинг 10 показывает код после этих двух рефакторингов.

Листинг 10. Код после рефакторинга

private static void addProp(Properties props, String s)
   {
     StringTokenizer st = new StringTokenizer(s, "=");
      if(st.countTokens() == 2)
      {
         String key = st.nextToken();
         String value = st.nextToken();
        props.setProperty(key, value);
      }
   }

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

Операция Extract Constant аналогична Extract Local Variable, но вы должны выбрать статические, константное выражение, которое рефакторинг преобразует в константу static final. Это полезно для удаления из вашего кода жестко закодированных чисел и строк. Например, в вышеприведенном коде мы используем "-D" для опции командной строки, определяющей пару имя-значение. Выделите "-D" в коде, выберите Refactor > Extract Constant и введите DEFINE как имя константы. Этот рефакторинг изменит код, как показано в Листинге 11.

Листинг 11. Код после рефакторинга

public class Extract
{
   private static final String DEFINE = "-D";
   public static void main(String[] args)
   {
      Properties props = new Properties();
      for (int i = 0; i < args.length; i++)
      {
         if (args[i].startsWith(DEFINE))
         {
            String s = args[i].substring(2);
            addProp(props, s);
         }
      }
   }
   // ...

Для каждой операции рефакторинга Extract... есть соответствующая операция рефакторинга Inline..., которая выполняет обратную операцию. Например, если вы выделите в вышеприведенном коде переменную s, выберите Refactor > Inline..., а затем нажмите OK, Eclipse будет использовать выражение args[i].substring(2) непосредственно в вызове addProp() следующим образом:

        if(args[i].startsWith(DEFINE))
         {
            addProp(props,args[i].substring(2));
         }

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

Таким же способом вы можете заменить переменную во встроенном выражении, вы можете также выделить имя метода или константу, описанную как static final. Выберите из меню Refactor > Inline..., и Eclipse заменит вызовы метода на код метода или ссылку на константу значением константы, соответственно.

Инкапсуляция полей

Обычно не является хорошим приемом выставление внутренней структуры ваших объектов. Вот почему класс Vehicle и его подклассы имеют либо private, либо protected поля и public методы-акцессоры для обеспечения доступа. Эти методы могут быть сгенерированы автоматически двумя разными способами.

Одним из способов генерации этих методов является Source > Generate Getter and Setter. Это отобразит диалоговое окно с предлагаемыми методам-акцессорами для каждого поля, которое еще их не имеет. Это, однако, не рефакторинг, поскольку при этом ссылки на поля не обновляются использованием новых методов, вам нужно сделать это самому, если это необходимо. Эта опция дает громадную экономию времени, но ее лучше использовать при начальном создании класса или при добавлении в класс новых полей, потому что еще нет ссылок на эти поля в коде, а значит, и нет другого кода, который нужно менять.

Второй способ генерации методов-акцессоров - выбрать поле и выбрать из меню Refactor > Encapsulate Field. Этот способ генерирует методы-акцесоры только для одного поля за раз, но, в отличие от Source > Generate Getter and Setter, он также меняет ссылки на поле на вызовы новых методов.

Например, начнем сначала с новой, простой версией класса Automobile, показанной в Листинге 12.

Листинг 12. Простой класс Automobile

public class Automobile extends Vehicle
{
   public String make;
   public String model;
}

Далее создадим класс, который создает экземпляр класса Automobile и обращается к полю make непосредственно, как показано в Листинге 13.

Листинг 13. Создание экземпляра Automobile

public class AutomobileTest
{
   public void race()
   {
      Automobilecar1 = new Automobile();
      car1.make= "Austin Healy";
      car1.model= "Sprite";
      // ...
   }
}

Теперь инкапсулируем поле make, выделив имя поля и выбрав Refactor > Encapsulate Field. В диалоге введем имена для методов-акцессоров - как вы можете предположить, они по умолчанию getMake() и setMake(). Вы можете также выбрать, должны ли методы, которые находятся в том же классе, обращаться к полю непосредственно или же эти ссылки должны быть заменены на использование методов-акцессоров, как во всех других классах. (Некоторые люди имеют установившееся предпочтение к тому или иному методу, но не имеет значения, что вы выберете в данном случае, поскольку в Automobile нет ссылок на поле make.) См. Рисунок 7.

Рисунок 7. Инкапсуляция поля

После нажатия OK поле make в классе Automobile будет private и будет иметь методы getMake() и setMake(), как показано в Листинге 14.

Листинг 14. Класс Automobile после рефакторинга

public class Automobile extends Vehicle
{
   private String make;
   public String model;

   public void setMake(String make)
   {
      this.make = make;
   }

   public String getMake()
   {
      return make;
   }
}

Класс AutomobileTest будет также обновлен использованием новых методов доступа, как показано в Листинге 15.

Листинг 15. AutomobileTest class

public class AutomobileTest
{
   public void race()
   {
      Automobilecar1 = new Automobile();
      car1.setMake("Austin Healy");
      car1.model= "Sprite";
      // ...
   }
}

Изменение сигнатуры метода

Последняя операция рефакторинга, рассматриваемая здесь, является наиболее трудной для использования: Change Method Signature. Совершенно очевидно, что она делает - изменяет параметры, видимость и возвращаемый тип метода. Что неочевидно, так это эффект, который это изменение имеет для метода или для кода, вызывающего этот метод. Тут нет никакого волшебства. Если изменения вызывают проблемы в коде, подвергающемся рефакторингу - поскольку они допускают неопределенные переменные или несоответствие типов - операции рефакторинга отметят это. Вы имеете выбор принять рефакторинг везде и откорректировать проблемы после этого или отменить рефакторинг. Если рефакторинг порождает проблемы в других методах, они игнорируются, и вам придется исправлять их самому после рефакторинга.

Чтобы прояснить это, рассмотрим следующий класс и метод в Листинге 16.

Листинг 16. Класс MethodSigExample

public class MethodSigExample
{
   public int test(String s, int i)
   {
      int x = i + s.length();
      return x;
   }
}

Метод test() в вышеприведенном классе вызывается методом в другом классе, как показано в Листинге 17.

Листинг 17. Метод callTest

public void callTest()
   {
     MethodSigExample eg = new MethodSigExample();
     int r = eg.test("hello", 10);
   }

Выделите test в первом классе и выберите Refactor > Change Method Signature. Появится диалог, показанный на Рисунке 8.

Рисунок 8. Опция Change Method Signature

Первая опция состоит в изменении видимости метода. В данном примере изменение на protected или private защитит метод callTest() из второго класса от обращения. (Если они в раздельных пакетах, изменение доступа на доступ по умолчанию также породит эту проблему) Eclipse не отметит эту ошибку при выполнении рефакторинга; это вы должны выбрать соответствующее значение.

Следующая опция состоит в изменении возвращаемого типа. Изменение возвращаемого типа на float, например, не отмечается как ошибка, поскольку int в операторе return метода test() автоматически превращается в float. Однако это вызовет проблемы в методе callTest() во втором классе, поскольку float не может быть преобразовано в int. Вам нужно будет либо преобразовать возвращаемое значение test() в int, либо изменить тип r в callTest() на float.

Такие же соображения применимы, если мы изменяем тип первого параметра из String в int. Это будет отмечено во время рефакторинга, потому что это вызывает проблему в методе, который подвергается рефакторингу: int не имеет метода length(). Однако, изменение его на StringBuffer не вызовет проблемы, потому что он имеет метод length(). Это, конечно, вызовет проблему в методе callTest(), потому что он все еще передает String, когда вызывает test().

Как было отмечено выше, в случае, когда рефакторинг порождает ошибку, отмечена она или нет, вы можете продолжать, просто корректируя ошибки в каждом отдельном случае. Другой подход - предупреждение ошибок. Если вы хотите удалить параметр i, потому что он не нужен, вы можете начать с удаления ссылок не него в методе, который подвергается рефакторингу. Удаление параметра тогда пройдет более гладко.

Последнее, что должно быть объяснено - опция Default Value. Она используется только тогда, когда параметр добавляется в сигнатуру метода. Это используется для обеспечения значения, когда параметр добавляется в вызывающие методы. Например, если мы добавляем параметр String с именем n и значением по умолчанию world, вызов test() в методе callTest() будет изменен следующим образом:

   public void callTest()
   {
      MethodSigExample eg = new MethodSigExample();
      int r = eg.test("hello", 10, "world");
   }

Поводом ухода от обсуждения этой страшной на вид операции рефакторинга Change Method Signature состоит не в том, что она проблематична, а в том, что это мощная, экономящая время операция, для успешного использования которой часто требуется некоторое планирование.

Резюме

Инструменты Eclipse делают рефакторинг легким, и знакомство с ними поможет вам повысить свою производительность. Методы быстрой разработки, которые добавляют свойства в программу итеративно, зависят от рефакторинг, как от приема изменения программного проекта и добавления в него. Но даже если вы не используете формальный метод, который требует рефакторинга, инструменты рефакторинга Eclipse обеспечат способ сохранения времени при выполнении типовых изменений кода. Затрата некоторого времени на знакомство с ними, чтобы вы могли распознать ситуацию, в которой они могут быть применены, является выгодным вложением вашего времени.

Ресурсы

Книги

Web-сайты

Статьи и учебники на developerWorks


КаталогИндекс раздела