…przynajmniej nie wszędzie.
Teoria
Dawno, dawno temu, kiedy „na poważnie” wchodziłem w świat programowania (czyli napisałem coś więcej niż proste „Hello World” 😉 ), ze wszystkich stron docierały do mnie dobre rady:
Twórz interfejsy i klasy je implementujące. Dzięki temu będziesz mógł na przykład tworzyć odrębne implementacje na potrzeby testów albo być może przyjdzie kiedyś ktoś chcący napisać własną implementację Twojego interfejsu w jakimś zacnym celu. Kontener wstrzykiwania zależności pomoże Ci to wszystko skleić i będzie cacy.
Przez długi czas grzecznie się słuchałem. Bardzo długi czas. Zbyt długi. Dziś mówię…
G***o prawda!
Nie neguję całej idei, bo w ogólności jest słuszna. Niestety wiele osób zapomina o tym, że liczy się kontekst. Zawsze.
Cykl artykułów "Vademecum Juniora"
Ten wpis jest częścią cyklu, w ramach którego opisuję moje doświadczenia i spostrzeżenia na temat programistycznego rzemiosła. Cykl jest dedykowany osobom rozpoczynającym swoją przygodę z zawodowym programowaniem lub planują taki krok w przyszłości.
Artykuły składające się na Vademecum Juniora zawierają różne porady, wskazówki i przykłady z pracy jako programista. Nie są one ani uporządkowane chronologicznie, ani mocno ze sobą powiązane. Możesz je więc czytać niezależnie od siebie i w dowolnej kolejności. Polecam jednak zapoznać się z treścią wszystkich wpisów, gdyż wierzę, że z każdego możesz dowiedzieć się czegoś wartościowego.
Praktyka
Teoria teorią, ale przyjrzyjmy się jak to wygląda w praktyce.
Aplikacje webowe
Jako programista aplikacji webowych mający za sobą kilkanaście mniejszych lub większych projektów mogę powiedzieć, że 99% napisanych/widzianych przeze mnie interfejsów w tego typu aplikacjach miała jedną, jedyną, jedniutką, jedniunią implementację. Finalnie w każdym przypadku mieliśmy interfejs FuckingAwesomeService i jego implementację FuckingAwesomeServiceImpl (albo DefaultFuckingAwesomeService). W efekcie powstawała podwojona liczba typów, gdy tak naprawdę nie miało to żadnego praktycznego zastosowania.
Aplikacja webowa jest „produktem końcowym” – nikt nie użyje jej jako biblioteki, żeby skorzystać z jej funkcjonalności i wprowadzić ich własną implementację. Pisząc testy, w zależności od sytuacji albo mockowaliśmy obiekty, albo używaliśmy produkcyjnej implementacji. Nie pamiętam sytuacji, w której specjalnie dla testów napisaliśmy odrębną implementację jakiegoś interfejsu.
Krótko mówiąc, trzymanie się konwencji interfejs -> implementacja w aplikacjach webowych ma sens tylko w specyficznych przypadkach.
Biblioteki
Inaczej jest we wspomnianych bibliotekach. Jeżeli tworzysz coś, co nie jest „produktem końcowym”, ale „klockiem” do wykorzystania, to trzymanie się interfejsów jest jak najbardziej wskazane. Nie trzeba długo szukać przykładu. Wystarczy zajrzeć do pakietu java.util z JDK. Znajduje się tam choćby interfejs Collection, który w samym JDK posiada kilkadziesiąt implementacji. Dzięki trzymaniu się interfejsu, JDK daje nam możliwość wprowadzenia własnej implementacji jeżeli w „gotowcach” nie znajdziemy dla siebie niczego odpowiedniego. To jest wzorcowy przykład wykorzystania interfejsów. Polecam 🙂
Dosyć!
Nadal pracuję głównie nad aplikacjami webowymi, ale od około dwóch lat nie stosuje(my) już konwencji interfejs -> implementacja. „Nie stosujemy” jest może trochę nieprecyzyjne. Stosujemy, ale tylko w momencie, kiedy faktycznie ma to sens, czyli na przykład wtedy, kiedy pojawia się realna potrzeba wprowadzenia drugiej implementacji. Nie programujemy „na zapas”.
I powiem Ci, że… bardzo mi z tym dobrze 🙂 Kodu jest mniej, nikt nie umarł i w ogóle tak jakoś lżej na duchu 😉
Wniosek jest jeden – nie wszystko jest „prawdą objawioną” i nie wszystko aplikuje się do wszystkiego. Kontekst zawsze miał, ma i będzie miał znaczenie. To właśnie dlatego najczęstszą odpowiedzią doświadczonego programisty jest: „To zależy” 😉 Pamiętaj o tym.
A Ty co myślisz o wpakowywaniu wszędzie interfejsów? Praktykujesz? Czy używasz tylko tam gdzie jest to faktycznie wykorzystywane? Podziel się swoją opinią w komentarzu. Chętnie ją poznam.
Bądź na bieżąco!
Podobają Ci się treści publikowane na moim blogu? Nie chcesz niczego pominąć? Zachęcam Cię do subskrybowania kanału RSS, polubienia fanpage na Facebooku, zapisania się na listę mailingową:
lub śledzenia mnie na Twitterze. Generalnie polecam wykonanie wszystkich tych czynności, bo często zdarza się tak, że daną treść wrzucam tylko w jedno miejsce. Zawsze możesz zrobić to na próbę, a jeśli Ci się nie spodoba – zrezygnować
Dołącz do grup na Facebooku
Chcesz więcej? W takim razie zapraszam Cię do dołączenia do powiązanych grup na Facebooku, gdzie znajdziesz dodatkowe informacje na poruszane tutaj tematy, możesz podzielić się własnymi doświadczeniami i przemyśleniami, a przede wszystkim poznasz ludzi interesujących się tą samą tematyką co Ty.
W grupie Programista Na Swoim znajdziesz wiele doświadczonych osób chętnych do porozmawiania na tematy krążące wokół samozatrudnienia i prowadzenia programistycznej działalności gospodarczej. Vademecum Juniora przeznaczone jest zaś do wymiany wiedzy i doświadczeń na temat życia, kariery i problemów (niekoniecznie młodego) programisty.
Wesprzyj mnie
Jeżeli znalezione tutaj treści sprawiły, że masz ochotę wesprzeć moją działalność online, to zobacz na ile różnych sposobów możesz to zrobić. Niezależnie od tego co wybierzesz, będę Ci za to ogromnie wdzięczny.
Na wsparciu możesz także samemu zyskać. Wystarczy, że rzucisz okiem na listę różnych narzędzi, które używam i polecam. Decydując się na skorzystanie z któregokolwiek linku referencyjnego otrzymasz bonus również dla siebie.
Picture Credits
20 grudnia 2021 at 12:13
Każdemu, kto nie rozumie, co autor miał na myśli, polecam sprawdzić czym jest AHA:
BOOM!
20 stycznia 2018 at 13:51
Stop blaming interfaces. Its not their fault. It’s blame of enterprise patterns like „service”, „controller”, „DAO” and frameworks built on them. There is a thing like SOLID, and from SOLID perspective these patterns are total crap:
SRP – services are never single responcible. They usually are nothing but a bunch of procedures placed in one module. Making them single responcible would mean to place every method of it into a separate interface, which is inconvenient so nobody does it that way.
OCP – due to the SRP violation, it looks like a common practice to add a new procedure into existing service or controller. But it wasn’t supposed to be that way – according to OCP it was supposed to be close from modifications.
LSP – that’s what you are writing about. Indeed, when there is a service contract with a bunch of incohesive procedures, it is hard to imagine more than one implementation of it which aren’t violating LSP. But again – it is not the interfaces blame that we tend to make our modules incohesive.
ISP – Interfaces instability of services tend to make this principle fragile. Imagine you are injecting a one service to another service. Is there a guarantee that the latter uses all the capabililties of the former? According to ISP, a module should depend only on items which it uses, but in reality – with services it never happens.
DIP – Interfaces virtue is in their stability. Without stability they indeed make no sense. Stable interfaces are easy to test and stub, they keep coupling between modules under control. This stability is hard to acheve with service-like approach, because we tend to constantly violate OCP with them.
So. You still blaming interfaces for that? But getting rid of them won’t solve the problems enumerated above. Whether you agree it or not, SOLID, cohesion and coupling terms are still actual and will be actual forever to every language, framework and paradigm which has implicitly or explicitly the notion of „module”. Interfaces virtue in OOP is in their ability to decouple things, keep details encapsulated in volatile implementors, but this virtue is neglected when the interface itself is volatile. Good interface is stable, and it’s hard to keep it stable for a service or controller.
More about SOLID, cohesion and coupling could be read in Uncle Bob’s „Clean architecture”.
22 stycznia 2018 at 13:33
Sergey, thanks for first not-Polish comment on my blog 🙂
I’m not blaming interfaces for that they exist. Same as I’m not blaming dynamite for that it exists 😉 I only want to pay attention that best practices are not silver bullets. Even though they applies in 90% of situations there are still 10% where commonly known best practice is not necessary the best possible solution.
There is a great quote in „Effective Java” by J. Bloch for what I mean:
„While the rules in this book do not apply 100 percent of the time, they do characterize best programming practices in the great majority of cases. You should not slavishly follow these rules, but violate them only occasionally and with good reason. Learning the art of programming, like most other disciplines, consists of first learning the rules and then learning when to break them.”
22 stycznia 2018 at 16:06
Well, maybe „blaming” word was not correct there. I wasn’t meant to be offensive, sorry if it felt that way. Anyway, you entitled your article as „interfaces are not nesessary”, so let da flame begin 🙂
IMO if we are saying that „interfaces are not nesessary”, then it means that we are doing one more step outwards of basic maintainability principles into an abyss. You gave really good examples in the article – indeed, for the services and controllers interfaces give extra burden – we have two entities instead of one, but IMO the conclusion was incorrect.
Interfaces were supposed to be the abstraction, which is stable enough to tie unstable implementations to. Good interfaces are stable and cohesive. If they are not stable and not cohesive, then they are rubbish. And what we see nowadays have no such capabilities. Cohesive interface can’t contain decades of methods like java.util.Collection has – hard to imagine they all serve the single responsibility. Stable interfaces aren’t changing on first demand from the customer, like services and controllers are – changing in stable abstraction multiplies the cost of such change. And if there is no interfaces, then what you use for that stable abstraction role instead?
And while Bloch was right saying that there is no 100% truth about that all, IMO maintainability is the most underestimated and least spoken out term nowadays. It’s sad.
PS: I am a big fan of pure OOP approach, „Elegant Objects”, and object composition. And personally, I can’t imagine it without interfaces.
23 stycznia 2018 at 22:17
„maintainability is the most underestimated and least spoken out term nowadays” – beautiful words my friend 🙂
The title of this post had „clickbait” purpose… and as I see it worked 😉 I can’t imagine working without interfaces either and I not calls to throw them out. I just described one particular situation of blindly following of „best practices” instead of thinking for maintainability.
BTW. How did you find the post? 🙂 Can you read Polish? I’m just curious.
24 stycznia 2018 at 11:23
> The title of this post had „clickbait” purpose… and as I see it worked 😉
Indeed, worked) You troll, you know that? 🙂
Speaking seriously – IMO people who blindly follow the precedence instead of thinking objectively will blindly follow your title’s idea. Besides – I don’t know, maybe my translation was wrong, but I haven’t seen this idea (thinking of maintainability instead of blindly following best practices) in the article.
> BTW. How did you find the post? 🙂 Can you read Polish? I’m just curious.
It was referenced here: http://lkonopski.blogspot.ru/2018/01/oop-is-not-dead-elegant-objects.html. I’m not speaking Polish but Google Translate did a favor for me)
24 stycznia 2018 at 11:42
> IMO people who blindly follow the precedence instead of thinking objectively will blindly follow your title’s idea
Probably true. I see where is the problem now. Łukasz Konopski, autor of this post blindly understood that I am calling to throw out the interfaces from OO languages. This is not true. I described only one particular situation. I need to talk with him.
8 września 2017 at 12:47
Tworzenie od razu konkretnych klas kusi do przestawienia myślenia pod implementację. Poza tym, interfejsy łatwiej pozwalają wyrazić intencję (łatwiej się czyta bez całego mięsa), np. dobrze to widać we wszelkiego rodzaju „straegiach” czy „politykach”. Często implementacja ma też dosyć konkretną nazwę np. ApacheCommonFtpFileService, co też razi w oczy gdy próbujesz zrozumieć intencję, a nie implementację. Jeszcze inna sprawa – często interfejsy są w innym pakiecie, a implemetacje w innym – dzięki temu można łatwo pofragmentować kod na warstwy co przydaje się np. podczas analizy JArchitectem (czy nie są naruszone jakieś rule wartstwowości). Są też pewne techniczne korzyści z używania interfejsów (np. w Springu używając AOP/Transactional etc). Podsumowując – zawsze lepiej się zastanowić niż ślepo kopiować, ale imo interfejsy z 1 implementacją nadal są ok (i 1 dodatkowy komponent to wcale nie problem).
8 września 2017 at 13:06
Nie mówię, że nie jest OK i przyjmuję wszystkie konstruktywne kontrargumenty, jak Twój 🙂 Chciałem tylko podkreślić to o czym wspomniałeś – nie wszędzie trzeba ślepo kopiować rozwiązań. „There is no silver bullet” jak mawiają. Nam udało się poprowadzić 3 projekty w tym stylu i nie doświadczyliśmy z tego powodu przykrości 😉
1 dodatkowy komponent spoko, ale jak serwisów, repozytoriów i innych komponentów są setki, to tych dodatkowych komponentów są również setki.
Tak czy inaczej: myślenie jest wskazane 😉
8 września 2017 at 11:24
W springu robi się interfejsy dla serwisów (mimo, że jest jedna impl) aby dostać inny typ proxy. Łatwiej to debugować (wiem, że jak mamy duże pokrycie testami to mniej debugujemy ale czasem jednak debugujemy:)
8 września 2017 at 12:50
A widzisz… o tym nie pomyślałem. Być może właśnie ze względu na duże pokrycie testami i rzadkość debugowania. Mimo wszystko, z tego co pamiętam taki argument nie padał z ust wyjaśniających mi dlaczego wszędzie piszemy interfejsy. Ale może nie pamiętam po prostu 🙂 Dzięki!
8 września 2017 at 10:09
dokładnie, prostota jest ładna;
lepiej krótko niż długo
dlatego przesiadam się na Pajtona, ale ciii…
8 września 2017 at 12:12
Nie martw się, nikt się nie dowie 😉
7 września 2017 at 21:28
Ale interfejsy do testów to ty szanuj 🙂 Generalnie masz rację, że często bezmyślnie robimy coś bo ktoś tak kazał, ale te mocki o których piszesz dużo łatwiej osiągnąć jak używa się interfejsów (przynajmniej w statycznie typowanych językach), chyba że wszystko jest virtualne by default jak w Javie 😉 albo używasz jakiegoś języka który pozwala na duck typing. Peace!
8 września 2017 at 06:36
Przepraszam, zachowałem się jak gówniarz 😀 Prawdę mówisz o tych mockach – jakoś tak wyszedłem z założenia, że to we wszystkich językach będzie tak samo pomijalne jak w Java/Groovy. Przyzwyczajenie do własnego grajdołka 😉
Lesson learnt: wyraźnie komunikuj o jaki język Ci chodzi 🙂
Peace!