Co powinno mieć pierwszeństwo: YAGNI lub Dobry Design?

76

W którym momencie YAGNI powinien mieć pierwszeństwo przed dobrymi praktykami kodowania i odwrotnie? Pracuję nad projektem w pracy i chcę powoli wprowadzać dobre standardy kodu dla moich współpracowników (obecnie ich nie ma i wszystko jest po prostu zhakowane razem bez rymowania i powodu), ale po utworzeniu serii klas (my nie wykonuj TDD ani, niestety, żadnych testów jednostkowych) Cofnąłem się o krok i pomyślałem, że to narusza YAGNI, ponieważ jestem pewien, że nie potrzebujemy rozszerzać niektórych z tych klas.

Oto konkretny przykład tego, co mam na myśli: mam warstwę dostępu do danych owijającą zestaw procedur przechowywanych, która wykorzystuje podstawowy wzorzec w stylu Repozytorium z podstawowymi funkcjami CRUD. Ponieważ istnieje kilka metod, których potrzebują wszystkie moje klasy repozytoriów, stworzyłem ogólny interfejs dla moich repozytoriów, zwany IRepository. Jednak potem stworzyłem interfejs „znacznika” (tj. Interfejs, który nie dodaje żadnej nowej funkcjonalności) dla każdego typu repozytorium (np. ICustomerRepository) I konkretna klasa go implementuje. Zrobiłem to samo z implementacją Factory, aby zbudować obiekty biznesowe z DataReaders / DataSets zwróconych przez procedurę przechowywaną; sygnatura mojej klasy repozytorium wygląda mniej więcej tak:

public class CustomerRepository : ICustomerRepository
{
    ICustomerFactory factory = null;

    public CustomerRepository() : this(new CustomerFactory() { }

    public CustomerRepository(ICustomerFactory factory) {
        this.factory = factory;
    }      

    public Customer Find(int customerID)
    {
        // data access stuff here
        return factory.Build(ds.Tables[0].Rows[0]);
    }
}

Obawiam się tutaj, że naruszam YAGNI, ponieważ wiem z 99% pewnością, że nigdy nie będzie powodu, aby dać CustomerFactorytemu repozytorium cokolwiek innego niż konkret ; ponieważ nie mamy testów jednostkowych, nie potrzebuję MockCustomerFactoryani podobnych rzeczy, a posiadanie tak wielu interfejsów może dezorientować moich współpracowników. Z drugiej strony zastosowanie konkretnej implementacji fabryki wydaje się zapachem projektowym.

Czy istnieje dobry sposób na kompromis między właściwym projektowaniem oprogramowania a niewystarczającą ochroną rozwiązania? Pytam, czy muszę mieć wszystkie „interfejsy pojedynczej implementacji”, czy też mógłbym poświęcić trochę dobrego projektu i po prostu mieć na przykład interfejs podstawowy, a następnie pojedynczy konkretny, i nie martwić się o programowanie w interfejs, jeśli implementacja będzie kiedykolwiek używana.

Wayne Molina
źródło
17
Mówisz „skoro nie mamy testów jednostkowych, nie potrzebuję MockX”, co oczywiście prowadzi do „Nie potrzebuję IX, potrzebuję tylko X”. Odwracam to, fakt, że nie masz testów jednostkowych, podkreśla fakt, że potrzebujesz IX i MockX, ponieważ te rzeczy pomogą ci przeprowadzić testy jednostkowe. Nie akceptuj rzeczywistości braku testów, traktuj to jako tymczasowy problem, który zostanie naprawiony przez (prawdopodobnie dobry, długi) czas.
Anthony Pegram,
10
Chociaż google jest trywialne, ktoś powinien wspomnieć, że YAGNI oznacza „Nie będziesz go potrzebować”
thedaian
1
Pomyślałbym, że jeśli piszesz takie nowe klasy, chciałbyś dodać kilka testów jednostkowych. Nawet jeśli twoi współpracownicy ich nie uruchomią. Przynajmniej później możesz powiedzieć: „Zobacz! Moje testy jednostkowe złapały go, gdy złamałeś mój kod! Zobacz, jak świetne są testy jednostkowe!” W takim przypadku warto tu naśmiewać się. (Chociaż wolałbym, aby obiekt można było wyśmiewać bez definiowania interfejsu)
Winston Ewert
Czy testy jednostkowe nie zmusiłyby mnie do stworzenia (lub użycia) fałszywego frameworka, aby nie uruchamiać procedur przechowywanych na żywo? To jest główny powód, dla którego zwykle nie dodam testów - każdy z nas ma lokalną kopię produkcyjnej bazy danych, na której testujemy i piszemy kod.
Wayne Molina
3
@Anthony A czy kpiny zawsze usprawiedliwiają dodatkowy narzut związany z nimi? Wyśmiewanie jest dobrym narzędziem, ale jego przydatność również musi być wyważona względem kosztów, a czasami skala jest przechylana w drugą stronę przez nadmiar pośredni. Jasne, istnieją narzędzia, które pomogą w dodatkowej złożoności, ale nie spowodują, że złożoność zniknie. Wydaje się, że istnieje rosnący trend traktowania „testowania za wszelką cenę”. Wierzę, że to nieprawda.
Konrad Rudolph,

Odpowiedzi:

75

Czy istnieje dobry sposób na kompromis między właściwym projektowaniem oprogramowania a niewystarczającą ochroną rozwiązania?

YAGNI.

Mógłbym poświęcić trochę dobrego projektu

Fałszywe założenie.

i interfejs podstawowy, a następnie pojedynczy beton,

To nie jest „ofiara”. Że to dobry projekt.

S.Lott
źródło
72
Doskonałość osiąga się nie wtedy, gdy nie ma już nic do dodania, ale kiedy nie ma już nic do zabrania. Antoine De St-Exupery
Newtopian
5
@Newtopian: ludzie mają mieszane odczucia co do najnowszych produktów Apple. :)
Roy Tinker
14
Mam ogromny problem z tego rodzaju odpowiedziami. Czy „X jest prawdą, ponieważ Y to mówi, a Y jest dobrze poparte” uzasadnieniem społeczności? Jeśli ktoś w prawdziwym życiu sprzeciwiałby się YAGNI, czy dałbyś mu tę odpowiedź jako argument?
vemv
2
@vemv. Nikt tutaj nie sprzeciwia się YAGNI. Wszystko, co powiedział S.Lott, to to, że postrzegana przez OP sprzeczność między 2 godnymi celami jest błędem. BTW, czy znasz jakiegoś aktywnego programistę w dzisiejszym świecie oprogramowania, który sprzeciwiałby się YAGNI? Jeśli pracujesz nad prawdziwymi projektami, wiesz, że wymagania ciągle się zmieniają, a użytkownicy i menedżerowie na ogół nie wiedzą, co robią lub czego nie chcą, dopóki ich nie przedstawisz. Wdrażanie tego, co konieczne, jest BARDZO pracą - po co tracić czas, energię i marnować pieniądze (lub ryzykować swoją pracę), pisząc kod, który próbuje przewidzieć przyszłość?
Wektor
6
Nie chodzi mi o YAGNI - chodzi o jakość odpowiedzi. Nie twierdzę, że ktoś w szczególności rantował, to było częścią mojego rozumowania. Przeczytaj to jeszcze raz.
vemv
74

