Wstrzykiwanie zależności a testy jednostkowe - złoty środek

0
Wibowit napisał(a):

Dyscyplina się nie skaluje. Jak zauważył @jarekr000000 kontener DI ułatwia mnożenie zależności. Prawa Murphy'ego działają - jeżeli istnieje nieznikoma szansa, że zaplątamy się w sieć zależności to to się stanie.

No dobrze, ale ktoś nad tym stadkiem seniorów powinien jakoś panować, review robić. Sonar powinien liczyć powiązania i alarmować, gdy coś się rozrasta. No, i najważniejsza rzecz - edukacja.

Ojejciu, ale żeś pojechał. Może pokażesz przewagę tego wspaniałego C# nad Javą?

Widzę, że niektórym się płyta zacięła. :P Nie twierdzę, że C# jest wspaniały, w ogóle nic o nim nie pisałem.

Nie widzę specjalnie jak C# ułatwia przekazywanie zależności porównując do Javy.

W żaden sposób.

Niemniej jednak, @jarekr000000 sporo tutaj na Javę i jej frameworki narzekał - tylko do tego się odnosiłem. Coś ewidentnie w ekosystemie Javy jest nie tak.
Z drugiej strony, w .NET mamy zdaje się port Springa i jeszcze parę kontenerów, które wspierają konfigurację poprzez XML. Tylko tu znowu problemem są ludzie, którzy piszą (albo każą innym pisać) te XMLe. A drugim problemem są bezwolni programiści, którzy robią to, co im jakiś lead/architekt każe, kompletnie bezrefleksyjnie i bez próby jakiegokolwiek zakwestionowania idiotyczności niektórych pomysłów.

Ponadto w Scali są takie bajery jak argumenty implicit (w Javie wszystkie argumenty są explicit) czy miksowanie traitów, które mogą zawierać stan (interfejsy w Javie nie mogą zawierać stanu) co razem daje duże możliwości na stworzenie zakręconej hierarchii fixtures (zamiast modułów w kontenerach DI) i "wstrzykiwania" parametrów. Zaletą używania argumentów implicit i miksowania traitów nad używaniem kontenera DI jest to, że argumenty implicit i traity to podstawowe elementy języka i są w pełni wspierane przez IDE i kompilator. Elementy są wrzucane w konstruktory na etapie kompilacji (a więc kompilacja się sypnie jak nie będzie żadnego argumentu implicit w zasięgu do dobrania przez kompilator), a sam kod poddaje się dobrze statycznej analizie, więc mamy wszelkie udogodnienia od IDE jak nawigacja po kodzie (skąd się co bierze - IntelliJ pokazuje lokalizację argumentów implicit), itd

Wydaje mi się, że doskonale w ten sposób potwierdzasz moje stwierdzenie, że Java to podjęzyk. Może nie przyznajesz tego wprost, ale to potwierdzasz.

jarekr000000 napisał(a):

Dzięki za docenienie, staram się :-). Wiele z tego co się nauczyłem zawdzięczam innym ludziom, którzy robili taką niepotrzebną robotę - staram się to powoli odpracowywać (w końcu mam czas).

Tylko, że oni robili to w godzinach pracy. No, ale ok. Nie moja sprawa.

