Open-closed principle za wszelką cenę?

0

Cześć, mam luźne pytanie natury stricte filozoficznej dotyczące open-closed principle.
Tak się składa, że stworzyłem dziś pierwszy interface w projekcie (na potrzeby zwiększenia testowalności kodu operującego czasem) przy czym serwisów zawierających jakąś tam logikę jest ponad 10 (architektura warstwowa).
Czy Waszym zdaniem tworzenie **zawczasu **interfejsów (zakładając, że na chwilę obecną jest małe prawdopodobieństwo by miały pojawić się w przyszłości nowe implementacje) i ich wykorzystanie w roli typów jest **nadmiarowe **(mimo, że sprowadza się to do: formalnego zdefiniowania interfejsu, uzupełnienia implementacji o implements xyz oraz zmiany typów zależności w klasach które je wykorzystują)?
Patrzę na to nieco filozoficznie, że *a nuż kiedyś się to przyda *(chociaż kłóci się to z YAGNI), to bezpieczny refactoring który zająłby pewnie max godzinę, "popraw teraz bo jak odłożysz na później to nie poprawisz nigdy".

Zdaję sobie sprawę, że nadgorliwość jest gorsza od faszyzmu oraz, że wszelkiego rodzaju zasady to drogowskaz a nie coś czego zawsze i wszędzie należy się sztywno trzymać i ciekaw jestem jak wygląda Wasz złoty środek.

2

To zależy czy ten Twój interfejs jest na granicy pomiędzy dwoma bytami (np GUI a logika biznesów), czy jest toć tylko szczegół implementacyjny - polimorfizm zamiast ifów.

Jeśli to pierwsze, to trzeba by je przenazliwać, czy mają sens. Jeśli to drugie to możesz spokojnie zostawić tak jak jest.

7

A co ma interface do Open Close Principle?

To, że zastosujesz interface nie oznacza, że twój kod spełnia te zasadę.

Tu masz przykład bez żadnego interfacu
https://code-maze.com/open-closed-principle/

4

„Za wszelką cenę” - nie, żadna zasada nie jest celem samym w sobie. Podobnie odnośnie robienia rzeczy „a może kiedyś się przyda” - na 90% się nie przyda, a będzie mniej kodu (czyli lepiej) i spożytkujesz ten czas na rzeczy o większym priorytecie.

2

Z Twojego posta wynika tylko tyle, że stworzyłeś jakiś interfejs. Do OC principle ma się to nijak. Generalnie IMO pisząc jakąś logikę - jeśli nie przewidujesz więcej niż jednej implementacji to po prostu nie ma sensu. Ewentualny refactor gdy trzeba będzie dorzucić inną implementację to 15 sekund w intelliju. Jeśli tego nigdy nie robiłeś to może 2 minuty.
Nawalenie interfejsów po całym projekcie tylko zmniejsza czytelność. Ma sens tylko gdy piszesz bibliotekę - wtedy fajnie dać możliwość customizacji użytkownikowi i nieraz ułatwiło mi to życie, że biblioteka gdzieś zwracała interfejs, a nie konkretną klasę.

I jeszcze na wszelki wypadek... Pewnie nie o to Ci chodziło, ale mówisz o stworzeniu interfejsu, a zaraz o jakiś serwisach. Jeśli zrobiłeś coś takiego:

class MyServiceImpl implements MyService

to nie, nie ma to sensu i jest to wręcz idiotyczne. To jest jakiś antypattern przekazywany przez pokolenia i już nikt nie wie dlaczego i po co.

2

Jak dla mnie, w kodzie nie powinno być niczego na przyszłość. Takie myślenie, że coś pewnie się przyda to najprostsza droga do stworzenia wielkiego rozlazłego raka. Odkąd programuję małymi przyrostami, nie implementując absolutnie nic poza poszczególną małą cechą, mój kod jest znacznie przyjemniejszy w utrzymaniu. Jak to się ma do zasady Open-Closed? Rzeczywiście, raczej dopisuję kod niż go zmieniam (choć czasem refactor zrobić trzeba). Idealnie jeśli dopisuję nowe funkcje, ale czasem zdarza się dopisać coś do metody, żeby obsłużyć szczególny przypadek. Wtedy to jest kwestia ceny, bo mogę opakować to w inną klasę albo zaimplementować w klasie wyzwalacze (hooks), tylko po co, jeśli to jest zachowanie, którego oczekuję zawsze i nie spodziewam się używać podobnych rozwiązań w przyszłości? Myślę, że dobre API jest ważniejsze. To jednak jest kwestia jaki masz kod. W spaghetti lepiej tego nie robić, bo możesz naruszyć kruchą równowagę kodu. Co innego jeśli masz prosty kod, w którym dodanie kawałka kodu powoduje jasne konsekwencje bez nieoczekiwanych przypadków. Gdy programujesz funkcyjnie, dopisanie funkcji, która działa jak preprocesor danych wejściowych, nie powinna niczego zepsuć. Już w ogóle, nie uważam za złamanie tej reguły dopisanie nowej, niezależnej metody czy dodanie opcjonalnego parametu (którego brak daje dotychczasowe działanie).