W większości przypadków unikanie kodu, którego nie będziesz potrzebować, prowadzi do lepszego projektowania. Najbardziej konserwowalny i przyszłościowy projekt to taki, który wykorzystuje najmniejszą ilość dobrze nazwanego, prostego kodu, który spełnia wymagania.

Najprostsze projekty są najłatwiejsze do rozwinięcia. Nic nie zabija łatwości utrzymania, jak bezużyteczne, przepracowane warstwy abstrakcji.

Michael Borgwardt
źródło
8
Uważam, że „unikanie kodu YAGNI” jest denerwująco dwuznaczne. Może to oznaczać kod, którego nie będziesz potrzebować, lub kod zgodny z zasadą YAGNI . (Por. „Kod KISS”)
patrz
2
@sehe: To wcale nie jest dwuznaczne (podczas gdy „przestrzeganie zasady YAGNI” jest wprost sprzeczne z sobą), jeśli przeliterujesz: co może oznaczać „Nie potrzebujesz kodu”, ale kod nie będziesz potrzebować?
Michael Borgwardt,
1
Wydrukuję tę odpowiedź dużą czcionką i powiesię ją tam, gdzie mogą ją przeczytać wszyscy architekci. +1!
kirk.burleson
1
+1. Nadal pamiętam mój pierwszy projekt korporacyjny, który otrzymałem w celu wsparcia i dodania nowych funkcji. Pierwszą rzeczą, jaką zrobiłem było usunąć 32.000 linii kodu z bezużytecznego programu 40000 linii, nie tracąc żadnej funkcjonalności. Oryginalny programator został zwolniony wkrótce potem.
EJ Brennan,
4
@vemv: czytaj „bezużyteczny” jako „nieużywany obecnie, z wyjątkiem trywialnych”, tj. przypadek YAGNI. A „nadinżynieria” jest nieco bardziej szczegółowa niż „zła”. W szczególności oznacza to „złożoność spowodowaną koncepcjami teoretycznymi lub wyobrażonymi możliwymi wymaganiami, a nie konkretnymi, bieżącymi wymaganiami”.
Michael Borgwardt,
62

