Czy powinniśmy unikać stosowania wzorców projektowych w ciągle zmieniających się projektach?

32

Mój przyjaciel pracuje dla małej firmy nad projektem, którego każdy programista nienawidziłby: jest zmuszany do jak najszybszego zwolnienia, jest jedynym, który wydaje się dbać o dług techniczny, klient nie ma zaplecza technicznego itp.

Opowiedział mi historię, która kazała mi pomyśleć o stosowności wzorców projektowych w projektach takich jak ten. Oto historia.

Musieliśmy wyświetlać produkty w różnych miejscach na stronie. Na przykład menedżerowie treści mogą przeglądać produkty, ale także użytkowników końcowych lub partnerów za pośrednictwem interfejsu API.

Czasami brakowało informacji o produktach: na przykład kilka z nich nie miało żadnej ceny, gdy produkt został właśnie utworzony, ale cena nie została jeszcze określona. Niektóre nie miały opisu (opis jest złożonym obiektem z historiami modyfikacji, zlokalizowaną zawartością itp.). Niektórym brakowało informacji o wysyłce.

Zainspirowany moimi ostatnimi czytaniami o wzorach projektowych, pomyślałem, że to doskonała okazja do użycia magicznego wzoru Null Object . Zrobiłem to i wszystko było gładkie i czyste. Wystarczyło zadzwonić, product.Price.ToString("c")żeby wyświetlić cenę lub product.Description.Currentopis; nie są wymagane żadne warunki warunkowe. Aż pewnego dnia interesariusz poprosił o wyświetlenie go inaczej w interfejsie API, poprzez nullużycie JSON. A także inaczej dla menedżerów treści, pokazując „Cena nieokreślona [Zmień]”. Musiałem zamordować mój ukochany wzór Null Object, ponieważ nie było już takiej potrzeby.

W ten sam sposób musiałem usunąć kilka abstrakcyjnych fabryk i kilku budowniczych, w końcu zastąpiłem mój piękny wzór fasady bezpośrednimi i brzydkimi połączeniami, ponieważ podstawowe interfejsy zmieniały się dwa razy dziennie przez trzy miesiące, a nawet Singleton mnie opuścił kiedy wymagania mówiły, że dany obiekt musiał być inny w zależności od kontekstu.

Ponad trzy tygodnie pracy polegały na dodawaniu wzorców projektowych, a następnie ich rozerwaniu miesiąc później, a mój kod w końcu stał się na tyle spaghetti, że nikt, w tym ja, nie może go utrzymać. Czy nie lepiej byłoby nigdy nie używać tych wzorów?

Rzeczywiście musiałem popracować nad projektami, w których wymagania ciągle się zmieniają i są podyktowane przez osoby, które tak naprawdę nie mają na myśli spójności ani spójności produktu. W tym kontekście nie ma znaczenia, jak zwinny jesteś, otrzymasz eleganckie rozwiązanie problemu, a kiedy go w końcu wdrożysz, dowiesz się, że wymagania zmieniły się tak drastycznie, że Twoje eleganckie rozwiązanie nie pasuje już dłużej.

Jakie byłoby rozwiązanie w tym przypadku?

  • Nie używasz żadnych wzorców projektowych, przestajesz myśleć i piszesz kodu bezpośrednio?

    Interesujące byłoby doświadczenie, w którym zespół pisze kod bezpośrednio, podczas gdy inny zastanawia się dwa razy przed pisaniem, ryzykując wyrzucenie oryginalnego projektu kilka dni później: kto wie, może obie drużyny miałyby ten sam dług techniczny. W przypadku braku takich danych twierdziłbym jedynie, że pisanie kodu bez uprzedniego myślenia podczas pracy nad projektem trwającym 20 miesięcy nie wydaje się właściwe .

  • Utrzymać wzorzec projektowy, który nie ma już sensu, i spróbować dodać więcej wzorców dla nowo utworzonej sytuacji?

    To też nie wydaje się właściwe. Wzory są używane w celu uproszczenia zrozumienia kodu; umieścić zbyt wiele wzorców, a kod stanie się bałaganem.

  • Czy zaczniesz myśleć o nowym projekcie, który obejmuje nowe wymagania, a następnie powoli przebudujesz stary projekt na nowy?

    Jako teoretyk i ten, który faworyzuje Agile, jestem całkowicie w to zaangażowany. W praktyce, gdy wiesz, że będziesz musiał co tydzień wracać do tablicy i przerabiać większą część poprzedniego projektu, a klient po prostu nie ma wystarczających środków, aby ci za to zapłacić, ani wystarczająco dużo czasu, aby czekać , to prawdopodobnie nie zadziała.

