Dlaczego potrzebujemy tylu klas wzorców projektowych?

208

Jestem młodszym programistą wśród seniorów i mam duże problemy ze zrozumieniem ich sposobu myślenia i rozumowania.

Czytam DDD ( Domain-Driven Design ) i nie rozumiem, dlaczego musimy tworzyć tak wiele klas. Jeśli zastosujemy tę metodę projektowania oprogramowania, otrzymamy 20-30 klas, które można zastąpić maksymalnie dwoma plikami i 3-4 funkcjami. Tak, może być bałagan, ale jest o wiele łatwiejszy w utrzymaniu i czytelny.

Ilekroć chcę zobaczyć, co robi jakiś rodzaj EntityTransformationServiceImpl, muszę śledzić wiele klas, interfejsów, ich wywołań funkcji, konstruktorów, ich tworzenia i tak dalej.

Prosta matematyka:

  • 60 linii fałszywego kodu w porównaniu z 10 klasami X 10 (powiedzmy, że mamy zupełnie inną taką logikę) = 600 linii niechlujnego kodu w porównaniu do 100 klas + więcej do zawinięcia i zarządzania nimi; nie zapomnij dodać zastrzyku zależności.
  • Czytanie 600 wierszy niechlujnego kodu = jeden dzień
  • 100 zajęć = tydzień, wciąż zapomnij, który z nich robi co, kiedy

Wszyscy mówią, że jest łatwy w utrzymaniu, ale po co? Za każdym razem, gdy dodajesz nową funkcjonalność, dodajesz pięć kolejnych klas z fabrykami, jednostkami, usługami i wartościami. Wydaje mi się, że ten rodzaj kodu porusza się znacznie wolniej niż nieporządny kod.

Powiedzmy, że jeśli napiszesz niechlujny kod 50K LOC w ciągu jednego miesiąca, sprawa DDD wymaga wielu recenzji i zmian (nie mam nic przeciwko testom w obu przypadkach). Jeden prosty dodatek może zająć tydzień, jeśli nie więcej.

W ciągu jednego roku piszesz dużo niechlujnego kodu, a nawet możesz wielokrotnie go przepisywać, ale w stylu DDD wciąż nie masz wystarczającej liczby funkcji, aby konkurować z niechlujnym kodem.

Proszę wytłumacz. Dlaczego potrzebujemy tego stylu DDD i wielu wzorów?

UPD 1 : Otrzymałem tak wiele świetnych odpowiedzi, czy możecie gdzieś dodać komentarz lub edytować swoją odpowiedź za pomocą linku do listy lektur (nie jestem pewien, od czego zacząć, DDD, wzorce projektowe, UML, kod ukończenia, refaktoryzacja, Pragmatic,. .. tylu dobrych książek), oczywiście z sekwencją, abym mógł zacząć rozumieć i stać się starszym, jak niektórzy z was.

użytkownik1318496
źródło
81
Myślę, że jest tutaj dobre pytanie, ale kryje się ono za hiperbolą i frustracją. @ user1318496, możesz nieco zmienić sformułowanie pytania.
MetaFight,
48
Ryzykujesz nadepnięcie na palce, ponieważ twój język jest do bani. Nie bierz tego więcej niż jest w rzeczywistości: podstępny język jest często właściwym wyborem technicznym z różnych powodów, ale to nie czyni go pechowym. Jeśli chodzi o twoje aktualne pytanie, poprawność jest ważniejsza niż czytelność. Innymi słowy: czytelność ma znaczenie, ponieważ umożliwia rozumowanie na temat poprawności. Więc co jest łatwiejsze do przetestowania, twoje 100 klas lub monolityczna klasa boga? Obstawiam 100 prostych klas.
Jared Smith
87
„Tak, może to być bałagan, ale jest o wiele łatwiejsze w utrzymaniu i czytelności.” Jeśli jest bałagan, jak może być czytelny i łatwy w utrzymaniu?
Polygnome,
37
@Polygnome: Już miałem wpisać dokładnie ten sam komentarz. Innym komentarzem do wyboru charakterystycznym dla młodszych programistów jest: „Czuję, że ten rodzaj kodu porusza się o wiele wolniej niż bałagan”. Nie ma sensu biegać tak szybko, jak to możliwe, jeśli spędzasz połowę czasu wbiegając w ściany! Powolne jest gładkie, gładkie jest szybkie.
Eric Lippert,
40
Nie, chyba że używasz Javy. Gdy wszystko, co masz, to Java, wszystko wygląda jak klasa.
hobbs

Odpowiedzi:

335

Jest to problem z optymalizacją