1
RequiredNickname napisał(a):

Cześć, mam luźne pytanie natury stricte filozoficznej dotyczące open-closed principle.

Tak się składa, że stworzyłem dziś pierwszy interface w projekcie (na potrzeby zwiększenia testowalności kodu operującego czasem) przy czym serwisów zawierających jakąś tam logikę jest ponad 10 (architektura warstwowa).
Czy Waszym zdaniem tworzenie **zawczasu **interfejsów (zakładając, że na chwilę obecną jest małe prawdopodobieństwo by miały pojawić się w przyszłości nowe implementacje) i ich wykorzystanie w roli typów jest **nadmiarowe **(mimo, że sprowadza się to do: formalnego zdefiniowania interfejsu, uzupełnienia implementacji o implements xyz oraz zmiany typów zależności w klasach które je wykorzystują)?
Patrzę na to nieco filozoficznie, że *a nuż kiedyś się to przyda *(chociaż kłóci się to z YAGNI), to bezpieczny refactoring który zająłby pewnie max godzinę, "popraw teraz bo jak odłożysz na później to nie poprawisz nigdy".

Zdaję sobie sprawę, że nadgorliwość jest gorsza od faszyzmu oraz, że wszelkiego rodzaju zasady to drogowskaz a nie coś czego zawsze i wszędzie należy się sztywno trzymać i ciekaw jestem jak wygląda Wasz złoty środek.

Czytałem wypowiedź wyżej, od @elwis i z jednej strony się zagadzam, z drugiej nie.

Czy warto pisać kod na przyszłość - nie ma sensownego sposobu żeby na to odopowiedzieć - pytanie jest zbyt ogólne.

  • Czy warto stworzyć klasę, której mogę użyć później ale nie używam teraz?
  • Czy warto Tworzyć abstrakcję w miejscach, A, B, C?
  • Czy warto podzielić tą klasę na mniejsze kawałki, gdybym miał ich kiedyś użyć? Czy może jest inny powód żeby je podzieć?

Heck, a co z testami? Przecież testy to jest kod, który się pisze na przyszłość.