Jakieś sugestie?

Arseni Mourzenko
źródło
43
Myślę, że to fałszywy dylemat. Według przyznania twojego przyjaciela, kod jest teraz niemożliwym do utrzymania talerzem spaghetti. To nie jest wina wzorców oprogramowania; niezdolność twojego przyjaciela do prawidłowego użycia tych wzorców w sposób, który zwiększa łatwość utrzymania, a nie zmniejsza. Kiedy mogę wymyślić konkretne przykłady, opublikuję poprawną odpowiedź.
Robert Harvey
5
Ponadto, FWIW, każdy klient, który nie ma pewnej tolerancji dla kosztów dryfowania, prawdopodobnie nie powinien robić zwinnego, chyba że istnieją koszty wbudowane w koszty związane z przesunięciem wymagań.
Robert Harvey,
5
podejrzewam, że bez wzorców projektowych kod osiągnąłby stan niemożliwy do utrzymania znacznie wcześniej
Steven A. Lowe
28
To pytanie nie ma sensu. Jedynym sposobem na „uniknięcie wzorców projektowych” jest w ogóle nie pisanie oprogramowania. Rzeczy takie jak „wzorzec XYZ” to tylko nazwy nadane wspólnym strategiom kodowania, aby umożliwić nam programiście przekazywanie sobie nawzajem informacji i porad dotyczących naszej struktury kodu i wyborów. Każdemu wyborowi projektowemu w kodzie można nadać nazwę i nazwać go „wzorcem projektowym”, choć niekoniecznie powszechnie znanym (chyba że, jak sądzę, jesteś dumny ze swojego unikalnego projektu i wystarczająco zmotywowany, aby nadać mu nazwę to czy coś).
Jason C
9
Tj. Możesz uniknąć nazywania swojej stopy „stopą”, ale nadal masz tę stopę, po prostu trudniej jest z nią rozmawiać, jeśli nie nazwiesz jej „stopą”. Może ci się wydawać, że „unikasz wzorców projektowych”, ale jeśli wymyślisz przyzwoity projekt, który działa, to cofnij się i spójrz, prawdopodobnie okaże się, że twój projekt i tak pasował do jednego z popularnych nazwanych wzorów, czy tak to nazywacie, czy nie.
Jason C,

Odpowiedzi:

86

Widzę kilka błędnych założeń w tym pytaniu:

  • kod z wzorami projektowymi, chociaż zastosowany poprawnie, wymaga więcej czasu na wdrożenie niż kod bez tych wzorców.

Wzory projektowe nie są celem samym w sobie, powinny ci służyć, a nie odwrotnie. Jeśli wzorzec projektowy nie ułatwia implementacji kodu, a przynajmniej lepiej go rozwija (co oznacza: łatwiejszy do dostosowania do zmieniających się wymagań), wówczas wzorzec nie spełnia swojego celu. Nie stosuj wzorców, gdy nie ułatwiają „życia” zespołowi. Jeśli nowy wzorzec obiektu Null służył Twojemu znajomemu przez czas, w którym go używał, wszystko było w porządku. Jeśli miałoby to zostać później wyeliminowane, mogłoby to być również w porządku. Jeśli wzorzec obiektu zerowego spowolnił (poprawną) implementację, wówczas jego użycie było nieprawidłowe. Zauważ, że z tej części historii nie można do tej pory wyciągać wniosków na temat „kodu spaghetti”.

  • winny jest klient, ponieważ nie ma zaplecza technicznego i nie dba o spójność ani spójność produktu

To nie jest ani jego praca, ani jego wina! Twoim zadaniem jest dbanie o spójność i spójność. Gdy wymagania zmieniają się dwa razy dziennie, rozwiązaniem nie powinno być poświęcenie jakości kodu. Po prostu powiedz klientowi, jak długo to potrwa, a jeśli uważasz, że potrzebujesz więcej czasu, aby uzyskać właściwy projekt, dodaj wystarczająco duży margines bezpieczeństwa do każdej oceny. Zwłaszcza, gdy klient próbuje wywierać na ciebie presję, skorzystaj z „zasady Scotty” . A kiedy spieramy się z klientem nietechnicznym o wysiłku, unikaj pojęć takich jak „refaktoryzacja”, „testy jednostkowe”, „wzorce projektowe” lub „dokumentacja kodu” - to rzeczy, których nie rozumie i prawdopodobnie uważa za „niepotrzebne” bzdury ”, ponieważ nie widzi w tym żadnej wartości. lub przynajmniej zrozumiałe dla klienta (funkcje, funkcje podrzędne, zmiany zachowania, dokumenty użytkownika, poprawki błędów, optymalizacja wydajności itd.).

  • rozwiązaniem dla szybko zmieniających się wymagań jest szybka zmiana kodu