Dobry inżynier rozumie, że problem optymalizacji nie ma znaczenia bez celu. Nie można po prostu optymalizacji, trzeba zoptymalizować dla czegoś. Na przykład opcje kompilatora obejmują optymalizację szybkości i optymalizację rozmiaru kodu; są to czasem przeciwne cele.

Chciałbym powiedzieć mojej żonie, że moje biurko jest zoptymalizowane pod kątem dodatków. To tylko stos i bardzo łatwo jest dodawać różne rzeczy. Moja żona wolałaby, gdybym zoptymalizował pod kątem wyszukiwania, tj. Trochę uporządkował moje rzeczy, aby móc znaleźć rzeczy. To oczywiście utrudnia dodawanie.

Oprogramowanie działa w ten sam sposób. Z pewnością możesz zoptymalizować tworzenie produktu - wygeneruj tonę monolitycznego kodu tak szybko, jak to możliwe, nie martwiąc się o jego uporządkowanie. Jak już zauważyłeś, może to być bardzo, bardzo szybkie. Alternatywą jest optymalizacja pod kątem konserwacji - utrudnienie tworzenia, ale modyfikacje łatwiejsze lub mniej ryzykowne. Taki jest cel kodu strukturalnego.

Sugerowałbym, że odnoszący sukcesy program zostanie utworzony tylko raz, ale zmodyfikowany wiele, wiele razy. Doświadczeni inżynierowie widzieli, jak nieustrukturyzowane bazy kodu zaczynają żyć własnym życiem i stają się produktami, które stają się coraz większe i bardziej złożone, aż nawet niewielkie zmiany są bardzo trudne do wprowadzenia bez wprowadzenia dużego ryzyka. Jeśli kod ma strukturę, ryzyko można ograniczyć. Właśnie dlatego zadajemy sobie tyle trudu.

Złożoność wynika z relacji, a nie z elementów

Zauważyłem w twojej analizie, że patrzysz na ilości - ilość kodu, liczbę klas itp. Chociaż są one dość interesujące, prawdziwy wpływ wywierają relacje między elementami, które eksplodują kombinatorycznie. Na przykład, jeśli masz 10 funkcji i nie wiesz, od czego zależy, masz 90 możliwych relacji (zależności), musisz się martwić - każda z dziesięciu funkcji może zależeć od którejkolwiek z dziewięciu pozostałych funkcji i 9 x 10 = 90. Być może nie masz pojęcia, które funkcje modyfikują zmienne lub w jaki sposób dane są przekazywane, więc koderzy mają mnóstwo rzeczy do zmartwienia podczas rozwiązywania konkretnego problemu. Dla kontrastu, jeśli masz 30 klas, ale są one sprytnie ułożone, mogą mieć zaledwie 29 relacji, np. Jeśli są ułożone warstwowo lub ułożone w stos.

Jak to wpływa na przepustowość twojego zespołu? Cóż, jest mniej zależności, problem jest znacznie łatwiejszy do rozwiązania; koderzy nie muszą żonglować milionem rzeczy w swoich głowach, ilekroć dokonają zmiany. Zatem minimalizowanie zależności może znacznie zwiększyć zdolność kompetentnego rozumowania problemu. Dlatego dzielimy rzeczy na klasy lub moduły, a zmienne zakresowe możliwie najściślej i stosujemy zasady SOLID .

John Wu
źródło
103
Chciałbym jednak zauważyć, że nadmiar klas i pośredni NIE pomaga w zrozumieniu utrzymania NOR. I zawiłe sterowanie nie przepływa (inaczej piekło zwrotne).
Matthieu M.,
9
@JohnWu to wyjaśnienie jest najlepszym i łatwiejszym do zrozumienia, jakie kiedykolwiek czytałem.
Adriano Repetti
13
Dobrze napisane, ale może brakuje, dlaczego „raz stworzono, ale wiele razy zmodyfikowano” jest ważne? (Jeśli cały kod jest w EntityTransformationServiceImplśrodku, musisz dowiedzieć się, jak działa cała rzecz, zanim będziesz w stanie to naprawić - ale jeśli np. Coś jest nie tak z formatem i Formatterklasą to obsługuje, musisz tylko nauczyć się, jak ta część działa . Plus czytanie własnego kodu po ~ 3 miesiącach jest jak czytanie kodu nieznajomego.) Mam wrażenie, że większość z nas podświadomie o tym myślała, ale może to wynikać z doświadczenia. Nie jestem tego w 100% pewien.
R. Schmitz
17
Wybrałbym pełne 10 × 10 = 100 dla kombinatoryki: funkcje te mogą być rekurencyjne, a szczególnie w kiepskim kodzie, niekoniecznie oczywiście tak.
KRyan,
32
„Alternatywą jest optymalizacja pod kątem konserwacji - utrudnienie tworzenia, ale modyfikacje łatwiejsze lub mniej ryzykowne. Taki jest cel kodu strukturalnego”. Mam problem z domyślnym przekonaniem, że więcej klas = większa łatwość konserwacji. W szczególności rozproszenie logiki w wielu klasach często utrudnia znalezienie nadrzędnych logicznych pojęć w kodzie (szczególnie bez bardzo celowego upewnienia się, że koncepcje są bardzo widoczne), co ogromnie obniża łatwość utrzymania.
jpmc26,
66

