Рефакторингът може да бъде трудна задача, особено когато разглеждате голям, сложен Java проект. Въпреки това, това е необходим процес, който помага да се подобри структурата на вашия код, като същевременно се запази неговата функционалност. Тази публикация в блога представя казус от рефакторинг на Java проект от началото до края.

Въведение

Нека разгледаме един проект, който наследихме – приложение за онлайн книжарница. Въпреки че е пълна с функции и функционална, кодовата база е нараснала значително с течение на времето и има остра нужда от почистване. Изправени сме пред комбинация от стилове на кодиране, преплетени зависимости и ограничено тестово покритие.

Идентифициране на области за рефакторинг

Първата стъпка в процеса на рефакторинг е идентифицирането на области, които се нуждаят от подобрение. За целта използваме инструмент, наречен SonarQube, платформа с отворен код за непрекъсната проверка на качеството на кода. След като стартирахме нашия проект чрез SonarQube, открихме множество миризми на код, грешки и горещи точки за сигурност, които изискват нашето внимание.

Подобряване на структурата на кода

Едно от идентифицираните ключови опасения беше високото ниво на свързване между класовете. Много класове използваха конкретни класове вместо абстрактни интерфейси, което доведе до тясно свързване. Това от своя страна прави нашия код по-малко гъвкав и по-труден за поддържане или разширяване.

Нека преработим това, като приложим принципа на инверсия на зависимостта (DIP) от принципите на SOLID. Вместо класовете да зависят директно един от друг, те ще зависят от абстракции.

Преди:

public class BookStore {
  private BookInventory inventory = new BookInventory();
  ...
}

След:

public class BookStore {
  private IInventory inventory;
  
  public BookStore(IInventory inventory) {
    this.inventory = inventory;
  }
  ...
}

Сега BookStore вече не зависи от конкретния BookInventory клас, а от IInventory интерфейса.

Премахване на миризми на код

Също така открихме много случаи на дълги методи и големи класове, типични миризми на код. Извличането на методи и класове може значително да подобри четливостта и поддръжката на кода.

Един такъв пример е методът placeOrder в класа OrderProcessing. Той отговаря за проверката на инвентара, изчисляването на цените, прилагането на отстъпки и създаването на поръчки за доставка. Това е класически пример за метод, който прави твърде много.

Чрез извличане на тези отговорности в техните собствени методи, ние правим кода по-лесен за четене и тестване. Например, можем да извлечем изчислението на цената в нов calculateTotalPrice метод.

Преди:

public class OrderProcessing {
  ...
  public Order placeOrder(Customer customer, List<Book> books) {
    // check inventory, calculate prices, apply discounts, create order
  }
  ...
}

След:

public class OrderProcessing {
  ...
  public Order placeOrder(Customer customer, List<Book> books) {
    // check inventory, apply discounts, create order
  }
  
  private BigDecimal calculateTotalPrice(List<Book> books) {
    // calculate prices
  }
  ...
}

Подобряване на тестовото покритие

Когато за първи път започнахме, нашият проект имаше ограничено тестово покритие, което го правеше рисковано за рефакторинг. За да сведем до минимум риска от нарушаване на функционалността, решихме да подобрим нашето тестово покритие.

Чрез внедряване на тестове на множество нива (единица, интеграция и система) и гарантиране, че всички критични пътища са покрити, ние можем да преработим с увереност. Например въведохме модулни тестове за всички нови методи, извлечени по време на рефакторинг. Използвахме JUnit, популярна тестова рамка за Java.

@Test
public void testCalculateTotalPrice() {
  OrderProcessing op = new OrderProcessing();
  List<Book> books = Arrays.asList(new Book("Book1", BigDecimal.valueOf(10)), new Book("Book2", BigDecimal.valueOf(20)));
  
  BigDecimal totalPrice = op.calculateTotalPrice(books);
  
  assertEquals(BigDecimal.valueOf(30), totalPrice);
}

Заключение

След внедряването на тези и други промени успяхме да намалим свързването, да премахнем миризмите на кода и да подобрим тестовото покритие в нашия проект, което значително подобри поддържаемостта и разширяемостта на кодовата база.

Рефакторингът може да отнеме много време и дори плашещо, когато се занимавате с големи проекти, но със систематичен подход, правилните инструменти и смелостта да подобрите покритието на теста, можете значително да подобрите качеството на вашия код. Запомнете, „Рефакторингът не е събитие, а част от ежедневния ви процес на разработка.“

Не забравяйте, че рефакторингът е пътуване, а не дестинация. Изисква постоянно внимание и постепенни промени. Но с правилния подход и наличните инструменти, това е пътуване, което си струва да предприемете. Резултатът е кодова база, с която се работи по-лесно, по-здрава и по-податлива на по-нататъшно развитие.

Надяваме се, че този случай от практиката предоставя ценни прозрения за вашите собствени начинания за рефакторинг. Приятно кодиране!

  1. Рефакторинг в IntelliJ IDEA
  2. SonarQube
  3. Принципи SOLID в Java

Хареса ли ви четенето? Все още не сте среден член? Можете да подкрепите работата ми директно, като се регистрирате чрез моята реферална връзка тук. Става бързо, лесно и не струва нищо допълнително. Благодаря за подкрепата!