Szczerze mówiąc, jeśli „podstawowe interfejsy zmieniają się dwa razy dziennie przez trzy miesiące”, rozwiązaniem nie powinno być reagowanie poprzez zmianę kodu dwa razy dziennie. Prawdziwym rozwiązaniem jest pytanie, dlaczego wymagania zmieniają się tak często i czy można dokonać zmiany w tej części procesu. Może pomoże trochę więcej wstępnych analiz. Być może interfejs jest zbyt szeroki, ponieważ granica między komponentami jest źle wybrana. Czasami pomaga poprosić o więcej informacji na temat tego, która część wymagań jest stabilna, a która jest nadal przedmiotem dyskusji (i faktycznie opóźnia wdrożenie w odniesieniu do omawianych kwestii). A czasem niektórzy ludzie muszą zostać „wyrzuceni w tyłek” za to, że nie zmieniają zdania dwa razy dziennie.

Doktor Brown
źródło
30
+1 - nie możesz rozwiązać problemów ludzi za pomocą rozwiązań technicznych.
Telastyn
1
+1, ale „lub przynajmniej lepiej ewoluuje (to znaczy: łatwiej dostosować się do zmieniających się wymagań)” - kwalifikowałbym to z rozsądnie zmieniającymi się wymaganiami, prawda?
Fuhrmanator
1
@Fuhrmanator: Myślę, że trudno to omówić ogólnie. IMHO jest oczywiste, że żaden wzorzec projektowy nie pomoże ci, gdy twoim pierwszym wymaganiem jest „potrzebujemy edytora tekstu”, a to zmienia się w „potrzebujemy symulatora lotu”. W przypadku mniej drastycznych zmian wymagań nie zawsze łatwo jest zdecydować, co pomoże ci w rozwoju oprogramowania. Najlepszą rzeczą jest to, że IMHO nie stosuje zbyt wielu wzorców, ale niektóre zasady - głównie zasady SOLID i YAGNI. I zgadzam się w 100% z Telastyn - kiedy wymagania zmieniają się zbyt często, prawdopodobnie nie jest to problem techniczny.
Doc Brown
4
+1 - „Szczerze mówiąc, jeśli„ podstawowe interfejsy zmieniają się dwa razy dziennie przez trzy miesiące ”, wówczas rozwiązaniem nie powinno być reagowanie poprzez zmianę kodu dwa razy dziennie. Prawdziwe rozwiązanie polega na pytaniu, dlaczego wymagania zmieniają się tak często, a jeśli w tej części procesu można dokonać zmian. ”Jeśli ciągle otrzymujesz nowe wskazówki, najlepiej usiąść ze wszystkimi zainteresowanymi stronami i potwierdzić oczekiwania. Wypracuj swoje różnice i, miejmy nadzieję, nie marnuj czasu i pieniędzy wszystkich, dając projektowi wyraźniejszy cel.
krillgar
1
@Cornelius Doc Brown powiedział, że jest to trudne bez konkretów. Wymagania przejścia z edytora tekstu na symulator lotu nie byłyby rozsądne; żaden wzorzec projektowy nie pomógłby. Między jego przykładem jest mnóstwo szarej strefy i, powiedzmy, dodanie nowego formatu pliku do funkcji Zapisz edytora tekstów (co jest bardzo rozsądne). Bez szczegółów trudno dyskutować. Nie chodzi też o to, że nie chce się zmieniać. Chodzi o to, że takie zmiany są trudne, jeśli wcześniej dokonałeś wyboru projektu na podstawie wymagań. Edytor tekstu a symulator lotu to świetny przykład wczesnego wyboru.
Fuhrmanator,
43

Moim skromnym zdaniem jest to, że nie należy unikać wzorców projektowych ani ich unikać.

Wzory projektowe są po prostu dobrze znanymi i zaufanymi rozwiązaniami ogólnych problemów, którym nadano nazwy. Nie różnią się one pod względem technicznym od innych rozwiązań lub projektów, o których można pomyśleć.

Myślę, że źródłem problemu może być to, że twój przyjaciel myśli w kategoriach „zastosowania lub niestosowania wzorca projektowego”, zamiast myślenia w kategoriach „najlepszego rozwiązania, jakie mogę wymyślić, w tym między innymi wzorców Wiem".

Być może takie podejście prowadzi go do używania wzorów w częściowo sztuczny lub wymuszony sposób, w miejscach, do których nie należą. I to powoduje bałagan.