Po pierwsze, czytelność i łatwość konserwacji są często w oczach patrzącego.

To, co jest dla ciebie czytelne, może nie być dla twojego sąsiada.

Utrzymywalność często sprowadza się do wykrywalności (łatwość zachowania lub koncepcji odkrytej w bazie kodu), a wykrywalność to kolejna subiektywna rzecz.

DDD

Jednym ze sposobów, w jaki DDD pomaga zespołom programistów, jest sugerowanie określonego (ale wciąż subiektywnego) sposobu organizowania koncepcji i zachowań kodu. Ta konwencja ułatwia odkrywanie rzeczy, a tym samym łatwiejsze utrzymanie aplikacji.

  • Koncepcje domen są kodowane jako Encje i Agregaty
  • Zachowanie domeny dotyczy podmiotów lub usług domenowych
  • Spójność zapewnia Korzenie Agregatów
  • Problemy z utrzymaniem są obsługiwane przez repozytoria

Taki układ nie jest obiektywnie łatwiejszy do utrzymania. Jest to jednak znacznie łatwiejsze do utrzymania, gdy wszyscy rozumieją, że działają w kontekście DDD.

Klasy

Klasy pomagają w utrzymaniu, czytelności, wykrywalności itp., Ponieważ są dobrze znaną konwencją .

W ustawieniach obiektowych klasy są zwykle używane do grupowania ściśle powiązanych zachowań i do enkapsulacji stanu, który należy dokładnie kontrolować.

Wiem, że to brzmi bardzo abstrakcyjnie, ale możesz o tym pomyśleć w ten sposób:

Dzięki Classes niekoniecznie musisz wiedzieć, jak działa w nich kod. Musisz tylko wiedzieć, za co klasa jest odpowiedzialna.

Klasy pozwalają uzasadnić swoją aplikację pod względem interakcji między dobrze zdefiniowanymi komponentami .

Zmniejsza to obciążenie poznawcze podczas wnioskowania na temat działania aplikacji. Zamiast pamiętać o tym, co osiąga 600 linii kodu , możesz pomyśleć o interakcji 30 komponentów .

Biorąc pod uwagę, że te 30 składników prawdopodobnie obejmuje 3 warstwy aplikacji, prawdopodobnie tylko każdy musi mieć na uwadze około 10 składników na raz.

To wydaje się całkiem wykonalne.

Podsumowanie

Zasadniczo to, co robią starsi deweloperzy, to:

Dzielą aplikację na łatwe do uzasadnienia na temat klas.

Następnie organizują je w łatwe do uzasadnienia warstwy.

Robią to, ponieważ wiedzą, że wraz ze wzrostem aplikacji coraz trudniej jest ją rozumieć jako całość. Podział na warstwy i klasy oznacza, że ​​nigdy nie muszą rozumować o całej aplikacji. Muszą tylko rozumować o niewielkiej ich części.

MetaFight
źródło
5
Poza tym mniejsze klasy łatwiej jest wymienić / refaktoryzować.
Zalomon,
12
Nadmierna inżynieria nie daje bardziej łatwego w utrzymaniu lub zrozumiałego kodu - wręcz przeciwnie, pomimo tego, co twierdzisz. Kto potrzebuje, AddressServiceRepositoryProxyInjectorResolverkiedy możesz rozwiązać problem bardziej elegancko? Wzory projektowe ze względu na wzorce projektowe prowadzą do niepotrzebnej złożoności i szczegółowości.
vikingsteve
27
Tak, nadinżynieria jest zła. Podobnie zabijanie szczeniąt. Nikt tutaj też się nie opowiada. logicallyfallacious.com/tools/lp/Bo/LogicalFallacies/169/...
MetaFight
5
Również „elegancja” w kontekście inżynierii oprogramowania jest często słowem łasicy. en.wikipedia.org/wiki/Weasel_word
MetaFight
11
To samo można powiedzieć o każdym podejściu do organizacji kodu. Wszystko zależy od zespołu konserwacyjnego, który rozumie, dlaczego kod jest tak zorganizowany, jak jest. Taki jest sens stosowania dobrze ustalonych i zrozumiałych konwencji.
MetaFight,
29

