Перейти к основному содержимому

Рефакторинг в Eclipse

· 11 мин. чтения

1. Обзор

На сайте refactoring.com мы читаем, что «рефакторинг — это метод реструктуризации существующего кода, изменение его внутренней структуры без изменения внешнего поведения».

Как правило, мы можем захотеть переименовать переменные или методы или сделать наш код более объектно-ориентированным, введя шаблоны проектирования. Современные IDE имеют множество встроенных функций, которые помогают нам достичь таких целей рефакторинга и многих других.

В этом руководстве мы сосредоточимся на рефакторинге в Eclipse, популярной бесплатной среде разработки Java.

Прежде чем мы начнем какой-либо рефакторинг, желательно иметь надежный набор тестов, чтобы убедиться, что мы ничего не сломали во время рефакторинга.

2. Переименование

2.1. Переименование переменных и методов

Мы можем переименовать переменные и методы, выполнив следующие простые шаги :

  • Выберите элемент

  • Щелкните элемент правой кнопкой мыши

  • Щелкните параметр « Рефакторинг » > «Переименовать ».

  • Введите новое имя
  • Нажмите Enter

./b74fa1e6ab0a5543ed1e9202eb4f6e95.png

Мы также можем выполнить второй и третий шаги, используя сочетание клавиш Alt+Shift+R .

Когда вышеуказанное действие будет выполнено, Eclipse найдет каждое использование этого элемента в этом файле и заменит их все на месте.

Мы также можем использовать расширенную функцию для обновления ссылки в других классах , наведя указатель мыши на элемент, когда рефакторинг включен, и нажав « Параметры » :

./79551e696f41650acfaa5b8245c6926e.png

Это откроет всплывающее окно, в котором мы можем переименовать переменную или метод и иметь возможность обновить ссылку в других классах:

./e378e2560950ee3a7526c048d74f3c8c.png

2.2. Переименование пакетов

Мы можем переименовать пакет, выбрав имя пакета и выполнив те же действия, что и в предыдущем примере. Сразу же появится всплывающее окно, в котором мы можем переименовать пакет с такими параметрами, как обновление ссылок и переименование подпакетов.

./92199788817a71f77d9df0ef1441986e.png

Мы также можем переименовать пакет из представления Project Explorer, нажав F2 :

./db35d87c93b30799cc89bc7627b5ba49.png

2.3. Переименование классов и интерфейсов

Мы можем переименовать класс или интерфейс, используя те же действия или просто нажав F2 в Project Explorer. Это откроет всплывающее окно с параметрами для обновления ссылок, а также с несколькими дополнительными параметрами:

./660ebf732d1d4383ec53fc40cb3ecc2e.png

3. Извлечение

Теперь поговорим об экстракции. Извлечение кода означает взятие фрагмента кода и его перемещение.

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

Eclipse предоставляет множество способов извлечения данных, которые мы продемонстрируем в следующих разделах.

3.1. Извлечь класс

Предположим, у нас есть следующий класс Car в нашей кодовой базе:

public class Car {

private String licensePlate;
private String driverName;
private String driverLicense;

public String getDetails() {
return "Car [licensePlate=" + licensePlate + ", driverName=" + driverName
+ ", driverLicense=" + driverLicense + "]";
}

// getters and setters
}

Теперь предположим, что мы хотим извлечь информацию о драйвере в другой класс. Мы можем сделать это, щелкнув правой кнопкой мыши в любом месте класса и выбрав параметр Refactor > Extract Class :

./2229c1dc0c2958fa88abb6551d8017a1.png

Это откроет всплывающее окно, в котором мы можем назвать класс и выбрать, какие поля мы хотим переместить, а также несколько других параметров:

./43778db60b3a145b7b9dea49c7abad7b.png

Мы также можем предварительно просмотреть код, прежде чем двигаться дальше. Когда мы нажмем OK , Eclipse создаст новый класс с именем Driver , а предыдущий код будет преобразован в:

public class Car {

private String licensePlate;

private Driver driver = new Driver();

public String getDetails() {
return "Car [licensePlate=" + licensePlate + ", driverName=" + driver.getDriverName()
+ ", driverLicense=" + driver.getDriverLicense() + "]";
}

//getters and setters
}

3.2. Извлечь интерфейс

Мы также можем извлечь интерфейс аналогичным образом. Предположим, у нас есть следующий класс EmployeeService :

public class EmployeeService {

public void save(Employee emp) {
}

public void delete(Employee emp) {
}

public void sendEmail(List<Integer> ids, String message) {
}
}

Мы можем извлечь интерфейс, щелкнув правой кнопкой мыши в любом месте класса и выбрав параметр « Рефакторинг»> «Извлечь интерфейс », или мы можем использовать команду сочетания клавиш Alt + Shift + T , чтобы вызвать меню напрямую:

./32b1281eba81953dd66907ba50b35e2a.png

Это откроет всплывающее окно, в котором мы можем ввести имя интерфейса и решить, какие члены объявить в интерфейсе:

./be4ba3e879e74f7d8704fabdaafc9035.png

В результате этого рефакторинга у нас будет интерфейс IEmpService , а также изменится наш класс EmployeeService :

public class EmployeeService implements IEmpService {

@Override
public void save(Employee emp) {
}

@Override
public void delete(Employee emp) {
}

public void sendEmail(List<Integer> ids, String message) {
}
}

3.3. Извлечь суперкласс

Предположим, у нас есть класс Employee , содержащий несколько свойств, которые не обязательно относятся к занятости человека:

public class Employee {

private String name;

private int age;

private int experienceInMonths;

public String getName() {
return name;
}

public int getAge() {
return age;
}

public int getExperienceInMonths() {
return experienceInMonths;
}
}

Мы можем захотеть извлечь свойства, не связанные с работой, в суперкласс Person . Чтобы извлечь элементы в суперкласс, мы можем щелкнуть правой кнопкой мыши в любом месте класса и выбрать параметр « Рефакторинг»> «Извлечь суперкласс » или использовать Alt + Shift + T , чтобы вызвать меню напрямую:

./4d438576b6af7798a2dab58c5b698b02.png

Это создаст новый класс Person с выбранными нами переменными и методом, а класс Employee будет преобразован в:

public class Employee extends Person {

private int experienceInMonths;

public int getExperienceInMonths() {
return experienceInMonths;
}
}

3.4. Метод извлечения

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

Допустим, например, что у нас есть цикл for, встроенный в наш метод:

public class Test {
public static void main(String[] args) {
for (int i = 0; i < args.length; i++) {
System.out.println(args[i]);
}
}
}

Чтобы вызвать мастер извлечения метода , нам необходимо выполнить следующие шаги:

  • Выберите строки кода, которые мы хотим извлечь
  • Щелкните правой кнопкой мыши выбранную область
  • Щелкните параметр « Рефакторинг » > «Извлечь метод ».

./072d4eac5cc53872adca45fc3725dbef.png

Последние два шага также можно выполнить с помощью сочетания клавиш Alt+Shift+M . Давайте посмотрим на диалоговое окно Extract Method :

./599eb946e472609da05aef37e10448bc.png

Это приведет к рефакторингу нашего кода:

public class Test {

public static void main(String[] args) {
printArgs(args);
}

private static void printArgs(String[] args) {
for (int i = 0; i < args.length; i++) {
System.out.println(args[i]);
}
}
}

3.5. Извлечь локальные переменные

Мы можем извлечь определенные элементы как локальные переменные, чтобы сделать наш код более читабельным.

Это удобно, когда у нас есть строковый литерал:

public class Test {

public static void main(String[] args) {
System.out.println("Number of Arguments passed =" + args.length);
}
}

и мы хотим извлечь его в локальную переменную.

Для этого нам необходимо:

  • Выберите элемент
  • Щелкните правой кнопкой мыши и выберите « Рефакторинг» > «Извлечь локальную переменную».

./859429b07380fd996d4918fb5801e62f.png

Последний шаг также можно выполнить с помощью сочетания клавиш Alt+Shift+L . Теперь мы можем извлечь нашу локальную переменную:

./cf4a1be34dc25b2334d283ae41308f0d.png

И вот результат этого рефакторинга:

public class Test {

public static void main(String[] args) {
final String prefix = "Number of Arguments passed =";
System.out.println(prefix + args.length);
}
}

3.6. Извлечь константу

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

Мы могли бы извлечь значение 3.14 в локальную переменную, как мы только что видели:

public class MathUtil {

public double circumference(double radius) {
return 2 * 3.14 * radius;
}
}

Но, возможно, было бы лучше извлечь его как константу, для чего нам нужно:

  • Выберите элемент
  • Щелкните правой кнопкой мыши и выберите « Рефакторинг» > «Извлечь константу».

./370302ef31ee1ce965dbfd788a284520.png

Это откроет диалоговое окно, в котором мы можем дать константе имя и настроить ее видимость, а также несколько других параметров:

./cc34dd58bb6e02cffa5d79dc278d906a.png

Теперь наш код выглядит немного читабельнее:

public class MathUtil {

private static final double PI = 3.14;

public double circumference(double radius) {
return 2 * PI * radius;
}
}

4. Встраивание

Мы также можем пойти другим путем и использовать встроенный код.

Рассмотрим класс Util , в котором есть локальная переменная, которая используется только один раз:

public class Util {

public void isNumberPrime(int num) {
boolean result = isPrime(num);
if (result) {
System.out.println("Number is Prime");
} else {
System.out.println("Number is Not Prime");
}
}

// isPrime method
}

Мы хотим удалить локальную переменную результата и встроить вызов метода isPrime . Для этого мы выполняем следующие шаги:

  • Выберите элемент, который мы хотим встроить
  • Щелкните правой кнопкой мыши и выберите параметр « Рефакторинг » > «Встроенный ».

./05504b5141e40e987cb0e356dbe2a8f8.png

Последний шаг также можно выполнить с помощью сочетания клавиш Alt+Shift+I :

./f75e6b74cfa5062ecb1e962b129e3507.png

После этого у нас будет на одну переменную меньше, которую нужно отслеживать:

public class Util {

public void isNumberPrime(int num) {
if (isPrime(num)) {
System.out.println("Number is Prime");
} else {
System.out.println("Number is Not Prime");
}
}

// isPrime method
}

5. Нажмите вниз и потяните вверх

Если у нас есть отношение родитель-потомок (как в нашем предыдущем примере Employee и Person ) между нашими классами, и мы хотим переместить между ними определенные методы или переменные, мы можем использовать параметры push/pull, предоставляемые Eclipse.

Как следует из названия, параметр Push Down перемещает методы и поля из родительского класса во все дочерние классы, а параметр Pull Up перемещает методы и поля из определенного дочернего класса в родительский, что делает этот метод доступным для всех дочерних классов.

Чтобы переместить методы в дочерние классы, нам нужно щелкнуть правой кнопкой мыши в любом месте класса и выбрать параметр Refactor > Push Down :

./19e783da2f2bb2e60d57c53a9de7b681.png

Это откроет мастер, в котором мы можем выбрать элементы для нажатия:

./c78756b9dcb9fe1391059aee3c98c242.png

Точно так же для перемещения методов из дочернего класса в родительский класс нам нужно щелкнуть правой кнопкой мыши в любом месте класса и выбрать Refactor > Pull Up :

./091a8e19452c6fbbf5a4555ae5eafd75.png

Это откроет аналогичный мастер, в котором мы можем выбрать элементы для извлечения:

./cbaa875b2064cb6d8680bb563bcf1400.png

6. Изменение подписи метода

Чтобы изменить сигнатуру существующего метода, мы можем выполнить несколько простых шагов:

  • Выберите метод или поместите курсор где-нибудь внутри
  • Щелкните правой кнопкой мыши и выберите « Рефакторинг» > «Изменить подпись метода».

Последний шаг также можно выполнить с помощью сочетания клавиш Alt+Shift+C.

Это откроет всплывающее окно, в котором вы можете соответствующим образом изменить сигнатуру метода:

./9cff73bd1d43a8729f1ef0d707ab648d.png

7. Переезд

Иногда мы просто хотим переместить методы в другой существующий класс, чтобы сделать наш код более объектно-ориентированным .

Consider the scenario where we have a Movie class:

public class Movie {

private String title;
private double price;
private MovieType type;

// other methods
}

And MovieType is a simple enum:

public enum MovieType {
NEW, REGULAR
}

Suppose also that we have a requirement that if a Customer rents a movie that is NEW , it will be charged two dollars more, and that our Customer class has the following logic to calculate the totalCost ():

public class Customer {

private String name;
private String address;
private List<Movie> movies;

public double totalCost() {
double result = 0;
for (Movie movie : movies) {
result += movieCost(movie);
}
return result;
}

private double movieCost(Movie movie) {
if (movie.getType()
.equals(MovieType.NEW)) {
return 2 + movie.getPrice();
}
return movie.getPrice();
}

// other methods
}

Clearly, the calculation of the movie cost based on the MovieType would be more appropriately placed in the Movie class and not the Customer class. We can easily move this calculation logic in Eclipse:

  • Select the lines you want to move
  • Right-click and choose the Refactor > Move option

The last step can also be achieved by keyboard shortcut Alt+Shift+V :

./5762786667bf5576db19182a315f1728.png

Eclipse is smart enough to realize that this logic should be in our Movie class. We can change the method name if we want, along with other advanced options.

The final Customer class code will be refactored to:

public class Customer {

private String name;
private String address;
private List<Movie> movies;

public double totalCost() {
double result = 0;
for (Movie movie : movies) {
result += movie.movieCost();
}
return result;
}

// other methods
}

As we can see, the movieCost method has been moved to our Movie class and is being used in the refactored Customer class.

8. Conclusion

In this tutorial, we looked into some of the main refactoring techniques provided by Eclipse. We started with some basic refactoring like renaming and extracting. Later on, we saw moving methods and fields around different classes.

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