Aviv Cohn
źródło
13
+1 „Wzorce projektowe są po prostu dobrze znanymi i zaufanymi rozwiązaniami ogólnych problemów, którym nadano nazwy. Nie różnią się one pod względem technicznym od innych rozwiązań lub projektów, o których można pomyśleć.” Dokładnie. Ludzie są tak pochłonięci nazwanymi wzorami projektowymi, że zapominają, że są to tylko nazwy nadane różnym strategiom, aby ułatwić nam programistom komunikowanie się ze sobą na temat naszego kodu i naszych wyborów projektowych. Takie podejście bardzo często wiąże się z próbą wymuszenia niewłaściwych „wzorów” na problemach, które niekoniecznie przynoszą korzyści - wielki bałagan.
Jason C,
14

W twoim przykładzie użycia wzorca Null Object uważam, że ostatecznie się nie udał, ponieważ spełnił potrzeby programisty, a nie potrzeby klienta. Klient musiał wyświetlić cenę w formie odpowiedniej do kontekstu. Programista musiał uprościć część kodu wyświetlacza.

A zatem, jeśli wzór nie spełnia wymagań, czy mówimy, że wszystkie wzory są stratą czasu, czy mówimy, że potrzebujemy innego wzoru?

BobDalgleish
źródło
9

Wygląda na to, że błędem było raczej usunięcie obiektów wzoru niż ich użycie. W pierwotnym projekcie wydaje się, że obiekt zerowy dostarczył rozwiązanie problemu. To może nie być najlepsze rozwiązanie.

Jako jedyna osoba pracująca nad projektem masz szansę doświadczyć całego procesu rozwoju. Dużą wadą jest brak kogoś, kto będzie twoim mentorem. Poświęcenie czasu na naukę i stosowanie najlepszych lub lepszych praktyk może się szybko zwrócić. Sztuczka polega na określeniu, której praktyki się nauczyć.

Łańcuchy referencyjne w formie product.Price.toString („c”) naruszają prawo Demeter . Widziałem wiele problemów związanych z tą praktyką, z których wiele dotyczy zer. Metoda taka jak product.displayPrice („c”) może wewnętrznie obsługiwać ceny zerowe. Podobnie product.Description.Current może być obsługiwany przez product.displayDescription (), product.displayCurrentDescription (). lub product.diplay („Bieżący”).

Reakcja na nowy wymóg dla menedżerów i dostawców treści musi być realizowana poprzez reagowanie na kontekst. Istnieje wiele różnych podejść, które można zastosować. Metody fabryczne mogą wykorzystywać różne klasy produktów w zależności od klasy użytkownika, w której będą wyświetlane. Innym podejściem byłoby opracowanie metod wyświetlania klas produktów w celu tworzenia różnych danych dla różnych użytkowników.

Dobra wiadomość jest taka, że ​​przyjaciel zdaje sobie sprawę, że sprawy wymykają się spod kontroli. Mam nadzieję, że ma kod w kontroli wersji. Pozwoli mu to wycofać się z złych decyzji, które niezmiennie będzie podejmował. Częścią uczenia się jest wypróbowanie różnych metod, z których niektóre się nie powiodą. Jeśli uda mu się poradzić sobie przez kilka następnych miesięcy, może znaleźć rozwiązania upraszczające jego życie i posprzątać spaghetti. Mógłby spróbować naprawić jedną rzecz każdego tygodnia.

BillThor
źródło
2
Może to również wskazywać, widząc, że „musisz” złamać prawo Demeter, że model zastosowany na powierzchni był nieodpowiedni. Dlaczego „model widoku” (używany w luźnym znaczeniu) ma nie tylko opis do wyświetlenia? (Tj. Dlaczego jest coś więcej niż bieżący opis na poziomie interfejsu użytkownika?) Warstwa biznesowa może przygotować odpowiednio wypełniony obiekt do warstwy interfejsu użytkownika, który ma już inną zawartość w zależności od tego, czy jest to menedżer, czy nie.
Cornelius
7

Pytanie wydaje się błędne w tak wielu punktach. Ale te rażące to:

  • Dla wzorca wzorca o wartości Null, o którym wspomniałeś, po zmianie wymagań zmienisz trochę kodu. W porządku, ale to nie znaczy, że „zamordujesz” Null Object Pattern (nawiasem mówiąc, bądź ostrożny ze swoim brzmieniem, to brzmi zbyt ekstremalnie, niektórzy zbyt paranoiczni nie uznają tego za zabawne).