Nie wiem co to za bilblioteki automockujące - wpisałem sobie, żeby się temu przyjrzeć (biblioteki C# już nieraz mnie zaskoczyły pomysłami), ale może podasz pomocny konkret?

Chodzi mi konkretnie o to: https://github.com/AutoFixture/AutoFixture

Raz, że potrafi dostarczyć losowe (bądź nie) dane do testów, dwa że dostarcza działającą implementację testowanej klasy z wstrzykniętymi wszystkim zależnościami, które sobie sam wyciąga i tworzy. A jeśli zależności są interfejsami, to może użyć frameworka mockującego do automatycznego zamockowania tychże, przy czym zamockowane automatycznie metody będą zwracały domyślnie losowe dane. (Ale oczywiście można wstrzyknąć zwykłego mocka ze sztywnym wynikiem, jeśli tego potrzebujemy.)

Głównym problemem przy testowaniu jest aranżacja testów. Jeśli testowany kod korzysta z zależności od których wymaga
zwrócenia wyników, aby przejść dalej, zawiera jakąś ifologię, to w efekcie mimo, że chcemy przetestować najprostszą ścieżkę musimy najpierw napisać wiele linii konfigurujących mocki - tylko po to, żeby się nie wywaliły na jakimś NullExceptionie. Co więcej, jeśli zmienimy sobie sygnaturę naszej klasy, to testy nam się przestaną kompilować.
AutoFixture pozwala tego uniknąć czyniąc kod testowy znacznie przyjemniejszym w utrzymaniu.

Ogólnie to świetna rzecz - zarówno dla młodych projektów, jak i dla jakichś legacy testów, które można dzięki temu znacznie skrócić.

Tak, oczywiście lepszym rozwiązaniem jest mieć mniej zależności i ja się z tym już w tym wątku zgodziłem - tylko nie zawsze tak się da - zwykle im wyżej klasa leży w hierarchii, albo im bardziej jest na zewnątrz, tym tych zależności trudniej uniknąć.

Bo, w sumie jak są takie automoki cudowne - to ja w sumie zrobił bym tą jedną linijką Automoka i puścił na produkcję.

Jeśli nie przeszkadzają Ci losowe dane, to czemu nie?

Skoro jest na tyle dobry żeby zastąpić mockowaną klasę w testach - to powinien być też dobry na produkcję (albo raczej się nie rozumiemy - bo naprawdę nie wyobrażasz sobie co ludzie potrafią zamockować (niepotrzebnie)).

Być może nie... ja już wiele razy myślałem, że wszystko w życiu widziałem, zwykle po miesiącu w nowej pracy stwierdzałem, że nie miałem racji.
Np. ostatnio odkryłem, że ludzie potrafią zrobić interfejs dla DTO, bo potrzebują mocka w testach, a zwykłej implementacji nie mogą użyć, bo w konstruktorze czytają z konkretnego pliku. (Dobrze, że to programiści, a nie np. murarze, bo budując dom zapewne postawiliby pełen sześcian z cegieł, a potem wykuwali w nim pokoje.)

(Btw: w javie zaimplementowałem kiedyś mały serwisik na Mockach - ale to było po piwie, miałem też pomysł, żeby to pociągnąć dalej i testy dla odmiany zrobić na konkretnych klasach (ale skończyło mi się piwo)).

Ja kiedyś na mockach zaimplementowałem kontekst użytkownika - wiedziałem, że na razie będzie tylko jeden użytkownik, więc zwracałem na sztywno jego ID i nazwę, i mogłem przełożyć w czasie pracę nad właściwą implementacją.
Dodatkowym plusem było to, że było to 100% bezpieczne rozwiązanie. ;)

Trochę masz racji, ale ja uważam, że Java nie jet aż tak zła - jak złe jest jej "enerprisey" community. Goście utkwili w roku 2006 i nie potrafią się z niego wydostać...

A co gorsza zarazili tych od .NETa.

0

Chodzi mi konkretnie o to: https://github.com/AutoFixture/AutoFixture

Raz, że potrafi dostarczyć losowe (bądź nie) dane do testów, dwa że dostarcza działającą implementację testowanej klasy z wstrzykniętymi wszystkim zależnościami, które sobie sam wyciąga i tworzy. A jeśli zależności są interfejsami, to może użyć frameworka mockującego do automatycznego zamockowania tychże, przy czym zamockowane automatycznie metody będą zwracały domyślnie losowe dane. (Ale oczywiście można wstrzyknąć zwykłego mocka ze sztywnym wynikiem, jeśli tego potrzebujemy.)

Głównym problemem przy testowaniu jest aranżacja testów. Jeśli testowany kod korzysta z zależności od których wymaga
zwrócenia wyników, aby przejść dalej, zawiera jakąś ifologię, to w efekcie mimo, że chcemy przetestować najprostszą ścieżkę musimy najpierw napisać wiele linii konfigurujących mocki - tylko po to, żeby się nie wywaliły na jakimś NullExceptionie. Co więcej, jeśli zmienimy sobie sygnaturę naszej klasy, to testy nam się przestaną kompilować.
AutoFixture pozwala tego uniknąć czyniąc kod testowy znacznie przyjemniejszym w utrzymaniu.

Połączenie property-based testing z głębokimi mockami brzmi jak dodatkowa magia.

W Scali są biblioteki jak np: https://github.com/rickynils/scalacheck/blob/master/doc/UserGuide.md Można sobie generować losowe dane (a nawet losowe hierarchie klas), ale nie ma żadnych mocków. Mocki zbyt mocno trącą Javą i są zbyt mało potrzebne, by Scalowcy się nimi jakoś mocno przejmowali.

Wydaje mi się, że doskonale w ten sposób potwierdzasz moje stwierdzenie, że Java to podjęzyk. Może nie przyznajesz tego wprost, ale to potwierdzasz.

Moim zdaniem zarówno Java jak i C# są gorsze od Scali. A C# bezmyślnie kopiuje Javę. Nie dość, że C# powstał jako totalny bezczelny klon Javy (MS nie pokusił się nawet o wyprostowanie składni) to C#-owe frameworki to głównie klony tych z Javy. Okazuje się również że podejście do testowania jest podobne - skoro Javowcy sobie namiętnie mockują, to C#-owcy muszą to zmałpować.