Wyjaśnij mi, dlaczego potrzebujemy tego stylu DDD, wielu wzorów?

Po pierwsze, uwaga: ważną częścią DDD nie są wzorce , ale dostosowanie wysiłków rozwojowych do biznesu. Greg Young zauważył, że rozdziały w niebieskiej książce są w niewłaściwej kolejności .

Ale na twoje konkretne pytanie: istnieje o wiele więcej klas, niż można się spodziewać, (a) ponieważ podjęto wysiłek, aby odróżnić zachowanie domeny od hydrauliki i (b) ponieważ podjęto dodatkowy wysiłek, aby zapewnić, że koncepcje w modelu domeny są wyrażone jawnie.

Mówiąc wprost, jeśli masz dwie różne koncepcje w domenie, powinny one być odrębne w modelu, nawet jeśli akurat mają tę samą reprezentację w pamięci .

W efekcie budujesz język specyficzny dla domeny, który opisuje Twój model w języku firmy, tak aby ekspert domeny mógł go zobaczyć i wykryć błędy.

Dodatkowo widzisz nieco więcej uwagi poświęconej oddzielaniu obaw; oraz pojęcie izolowania niektórych zdolności konsumentów od szczegółów wdrożenia. Zobacz DL Parnas . Wyraźne granice umożliwiają zmianę lub rozszerzenie implementacji bez efektu falowania całego rozwiązania.

Motywacja tutaj: w przypadku aplikacji, która jest częścią podstawowej kompetencji firmy (czyli miejsca, w którym czerpie przewagę konkurencyjną), będziesz chciał móc łatwo i tanio zastąpić zachowanie domeny lepszą odmianą. W efekcie masz części programu, które chcesz szybko ewoluować (jak ewoluuje stan w czasie) oraz inne części, które chcesz zmieniać powoli (jak stan jest przechowywany); dodatkowe warstwy abstrakcji pomagają uniknąć przypadkowego połączenia jednego z drugim.

Mówiąc szczerze: niektóre z nich to także Uszkodzenie Mózgu zorientowane obiektowo. Wzory pierwotnie opisane przez Evansa są oparte na projektach Java, w których uczestniczył ponad 15 lat temu; stan i zachowanie są ściśle powiązane w tym stylu, co prowadzi do komplikacji, których wolałbyś unikać; patrz Percepcja i działanie Stuarta Hallowaya lub Inlining Code autorstwa Johna Carmacka.

Bez względu na to, w jakim języku pracujesz, programowanie w funkcjonalnym stylu zapewnia korzyści. Powinieneś to zrobić, gdy jest to wygodne, i powinieneś dobrze przemyśleć decyzję, gdy nie jest to wygodne. Carmack, 2012

VoiceOfUnreason
źródło
2
Zamierzałem dodać własną odpowiedź, dopóki jej nie przeczytam. Chciałbym podkreślić akapit pierwszy, że celem jest modelowanie koncepcji biznesowych w oprogramowaniu w celu dostosowania oprogramowania do wymagań biznesowych. Uczciwie, analityk kierujący projektem powinien również wyrazić, ile czasu zajmuje kierowca do dostarczenia, a jakość / kompletność kodu / testu powinna zostać odpowiednio dostosowana.
Sentinel,
1
John Carmack jest doskonałym przykładem IMO, ponieważ jest dobrze znany z pisania kodu, który jest zarówno czysty, jak i łatwy do zrozumienia, a jednocześnie ma dobrą wydajność. Jest to szczególnie widoczne, gdy śledzisz jego kod za pomocą postępowych technologii - dzięki lepszym procesorom i pamięci możesz zobaczyć wiele rzeczy przesuniętych z „to musi być bardzo zwięzłe” do „to musi być naprawdę jasne / izolowane / ... „. Jeden z najbardziej pragmatycznych programistów, jakich znam :)
Luaan,
Myślę, że to najlepsza odpowiedź tutaj. Chociaż w żadnym wypadku nie jestem sprzedawany na DDD, ta odpowiedź z pewnością przynajmniej wyjaśnia, co to naprawdę jest i faktyczną motywację, zamiast odwoływać się do zwykłych frazesów, które nic nie znaczą.
jpmc26
29

W innych odpowiedziach jest wiele dobrych punktów, ale myślę, że pomijają lub nie podkreślają ważnego błędu koncepcyjnego, który popełniacie:

Porównujesz wysiłek, aby zrozumieć cały program.

W przypadku większości programów nie jest to realistyczne zadanie. Nawet proste programy składają się z tak dużej ilości kodu, że po prostu niemożliwe jest zarządzanie nimi wszystkimi w danym momencie. Jedyną szansą jest znalezienie części programu, która jest odpowiednia do danego zadania (naprawa błędu, wdrożenie nowej funkcji) i praca z tym.