YAGNI i SOLID (lub dowolna inna metodologia projektowania) nie wykluczają się wzajemnie. Są to jednak przeciwieństwa prawie biegunowe. Nie musisz przestrzegać 100%, ale będzie trochę dawania i przyjmowania; im więcej patrzysz na wysoce abstrakcyjny wzór używany przez jedną klasę w jednym miejscu, i mówisz YAGNI i upraszczasz, tym mniej SOLIDNY ​​staje się projekt. Odwrotna może być również prawda; wiele razy w fazie rozwoju projekt jest wdrażany SOLIDNIE „na wiarę”; nie widzisz, jak będziesz tego potrzebować, ale masz przeczucie. Może to być prawda (i jest coraz bardziej prawdopodobne, że im więcej zdobędziesz doświadczenia), ale może także narazić cię na tak duże zadłużenie techniczne, jak podejście „zrób to lekko”; zamiast bazy kodu „kod spaghetti” DIL, możesz otrzymać „kod lasagna”, posiadanie tak wielu warstw, że po prostu dodanie metody lub nowego pola danych staje się całodniowym procesem brodzenia przez serwery proxy i luźno powiązane zależności za pomocą tylko jednej implementacji. Lub możesz skończyć z „kodem ravioli”, który jest w tak małych kawałkach wielkości kęsa, że ​​poruszanie się w górę, w dół, w lewo lub w prawo w architekturze prowadzi przez 50 metod z 3 liniami każda.

Powiedziałem to w innych odpowiedziach, ale oto jest: Przy pierwszym przejściu spraw, by działało. Przy drugim przejściu spraw, aby był elegancki. Przy trzecim przejściu ustaw SOLID.

Podział tego:

Kiedy po raz pierwszy piszesz wiersz kodu, musi on po prostu działać. W tym momencie, jak wiadomo, jest to jednorazowe. Tak więc nie dostajesz żadnych punktów stylu za budowę architektury „wieży z kości słoniowej”, aby dodać 2 i 2. Rób to, co musisz zrobić i zakładaj, że nigdy więcej go nie zobaczysz.

Gdy następnym razem kursor znajdzie się w tym wierszu kodu, odrzuciłeś swoją hipotezę od momentu jej pierwszego napisania. Ponownie odwiedzasz ten kod, prawdopodobnie go rozszerzysz lub użyjesz go w innym miejscu, więc nie jest to jednorazowy przypadek. Teraz należy zaimplementować niektóre podstawowe zasady, takie jak DRY (nie powtarzaj się) i inne proste reguły projektowania kodu; wyodrębniaj metody i / lub formuj pętle dla powtarzającego się kodu, wydobywaj zmienne dla typowych literałów lub wyrażeń, być może dodaj kilka komentarzy, ale ogólnie twój kod powinien sam dokumentować. Teraz twój kod jest dobrze zorganizowany, nawet jeśli może być ściśle powiązany, a każdy, kto na niego patrzy, może łatwo dowiedzieć się, co robisz, czytając kod, zamiast śledzić go wiersz po wierszu.

Za trzecim razem, gdy kursor wpisze ten kod, jest to prawdopodobnie wielka sprawa; albo przedłużasz go jeszcze raz, albo staje się on użyteczny w co najmniej trzech różnych innych miejscach w bazie kodu. W tym momencie jest to kluczowy, jeśli nie podstawowy, element twojego systemu i powinien być tak zaprojektowany. W tym momencie zazwyczaj masz również wiedzę o tym, jak dotychczas był używany, co pozwoli Ci podejmować dobre decyzje projektowe dotyczące tego, jak zaprojektować projekt, aby usprawnić te zastosowania i wszelkie nowe. Teraz reguły SOLID powinny wprowadzić równanie; wyodrębnij klasy zawierające kod o określonych celach, zdefiniuj wspólne interfejsy dla dowolnych klas o podobnych celach lub funkcjach, skonfiguruj luźno powiązane zależności między klasami i zaprojektuj zależności, które można łatwo dodawać, usuwać lub zamieniać.

Od tego momentu, jeśli trzeba będzie dalej rozszerzać, reimplementować lub ponownie wykorzystywać ten kod, wszystko jest ładnie zapakowane i wyodrębnione w formacie „czarnej skrzynki”, który wszyscy znamy i kochamy; podłącz go tam, gdzie jest to potrzebne, lub dodaj nową odmianę motywu jako nową implementację interfejsu bez konieczności zmiany użycia tego interfejsu.