Tak na prawdę, caly ten temat sprowadza się do tego, że jeśli masz mało doświadczenia, to jesteś trochę jak grzesznik który spotyka proroka. Nie potrafisz rozróżnić dobra od zła (legacy code'u od dobrego designu), więc popełniasz błędy. Fajnie, że próbujesz się edukować, i pytasz - jak pisać software - ale zadałeś zbyt ogólne pytanie. Jak pytasz na forum "Czy stosować Open/Close, i kiedy?" to tak jakbyś pytał proroka "Jak być dobrym człowiekiem?" albo "Dokąd zmierzamy?". There's no way to answer that question.

Jeśli faktycznie chcesz się nauczyć, to pokaż nam aplikację nad którą pracujesz, i powiedz jakie masz obawy dokładnie. Zadaj pytanie na podstawie przykładu, może wtedy dostaniesz odpowiedź która faktycznie Ci pomoże.

1

W programowaniu, jak i w życiu. Niczego nie powinno się robić "za wszelką cenę". Jest kilka zasad, które powinno się przemyśleć. Są to zasady wypracowane przez lata, sprawiające, że kod jest czysty, testowalny, piękny itd. W jakimś stopniu przekłada się to na działanie aplikacji. Natomiast nic za wszelką cenę. Jest taka zasada YAGNI (You Ain't Gonna Need It). Mówi, żeby nie robić niczego, czego w tym momencie nie potrzebujesz.

Dokładanie do wszystkiego interfejsów na dzień dobry prowadzi do overengineeringu.

0

Pytania bardziej konkretnego nie zadam bo w zasadzie nie taki miałem cel. Potrzebowałem lekkiego skorygowania swojego kursu i jakiegoś skorygowania mojego toku myślenia i to właśnie otrzymałem, dzięki ;)

1

Czy Waszym zdaniem tworzenie zawczasu interfejsów (zakładając, że na chwilę obecną jest małe prawdopodobieństwo by miały pojawić się w przyszłości nowe implementacje) i ich wykorzystanie w roli typów jest nadmiarowe (mimo, że sprowadza się to do: formalnego zdefiniowania interfejsu, uzupełnienia implementacji o implements xyz oraz zmiany typów zależności w klasach które je wykorzystują

Kiedy stosujesz czysta architekturę albo hexagonalną (nie wiem czym to tak się bardzo rózni) posiadanie interfejsu z jedną implementacją nie jest takie złe. Np. masz CurrencyExchangeRateProvider i możesz na potrzeby logiki biznesowej a jego implementacja ktora jest szczegółem infrastrukturalnym który może być nawet w innym jarze.

1
ProgScibi napisał(a):

Kiedy stosujesz czysta architekturę albo hexagonalną (nie wiem czym to tak się bardzo rózni) posiadanie interfejsu z jedną implementacją nie jest takie złe. Np. masz CurrencyExchangeRateProvider i możesz na potrzeby logiki biznesowej a jego implementacja ktora jest szczegółem infrastrukturalnym który może być nawet w innym jarze.

No to według mnie jest dobre miejsce na interfejs nawet z jedną implementacją, bo to styk z zewnętrznym światem, tak samo interfejs z jedną implementacją ma sens jako kontrakt komunikacyjny pomiędzy modułami.

Ale jeśli miałbym teraz jakiś głupi orkiestrator, który woła tego CurrencyExchangeRateProvidera, a później coś zapisuje w repo (do którego też mogę mieć interfejs bo to styk z zewnętrznym serwisem) to nie widzę potrzeby żeby orkiestrator miał swój interfejs.

0

Jak nie piszesz biblioteki to interfejsy "na zaś" są często zbędne. Dobrym kontrprzykładem są np. interfejsy redukujące możliwości danego kodu np. wzorzec repozytorium. Przykładowo warto wprowadzić taki interfejs nawet gdy wiem, że moja aplikacja używa i będzie używała postgresa jako bazy i nie będę mockował repozytorium, bo będę testował zawsze z normalną bazą i brzydzę się mocków. To co dostaję to mentalną separację pomiędzy kodem bazowym a modelem dzięki czemu wiem gdzie mam szukać jak coś się zepsuje. Inną dobrą sytuacją jest maksymalna separacja pomiędzy czystym funkcyjnym kodem a imperatywnym wykorzystywanym do integracji np. interfejs do zegara albo coś do integracji z innym systemem, bo dzięki temu łatwiej mogę analizować czysty funkcyjny.

2

2021 rok, a my nadal tłuczemy Generic Repository, od którego facet może raka jajników dostać.
To jest świństwo i powinno się to gorącym żelazem wypalać. I nie, nijak znacząco to nie ułatwia testowania, tak zawczasu :P

A co do pierwotnego pytania - wg. mnie interfejs ma pełnić konkretną rolę.
W tym wypadku, jak m.in. wcześniej wspomniane - wyraźna komunikacja między warstwami, wiele implementacji.
Pisanie interfejsu "na zaś" albo dla sztuki sensu nie ma żadnego, bo po co? Tylko syf w kodzie robi i potem masz Javowe skakanie, w którym nawet IntelliJ nie pomoże.

Co do OCP - także, jak wcześniej wspomniano, interfejsy same w sobie niewiele mają bezpośrednio wspólnego.
Generalnie samo OCP niespecjalnie mi się nawet z YAGNI kłóci, bo jak masz konkretny case, to, w przypadku gdy "spodziewasz" się rozszerzenia, napisanie tego tak, żeby przygotować to do działania jako strategia i dorzucenia interfejsu później, ani specjalnie więcej kodu nie wymaga ani nie budujesz nadmiarowych mechanizmów.

Problem pojawia się właśnie z przewodzeniem kiedy się spodziewać potencjalnych rozszerzeń, ale to już kwestia gadania z klientem/PO/kogokolwiek tam sobie firma wymyśliła.
I tak, pewno odpowiedzą, że "nie trza" albo "ni wim".
Wtedy zdaj się na instynkt, bo co zrobisz, jak nic nie zrobisz?

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