Jeśli twój program składa się z ogromnych funkcji / metod / klas, jest to prawie niemożliwe. Musisz zrozumieć setki wierszy kodu, aby zdecydować, czy ta część kodu jest odpowiednia dla Twojego problemu. Dzięki podanym szacunkom łatwo jest spędzić tydzień, aby znaleźć fragment kodu, nad którym musisz popracować.

Porównaj to z bazą kodu z nazwanymi i uporządkowanymi małymi funkcjami / metodami / klasami w pakietach / przestrzeniach nazw, co sprawia, że ​​oczywiste jest, gdzie znaleźć / umieścić daną logikę. Po prawidłowym wykonaniu w wielu przypadkach możesz wskoczyć w odpowiednie miejsce, aby rozwiązać problem, lub przynajmniej w miejsce, z którego rozpalenie twojego debuggera doprowadzi Cię we właściwe miejsce w kilku przeskokach.

Pracowałem w obu rodzajach systemów. Różnica może być z łatwością rzędu dwóch rzędów wielkości w przypadku porównywalnych zadań i porównywalnej wielkości systemu.

Wpływa to na inne działania:

  • testowanie staje się znacznie łatwiejsze przy mniejszych urządzeniach
  • mniej konfliktów scalania, ponieważ szanse dwóch programistów na pracę nad tym samym fragmentem kodu są mniejsze.
  • mniej powielania, ponieważ łatwiej jest ponownie wykorzystać kawałki (i przede wszystkim znaleźć kawałki).
Jens Schauder
źródło
19

Ponieważ testowanie kodu jest trudniejsze niż pisanie kodu

Wiele odpowiedzi podało dobre uzasadnienie z perspektywy dewelopera - że konserwację można zmniejszyć, kosztem uczynienia kodu bardziej uciążliwym do pisania.

Należy jednak wziąć pod uwagę jeszcze jeden aspekt - testowanie może być tylko drobnoziarniste, tak jak oryginalny kod.

Jeśli wszystko piszesz w monolicie, jedynym skutecznym testem, jaki możesz napisać, jest „biorąc pod uwagę te dane wejściowe, czy dane wyjściowe są prawidłowe?”. Oznacza to, że wszelkie znalezione błędy mają zasięg „gdzieś w tej gigantycznej kupie kodu”.

Oczywiście możesz wtedy poprosić programistę o zasadzenie się w debugerze i znalezienie dokładnie tego, gdzie zaczął się problem, i pracę nad poprawką. Wymaga to wielu zasobów i jest złym wykorzystaniem czasu programisty. Wyobraź sobie, że masz coraz mniejszy błąd, a programista musi ponownie debugować cały program.

Rozwiązanie: wiele mniejszych testów, z których każdy wskazuje jeden konkretny, potencjalny błąd.

Te małe testy (na przykład testy jednostkowe) mają tę zaletę, że sprawdzają określony obszar bazy kodu i pomagają znaleźć błędy w ograniczonym zakresie. To nie tylko przyspiesza debugowanie, gdy test się nie powiedzie, ale także oznacza, że ​​jeśli wszystkie małe testy zakończą się niepowodzeniem - łatwiej można znaleźć awarię w większych testach (tj. Jeśli nie jest to konkretna testowana funkcja, musi ona znajdować się w interakcji między nimi).

Jak powinno być jasne, wykonanie mniejszych testów oznacza, że ​​twoja baza kodu musi zostać podzielona na mniejsze testowalne fragmenty. Sposób na to, w dużej komercyjnej bazie kodu, często skutkuje kodem, który wygląda jak ten, nad którym pracujesz.

Na marginesie: nie oznacza to, że ludzie nie posuną się „za daleko”. Istnieje jednak uzasadniony powód dzielenia baz kodowych na mniejsze / mniej połączone części - jeśli jest to rozsądne.

Bilkokuya
źródło
5
Twoja odpowiedź dotyczy tylko testowania i testowalności, ale jest to najważniejsza kwestia, którą niektóre inne odpowiedzi całkowicie zignorowały, więc daje +1.
skomisa
17

Wyjaśnij mi, dlaczego potrzebujemy tego stylu DDD, wielu wzorów?

Wielu (większość ...) z nas naprawdę ich nie potrzebuje . Teoretycy i bardzo zaawansowani, doświadczeni programiści piszą książki na temat teorii i metodologii w wyniku wielu badań i ich głębokiego doświadczenia - to nie znaczy, że wszystko, co piszą, dotyczy każdego programisty w ich codziennej praktyce.