Wiele osób słusznie stwierdziło, że wzorce projektowe w dużej mierze dotyczą etykietowania i nazywania powszechnej praktyki. Pomyśl więc o koszuli, koszula ma kołnierzyk, z jakiegoś powodu zdejmujesz kołnierz lub jego część. Nazwy i etykiety zmieniają się, ale nadal jest to w istocie koszula. Dokładnie tak jest tutaj, drobne zmiany w szczegółach, co nie oznacza, że ​​„zamordowałeś” ten wzór. (znowu pamiętaj o ekstremalnych sformułowaniach)

  • Projekt, o którym mówiłeś, jest zły, ponieważ gdy zmiany wymagań pojawiają się jako drobne rzeczy, wprowadzasz ogromne strategiczne zmiany projektowe w różnych miejscach. O ile zmiany problemów biznesowych na wysokim poziomie nie ulegną zmianie, nie można usprawiedliwić wprowadzania dużych zmian w projekcie.

Z mojego doświadczenia wynika, że ​​gdy pojawiają się niewielkie wymagania, wystarczy zmienić tylko niewielką część bazy kodu. Niektóre mogą być nieco zuchwałe, ale nic zbyt poważnego, aby znacząco wpłynąć na łatwość konserwacji lub czytelność, a często wystarczy kilka linii komentarza, aby wyjaśnić hackerską część. Jest to również bardzo powszechna praktyka.

Poinformowano
źródło
7

Zatrzymajmy się na chwilę i spójrzmy na podstawową kwestię tutaj - zaprojektowanie systemu, w którym model architektury jest zbyt sprzężony z funkcjami niskiego poziomu w systemie, powodując częstą awarię architektury w procesie programowania.

Myślę, że musimy pamiętać, że wykorzystanie architektury i wzorców projektowych z tym związanych należy ułożyć na odpowiednim poziomie, a analiza właściwego poziomu nie jest trywialna. Z jednej strony możesz łatwo utrzymać architekturę systemu na zbyt wysokim poziomie z jedynie bardzo podstawowymi ograniczeniami, takimi jak „MVC” lub podobnymi, co może prowadzić do utraty szans, takich jak jasne wytyczne i dźwignia kodu oraz gdzie kod spaghetti może łatwo rozkwitać w całej tej wolnej przestrzeni.

Z drugiej strony możesz równie dobrze nad-architekturować swój system, jak ustawiając ograniczenia na szczegółowy poziom, przy założeniu, że możesz polegać na ograniczeniach, które w rzeczywistości są bardziej niestabilne, niż się spodziewasz, stale przełamując ograniczenia i zmuszając cię do ciągłej przebudowy i odbudowy, aż zaczniesz rozpaczać.

Zmiany wymagań dotyczących systemu zawsze będą istnieć, w mniejszym lub większym stopniu. Potencjalne korzyści wynikające ze stosowania architektury i wzorców projektowych zawsze będą istnieć, więc tak naprawdę nie ma kwestii korzystania z wzorców projektowych, ale na jakim poziomie powinieneś ich używać.

Wymaga to nie tylko zrozumienia aktualnych wymagań proponowanego systemu, ale także określenia, jakie aspekty można uznać za stabilne podstawowe właściwości systemu i jakie właściwości mogą ulec zmianie w trakcie rozwoju.

Jeśli okaże się, że ciągle musisz walczyć z niezorganizowanym kodem spaghetti, prawdopodobnie nie robisz wystarczającej architektury lub na wysokim poziomie. Jeśli zauważysz, że twoja architektura często się psuje, prawdopodobnie robisz zbyt szczegółową architekturę.

Wykorzystanie architektury i wzorów projektowych nie jest czymś, co można po prostu „pokryć” systemem, tak jakbyś malował biurko. Są to techniki, które należy stosować z rozwagą, na poziomie, na którym ograniczenia muszą polegać, mają dużą możliwość zachowania stabilności, i gdzie te techniki rzeczywiście są warte kłopotu z modelowaniem architektury i wdrażaniem rzeczywistych ograniczeń / architektury / wzorców jako kod.

Odnosząc się do kwestii zbyt szczegółowej architektury, równie dobrze można włożyć dużo wysiłku w architekturę, w której nie przynosi ona dużej wartości. Zobacz architekturę opartą na ryzyku w celach informacyjnych, podoba mi się ta książka - wystarczy architektury oprogramowania , może ty też.

Edytować