KeithS
źródło
Popieram niesamowitość.
Filip Dupanović,
2
Tak. Kiedy zwykłem projektować do ponownego użycia / rozszerzenia z góry, stwierdziłem, że kiedy chcę ponownie użyć lub rozszerzyć, będzie to w inny sposób, niż się spodziewałem. Prognozy są trudne, zwłaszcza jeśli chodzi o przyszłość. Dlatego popieram twoją zasadę 3 uderzeń - do tego czasu masz rozsądny pomysł, w jaki sposób zostanie ona ponownie wykorzystana / przedłużona. Uwaga: wyjątkiem jest sytuacja, gdy już wiesz, jak to będzie (np. Z poprzednich projektów, wiedzy o domenie lub już określone).
13ren,
Na czwartym przejeździe spraw, by było NIESAMOWITE.
Richard Neil Ilagan
@KeithS: Wygląda na to, że John Carmack zrobił coś podobnego: „kod źródłowy Quake II ... ujednolic Quake 1, Quake World i QuakeGL w jedną piękną architekturę kodu”. fabiensanglard.net/quake2/index.php
13ren
+1 Myślę, że nadam twoją zasadę: „Praktyczne refaktoryzacja” :)
Songo,
31

Zamiast jednego z nich wolę WTSTWCDTUAWCROT?

(Jaka najprostsza rzecz, jaką możemy zrobić, jest przydatna i którą możemy wydać w czwartek?)

Prostsze akronimy znajdują się na mojej liście rzeczy do zrobienia, ale nie są priorytetem.

Mike Sherrill „Cat Recall”
źródło
8
Ten akronim narusza zasadę YAGNI :)
riwalk
2
Użyłem dokładnie potrzebnych liter - nie więcej i nie mniej. W ten sposób jestem trochę jak Mozart. Tak. Jestem Mozartem akronimów.
Mike Sherrill „Cat Recall”
4
Nigdy nie wiedziałem, że Mozart był okropny w tworzeniu skrótów. Codziennie uczę się czegoś nowego na stronie SE. : P
Cameron MacFarland
3
@Mike Sherrill „CatRecall”: Może należy to rozszerzyć na WTSTWCDTUWCROTAWBOF = "Jaka najprostsza rzecz, jaką możemy zrobić, jest przydatna, możemy wydać w czwartek, a nie w piątek?" ;-)
Giorgio
1
@ Stargazer712 nie :) narusza POLA.
v.oddou
25

YAGNI i dobry design nie są sprzeczne. YAGNI dotyczy (nie) wspierania przyszłych potrzeb. Dobry projekt polega na tym, aby przejrzyste było to, co robi teraz twoje oprogramowanie i jak to robi.

Czy wprowadzenie fabryki uprości istniejący kod? Jeśli nie, nie dodawaj go. Jeśli tak, na przykład podczas dodawania testów (co powinieneś zrobić!), Dodaj go.

YAGNI nie polega na dodawaniu złożoności do obsługi przyszłych funkcji.
Dobry projekt polega na usuwaniu złożoności przy jednoczesnym wspieraniu wszystkich bieżących funkcji.

Jaap
źródło
15

Nie są w konflikcie, twoje cele są błędne.

Co próbujesz osiągnąć?

Chcesz pisać wysokiej jakości oprogramowanie, a do tego chcesz mieć małą bazę kodu i nie mieć problemów.

Teraz dochodzimy do konfliktu, w jaki sposób chronimy wszystkie sprawy, jeśli nie piszemy spraw, których nie będziemy używać?

Oto jak wygląda twój problem.

wprowadź opis zdjęcia tutaj (każdy zainteresowany, nazywa się to odparowywaniem chmur )

Więc co to napędza?

  1. Nie wiesz, czego nie będziesz potrzebować
  2. Nie chcesz tracić czasu i nadużywaj swojego kodu

Który z nich możemy rozwiązać? Wygląda na to, że nie chcemy marnować czasu, a nadużywanie kodu jest wielkim celem i ma sens. Co z tym pierwszym? Czy możemy dowiedzieć się, czego będziemy potrzebować do kodowania?

Pracuję nad projektem w pracy i chcę powoli wprowadzać dobre standardy kodu dla moich współpracowników (obecnie ich nie ma i wszystko jest po prostu zhakowane bez rymowania i powodu) [...] Cofnąłem się o krok i pomyślałem, że narusza to YAGNI, ponieważ prawie na pewno wiem, że nie potrzebujemy rozszerzać niektórych z tych klas.