Jako młodszy programista dobrze jest czytać książki, takie jak ta, o której wspomniałeś, aby poszerzyć swoje perspektywy i uświadomić sobie pewne problemy. Pozwoli ci to również uniknąć zawstydzenia i pomieszania, gdy Twoi starsi koledzy używają terminologii, której nie znasz. Jeśli znajdziesz coś bardzo trudnego i wydaje się, że nie ma sensu lub nie wydaje się przydatny, nie zabijaj się nad tym - po prostu zanotuj sobie w głowie, że istnieje taka koncepcja lub podejście.

W codziennym rozwoju, chyba że jesteś akademikiem, Twoim zadaniem jest znalezienie wykonalnych, możliwych do utrzymania rozwiązań. Jeśli pomysły, które znajdziesz w książce, nie pomogą Ci osiągnąć tego celu, nie martw się o to w tej chwili, o ile twoja praca jest uważana za zadowalającą.

Może przyjść chwila, gdy odkryjesz, że możesz użyć części tego, o czym czytałeś, ale na początku nie do końca „dostałeś”, a może nie.

Wektor
źródło
12
Chociaż na czystym poziomie jest to poprawne, brzmi to jak DDD, a inne Wzorce są tylko teorią. Nie są. Są ważnymi narzędziami do uzyskania czytelnego i łatwego do utrzymania kodu.
Jens Schauder,
9
@PeteKirkham dylemat PO jest nadużywaniem wzorców projektowych. Czy naprawdę potrzebujesz AccountServiceFacadeInjectResolver(prawdziwy przykład, który właśnie znalazłem w systemie w pracy) - odpowiedź najprawdopodobniej nie.
vikingsteve
4
@vikingsteve OP nie jest programowym guru komentującym ogólne nadużywanie wzorców projektowych. OP jest uczniem i uważa, że są nadużywane w jego sklepie . Wiem, że początkujący ja myślałbym tak samo o kodzie, który piszę obecnie, ale początkujący miał wiele osobistych projektów, które zawiodły, ponieważ okazały się zbyt skomplikowane.
R. Schmitz
3
@ R. Schchitz To powiedziawszy, początkujący ja miałem wiele osobistych projektów, które zawiodły, ponieważ zbyt mocno starały się, aby rzeczy były dobrze zaprojektowane, zorganizowane, ustrukturyzowane, OOP ... Wszystkie te są narzędziami. Muszą być zrozumiane i właściwie stosowane, a młot nie można winić za słabe wkręcanie. Zaskakujące wydaje się, że zrozumienie tej prostej rzeczy wymaga dużo wglądu i doświadczenia :)
Luaan
3
@Luaan Jeśli już ci zależało na dobrze zaprojektowanym, zorganizowanym, zorganizowanym OOP, nie nazwałbym tego początkującym - ty, ale tak, nadużywanie jest złe. Pytanie dotyczy jednak tego, w jaki sposób OP tak naprawdę nie rozumie, jakie problemy rozwiązują te rzeczy. Jeśli nie znasz przyczyny, wygląda na to, że nie ma żadnego powodu - ale tak naprawdę nie znasz go jeszcze.
R. Schmitz
8

Ponieważ twoje pytanie obejmuje wiele gruntów i wiele założeń, wyróżnię temat twojego pytania:

Dlaczego potrzebujemy tylu klas wzorców projektowych

My nie. Nie ma ogólnie przyjętej reguły, która mówi, że we wzorcach projektowych musi istnieć wiele klas.

Istnieją dwa kluczowe przewodniki dotyczące decydowania, gdzie umieścić kod i jak podzielić zadania na różne jednostki kodu:

  • Spójność: każda jednostka kodu (paczka, plik, klasa lub metoda) powinna należeć do siebie . Tj. Każda konkretna metoda powinna mieć jedno zadanie i wykonać je dobrze. Każda klasa powinna być odpowiedzialna za jeden większy temat (cokolwiek to może być). Chcemy wysokiej spójności.
  • Łączenie: dowolne dwie jednostki kodu powinny być od siebie jak najmniej zależne - w szczególności nie powinny występować zależności cykliczne. Chcemy niskiego sprzężenia.

