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

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

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

Дэвид Галлардо, Консультант по программному обеспечению

Дэвид Галлардо - независимый консультант по программному обеспечению и автор, специализирующийся на локаизации ПО, Java Web-приложениях и разработке баз данных. В течение пятнадцати лет он был разработчиком программного обеспечения и получил опыт в работе с многими операционными системами, языками программирования и сетевыми протоколами. Его последний опыт включает в себя ведение базы данных и интернационализацию разработки в компании ведуще электронную коммерцию по принципу бизнес-для-бизнеса TradeAccess, Inc. До этого Дэвид работал старшим инженером в Группе Разработки Международных Продуктов Lotus Development Corporation, где внес большой вклад в разработку кросс-платформенной библиотики, поддерживающей Unicode и международную поддержку языков для продуктов Lotus, включая Domino. Вы можете связаться с Дэвидом по электронной почте david@gallardo.org.



09.09.2003

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

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

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

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

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

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


Типы рефакторинга в 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 - нет.

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

После выбора 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
Изменение Automobile на его супертип

Необходимость использования супертипа часто возникает в языке 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
Опция 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, потому что он не нужен, вы можете начать с удаления ссылок не него в методе, который подвергается рефакторингу. Удаление параметра тогда пройдет более гладко.

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

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

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


Резюме

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

Ресурсы

Книги

  • Ключевым текстом по рефакторингу является Refactoring: Improving the Design of Existing Code, авторы - Martin Fowler, Kent Beck, John Brant, William Opdyke и Don Roberts (Addison-Wesley, 1999).
  • Pефакторинг, как непрерывный процесс обсуждается автором в контексте проектирования и разработки проекта в Eclipse в Eclipse In Action: A Guide for Java Developers, авторы - David Gallardo, Ed Burnette и Robert McGovern (Manning, 2003).
  • Шаблоны (такие, как Метод Фабрики, упомянутый в этой статье) являются важным инструментом для понимания и обсуждения объектно-ориентированного проекта. Классическим текстом является Design Patterns: Elements of Reusable Object-Oriented Software, авторы - Erich Gamma, Richard Helm, Ralph Johnson и John Vlissides (Addison-Wesley, 1995).
  • One Одним из недостатков для Java-программистов является то, что примеры в Design Patterns используют C++; книгу, которая переводит шаблоны на язык Java, см. Patterns in Java, Volume One: A Catalog of Reusable Design Patterns Illustrated with UML, автор -Mark Grand (Wiley, 1998).
  • Введение в один из вариантов быстрого программирования см. в Extreme Programming Explained: Embrace Change, автор - Kent Beck (Addison-Wesley, 1999).

Web-сайты

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

Комментарии

developerWorks: Войти

Обязательные поля отмечены звездочкой (*).


Нужен IBM ID?
Забыли Ваш IBM ID?


Забыли Ваш пароль?
Изменить пароль

Нажимая Отправить, Вы принимаете Условия использования developerWorks.

 


Профиль создается, когда вы первый раз заходите в developerWorks. Информация в вашем профиле (имя, страна / регион, название компании) отображается для всех пользователей и будет сопровождать любой опубликованный вами контент пока вы специально не укажите скрыть название вашей компании. Вы можете обновить ваш IBM аккаунт в любое время.

Вся введенная информация защищена.

Выберите имя, которое будет отображаться на экране



При первом входе в developerWorks для Вас будет создан профиль и Вам нужно будет выбрать Отображаемое имя. Оно будет выводиться рядом с контентом, опубликованным Вами в developerWorks.

Отображаемое имя должно иметь длину от 3 символов до 31 символа. Ваше Имя в системе должно быть уникальным. В качестве имени по соображениям приватности нельзя использовать контактный e-mail.

Обязательные поля отмечены звездочкой (*).

(Отображаемое имя должно иметь длину от 3 символов до 31 символа.)

Нажимая Отправить, Вы принимаете Условия использования developerWorks.

 


Вся введенная информация защищена.


static.content.url=http://www.ibm.com/developerworks/js/artrating/
SITE_ID=40
Zone=Open source, Технология Java
ArticleID=156934
ArticleTitle=Рефакторинг для всех
publish-date=09092003