Powtórzmy to wszystko

  • Brak standardów kodowych
  • Brak planowania projektu
  • Kowboje wszędzie robią swoje własne cholerstwo (a ty próbujesz grać szeryfa na dzikim dzikim zachodzie), tak.

Czy istnieje dobry sposób na kompromis między właściwym projektowaniem oprogramowania a niewystarczającą ochroną rozwiązania?

Nie potrzebujesz kompromisu, potrzebujesz kogoś, kto będzie zarządzał zespołem, który jest kompetentny i ma wizję całego projektu. Potrzebujesz kogoś, kto może zaplanować to, czego będziesz potrzebować, zamiast wrzucać rzeczy, których NIE BĘDĄ ci potrzebne, ponieważ jesteś tak niepewny co do przyszłości, ponieważ ... dlaczego? Powiem ci dlaczego, ponieważ nikt nie ma cholernego planu między wami wszystkimi. Próbujesz wprowadzić standardy kodu, aby rozwiązać zupełnie osobny problem. Podstawowym problemem, który musisz rozwiązać, jest przejrzysta mapa drogowa i projekt. Gdy to zrobisz, możesz powiedzieć: „normy kodowe pomagają nam osiągać ten cel bardziej skutecznie jako zespół”, co jest absolutną prawdą, ale poza zakresem tego pytania.

Poproś kierownika projektu / zespołu, który może robić te rzeczy. Jeśli masz taką kartę, musisz poprosić ją o mapę i wyjaśnić problem YAGNI, którego nie przedstawia mapa. Jeśli są rażąco niekompetentni, sam napisz plan i powiedz „Oto mój raport na temat potrzebnych nam spraw, przejrzyj go i daj nam znać o swojej decyzji”.

Incognito
źródło
Niestety nie mamy takiego. Menedżer ds. Rozwoju bardziej martwi się robieniem rzeczy szybciej niż standardami / jakością, lub sprawiłby, że standardowe praktyki, takie jak używanie surowych zestawów danych w dowolnym miejscu, zwracane bezpośrednio z klas, kodowanie w stylu VB6 i wszystko związane z kodem ze zduplikowaną logiką kopiowane i wklejane wszystkie koniec.
Wayne Molina,
W porządku, będzie trochę wolniej, ale wtedy musisz porozmawiać z ich zmartwieniami. Wyjaśnij, że ten problem YAGNI / Bezużyteczny kod marnuje czas, zasugeruj mapę drogową, daj mu mapę drogową, wyjaśnij, że przyspieszy to pracę. Kiedy go kupi, przyjdź z problemami, które mają słabe standardy do tempa rozwoju, sugeruj lepsze. Chcą, żeby projekt został ukończony, po prostu nie wiedzą, jak sobie z tym poradzić, albo musisz go zastąpić, albo go pokochać ... albo zrezygnować.
Incognito,
Eee ... Myślę, że głównym celem będzie „chcesz mieć dobry, cenny produkt”?
patrz
@ ta decyzja nie jest jego: p.
Incognito,
3
Świetna odpowiedź. Stoi przed bitwą pod górę. Wykonanie tego będzie trudne, jeśli zarząd nie dokona zakupu. Masz rację, że pytanie jest przedwczesne i nie rozwiązuje prawdziwego problemu.
Seth Spearman
10

Zezwolenie na rozszerzenie kodu o testy jednostkowe nigdy nie będzie objęte programem YAGNI, ponieważ będzie on potrzebny. Jednak nie jestem przekonany, że zmiany w projekcie interfejsu pojedynczej implementacji faktycznie zwiększają testowalność kodu, ponieważ CustomerFactory już dziedziczy z interfejsu i można go w dowolnym momencie zamienić na MockCustomerFactory.

DeadMG
źródło
1
+1 za pomocny komentarz na temat rzeczywistego problemu, a nie tylko kosmicznej bitwy i / lub miłości między Dobrym Projektem a YAGNI.
psr
10

Pytanie stanowi fałszywy dylemat. Właściwe stosowanie zasady YAGNI nie jest niczym niepowiązanym. To jeden z aspektów dobrego projektu. Każda z zasad SOLID jest również aspektem dobrego projektowania. Nie zawsze możesz w pełni zastosować każdą zasadę w dowolnej dyscyplinie. Problemy w świecie rzeczywistym nakładają na kod wiele sił, a niektóre z nich popychają w przeciwnych kierunkach. Zasady projektowania muszą uwzględniać je wszystkie, ale żadna garść zasad nie pasuje do wszystkich sytuacji.

Spójrzmy teraz na każdą zasadę ze zrozumieniem, że chociaż czasami mogą one zmierzać w różnych kierunkach, nie są one z natury rzeczy w konflikcie.