Dlaczego te dwa powinny być ważne?

  • Spójność: metoda, która robi wiele rzeczy (np. Staromodny skrypt CGI, który robi GUI, logikę, dostęp do bazy danych itp. W jednym długim bałaganie kodu) staje się niewygodna. W chwili pisania tego artykułu kuszące jest po prostu wprowadzenie długiego sposobu myślenia. To działa, jest łatwe do zaprezentowania i takie, i można to zrobić. Problemy pojawiają się później: po kilku miesiącach możesz zapomnieć o tym, co zrobiłeś. Linia kodu na górze może znajdować się kilka ekranów od linii na dole; łatwo zapomnieć o wszystkich szczegółach. Każda zmiana w dowolnym miejscu w metodzie może przerwać dowolną liczbę zachowań złożonej rzeczy. Będzie to dość kłopotliwe dla refaktoryzacji lub ponownego wykorzystania części metody. I tak dalej.
  • Łączenie: za każdym razem, gdy zmieniasz jednostkę kodu, potencjalnie psujesz wszystkie inne jednostki, które od niej zależą. W ścisłych językach, takich jak Java, możesz uzyskać wskazówki podczas kompilacji (tj. Dotyczące parametrów, zadeklarowanych wyjątków i tak dalej). Ale wiele zmian nie wywołuje takich (tj. Zmian behawioralnych), a inne, bardziej dynamiczne języki nie mają takich możliwości. Im wyższe sprzężenie, tym trudniej będzie coś zmienić, a ty możesz się zatrzymać, gdzie całkowite przepisanie jest konieczne, aby osiągnąć jakiś cel.

Te dwa aspekty są podstawowymi „sterownikami” do dowolnego wyboru „gdzie umieścić” w dowolnym języku programowania i dowolnym paradygmacie (nie tylko OO). Nie wszyscy są tego wyraźnie świadomi i potrzeba czasu, często lat, aby naprawdę zakorzenić się w automatycznym odczuciu, w jaki sposób wpływają one na oprogramowanie.

Oczywiście te dwa pojęcia nie mówią nic o tym, co należy zrobić . Niektórzy popełniają błędy po stronie zbyt wielu, inni po stronie za mało. Niektóre języki (patrząc na ciebie tutaj, Java) mają tendencję do faworyzowania wielu klas z powodu wyjątkowo statycznej i pedantycznej natury samego języka (nie jest to wyrażenie wartości, ale takie, jakie jest). Staje się to szczególnie zauważalne, gdy porównasz go z dynamicznymi i bardziej ekspresyjnymi językami, na przykład Ruby.

Innym aspektem jest to, że niektórzy ludzie opowiadają się za zwinnym podejściem polegającym na pisaniu tylko kodu, który jest w tej chwili konieczny , a później, w razie potrzeby, dokonują refaktoryzacji później. W tym stylu rozwoju nie stworzyłbyś, interfacegdy masz tylko jedną klasę implementującą. Po prostu zaimplementujesz konkretną klasę. Jeśli później będziesz potrzebować drugiej klasy, dokonaj refaktoryzacji.

Niektóre osoby po prostu nie działają w ten sposób. Tworzą interfejsy (lub, bardziej ogólnie, abstrakcyjne klasy podstawowe) dla wszystkiego, co kiedykolwiek mogłoby być użyte bardziej ogólnie; prowadzi to szybko do wybuchu klasy.

Ponownie są argumenty za i przeciw, i nie ma znaczenia, który ja lub ty wolę. W swoim życiu jako programista napotkasz wszelkie skrajności, od długich metod spaghetti, poprzez oświecone, wystarczająco duże projekty klasowe, aż do niewiarygodnie rozbudowanych schematów klasowych, które są o wiele nadinżynieryjne. W miarę zdobywania doświadczenia wrastasz w role „architektoniczne” i możesz zacząć wpływać na to w pożądanym kierunku. Dowiesz się o złotym środku dla siebie i nadal przekonasz się, że wiele osób się z tobą nie zgadza, cokolwiek zrobisz.

Dlatego najważniejsze jest tutaj zachowanie otwartego umysłu i to byłaby moja podstawowa rada, ponieważ wydaje się, że odczuwasz wielki ból w tej sprawie, sądząc po pozostałej części pytania ...

AnoE
źródło
7

Doświadczeni koderzy nauczyli się:

  • Ponieważ te małe programy i klasy zaczynają rosnąć, szczególnie te odnoszące sukcesy. Te proste wzorce, które działały na prostym poziomie, nie skalują się.
  • Ponieważ może wydawać się uciążliwe, aby dodawać / zmieniać kilka artefaktów dla każdego dodawania / zmiany, ale wiesz, co dodać i łatwo to zrobić. 3 minuty pisania pokonują 3 godziny sprytnego kodowania.
  • Wiele małych klas nie jest „nieuporządkowanych” tylko dlatego, że nie rozumiesz, dlaczego koszty ogólne są tak naprawdę dobrym pomysłem
  • Bez dodanej wiedzy na temat domeny kod „oczywisty dla mnie” jest często tajemniczy dla moich kolegów z zespołu ... i dla mnie w przyszłości.
  • Wiedza plemienna może sprawić, że projekty będą niezwykle trudne do dodania członków zespołu, aby szybko i wydajnie.
  • Nazywanie jest jednym z dwóch trudnych problemów w przetwarzaniu danych, które jest nadal prawdziwe, a wiele klas i metod jest często intensywnym ćwiczeniem nazewnictwa, co dla wielu jest wielką korzyścią.