Wyjaśniłem swoją odpowiedź, ponieważ zdałem sobie sprawę, że często wyrażałem się jako „zbyt dużo architektury”, gdzie naprawdę miałem na myśli „zbyt szczegółową architekturę”, co nie jest dokładnie takie samo. Zbyt szczegółowa architektura może być często postrzegana jako „zbyt duża” architektura, ale nawet jeśli utrzymasz architekturę na dobrym poziomie i stworzysz najpiękniejszy system, jaki ludzkość kiedykolwiek widziała, może to być zbyt duży wysiłek w architekturze, jeśli jej priorytetami są na temat funkcji i czasu wprowadzenia na rynek.

Alex
źródło
+1 Są to bardzo dobre przemyślenia na temat bardzo wysokiego poziomu. Myślę, że musisz zajrzeć do systemu, ale jak powiedziałeś, wymaga to dużego doświadczenia w projektowaniu oprogramowania.
Samuel
4

Twój przyjaciel wydaje się mieć do czynienia z licznymi przeciwnościami wiatru na podstawie swojej anegdoty. Jest to niefortunne i może być bardzo trudnym środowiskiem do pracy. Mimo trudności był na właściwej ścieżce, używając wzorów, aby ułatwić sobie życie, i szkoda, że ​​opuścił tę ścieżkę. Kod spaghetti to najlepszy wynik.

Ponieważ istnieją dwa różne obszary problemów, techniczne i interpersonalne, zajmę się każdym z nich osobno.

Interpersonalne

Zmagania twojego przyjaciela dotyczą szybko zmieniających się wymagań i tego, jak wpływa to na jego zdolność do pisania łatwego do utrzymania kodu. Najpierw powiedziałbym, że wymagania zmieniające się dwa razy dziennie, każdego dnia przez tak długi okres czasu, stanowią większy problem i mają nierealistyczne domniemane oczekiwania. Wymagania zmieniają się szybciej niż kod może się zmienić. Nie możemy oczekiwać, że kod lub programista dotrzymają kroku. To szybkie tempo zmian jest objawem niepełnej koncepcji pożądanego produktu na wyższym poziomie. To jest problem. Jeśli nie wiedzą, czego naprawdę chcą, będą marnować dużo czasu i pieniędzy, aby nigdy tego nie uzyskać.

Dobrze jest ustawić granice zmian. Grupuj razem zmiany w zestawy co dwa tygodnie, a następnie zamrażaj je na dwa tygodnie podczas ich wdrażania. Utwórz nową listę na następne dwa tygodnie. Mam wrażenie, że niektóre z tych zmian nakładają się lub są ze sobą sprzeczne (np. Wahanie między dwiema opcjami). Kiedy zmiany nadchodzą szybko i wściekle, wszystkie mają najwyższy priorytet. Jeśli pozwolisz im zgromadzić się na liście, możesz z nimi współpracować w celu uporządkowania i ustalenia priorytetów tego, co najważniejsze, aby zmaksymalizować wysiłki i wydajność. Mogą zobaczyć, że niektóre z ich zmian są głupie lub mniej ważne, co daje Twojemu przyjacielowi chwilę wytchnienia.

Te problemy nie powinny jednak powstrzymywać Cię przed pisaniem dobrego kodu. Zły kod prowadzi do gorszych problemów. Refaktoryzacja jednego rozwiązania może wymagać czasu, ale sam fakt, że jest to możliwe, pokazuje zalety dobrych praktyk kodowania dzięki wzorcom i zasadom.

W warunkach częstych zmian w pewnym momencie pojawi się dług techniczny . O wiele lepiej jest dokonywać płatności w tym kierunku niż wrzucać ręcznik i czekać, aż stanie się zbyt duży, aby go pokonać. Jeśli wzorzec nie jest już użyteczny, przerób go, ale nie wracaj do kodowania kowbojów.

Techniczny

Twój przyjaciel zdaje się dobrze orientować w podstawowych wzorach projektowych. Obiekt zerowy to dobre podejście do problemu, z którym się borykał. W rzeczywistości jest to nadal dobre podejście. Tam, gdzie wydaje się, że ma on wyzwania, rozumie zasady leżące u podstaw tych wzorów, dlaczego tak naprawdę są. W przeciwnym razie nie sądzę, by porzucił swoje podejście.

(Poniżej znajduje się zestaw rozwiązań technicznych, o które nie pytano w pierwotnym pytaniu, ale które pokazują, w jaki sposób możemy zastosować wzorce w celach ilustracyjnych).