YAGNI został stworzony, aby pomóc programistom uniknąć szczególnego rodzaju przeróbek: tego, co wynika z budowania niewłaściwej rzeczy. Robi to, prowadząc nas do unikania zbyt wczesnych błędnych decyzji w oparciu o założenia lub prognozy dotyczące tego, co naszym zdaniem zmieni się lub będzie potrzebne w przyszłości. Wspólne doświadczenie mówi nam, że kiedy to robimy, zwykle się mylimy. Na przykład YAGNI powiedziałby ci, żebyś nie tworzył interfejsu w celu ponownego użycia , chyba że wiesz już , że potrzebujesz wielu implementatorów. Podobnie YAGNI powiedziałbym nie stworzyć „ScreenManagera” zarządzać jednego formularza w aplikacji, chyba że wiesz już teraz , że masz zamiar mieć więcej niż jeden ekran.

W przeciwieństwie do tego, co myśli wielu ludzi, SOLID nie polega na możliwości ponownego użycia, generyczności, a nawet abstrakcji. SOLID ma na celu pomóc Ci napisać kod, który jest przygotowany na zmianę , nie mówiąc nic o tym, jaka może być ta konkretna zmiana. Pięć zasad SOLID tworzy strategię budowania kodu, która jest elastyczna, nie jest zbyt ogólna i prosta, nie będąc naiwna. Właściwe stosowanie kodu SOLID tworzy małe, skoncentrowane klasy o ściśle określonych rolach i granicach. Praktycznym rezultatem jest to, że przy każdej potrzebnej zmianie wymagań należy dotknąć minimalną liczbę klas. Podobnie, przy każdej zmianie kodu istnieje zminimalizowana ilość „falowania” do innych klas.

Patrząc na przykładową sytuację, zobaczymy, co mogliby powiedzieć YAGNI i SOLID. Rozważasz wspólny interfejs repozytorium, ponieważ wszystkie repozytoria wyglądają tak samo z zewnątrz. Ale wartością wspólnego, ogólnego interfejsu jest możliwość korzystania z dowolnego z implementatorów bez konieczności określania, który z nich jest konkretny. O ile w Twojej aplikacji nie ma takiego miejsca, w którym byłoby to konieczne lub przydatne, YAGNI mówi, nie rób tego.

Do obejrzenia jest 5 SOLIDNYCH zasad. S to pojedyncza odpowiedzialność. Nie mówi to nic o interfejsie, ale może powiedzieć coś o twoich konkretnych klasach. Można argumentować, że za obsługę samego dostępu do danych najlepiej może odpowiadać jedna lub więcej innych klas, podczas gdy odpowiedzialność repozytoriów polega na tłumaczeniu z kontekstu niejawnego (CustomerRepository jest repozytorium niejawnie dla podmiotów klienta) na jawne wywołania do uogólniony interfejs API dostępu do danych określający typ jednostki klienta.

O jest otwarte-zamknięte. Chodzi głównie o dziedziczenie. Miałoby to zastosowanie, gdybyś próbował czerpać swoje repozytoria ze wspólnej bazy implementującej wspólną funkcjonalność lub gdybyś spodziewał się, że będziesz czerpał dalej z różnych repozytoriów. Ale nie jesteś, więc nie.

L oznacza substytucyjność Liskowa. Ma to zastosowanie, jeśli zamierzasz korzystać z repozytoriów za pośrednictwem wspólnego interfejsu repozytorium. Nakłada ograniczenia na interfejs i implementacje, aby zapewnić spójność i uniknąć specjalnego obchodzenia się z różnymi impelementerami. Powodem tego jest to, że taka specjalna obsługa podważa cel interfejsu. Przydatne może być rozważenie tej zasady, ponieważ może to ostrzec cię przed używaniem wspólnego interfejsu repozytorium. Jest to zgodne z wytycznymi YAGNI.

I to Segregacja interfejsu. Może to mieć zastosowanie, jeśli zaczniesz dodawać różne operacje zapytań do swoich repozytoriów. Segregacja interfejsu ma zastosowanie tam, gdzie można podzielić członków klasy na dwa podzbiory, z których jeden będzie używany przez niektórych konsumentów, a drugi przez innych, ale żaden konsument prawdopodobnie nie użyje obu podzbiorów. Wytyczne dotyczą utworzenia dwóch oddzielnych interfejsów zamiast jednego wspólnego. W twoim przypadku jest mało prawdopodobne, że pobieranie i zapisywanie pojedynczych instancji byłoby zajęte przez ten sam kod, który przeprowadzałby ogólne zapytania, więc może być przydatne rozdzielenie ich na dwa interfejsy.

