Tool methods: static vs DI

0

Witam,
Zastanawia mnie czy używanie metod statycznych dla drobnych metod toolowych jak np. specjalistyczne Spring Utils (generalnie pisanie własnego String Utils jest bez sensu, bo to już zostało napisane, jednak chodzi tylko o sam przykład).

Zastanawiam się nad dwoma podejściami:
a) dependency injection

@Inject
private SpringUtils sprUtils;

void metoda(String a) {
    String result = sprUtils.toolMethod(a);
    // pozostalt kod
}

b) metoda statyczna

void metoda(String a) {
    String result = SpringUtils .toolMethod(a);
    // pozostalt kod
}

Które podejście jest lepsze i dlaczego? Jedno i drugie mogę łatwo testować w przypadku prostych toolkitów. Jeżeli nie mam rzeczywistego obiektu, który przechowuje stan (tylko np. zwracam przerobiony String nie mam raczej też ryzyka wyścigów w przypadku dostępu przez wiele wątków, bo nie mam stanu).

Pozdrawiam,

0

A jak będziesz ten swój toolkit testował? Są co prawda cuda takie jak PowerMock który potrafi mockować statici, ale generalnie jest to trudniejsze niż testowanie zwykłych klas i obiektów.

0

Dopiero wchodzę w temat testów. Generalnie z samym toolkitem problemu nie ma, zwykłe asserty jak najabardziej działają. A dane w metodzie statycznej nie działają na obiekcie.

Wspominasz o mockowaniu metody statycznej. Zastanawia mnie czy to ma jakikolwiek sens w przypadki mojego toolkitu, ponieważ metody w nim zawarte są bardzo proste. I nie odwołują się do żadnych danych kontekstowych lub bazy danych: czysta logika, niezwiązana z obiektem.

Wydaje mi się, że prawidłowa odpowiedź to: metody statyczne są w porządku póki ich testowanie jest łatwe.

1

Czysta logika to jest właśnie to co najczęściej powoduje jakieś błędy ;) Nawet w prostej sytuacji, można zrobić jakiś trywialny błąd typu przesunięcie o jeden albo jakaś nadmiarowa spacja czy cokolwiek innego. Przetestowanie statycznego toolkitu nie stanowi problemu, ale problem stanowi przygotowanie testu tego obiektu w którym tego statica wykorzystasz. Testy powinny być odseparowane, a u Ciebie błąd w tym statycznym toolkicie spowoduje propagację błędu do wszystkich miejsc gdzie jest on wykorzystywany.

0

Dziękuję za wytłumaczenie. Po wstrzyknięciu obiektu np. przez CDI równie łatwo go używać jak statica. A problem nie występuje. Będę więc testował z użyciem Dependency Injection.

0

To nawet nie jest kwestia wstrzykiwania przez CDI czy jakikolwiek inny magiczny mechanizm. Nawet gdybyś musiał ręcznie stworzyć obiekt i przekazać go do konstruktora to warto było by zastosować takie podejście. Uzyskujesz dzięki temu odseparowane komponenty które w razie czego można np. podmienić. Jeżeli masz statyczną metodę, to Twoje komponenty są silnie sprzężone ze sobą i nie jesteś w stanie nic z tym zrobić bez refactoringu.

0

Dzięki.

0

Oj, widzę, że się wytworzyło środowisko antystaticowe ;)

Generalnie statyczne utilities wszelkiego rodzaju są OK, pod warunkiem że to rzeczywiście utilities.

Oto dlaczego:
Charakterystyczne cechy metod utilsowych:
a) niewielka złożoność
b) bardzo rzadko się zmieniają
c) pisane są "na bieżąco", wraz z implementacją bardziej skomplikowanej logiki

Jeśli w projekcie jest jakiś mechanizm DI to można się jeszcze kłócić - kwestia gustu, czy ktoś woli coś takiego:

public class BusinessServiceImpl implements BusinessService {
	
	@Inject
	private UtilsA utilsA;

	@Inject
	private UtilsB utilsB;

	@Inject
	private UtilsC utilsC;

	@Inject
	private UtilsD utilsD;

	@Override
	public BusinessResult get(BusinessData businessData){
		A a = utilsA.createA(businessData);
		B b = utilsB.createB(businessData);
		C c = utilsC.createC(businessData);
		D d = utilsD.createD(businessData);
		// do something
	}
} 

od:


public class BusinessServiceImpl implements BusinessService {

