Kategorie
Podcast

Ten w którym rozmawiamy o tworzeniu abstrakcji nad rzeczami systemowymi – OstraPiła #4

Cześć.

Prezentujemy czwarty odcinek podcastu. Tym razem możecie posłuchać o tworzeniu abstrakcji nad rzeczami systemowymi.

Zapraszamy do subskrybowania: RSS, iTunes

Linki:

Robert C. Martin – Clean Architecture.

Unit testing vs Integration testing meme – Link1, Link2

9 odpowiedzi na “Ten w którym rozmawiamy o tworzeniu abstrakcji nad rzeczami systemowymi – OstraPiła #4”

O co chodzi z tym klaskaniem? Można mieć wymóg który ma uzasadnienie biznesowe, że twój system ma być do wdrożenia na różnych bazach danych. Testowanie – czy kiedyś zrobiliście z metody prywatnej petodę publiczną, żeby przetestować – pewnie nie. Czemu piszecie interface po to żeby przetestować skoro interface nie jest potrzebny jeśliby testu by nie było? Nie masz abstrakcji jeśli twoje interfaces mają tylko jedną implementację.

Audio nagrywane jest niezależnie – mając taki wyraźny wzór (klaśnięcie) na początku strumienia łatwo je zsynchronizować 🙂

Ustosunkowując się do dalszej treści:
– ” czy kiedyś zrobiliście z metody prywatnej petodę publiczną, żeby przetestować – pewnie nie” – ofc nie, ale bardzo chciałem to zrobić bo uważałem, że sporo to by systemowi dało gdyby test w tym miejscu był. Często robiłem z prywatnej internal bo to już dawało sensowne możliwości testowania. Z private też w sumie się da testować, ale nie lubię tego Microsoftowego PrivateObject.

– „Czemu piszecie interface po to żeby przetestować skoro interface nie jest potrzebny jeśliby testu by nie było?” – Ale ja chcę aby test był. A interfejs po to, aby przetestować te rzeczy, które z nich korzystają. Jeśli masz klasę , która wysyła zapytanie HTTP i na podstawie kodu błędu coś robi to jak ją przetestujesz bez interfejsu? Jak zrobić, że w jednym teście będzie to 404 a w innym 200?

– „Nie masz abstrakcji jeśli twoje interfaces mają tylko jedną implementację.” – jeśli tak patrzymy na sprawę to moje interfejsy mają zawsze 2 implementacje. Faktyczną i mockową do testów.

Było ustawione UTC, już powinno być ok 🙂