Niektóre ze sposobów testowania w Scali: http://engineering.monsanto.com/2015/07/28/avoiding-mocks/

1

Moim zdaniem zarówno Java jak i C# są gorsze od Scali. A C# bezmyślnie kopiuje Javę.

A ja twierdzę że dopóki piszesz na windowsa/chcesz namiastkę funkcyjności F# jest lepszy od Scali, to są tylko przekonania i zawsze znajdziesz sposób żeby pokazać wyższość jednego języka nad drugim - można przedstawiać fakty ale problem w tym że korzystne fakty są po obu stronach. Zresztą co w tym dziwnego że Scala jest lepsza od javy i c# - jakiś postęp chyba musi być.

2
Wibowit napisał(a):

A C# bezmyślnie kopiuje Javę.

Oczywiście, bo to przecież C# dorobił się lambd 7 lat po Javie, a nie odwrotnie :D

0
some_ONE napisał(a):
Wibowit napisał(a):

A C# bezmyślnie kopiuje Javę.

Oczywiście, bo to przecież C# dorobił się lambd 7 lat po Javie, a nie odwrotnie :D

No właśnie za to kochamy c# - to właściwie kopia javy ale ma parę rzeczy które strasznie ułatwiają pracę. Każda wielka korporacja musi mieć swojego klona javy (google jeszcze robi coś w dart-cie?):D

0
some_ONE napisał(a):
Wibowit napisał(a):

A C# bezmyślnie kopiuje Javę.

Oczywiście, bo to przecież C# dorobił się lambd 7 lat po Javie, a nie odwrotnie :D

Gdyby Java skopiowała lambdy z C# to by to nie trwało 7 lat. Dyskusja nad lambdami była bardzo długa:
https://blogs.oracle.com/mr/entry/closures
http://openjdk.java.net/projects/lambda/

MS prawdopodobnie nie przejmował się zbytnio opinią społeczności i po prostu wrzucił lambdy.

Czy to lepiej czy gorzej ciężko stwierdzić. Trzeba też wziąć pod uwagę to, że Sun napotkał problemy finansowe i został przejęty przez Oracle.

A same lambdy w Javie są na tyle sprytne, że mogą reprezentować dowolną klasę z jedną metodą abstrakcyjną. Nie wiem czy tak jest w C#.

1
some_ONE napisał(a):
Wibowit napisał(a):

A C# bezmyślnie kopiuje Javę.

Oczywiście, bo to przecież C# dorobił się lambd 7 lat po Javie, a nie odwrotnie :D

EE tam Lambdy, gdyby nie C# to nie wiem kiedy Java dorobiła się generyków.
Tak, przed wersją 1.5 nie miała (generyków/templatów - WTF!) i dopiero postępująca ucieczka programistów do C# przekonała Suna, że może coś by warto zrobić.

0

Eee, na pewno? Popatrzyłem na Wiki i:

  • Java 5 weszła w październiku 2004,
  • C# 2.0 wszedł w listopadzie 2005,
    a więc C# miał generyki rok później po Javie.
0
Wibowit napisał(a):

Eee, na pewno? Popatrzyłem na Wiki i:

  • Java 5 weszła w październiku 2004,
  • C# 2.0 wszedł w listopadzie 2005,
    a więc C# miał generyki rok później po Javie.

Tak, officjalnie faktycznie. Choć jak się bardziej pogrzebie to sprawa jest bardziej skomplikowana,
bo używałem generyków w Javie jeszcze przed wydaniem 1.5 (były odpowiednie kompilatory)....
A robiłem to dlatego, że kumple od .NET już się tym bawili i zazdrościłem :-)
Możliwe, że też mieli coś przed releasem - teraz ciężko będzie dojść - ale łatwo w necie znaleźć porównania generyków C# i Java 1.5 jeszcze z roku 2004..

0

Dojechałem na wieczornej sesji do odcinka 7
https://github.com/javaFunAgain/magic_service_story/tree/70_CLEANING

Gdzie rozprawiam się z głupim equalsem - a przede wszystkim z głupimi testami.

I tutaj zasadnicze pytanie do autora wątku @Shalom :
Czy udzieliłem Ci już odpowiedzi na twoje pytania?
Czy też masz jeszcze jakieś wątpliwości, w takim razie jest kod i proszę o konkretne tych wątpliwości zaadresowanie.
(tam jeszcze można sporo pocisnąć...).

0