Zasada stojąca za obiektem zerowym polega na kapsułkowaniu tego, co różne . Ukrywamy jakie zmiany, więc nie musimy zajmować się tym wszędzie. W tym przypadku obiekt zerowy zawierał wariancję w product.Priceinstancji (nazwiemy go Priceobiektem, a cena obiektu zerowego będzie wynosić NullPrice). Priceto obiekt domeny, koncepcja biznesowa. Czasami w naszej logice biznesowej nie znamy jeszcze ceny. To się stało. Idealny przypadek użycia dla obiektu zerowego. Prices mają ToStringmetodę, która wyprowadza cenę lub pusty ciąg, jeśli nie jest znany (lub NullPrice#ToStringzwraca pusty ciąg). To rozsądne zachowanie. Potem zmieniają się wymagania.

Musimy wyprowadzić nulldo widoku API lub inny ciąg do widoku menedżerów. Jak to wpływa na naszą logikę biznesową? Tak nie jest. W powyższym stwierdzeniu dwukrotnie użyłem słowa „widok”. To słowo prawdopodobnie nie zostało wypowiedziane wprost, ale musimy ćwiczyć, aby usłyszeć ukryte słowa w wymaganiach. Dlaczego więc „widok” ma tak duże znaczenie? Ponieważ mówi nam, gdzie naprawdę musi nastąpić zmiana: naszym zdaniem.

Poza tym : to, czy używamy frameworka MVC, nie ma tutaj znaczenia. Chociaż MVC ma bardzo specyficzne znaczenie dla „Widoku”, używam go w bardziej ogólnym (i być może bardziej stosownym) znaczeniu fragmentu kodu prezentacji.

Naprawdę musimy to naprawić w widoku. Jak możemy to zrobić? Najłatwiejszym sposobem na to byłoby ifstwierdzenie. Wiem, że obiekt zerowy miał pozbyć się wszystkich ifs, ale musimy być pragmatyczni. Możemy sprawdzić ciąg znaków, aby zobaczyć, czy jest pusty, i przełączyć:

if(product.Price.ToString("c").Length == 0) { // one way of many
    writer.write("Price unspecified [Change]");
} else {
    writer.write(product.Price.ToString("c"));
}

Jak to wpływa na enkapsulację? Najważniejsze jest tutaj to, że logika widoku jest zamknięta w widoku . W ten sposób możemy całkowicie odizolować nasze obiekty logiki biznesowej / domeny od zmian w logice widoku. Jest brzydka, ale działa. Nie jest to jednak jedyna opcja.

Moglibyśmy powiedzieć, że nasza logika biznesowa zmieniła się tylko trochę, ponieważ chcemy wyprowadzać domyślne łańcuchy, jeśli nie zostanie ustawiona żadna cena. Możemy dokonać drobnych poprawek w naszej Price#ToStringmetodzie (faktycznie stworzyć przeciążoną metodę). Możemy zaakceptować domyślną wartość zwrotu i zwrócić, że jeśli nie zostanie ustalona cena:

class Price {
    ...
    // A new ToString method
    public string ToString(string c, string default) {
        return ToString(c);
    }
    ...
}

class NullPrice {
    ...
    // A new ToString method
    public string ToString(string c, string default) {
        return default;
    }
    ...
}

A teraz nasz kod widoku staje się:

writer.write(product.Price.ToString("c", "Price unspecified [Change]"));

Warunek zniknął. Jednak zbyt częste wykonywanie tej czynności może spowodować rozprzestrzenianie specjalnych metod tworzenia przypadków w obiektach domeny, więc ma to sens tylko wtedy, gdy wystąpi tylko kilka takich przypadków.

Zamiast tego możemy utworzyć IsSetmetodę, Pricektóra zwraca wartość logiczną:

class Price {
    ...
    public bool IsSet() {
        return return true;
    }
    ...
}

class NullPrice {
    ...
    public bool IsSet() {
        return false;
    }
    ...
}

Zobacz logikę:

if(product.Price.IsSet()) {
    writer.write(product.Price.ToString("c"));
} else {
    writer.write("Price unspecified [Change]");
}

Widzimy powrót warunku w widoku, ale sprawa jest silniejsza w przypadku logiki biznesowej, która mówi, czy cena jest ustawiona. Teraz możemy go używać Price#IsSetgdzie indziej, skoro mamy go dostępnego.

Wreszcie możemy zawrzeć pomysł przedstawienia ceny całkowicie w pomocniku dla widoku. To ukryłoby warunek, jednocześnie zachowując obiekt domeny tak bardzo, jak byśmy tego chcieli:

class PriceStringHelper {
    public PriceStringHelper() {}

    public string PriceToString(Price price, string default) {
        if(price.IsSet()) { // or use string length to not change the Price class at all
           return price.ToString("c");
        } else {
            return default;
        }
    }
}

Zobacz logikę:

writer.write(new PriceStringHelper().PriceToString(product.Price, "Price unspecified [Change]"));

Istnieje wiele innych sposobów dokonywania zmian (możemy uogólnić PriceStringHelperna obiekt, który zwraca wartość domyślną, jeśli ciąg jest pusty), ale są to kilka szybkich, które zachowują (w przeważającej części) zarówno wzorce, jak i zasady, ponieważ a także pragmatyczny aspekt dokonania takiej zmiany.

Cbojar
źródło
3

Złożoność wzoru projektowego może cię ugryźć, jeśli problem, który miał rozwiązać, nagle zniknie. Niestety, ze względu na entuzjazm i popularność wzorców projektowych, ryzyko to rzadko jest jawne. Anegdota twojego przyjaciela bardzo pomaga pokazać, w jaki sposób wzory się nie opłacają. Jeff Atwood ma kilka wybranych słów na ten temat.

Dokumentuj punkty zmienności (są to ryzyka) w wymaganiach

Wiele bardziej złożonych wzorców projektowych (nie tyle Null Object) zawiera koncepcję odmian chronionych , to znaczy „Zidentyfikuj punkty przewidywanej zmienności lub niestabilności; przydziel obowiązki, aby stworzyć wokół nich stabilny interfejs”. Adapter, gość, fasada, warstwy, obserwator, strategia, dekorator itp. Wykorzystują tę zasadę. „Opłacają się”, gdy oprogramowanie musi zostać rozszerzone w zakresie oczekiwanej zmienności, a „stabilne” założenia pozostają stabilne.

Jeśli twoje wymagania są tak niestabilne, że twoje „przewidywane odmiany” są zawsze błędne, wówczas zastosowane wzorce spowodują ból lub co najwyżej niepotrzebną złożoność.

Craig Larman mówi o dwóch możliwościach zastosowania odmian chronionych:

  • punkty zmienności - w istniejącym, bieżącym systemie lub wymaganiach, takich jak wiele interfejsów, które muszą być obsługiwane, oraz
  • punkty ewolucji - spekulacyjne punkty zmienności, które nie występują w istniejących wymaganiach.

Oba powinny być udokumentowane przez programistów, ale prawdopodobnie powinieneś mieć zaangażowanie klientów w punkty zmienności.

Aby zarządzać ryzykiem, można powiedzieć, że każdy wzór projektowy wykorzystujący PV powinien być przypisany do punktu zmienności wymagań podpisanych przez klienta. Jeśli klient zmieni punkt zmienności w wymaganiach, Twój projekt może wymagać radykalnej zmiany (ponieważ prawdopodobnie zainwestowałeś w projekt [wzorce] w celu obsługi tej zmiany). Nie trzeba wyjaśniać spójności, sprzężenia itp.

Na przykład klient chce, aby oprogramowanie współpracowało z trzema różnymi starszymi systemami inwentaryzacji. To punkt zmienności, który projektujesz. Jeśli klient nie spełni tego wymogu, to oczywiście masz wiele niepotrzebnej infrastruktury projektowej. Klient musi wiedzieć, że punkty zmienności kosztują coś.

CONSTANTS w kodzie źródłowym są prostą formą PV

Inną analogią do twojego pytania byłoby pytanie, czy CONSTANTSdobrym pomysłem jest użycie w kodzie źródłowym. Nawiązując do tego przykładu , powiedzmy, że klient zrezygnował z hasła. Tak więc MAX_PASSWORD_SIZEciągłe rozprzestrzenianie się w całym kodzie stałoby się bezużyteczne, a nawet utrudniałoby konserwację i czytelność. Czy obwiniłbyś użycie CONSTANTStego powodu?

Fuhrmanator
źródło
2

Myślę, że przynajmniej częściowo zależy to od charakteru twojej sytuacji.

Wspominałeś o ciągle zmieniających się wymaganiach. Jeśli klient mówi: „Chcę, aby ta aplikacja do pszczół działała również z osami”, wydaje się, że to taka sytuacja, w której staranne projektowanie pomogłoby w rozwoju, a nie utrudniało (szczególnie, jeśli weźmie się pod uwagę, że w przyszłości może chcieć trzymaj też muszki owocowe).

Z drugiej strony, jeśli charakter zmiany przypomina bardziej: „Chcę, aby ta aplikacja pszczół zarządzała listą płac mojego konglomeratu pralni”, żadna ilość kodu nie wykopie cię z dziury.

Wzory projektowe nie mają z natury nic dobrego. Są narzędziami jak każde inne - używamy ich tylko w celu ułatwienia naszej pracy w średnim i długim okresie. Jeśli inne narzędzie (takie jak komunikacja lub badania ) jest bardziej przydatne, wówczas go używamy.

Benjamin Hodgson
źródło