Usunięcie zależności w zastanym kodzie

Usunięcie zależności w zastanym kodzie

Wcześniejsze wpisy o zastanym kodzie
1. Jak zrozumieć legacy
2. Testy w legacy codzie

Usunięcie zależności

Często się zdarza, że zastany kod posiada zależności które utrudniają testowanie, są niewygodne. Mogą to być statyczne klasy, serwisy tworzone przez new, integracje z zewnętrznymi serwisami, komunikacja z bazą danych, elementy bibliotek i frameworków.  Na pierwszy rzut oka usunięcie zależności może być trudne ale dzięki temu wpisowi poznasz sposób jak to można osiągnąć.

Przykładowe legacy

    public Address updateStreetForUser(Long id,String street) {
        User user = DBUtils.getUser(id);
        Address address  = DBUtils.getAddress(user.getAddressId());
        address.setStreet(street);
        return DBUtils.saveAddress(address);
}

Powyższy metoda, zawiera operacje na bazie danych poprzez statyczną klasę DBUtils. Naszym celem będzie usunięcie zależności od tej klasy w kodzie.

Nowa klasa

Na początek oryginalną metodę pozostawiam bez żadnych zmian. Kopiuję tę metodę do oddzielnej metody lub klasy. To czy to będzie klasa lub metoda zależy od tego jak duża jest to metoda oraz ile metod prywatnych wywołuje. W wielu wypadkach legacy code jest duży i nie  spełnia SRP, więc nowa klasa nie jest w takim wypadku złym rozwiązaniem. W każdym razie najważniejsze jest aby obecnie oryginalnej metody jeszcze nie modyfikować.

Modyfikacja

Dla nowej klasy tworzę testy. Problematyczne wywołania opakowują innymi klasami i przekazuję je w konstruktorze. Przy okazji mogę wywołania pogrupować w odpowiednie serwisy. Dla powyższego przykładu będą to serwisy związane z userem oraz adresem.

@RequiredArgsConstructor
public class LegacyServiceWithoutDependency {

    private final UserRepository userRepository;
    private final AddressRepository addressRepository;

    public Address updateStreetForUser(Long id,String street) {
        User user = userRepository.getUser(id);
        Address address  = addressRepository.getAddress(user.getAddressId());
        address.setStreet(street);
        return addressRepository.saveAddress(address);
}

public class AddressRepository {
    public Address getAddress(Long addressId) {
      return  DBUtils.getAddress(addressId);
    }

    public Address saveAddress(Address address) {
        return DBUtils.saveAddress(address);
    }
}

public class UserRepository {
    public User getUser(Long id) {
        return DBUtils.getUser(id);
    }
}
usunięcie zalezności w kodzie
Usunięcie zależności w kodzie

DI

W legacy możesz nie mieć dostępu do frameworków z dependency injection. Ale nic nie stoi na przeszkodzie aby ręcznie je przygotować. Bazując na springowych klasach @Configuration możesz stworzyć klasę konfiguracyjną, która tworzy wszystkie Twoje klasy. Podobny patent Uncle Bob zawarł w swojej książce czysta architektura.

public class Configuration {

    public LegacyServiceWithoutDependency legacyServiceWithoutDependency() {
        return new LegacyServiceWithoutDependency(new UserRepository(), new AddressRepository());
    }
}

Zastąpienie oryginału

W końcu możesz zmodyfikować oryginalną metodę. Istnieje kilka możliwości z jakich możesz skorzystać. Pierwszą z nich jest usunięcie starego kodu i bezpośrednie wywoływanie Twojej nowej klasy. Inną opcją jest wywołanie poprzez delegację z oryginalnej metody nowego kodu z usuniętymi zależnościami.

  public Address updateStreetForUser(Long id,String street) {

        Configuration configuration = new Configuration();
        LegacyServiceWithoutDependency legacyServiceWithoutDependency = configuration.legacyServiceWithoutDependency();

        return legacyServiceWithoutDependency.updateStreetForUser(id, street);
}

Kod bez zależńości

Sposób który przedstawiłem nie jest zbyt skomplikowany a dzięki niemu łatwo można sprawić, że kod będzie bardziej testowalny. Innym plusem tego refactoringu jest to, że nowe klasy można później reużywać, grupować według funkcjonalności a także refaktoryzować. Wszystkie te zmiany sprawią, że zastany kod nie będzie straszny a łatwiejszy do ogarnięcia, mimo zależności.