@jarekr000000 hmm no tylko że tak na dobrą sprawę to moim zdaniem nic się nie zmieniło. Tzn mamy minimalną zmianę bo część zależności wpadła do jednej klasy agregującej a część do innej - to jest dość standardowa praktyka. Tylko że zdarza mi się jednak, ze jest niepraktyczna, bo wymaga tworzenia bardzo abstrakcyjnych bytów tylko po to żeby upakować kilka zależności do jednego worka. Ale faktycznie jest to pewna droga, chociaż nie eliminuje problemu z zależnościami jako takiego, tylko go ukrywa ;) A zależności jak były tak są.
Kwestia Either szczerze mówiąc trochę do mnie nie przemawia. Jestem w stanie sobie wyobrazić małe fragmenty kodu gdzie jest to wygodne rozwiązanie, podobnie jak użycie Optional, ale tutaj wystąpiło cudowne rozmnożenie, bo nagle wszędzie pojawił się ten Either. Co więcej poczyniłeś tutaj dość wygodne założenie że jednym takim enumem możemy sobie zamodelować wszystkie potencjale błędy, że nie mają żadnej hierarchii i że zostaną obsłużone w jednym miejscu :)
Największy zarzut jaki mam dlatego Either jest taki, że odpada nam wygodne hierarchiczne matchowanie wyjątków - z jednej strony wyjątki pewnego typu chcemy obsłużyć w podobny sposób (np. jak mamy network problem to leci sms do admina) ale jednocześnie chcemy mieć konkretną informacje o problemie.

0
Shalom napisał(a):

Ale faktycznie jest to pewna droga, chociaż nie eliminuje problemu z zależnościami jako takiego, tylko go ukrywa ;) A zależności jak były tak są.

A myślałem, że o to Ci chodziło...
Można oczywiście kod przepisać na kilka linijek, bez zależności. A to co chciałem pokazać - jak mając potencjalnie wiele zależności da się nimi zarządzać przez odpowiednie poskładanie (i unikanie potworków). W praktyce zamiast mieć jedną klasę z 6-ma zależnościami. Mam 3-4, z których każda ma 2 max 3. (w zasadzie tworzą drzewko). I tym sie da łatwo zarządzać, można sobie konkretne pośrednie implementacje (pomocnicze) potworzyć. Najważniejsze: można łatwo testowac - bez moków - a jak coś trzeba jakiś komponent wymienić to nadal kłopotu nie ma.

Kwestia Either szczerze mówiąc trochę do mnie nie przemawia. Jestem w stanie sobie wyobrazić małe fragmenty kodu gdzie jest to wygodne rozwiązanie, podobnie jak użycie Optional, ale tutaj wystąpiło cudowne rozmnożenie, bo nagle wszędzie pojawił się ten Either. Co więcej poczyniłeś tutaj dość wygodne założenie że jednym takim enumem możemy sobie zamodelować wszystkie potencjale błędy, że nie mają żadnej hierarchii i że zostaną obsłużone w jednym miejscu :)
Największy zarzut jaki mam dlatego Either jest taki, że odpada nam wygodne hierarchiczne matchowanie wyjątków - z jednej strony wyjątki pewnego typu chcemy obsłużyć w podobny sposób (np. jak mamy network problem to leci sms do admina) ale jednocześnie chcemy mieć konkretną informacje o problemie.

No to w następnym odcinku - hierarchiczne matchowanie na Either ze szczegółami błędów.

0

@jarekr000000 skoro masz dobre rozwiązanie na wszystko to jak rozwiązać problem singletona i jego testowania? Użyć normalnego singletona i testować tylko integracyjnie, użyć normalnego obiektu i przewalać go przez dziesiątki obiektów żeby zachować jedną instancje a może użyć @Singleton z Guice (moje aktualne rozwiązanie)? A może jest coś jeszcze lepszego?

0

w samym temacie juz chyba zostalo powiedziane wszystko. kiedys sie w to bawilam, teraz juz nie unit testuje/mockuje takich klas bez wlasciwej logiki, szkoda zycia ;) testy integracyjne za to odpowiadaja.

@tdudzik mysle ze singletony w ogole nie powinny byc tworzone poza stosowaniem null object pattern. po prostu przekazuj tego singletona-wanna-be tak jak kazda inna zaleznosc np. przez konstruktor

0

@katelx tylko że bez DI frameworku to oznacza czasem przewalanie jednego obiektu że dziesięc innych

0
tdudzik napisał(a):

@katelx tylko że bez DI frameworku to oznacza czasem przewalanie jednego obiektu że dziesięc innych

To znaczy, ze cos bez sensu jest ten twoj Singleton - po co tyle klas ma o nim wiedziec?

Moze podaj przyklad i naprawim.