Długo tak pisałem (i nad tak piszę), że każdej (prawie każdej) klasie na stracie generowałem interfejs. Do każdej klasy przekazywałem ten interfejs w konstruktorze. Budowanie zależności wtedy jest w jednym miejscu przy starcie systemu. To się sprawdza. W mądrych książkach o tym piszą – kupiłem ten pomysł. Można wtedy w każdym miejscu systemu sobie bardzo wygodnie napisać test. Ale ostatnio zacząłem myśleć o tym trochę inaczej. Uknułem sobie w głowie taką analogię z metodą prywatną, której nie zmienia się na publiczną tylko po to żeby ją przetestować. Idąc dalej tą analogią w stronę interfejsów: nie pisze się interfejsu do klasy tylko po to aby ją przetestować. To czy dana metoda jest publiczna czy prywatna wynika z architektury rozwiązania jakie sobie zaprojektowaliśmy – to jest oczywiste że nie będziemy degradować naszej architektury sztucznie robiąc jakąś metodę publiczną tylko na potrzeby testów. Teraz gdy to samo zdanie napiszemy w kontekście interfejsów to otrzymamy taką tezę: to czy dana klasa ma interfejs czy nie, wynika z architektury rozwiązania jaką sobie zaprojektowaliśmy – to jest oczywiste że nie będziemy degradować naszej architektury sztucznie robiąc interfejs tylko na potrzeby testów. Zmierzając do brzegu: ostatnio zacząłem się zastanawiać jak pisać kod dla systemu a nie dla testów?, jak projektować architekturę, abstrakcje tylko dla systemu a nie dla testów?, (i tutaj ważny mój warunek) tak by testowanie systemu nadal było łatwe, proste i przyjemne. Chciałbym tak projektować abstrakcje aby ona miała sens dla systemu a nie dla testów jednostkowych. Chciałbym tak pisać kod, że gdy jak wyrzucimy z naszego systemu testy jednostkowe to ta osierocona abstrakcja, którą zbudowaliśmy w naszym systemie nadal żeby miała sens. Może to jest zbyt idealistyczne podejście – może chcę za dużo – może to zależy. Nie wiem do końca czy to jest możliwe i jak to robić poprawnie chociaż mam pewną koncepcje, która mi pomału pączkuje. To są takie moje głośne myśli którymi postanowiłem się podzielić właśnie tutaj, z nadzieją na konstruktywne obalenie mojej teorii lub potwierdzenie słuszności. Tą koncepcją jest wysyłanie miedzy obiektami wiadomości w postaci zwykłych klas mających tyko gettery. Całą abstrakcję wtedy da się zbudować opierając się o te wiadomości nie musząc pisać sztucznych interfejsów (pisać interfejsy nadal będziemy ale tylko tam gdzie są potrzebne). Nawiązując do twojego przykładu z zapytaniem HTTP – „masz klasę która wysyła zapytanie HTTP i na podstawie błędu w odpowiedzi coś robi” – moja propozycja jest taka, że klasa ta nie ma w sobie żadnej zależności związanej danym klientem HTTP, ta klasa jedynie potrafi wysłać obiekt z zapytaniem oraz odebrać obiekt z odpowiedzią, przetworzyć tą odpowiedź i zareagować wysyłając nowy obiekt do systemu z inną wiadomością wynikająca z logiki przetwarzania tej odpowiedzi z błędem . Obiekty będące wiadomościami sami sobie projektujemy z odpowiednimi polami w których znajdują się odpowiednie dane. Jeśli chcesz przetestować reakcje na otrzymanie błędu w odpowiedzi to wystarczy, że wyślesz sobie do klasy taki spreparowany obiekt z tym błędem. W takim rozwiązaniu nie masz ani jednego interfejsu zrobionego na potrzeby testów a nadal wszystko jest łatwo testowalne. Nie wymyśliłem nic nowego bo to co opisałem bardzo skrótowo to jest inaczej mówiąc programowaniem reaktywnym. Bardziej ogólnie mówiąc chciałbym tak programować aby to czy powstanie interfejs czy nie zależało od mojej świadomej decyzji podyktowanej dobrem systemu, a nie tym czy będzie się coś dało testować przy równoczesnej możliwości testowania tego systemu (bez haków i refleksji oczywiście). Koncept wymieniania wiadomości między obiektami zamiast wywoływania metod na obiektach ma wiele inne zalet których tutaj wymienianie byłoby off-topikiem (i przypuszczam, że wad) ale na tą chwilę dostrzegam możliwość realizacji mojej purystycznej wyidealizowanej koncepcji w ten sposób własnie.

Wow. Rozpisałeś się 🙂

Interfejs to tylko jeden ze sposobów na tworzenie i umożliwianie abstrakcji. Jeśli dobrze zrozumiałem to co piszesz odnośnie tego jak ty byś zrobił przykład z HTTP to ja tu też widzę abstrakcję bo sobie tworzysz własne odpowiedzi i zapytania dla zapytań HTTP a więc jest to znów abstrakcja. Nikt też nie mówi o interfejsowaniu wszystkiego jak leci – nie o to chodzi aby wszędzie wstrzykiwać interfejsy i robić ich więcej niż faktycznych klas. Chodzi o to by robić je tam gdzie ma to sens i wg. mnie czasem ma to sens a czasem nie ma.

Hej Artur, znam ten ból gdy piszesz kod i nagle okazuje się, że musisz go lekko nagiąć aby można go było testować. Widywałem trochę specjalnie utworzonych wirtualnych metod które nie robiły nic, a służyły tylko do tego w testach ktoś to przeciążył i nadpisał, tak aby ostatecznie mieć furtkę do prywatnej części klasy. Takie nagięcie zasad na potrzeby testu bolą mnie do dziś.
Przeskoczenie na public/private przez ukrycie kodu za interfejsem boli mnie trochę mniej, ale tylko trochę.
Jednak wolę tutaj zrobić ukłon w stronę testów niż potem modlić się podczas release czy pisać dużo dużych testów, aby mieć pewność że prywatna część klasy działa tak jak było to zamierzone.
Wracając do testów/interface oczywiście można się bez tego obyć (patrz python – jakoś sobie radzą), moim zdaniem interface ułatwiają życie, ale nie są czymś obowiązkowym.

Artur – jeśli chcesz możemy ponownie poruszyć temat warstw lub architektury, wrzuć nam temat na tablicę trello (https://trello.com/b/yXGeD0Ud/tematy-podcastow) trzeba być zalogowanym, sama tablica jest publiczna.

Jak dla mnie największym plusem abstrakcjonowania nie jest możliwość testowania, a stworzenia lepszej architektury w systemie. Tematy oczywiście się łączą z samego testowania (szczególnie praktyki typu TDD) wynika lepsza architektura. W odcinku nie było o tym praktycznie nic i tego mi w tym temacie brakowało.
Pozdrawiam serdecznie 🙂

Możliwość komentowania została wyłączona.