D to wstrzyknięcie zależne. Wracamy do tego samego punktu, co S. Jeśli podzieliłeś zużycie interfejsu API dostępu do danych na osobny obiekt, ta zasada mówi, że zamiast tylko nowego wystąpienia tego obiektu, powinieneś przekazać go podczas tworzenia repozytorium. Ułatwia to kontrolowanie żywotności komponentu dostępu do danych, otwierając możliwość udostępniania referencji do niego między repozytoriami, bez konieczności podejmowania decyzji o uczynieniu go singletonem.

Należy zauważyć, że większość zasad SOLID niekoniecznie ma zastosowanie na tym konkretnym etapie tworzenia aplikacji. Na przykład to, czy należy zerwać dostęp do danych, zależy od tego, jak skomplikowane jest to zadanie i czy chcesz przetestować logikę repozytorium bez wchodzenia do bazy danych. Wygląda na to, że jest to mało prawdopodobne (niestety, moim zdaniem), więc prawdopodobnie nie jest konieczne.

Po tych wszystkich rozważaniach okazuje się, że YAGNI i SOLID rzeczywiście zapewniają jedną wspólną, solidną, natychmiastową poradę: prawdopodobnie nie jest konieczne tworzenie wspólnego ogólnego interfejsu repozytorium.

Cała ta uważna myśl jest niezwykle przydatna jako ćwiczenie edukacyjne. Nauka zajmuje dużo czasu, ale z czasem rozwijasz intuicję i staje się bardzo szybka. Będziesz wiedział, co należy zrobić, ale nie musisz myśleć o wszystkich tych słowach, chyba że ktoś poprosi cię o wyjaśnienie przyczyny.

Chris Ammerman
źródło
Myślę, że większość dyskusji na tej stronie pomija 2 duże podmioty „niskie sprzężenie” i „wysoką spójność” zasad GRAS. Kosztowne decyzje projektowe wynikają z zasady „niskiego sprzężenia”. Jak kiedy „aktywować” SRP + ISP + DIP ze względu na niskie sprzężenie. Przykład: jedna klasa -> 3 klasy we wzorze MVC. Lub nawet droższe: podzielone na moduły / zespoły .dll / .so. Jest to bardzo kosztowne ze względu na implikacje kompilacji, projekty, listy maklerów, serwer kompilacji, dodatki do plików w wersji źródłowej ...
v.oddou
6

Wydaje się, że wierzysz, że „dobry projekt” oznacza przestrzeganie pewnego rodzaju ideologii i formalnego zestawu zasad, które zawsze muszą być stosowane, nawet jeśli są bezużyteczne.

IMO to zły projekt. YAGNI jest elementem dobrego projektu, nigdy nie jest z nim sprzecznością.

Wektor
źródło
2

W twoim przykładzie powiedziałbym, że YAGNI powinien zwyciężyć. Nie będzie Cię to dużo kosztować, jeśli będziesz musiał później dodać interfejsy. Nawiasem mówiąc, czy naprawdę dobrym pomysłem jest posiadanie jednego interfejsu według klasy, jeśli w ogóle nie ma celu?

Jeszcze jedna myśl, być może czasem nie potrzebujesz dobrego projektu, ale wystarczającego projektu. Oto bardzo interesująca sekwencja postów na ten temat:

David
źródło
Twój pierwszy i trzeci link prowadzą do tego samego miejsca.
David Thornley,
2

Niektórzy twierdzą, że nazwy interfejsów nie powinny zaczynać się od I. W szczególności jednym z powodów jest fakt, że faktycznie przeciekasz zależność od tego, czy dany typ jest klasą czy interfejsem.

Co powstrzymuje cię od CustomerFactorybycia klasą na początku, a później przekształcania jej w interfejs, który będzie albo zaimplementowany przez, DefaultCustormerFactoryalbo UberMegaHappyCustomerPowerFactory3000? Jedyną rzeczą, którą powinieneś zmienić, jest miejsce, w którym implementacja jest generowana. A jeśli masz mniej dobry projekt, jest to najwyżej garść miejsc.

Refaktoryzacja jest częścią rozwoju. Lepiej mieć mało kodu, który jest łatwy do refaktoryzacji, niż mieć interfejs i klasę zadeklarowaną dla każdej klasy, co zmusza cię do zmiany każdej nazwy metody w co najmniej dwóch miejscach jednocześnie.

Prawdziwym celem korzystania z interfejsów jest osiągnięcie modułowości, która jest prawdopodobnie najważniejszym filarem dobrego projektowania. Zauważ jednak, że moduł jest definiowany nie tylko przez jego oddzielenie od świata zewnętrznego (chociaż tak postrzegamy go z perspektywy zewnętrznej), ale również przez jego wewnętrzne działanie.
Mam na myśli to, że oddzielanie rzeczy, które z natury należą do siebie, nie ma większego sensu. W pewnym sensie przypomina szafkę z indywidualną półką na filiżankę.