(W Scali jest cos takiego jak implicit- przez co przewalanie takich singletonow jest trywialne, sprawdzane kompilatorem a do tego ciagle testowalne. .. ale nadal jest to błąd ... ).

2

Ale po co przewalać coś przez dziesięć klas? Przecież umówiliśmy się, że wstrzykujemy zależności ręcznie przez konstruktor. Jeśli od czegoś bezpośrednio nie zależymy to nie wstawiamy tego do konstruktora. Składanie zależności jest poza klasą, która korzysta z tych zależności.

Przykład ręcznego wstrzykiwania zależności przez konstruktor:

object Testing {
  def main(args: Array[String]): Unit = {
    val application = ProductionModule.top
    application.start()
  }

  class Top(middle: Middle) {
    def start(): Unit = ???
  }

  class Middle(bottom: Bottom)

  class Bottom(deepDependency: DeepDependency)

  class DeepDependency

  // to nam zastępuje konfigurację z kontenera DI
  object ProductionModule {
    val deepDependency = new DeepDependency
    val bottom = new Bottom(deepDependency)
    val middle = new Middle(bottom)
    val top = new Top(middle)
  }
}

Gdybyśmy wyrzucili ręczne wstrzykiwanie zależności i powrócili do topornego modelu gdzie w jednej klasie miesza się tworzenie zależności i ich używanie to kod wyglądałby sporo gorzej:

object Testing {
  def main(args: Array[String]): Unit = {
    val deepDependency = new DeepDependency
    val application = new Top(deepDependency)
    application.start()
  }

  class Top(deepDependency: DeepDependency) {
    private val middle = new Middle(deepDependency)
    
    def start(): Unit = ???
  }

  class Middle(deepDependency: DeepDependency) {
    private val bottom = new Bottom(deepDependency)
  }

  class Bottom(deepDependency: DeepDependency)

  class DeepDependency
}

Oczywiście w tym przypadku nie widać zysku w postaci krótszego kodu (pierwszy jest tylko minimalnie krótszy biorąc pod uwagę niebiałe znaki), ale tutaj jest mało warstw i prosta hierarchia zależności. Widać jednak, że deepDependency jest w kółko powtarzane i jeśli tego typu głęboko schowanych zależności jest dużo to będzie dużo powtórzeń. Nie widać też czy parametry konstruktora to bezpośrednie zależności czy może coś z czego korzysta się gdzieś głębiej w hierarchii.

1
tdudzik napisał(a):

@katelx tylko że bez DI frameworku to oznacza czasem przewalanie jednego obiektu że dziesięc innych
praktycznie nie uzywam frameworkow DI i nie doskwiera mi ten problem przerzucania czegos przez 10 klas, moze w projektach webowych praktyki sa inne ale jak dla mnie koniecznosc przerzucania instancji przez 10 innych i rozwiazywanie tego problemu globalem nie brzmi za dobrze.

0

Pisaliscie o mockowaniu.

Co myslicie o mockist london school? https://codurance.com/2015/05/12/does-tdd-lead-to-good-design/

0
jarekr000000 napisał(a):

A to co chciałem pokazać - jak mając potencjalnie wiele zależności da się nimi zarządzać przez odpowiednie poskładanie (i unikanie potworków). W praktyce zamiast mieć jedną klasę z 6-ma zależnościami. Mam 3-4, z których każda ma 2 max 3. (w zasadzie tworzą drzewko).

Tylko to są oswojone (stosowane) i znane szerszej rzeszy potworki, a drzewko jest customowe i wymagające dodatkowych zasobów mentalno-analitycznych (pytanie retoryczne: łatwiej ogarnąć co robią 4 klasy, czy 8 linijek z adnotacjami?). Przekonaj biznes, że warto inwestować w koncepcje wujka Jarka, zamiast w Springa - osobiście trzymam kciuki.

0

Przekonaj biznes, że warto inwestować w koncepcje wujka Jarka, zamiast w Springa - osobiście trzymam kciuki.

Jaki biznes ma przekonywać? Strona biznesowa projektu nie powinna się mieszać w sprawy techniczne, bo się na tym nie znają. Natomiast całego świata IT nie trzeba przekonywać do swojego stylu programowania, żeby go stosować. Wystarczy się dogadać z kolegami z zespołu.

Tylko to są oswojone (stosowane) i znane szerszej rzeszy potworki, a drzewko jest customowe i wymagające dodatkowych zasobów mentalno-analitycznych (pytanie retoryczne: łatwiej ogarnąć co robią 4 klasy, czy 8 linijek z adnotacjami?).