Michael Durrant
źródło
5
Ale czy koszty ogólne są konieczne? W wielu przypadkach, które widzę, wzorce projektowe są nadużywane. W wyniku nadmiernej złożoności rosną trudności w zrozumieniu, utrzymywaniu, testowaniu i debugowaniu kodu. Kiedy masz 12 klas i 4 moduły maven wokół jednej prostej klasy usługowej (prawdziwy przykład, który właśnie widziałem), szybko staje się mniej zarządzalna, niż mogłoby się wydawać.
vikingsteve
4
@vikingsteve Ciągle powołujesz się na pojedynczy przykład wadliwej implementacji, mając nadzieję, że udowodni, że ogólnie ustrukturyzowany kod jest złym pomysłem. To po prostu nie śledzi.
MetaFight,
2
@MetaFight OP nie mówi o kodzie struktury. Mówi o tym, co widzi, o zbyt wielu abstrakcjach. Argumentuję, że jeśli masz zbyt wiele abstrakcji, szybko otrzymujesz bardzo nieustrukturyzowany kod i wiele prawie identycznych elementów, po prostu dlatego, że ludzie nie są w stanie poradzić sobie z ilością rzeczy, z którymi mają do czynienia.
Jaśniejsze
4
@ Clearer Jestem prawie pewien, że wszyscy są zgodni co do tego, że hipotetyczne niewłaściwe zastosowanie kodu strukturalnego jest złą rzeczą. OP twierdzi, że są Junior. Dlatego ludzie zakładają, że nie są zaznajomieni z zaletami kodu strukturalnego i powtarzają je.
MetaFight,
2

Odpowiedzi do tej pory, wszystkie są dobre, zaczynając od rozsądnego założenia, że ​​pytającemu brakuje czegoś, co również pyta. Możliwe jest również, że pytający ma w zasadzie rację i warto omówić, w jaki sposób może dojść do takiej sytuacji.

Doświadczenie i praktyka są potężne, a jeśli seniorzy zdobędą doświadczenie w dużych, złożonych projektach, w których jedynym sposobem na kontrolowanie wszystkiego jest wiele EntityTransformationServiceImpl, to stają się szybkie i wygodne dzięki wzorom projektowym i ścisłemu przestrzeganiu DDD. Byłyby one znacznie mniej skuteczne przy zastosowaniu lekkiego podejścia, nawet w przypadku małych programów. Jak dziwny, powinieneś się dostosować, a będzie to wspaniałe doświadczenie edukacyjne.

Jednak dostosowując się, powinieneś traktować to jako lekcję równowagi między głębokim uczeniem się jednego podejścia, do tego stopnia, że ​​możesz go uruchomić w dowolnym miejscu, w przeciwieństwie do pozostania wszechstronnym i wiedząc, jakie narzędzia są dostępne, niekoniecznie będąc ekspertem w żadnym z nich. Oba mają zalety i oba są potrzebne na świecie.

Josh Rumbut
źródło
-4

Zwiększenie klasy i funkcjonalności zgodnie z przeznaczeniem będzie świetnym podejściem do rozwiązywania problemów w określony sposób, który pomoże w przyszłości rozwiązać wszelkie problemy.

Wiele klas pomaga zidentyfikować jako podstawową pracę, a każdą klasę można wywołać w dowolnym momencie.

Jednak jeśli masz wiele klas i funkcji z ich dostępnej nazwy, łatwo jest do nich zadzwonić i zarządzać. Nazywa się to czystym kodem.

Sanjeev Chauhan
źródło
9
Przepraszam, ale co mówisz?
Deduplicator
@Deduplicator weźmy przykład w Javie, jeśli zaprojektowaliśmy, że do dziedziczenia używaliśmy wątków i klas jframes. Tylko z jednej klasy nie jesteśmy w stanie wykorzystać niektórych dzieł java do projektowania, dlatego musimy stworzyć więcej klas
Sanjeev Chauhan
2
Ta odpowiedź musi przedstawiać ideę (-y), którą zamierza przedstawić w jaśniejszym języku angielskim. Wydaje się, że podstawową ideą w odpowiedzi jest to, że mniejsze klasy, każda z jedną dobrze określoną funkcjonalnością, są dobrym pomysłem, ale straszna gramatyka sprawia, że ​​prawie niemożliwe jest zrozumienie. Również samo to twierdzenie może nie wystarczyć.
talonx