	@Override
	public BusinessResult get(BusinessData businessData){
		A a = UtilsA.createA(businessData);
		B b = UtilsB.createB(businessData);
		C c = UtilsC.createC(businessData);
		D d = UtilsD.createD(businessData);
		// do something
	}
} 

Moim zdaniem to drugie jest czytelniejsze i dlatego wolę to stosować. Jeśli tylko ludzie trzymają się tego, żeby tylko metody utilsowe nie były złożone oraz żeby zmiana biznesowa nie powodowała zmiany w takiej metodzie to nic złego się nie stanie.
Jak to testować? Jednostkowo. Za tym idzie oczywiście założenie, że w statycznym kontekście nie będzie niczego do mockowania (dane z zewnątrz - pliki, baza danych itp.). Proste wejście-wyjście, bez żadnych udziwnień.

Co do tworzenia instancji klasy takiego utils'a - dziwny pomysł. Niepotrzebnie nadmuchuje to metodę (jeśli utils'y są tworzone wewnątrz metody) lub obiekt (jeśli utils'y są tworzone wewnątrz klasy).

1

@wartek_no_log, jeżeli ktoś tworzy kod wymagający użycia kilku klas helperowych jak w twoim przykładzie to ja bym raczej się zastanowił czy aby na pewno nie można takiej klasy podzielić na kilka mniejszych.

Statyczne helpery są OK, ale pod warunkiem, że wzbogacają API biblioteki standardowej o metody, które tam powinny po prostu być. Przykładowo klasa Objects wprowadzona w Javie 7 powinna się tam znajdować "od zawsze" i dlatego powstały biblioteki typu commons-object, które łatały tą dziurę. Jeżeli twój helper rzeczywiście dostarcza tego typu funkcjonalności to niech będzie statyczny. Będzie można wykorzystać siłę import static by wprowadzić do kodu pewne nowe "metody kluczowe" (chodzi o metody spełniające podobną rolę jak metody z API biblioteki standardowej).

Jeżeli jednak w helperze zamykasz logikę powiązaną z domeną to warto używać ich jako niestatycznych. Z kilku powodów. Po pierwsze spada ich reużywalność, paradoksalnie jest to dobre ponieważ i tak zazwyczaj tego typu klas nie można użyć poza domeną. Przykładem niech będzie walidacja numeru identyfikującego klienta (jeżeli jest tworzony w jakiś specyficzny sposób). Po drugie łatwiej to przetestować. Helper taki dostarcza pewien element logiki biznesowej, a jej testy powinny być niezależne. Po trzecie taki helper zazwyczaj jest źle nazwaną klasą :) Serio. W trakcie rozwoju aplikacji na pewno trafisz na lepszą nazwę dla tej klasy niż coś w stylu FakturaHelper.

0

Panowie, mam następującą sytuację.

Mam klasę (JavaBean), w którym pojawiło się wstrzyknięcie utilka.

public class MojaKlasa {
 private int a;
 @Inject
 private Util util;

 // metody biznesowe

 // getters and setters 
}

Generalnie wcześniej był test, który robił to samo ale staticiem. W tej chwili test się wywala ze względu na NullPointerException (Weld nie może wstrzyknąć obiektu util), co jest zupełnie ok.

Pytania:

  1. Czy musze jakos niezaleznie dostarczyc ten obiekt w moim tescie (w rzeczywistosci jest to byly static, nie dziala na zadnych danych)? Jak najlepiej to zrobic (new i wstrzykniecie przez setter, ktory musialbym utworzyc: brzydko chyba)?

Znalazlem takie cos:
http://jglue.org/
Wydaje sie obiecujace w kontekscie testowania CDI. Warto?

  1. Czy musze przetestowac jego funkcjonalnosc (to co robi static) niezaleznie od pierwotnego testu? Jest to chyba niezbedne, jesli chce aby testy byly niezalezne, chociaz nie do konca podoba mi sie to.
0

Użyj Mockito, które wstrzyknie odpowiednio obiekt. Kod z wykorzystaniem JUnit4 (pisane z palaca więc pewno literówki w nazwach klas):

@RunWith(MockitoJunit4Runner.class)
public class MojaKlasaTest{
    @Mock
    private Util utilMock;

    @InjectMocks
    private MojaKlasa underTest;

   //....
}

Widzę, że mnie @Shalom wyprzedził z linkiem do dokumentacji to już nie będę tu tego powtarzać.

1 użytkowników online, w tym zalogowanych: 0, gości: 1