Nie wiem jak jest w Javce, ale w Scali przy korzystaniu z np Google Guice widzę same wady:

  • brak nawigacji po kodzie - mapa zależności jest tworzona w czasie wykonania, więc standardowe wsparcie od IDE oparte na statycznej analizie kodu nie ma szans działać,
  • więcej kodu - bo trzeba robić jakieś bindy, providery i inne bajery zamiast po prostu wywołać konstruktor,
  • więcej nauki - działania kontenera IoC trzeba się nauczyć - samo IoC może nie jest trudne, ale każda biblioteka wymaga nauki, by wykorzystywać ją efektywnie,

Jak dla mnie kontenery IoC są jedynie złagodzeniem podstawowego problemu, którym jest rozwlekłość Javy (oraz C# i podobnych). Nie ma konstruktora głównego jak w Scali, więc bez wstrzykiwania za pomocą refleksji trzeba pisać nadmiarowy kod do przerzucania zależności z konstruktora do pól prywatnych.

Aktualizacja:
Chociaż jak patrzę na https://github.com/google/guice/wiki/Motivation#dependency-injection-with-guice to widzę, że Google preferuje i tak wstrzykiwanie przez konstruktor + przepychanie do prywatnych finalnych pól. Dalej jest boilerplate.

W przytoczonym linku jest taki tekst:

Unfortunately, now the clients of BillingService need to lookup its dependencies. We can fix some of these by applying the pattern again! Classes that depend on it can accept a BillingService in their constructor. For top-level classes, it's useful to have a framework. Otherwise you'll need to construct dependencies recursively when you need to use a service:

Nie podają jednak argumentu w postaci porównania na przykładzie. Tzn nie widzę co by tu dawał Guice. Przecież zależności mam w jednym miejscu, po co je wielokrotnie tworzyć w wielu miejscach?

3
bobojak napisał(a):

Tylko to są oswojone (stosowane) i znane szerszej rzeszy potworki, a drzewko jest customowe i wymagające dodatkowych zasobów mentalno-analitycznych (pytanie retoryczne: łatwiej ogarnąć co robią 4 klasy, czy 8 linijek z adnotacjami?). Przekonaj biznes, że warto inwestować w koncepcje wujka Jarka, zamiast w Springa - osobiście trzymam kciuki.

(Sorry coś mnie wylogowało i zostałem Krzywym Szczurem)

Co do koncepcji wujka Jarka.... Ciesze się, że budujesz moją legende, ale po pierwsze - ja się tym sam całkiem dobrze zajmuje, po drugie programowania w Javie nie wymyśliłem niestety... A to jedyne co staram się wywalczyć - używajcie ludzie Javy skoro już w niej piszecie. Ma pewne braki - ale nie aż takie, żeby w każdy projekt ładować framework do DI (i podobne).

Btw. biznes i kolegow łatwo przekonać - po prostu wystarczy pisać kod - albo lepiej: kasować. Ja działam zwykle metodą oportunistyczną - czekam na narzek: "nasze testy sa do bani", "połowa kodu to mappery setterów do getterów" itp. I wtedy zaczynam wyciąganie kawałków projektu z kupy (na ogół przez wyrzucenie kilku warstw, frameworków itp.).

To z czym jest problem to są tzw. starzy architekci (nie zawsze sa starzy faktycznie, czasem sa młodsi ode mnie). Ale mają podejście - tak się nauczyłem programować 5-10 lat temu -więc musi to być dobre - nie warto czego innego próbować. Kujowo, ale stabilnie.
Nie rozumiem czemu @rafalitheo z innego wątku takim nie został - pasuje mentalnie idealnie. Raz się nauczysz czegoś i ignorujesz przez następne x lat wszystkie nowości. Da się? - pewnie, że się da.

W różnych firmach z tym idzie różnie - ogólnie okazuje się, że im bardziej tacy architekci sa ograniczani przez biznes, który patrzy na kasę, tym lepiej. -Jak możesz pokazać demko zrobione przez 2 dni w jakiejś nowej technologii, które robi to co koledzy w pokoju obok przez kilka miesięcy implementują... do tego wygląda dużo lepiej, i szybciej - to sprawa jest pozamiatana.

Ale są naprawdę duże firmy gdzie są architekci co przebywają bardzo wysoko ponad chmurami (tam gdzie nie ma już atmosfery), tak wysoko, że biznes tam nie dolatuje. Wtedy albo pozostaje ich ignorować i robić swoje (brak tlenu na pewnych wysokościach zaburza percepcję - więc pewnie nie zauważą). Albo się kłócić (na ogół bez sensu). Albo zmienić firmę - życie jest za krótkie, żeby sie pierniczyć z gościami uprawiającymi EnterprizeFizzBuzz.

1

@Wibowit chyba nie bardzo rozumiałeś to co podlinkowałeś. Zaletą DI jest to że w ogóle nie musisz się zastanawiać skąd sie dana zależność bierze ani gdzie i jak jest tworzona. Po prostu deklarujesz że jest potrzebna i tyle. Oczywiście dla banalnych przypadków silnie powiązanych obiektów nie ma to żadnego znaczenia, bo wszystko jest i tak tworzone w tym samym miejscu. Ale wyobraź sobie sytuację, że masz np. jakiś serwis który realizuje funkcjonalność potrzebną w wielu miejscach, ot choćby coś w stylu resolver ścieżek względnych/bezwzględnych.
Załóżmy na chwile że nie da się z niego zrobić statycznego utila bo potrzebuje mieć synchronizowany dostęp, ponieważ w czasie wykonania ścieżka bazowa może ulegać zmianie i chcemy jednak mieć możliwość subklasowania go kiedyś. Załóżmy też że nie możemy tego obiektu tworzyć on the fly, bo on potrzebuje dostępu do konfiguracji aplikacji.
Nie trudno zauważyć że ta funkcjonalność może być potrzebna w wielu miejscach (ot choćby podczas czytania czy pisania do plików przez nasz program). I teraz jeśli nagle w trakcie pisania uświadomisz sobie że jest ci to potrzebne to w przypadku DI dodajesz sobie @Inject i piszesz dalej, podobnie w przypadku jakiegoś ServiceLocatora, po prostu pobierasz co ci potrzebne. A w przypadku ręcznego budowania grafu obiektów? Musisz sprawdzić kto i gdzie tworzy obiekt który teraz piszesz, oraz kto i gdzie tworzy nasz PathResolver. I może się okazać że jesteś teraz gdzieś na dole grafu a niestety nikt w tym poddrzewie nie korzystał z path resolvera i musisz go teraz "przepchać" przez X poziomów. Czyli generalnie robisz dokładnie to samo co DI, tylko że produkujesz przy tym kupę zbędnego kodu.

Analogiczny problem pojawi się dla dowolnej funkcjonalności tego typu - np. logger czy transaction manager. Możesz sobie je oczywiscie pobrać z jakiejś statycznej metody (wiele osób tak robi dla Loggera), ale wtedy automatycznie spadają na ciebie wszystkie problemy związane ze staticami jak choćby brak możliwości zrobienia override. @Wibowit skąd więc bierzesz sobie loggera?

(wszystkie wspomniane wyżej klasy i obiekty są wymysłem autora i jakakolwiek zbieżność z prawdziwym kodem jest przypadkowa i niezamierzona, to tak na wypadek jakby @jarekr000000 postanowił naprawiać przykładowe klasy zamiast skupić się na omawianej kwestii :P)

0

Znowu wracamy do wyimaginowanego problemu przepychania zależności przez X poziomów podczas gdy umówiliśmy się, że korzystamy ze wstrzykiwania zależności, ale bez frameworka DI. Dosłownie kilka postów wcześniej podałem rozwiązanie: http://4programmers.net/Forum/Inzynieria_oprogramowania/276798-wstrzykiwanie_zaleznosci_a_testy_jednostkowe_-_zloty_srodek?p=1292297#id1292297
Dlaczego niby nie miałoby to działać i w tym przypadku?

@Wibowit chyba nie bardzo rozumiałeś to co podlinkowałeś. Zaletą DI jest to że w ogóle nie musisz się zastanawiać skąd sie dana zależność bierze ani gdzie i jak jest tworzona. Po prostu deklarujesz że jest potrzebna i tyle.

Ja jednak lubię widzieć graf zależności podany w przejrzysty sposób. Konfiguracja Google Guice przejrzysta na pewno nie jest. Adnotacje różnego typu (np Named czy własne adnotacje) rozsiane po całym projekcie to efektywnie rozsmarowanie konfiguracji po całym projekcie, a mieliśmy mieć przecież oddzielenie składania zależności od korzystania z nich.

Co do loggera to:

  • nie wstrzykuję go w żaden sposób, bo nie widzę potrzeby,
  • gdybym miał potrzebę to można zawsze zrobić implicit parametr konstruktora:
class Logic(dependency: Dependency)(implicit loggerFactory: LoggerFactory)

Wtedy w klasie składającej zależności mam jedną deklarację loggerFactory i nie muszę jej powtarzać przy tworzeniu elementów grafu zależności (bo kompilator sam sobie dopasowuje implicity).

  • do tego można domieszać traita (co zasugerował jarekr000000), a w nim mieć gotowego loggera z powyższej fabryki,
  • ewentualnie zamiast dodatkowych parametrów konstruktora można przyhaczyć i wykorzystać Thread Local Storage - przed konstruowaniem obiektu ustawiamy w TLS naszą LoggerFactory i we wspólnym traicie tworzymy lazy vala z loggerem,

Oto przykład:

import java.util.logging.Logger

object Loggering {

  def main(args: Array[String]): Unit = {
    ProductionModule.main
  }

  object ProductionModule {
    Logging.loggerFactory.set(cls => Logger.getLogger(cls.getName))
    // tu można napykać jeszcze więcej elementów grafu zależności
    // z których każdy korzysta z loggera
    val main = new Logic
  }

  class Logic extends Logging {
    logger.info("hey")
  }

  // myślę, że ten trait mógłby być zaimplementowany spokojnie też w Javie
  // jako interfejs z metodą domyślną
  // narzutem byłoby jednak zapisywanie loggera do pola w konstruktorzy klasy
  // bo Javowe interfejsy nie pozwalają na tworzenie pól (tylko metod)
  trait Logging {
    val logger: Logger = Logging.loggerFactory.get().apply(this.getClass)
  }

  object Logging {
    val loggerFactory: ThreadLocal[Class[_] => Logger] =
      new ThreadLocal[(Class[_]) => Logger]()
  }
}

Jedna, jak już napisałem, praktycznie nie testuję logowania, więc go też nie wstrzykuję. Jeżeli logowanie w pewnym miejscu jest ważne, bo służy do audytu to moim zdaniem bardzo dobrym rozwiązaniem jest: https://github.com/lancewalton/treelog

0
Wibowit napisał(a):

Znowu wracamy do wyimaginowanego problemu przepychania zależności przez X poziomów podczas gdy umówiliśmy się, że korzystamy ze wstrzykiwania zależności, ale bez frameworka DI. Dosłownie kilka postów wcześniej podałem rozwiązanie: http://4programmers.net/Forum/Inzynieria_oprogramowania/276798-wstrzykiwanie_zaleznosci_a_testy_jednostkowe_-_zloty_srodek?p=1292297#id1292297
Dlaczego niby nie miałoby to działać i w tym przypadku?

To proste - bo bardzo potrzeba znaleźć zastosowanie dla DI (skoro jest dorzucony do pom.xml (czy build.sbt) - głupio żeby się marnował). A żeby tekie znaleźć to trzeba bardzo nakomplikować sobie swój
problem. Faktycznie! Jeśli będziesz sobie odpowiednio długo komplikował sobie swój design - to w końcu też sie okaże, że będziesz potrzebował tego GUICE, Springa czy coś tam.

0

Kończę tegoroczne "tour the conf"
I taka ciekawostka z devoxxa wczoraj:

Fajna prezentacja, spring 5 reactive... ale jak dojedziecie do końca to zobaczycie Spring... bez Springa.
I dobry cytat z Q&A: "We have to find out the new ways..."..

Bo też niestety w raeactive wersji nie może wiele rzeczy działąć (np. ThreadLocal) - i ten cały wielki framework się wali..

Ale, że akurat chłopaki ze Springa nawet się na Javie trochę znają więc pewnie coś wymyślą.
Tylko ciekawe czy będzie im się naprawdę chciało ten pseudo-deklaratywny model z annotacjami utrzymywać.

1

Ja jestem zwolennikiem zasady by implementować coś jak najprościej, refaktorować w razie potrzeby. Czyli w praktyce najpierw robiłbym bezpośrednie wywołanie do metod w tych usługach (same te metody byłyby statyczne na tym etapie), gdyby pojawiła się potrzeba rozluźnienia tej zależności, mogę wprowadzić wskaźnik do tej funkcji jako parametr (wtedy klient tej metody przekaże wywołanie do rzeczywistego serwisu, w teście mockujemy metodę), jak się okaże że korzystamy z tej samej metody zbyt często można rozważyć np. przekazanie całego obiektu do tej klasy itd.

Bardzo fajnie ta zasada została opisana w artykule "Strategic Scala Style: Principle of Least Power", czyli o tym co sprawia że dobry kod jest dobry (czyli może zrobić jak najmniej):
http://www.lihaoyi.com/post/StrategicScalaStylePrincipleofLeastPower.html#dependency-injection

0

Znalazłem pewną ciekawostkę: https://github.com/ThoughtWorksInc/each

Each to uogólnienie async/ await. Działa nie tylko dla Future, ale także dla innych monad, czyli np Option. Nie testowałem, ale skoro chwalą się, że działa dla wszystkich monad to powinno i dla Eithera, a co za tym idzie obsługa błędów może być mocno zwięzła - o ile ktoś lubi składnię typu async/ await.

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