Znaczenie polega na opracowaniu dużego, złożonego problemu w mniejsze, prostsze podproblemy. I musisz zatrzymać się w punkcie, w którym stają się wystarczająco proste bez dalszych podziałów, w przeciwnym razie staną się bardziej skomplikowane. Można to uznać za następstwo YAGNI. I to zdecydowanie oznacza dobry projekt.

Celem nie jest jakoś rozwiązanie lokalnego problemu za pomocą jednego repozytorium i jednej fabryki. Chodzi o to, aby ta decyzja nie miała wpływu na resztę wniosku. Na tym polega modułowość.
Chcesz, aby Twoi współpracownicy spojrzeli na Twój moduł, zobaczyli fasadę z garstką prostych wyjaśnień i poczują się pewnie, że mogą z nich korzystać, nie martwiąc się o wszystkie potencjalnie wyrafinowane instalacje wewnętrzne.

back2dos
źródło
0

Tworzysz interfacewiele przyszłych implementacji. Równie dobrze możesz mieć I<class>dla każdej klasy w swojej bazie kodu. Nie rób

Wystarczy użyć pojedynczej klasy betonu zgodnie z YAGNI. Jeśli stwierdzisz, że potrzebujesz obiektu „próbnego” do celów testowych, zamień klasę oryginalną w klasę abstrakcyjną z dwiema implementacjami, jedną z oryginalną klasą konkretną, a drugą z próbną implementacją.

Będziesz oczywiście musiał zaktualizować wszystkie instancje oryginalnej klasy, aby utworzyć instancję nowej konkretnej klasy. Można to obejść za pomocą statycznego konstruktora z góry.

YAGNI mówi, aby nie pisać kodu, zanim będzie trzeba go napisać.

Dobry projekt mówi o użyciu abstrakcji.

Możesz mieć oba. Klasa jest abstrakcją.

Jesse
źródło
0

Dlaczego interfejsy znaczników? Uderza mnie, że nie robi nic poza „tagowaniem”. Jaki jest sens z innym „znacznikiem” dla każdego typu fabryki?

Interfejs ma na celu nadanie klasie „zachowania podobnego do”, nadanie jej „zdolności repozytorium”. Więc jeśli wszystkie Twoje konkretne typy repozytoriów zachowują się jak to samo IRepository (wszystkie implementują IRepository), wówczas wszystkie mogą być obsługiwane w ten sam sposób przez inny kod - dokładnie ten sam kod. W tym momencie twój projekt jest rozszerzalny. Dodając więcej konkretnych typów repozytoriów, wszystkie obsługiwane jako ogólne IRepository (s) - ten sam kod obsługuje wszystkie konkretne typy jak repozytoria „ogólne”.

Interfejsy służą do obsługi rzeczy opartych na podobieństwach. Ale niestandardowe interfejsy znaczników a) nie dodają żadnego zachowania. i b) zmusić cię do zajęcia się ich wyjątkowością.

W zakresie, w jakim projektujesz użyteczne interfejsy, zyskujesz tę zaletę, że nie musisz pisać specjalistycznego kodu do obsługi wyspecjalizowanych klas, typów lub niestandardowych interfejsów znaczników, które mają korelację 1: 1 z konkretnymi klasami. To bezcelowa redundancja.

Pod ręką widzę interfejs znaczników, jeśli na przykład potrzebujesz mocno napisanej kolekcji wielu różnych klas. W kolekcji wszystkie są „ImarkerInterface”, ale gdy je wyciągniesz, musisz rzucić je na odpowiednie typy.

radarbob
źródło
0

Czy potrafisz teraz zapisać dość rozsądne repozytorium ICustomer? Np. (Naprawdę docierając tutaj, prawdopodobnie zły przykład) w przeszłości Twoi klienci zawsze korzystali z PayPal? Czy wszyscy w firmie mają kłopoty z przyłączeniem się do Alibaba? Jeśli tak, możesz teraz użyć bardziej złożonego projektu i wyglądać na dalekowzrocznych dla swoich szefów. :-)

W przeciwnym razie poczekaj. Zgadywanie na interfejsie przed jedną lub dwiema rzeczywistymi implementacjami zwykle kończy się niepowodzeniem. Innymi słowy, nie uogólniaj / streszczaj / nie używaj fantazyjnego wzorca projektowego, dopóki nie masz kilku przykładów do uogólnienia.

użytkownik949300
źródło