Pracuję w zespole, w którym kierownik zespołu jest zjadliwym orędownikiem zasad rozwoju SOLID. Brakuje mu jednak dużego doświadczenia w uzyskiwaniu skomplikowanego oprogramowania.
Mamy sytuację, w której zastosował SRP do tego, co było już dość złożoną bazą kodu, która stała się teraz bardzo rozdrobniona, trudna do zrozumienia i debugowania.
Mamy teraz problem nie tylko z fragmentacją kodu, ale także z enkapsulacją, ponieważ metody w klasie, która mogła być prywatna lub chroniona, zostały uznane za „powód do zmiany” i zostały wyodrębnione do publicznych lub wewnętrznych klas i interfejsów, które nie jest zgodny z celami enkapsulacji aplikacji.
Mamy konstruktorów klas, które przejmują 20 parametrów interfejsu, więc nasza rejestracja IoC staje się potworem samym w sobie.
Chcę wiedzieć, czy istnieje jakaś metoda „refaktoryzacji z dala od SRP”, której moglibyśmy użyć, aby rozwiązać niektóre z tych problemów. Czytałem, że nie narusza SOLID, jeśli utworzę wiele pustych, gruboziarnistych klas, które owijają wiele ściśle powiązanych klas, aby zapewnić pojedynczy punkt dostępu do sumy ich funkcjonalności (tj. Naśladować mniej nadmierna implementacja klasy SRP'd).
Poza tym nie mogę wymyślić rozwiązania, które pozwoli nam pragmatycznie kontynuować nasze wysiłki rozwojowe, jednocześnie ciesząc wszystkich.
Jakieś sugestie ?
źródło
ISomething
). IMHO, te approches są znacznie łatwiejsze w obsłudze niż wstrzykiwanie zależności i dają bardziej czytelny kod.Odpowiedzi:
Jeśli twoja klasa ma 20 parametrów w konstruktorze, nie wygląda na to, że twój zespół całkiem wie, czym jest SRP. Jeśli masz klasę, która robi tylko jedną rzecz, w jaki sposób ma 20 zależności? To tak, jakby wybrać się na wyprawę wędkarską i zabrać ze sobą wędkę, sprzęt, pikowanie, kulę do kręgli, nunchucks, miotacz ognia itp. Jeśli potrzebujesz wszystkiego, aby łowić ryby, nie tylko łowisz ryby.
To powiedziawszy, SRP, jak większość dostępnych tam zasad, może być nadmiernie stosowane. Jeśli stworzysz nową klasę do zwiększania liczb całkowitych, to tak, może to być jedna odpowiedzialność, ale daj spokój. To niedorzeczne. Zazwyczaj zapominamy, że takie rzeczy, jak zasady SOLID są po to, aby mieć jakiś cel. SOLID jest środkiem do celu, a nie celem samym w sobie. Celem jest łatwość konserwacji . Jeśli zamierzasz uzyskać tę szczegółowość dzięki zasadzie pojedynczej odpowiedzialności, jest to wskaźnik, że zapał dla SOLID oślepił zespół do osiągnięcia celu SOLID.
Więc myślę, że mówię ... SRP nie jest twoim problemem. To albo nieporozumienie z SRP, albo jego niezwykle szczegółowe zastosowanie. Postaraj się, aby twój zespół zachował najważniejsze. A najważniejsze jest łatwość konserwacji.
EDYTOWAĆ
Niech ludzie zaprojektują moduły w sposób zachęcający do łatwości użytkowania. Pomyśl o każdej klasie jak o mini API. Pomyśl najpierw: „Jak chciałbym skorzystać z tej klasy”, a następnie zaimplementuj ją. Nie myśl tylko: „Co ta klasa musi zrobić”. SRP ma wielką tendencję do utrudniania korzystania z klas, jeśli nie zastanawiasz się nad użytecznością.
EDYCJA 2
Jeśli szukasz wskazówek na temat refaktoryzacji, możesz zacząć robić to, co zasugerowałeś - utwórz grubsze klasy, aby zawinąć kilka innych. Upewnij się, że gruboziarnista klasa nadal przestrzega SRP , ale na wyższym poziomie. Masz dwie możliwości:
Po zakończeniu refaktoryzacji (ale przed przystąpieniem do repozytorium) przejrzyj swoją pracę i zadaj sobie pytanie, czy refaktoryzacja rzeczywiście poprawiła łatwość konserwacji i łatwość użycia.
źródło
Customer
klasa i mieć łatwiejszy do utrzymania kod. Zobacz przykłady tutaj: codemonkeyism.com/…Myślę, że to właśnie w Refaktoryzacji Martina Fowlera przeczytałem kiedyś SRP, określając, gdzie idzie za daleko. Jest drugie pytanie, równie ważne jak „czy każda klasa ma tylko jeden powód do zmiany?” i to znaczy „czy każda zmiana dotyczy tylko jednej klasy?”
Jeśli odpowiedź na pierwsze pytanie brzmi „tak”, ale drugie pytanie „nie jest nawet bliskie”, musisz ponownie spojrzeć na to, jak wdrażasz SRP.
Na przykład, jeśli dodanie jednego pola do tabeli oznacza, że musisz zmienić DTO i klasę sprawdzania poprawności oraz klasę trwałości i obiekt modelu widoku itd., Oznacza to, że masz problem. Może powinieneś przemyśleć, jak wdrożyłeś SRP.
Być może powiedziałeś, że dodanie pola jest powodem zmiany obiektu klienta, ale zmiana warstwy trwałości (powiedzmy z pliku XML na bazę danych) to kolejny powód do zmiany obiektu klienta. Więc decydujesz się również utworzyć obiekt CustomerPersistence. Ale jeśli zrobisz to tak, że dodanie pola STILL wymaga zmiany w obiekcie CustomerPersisitence, to jaki był sens? Nadal masz obiekt z dwoma przyczynami do zmiany - to po prostu już nie Klient.
Jeśli jednak wprowadzisz ORM, jest całkiem możliwe, że możesz sprawić, aby klasy działały tak, że jeśli dodasz pole do DTO, automatycznie zmieni on SQL używany do odczytu tych danych. Zatem masz dobry powód, by rozdzielić te dwa problemy.
Podsumowując, oto, co zwykle robię: jeśli istnieje przybliżona równowaga między liczbą razy, gdy mówię „nie, istnieje więcej niż jeden powód, aby zmienić ten obiekt”, a liczbą razy, gdy mówię „nie, ta zmiana będzie wpływa na więcej niż jeden obiekt ”, to myślę, że mam równowagę między SRP a fragmentacją. Ale jeśli oba są nadal wysokie, zaczynam się zastanawiać, czy istnieje inny sposób na rozróżnienie problemów.
źródło
To, że system jest złożony, nie oznacza, że musisz go skomplikować . Jeśli masz klasę, która ma zbyt wiele zależności (lub współpracowników), takich jak to:
... to stało się zbyt skomplikowane i tak naprawdę nie śledzisz SRP , prawda? Założę się, że jeśli zapisałeś, co
MyAwesomeClass
robi na karcie CRC , nie zmieściłoby się na karcie indeksu lub będziesz musiał pisać naprawdę małymi, nieczytelnymi literami.To, co tu masz, to fakt, że twoi faceci przestrzegali tylko zasady segregacji interfejsów i mogli doprowadzić do skrajności, ale to zupełnie inna historia. Można argumentować, że zależności są obiektami domeny (co się zdarza), jednak posiadanie klasy, która obsługuje 20 obiektów domeny w tym samym czasie, rozciąga je nieco za daleko.
TDD zapewni ci dobry wskaźnik tego, ile klasa robi. Mówiąc wprost; jeśli metoda testowa ma kod instalacyjny, którego pisanie trwa wiecznie (nawet jeśli refaktoryzujesz testy), to
MyAwesomeClass
prawdopodobnie masz zbyt wiele do zrobienia.Jak więc rozwiązać tę zagadkę? Przenosisz obowiązki na inne klasy. Istnieje kilka kroków, które możesz podjąć w przypadku klasy, która ma ten problem:
Abstrakcyjny przykład obowiązków związanych z refaktoryzacją
Niech
C
będzie, że klasa ma kilka zależnościD1
,D2
,D3
,D4
że trzeba byłaby używać mniej. Kiedy ustalimy, jakie metodyC
wywołują zależności, możemy utworzyć prostą listę:D1
-performA(D2)
,performB()
D2
-performD(D1)
D3
-performE()
D4
-performF(D3)
Patrząc na listę, widzimy to
D1
iD2
są ze sobą spokrewnieni, ponieważ klasa potrzebuje ich jakoś razem. Możemy również zobaczyć teD4
potrzebyD3
. Mamy więc dwie grupy:Group 1
-D1
<->D2
Group 2
-D4
->D3
Grupy są wskaźnikiem, że klasa ma teraz dwie obowiązki.
Group 1
- Jeden do obsługi wywoływania dwóch obiektów, które potrzebują siebie nawzajem. Być może możesz pozwolić swojej klasieC
wyeliminować potrzebę obsługi obu zależności i pozostawić jedną z nich obsługującą te połączenia. W tej grupie oczywiste jest, żeD1
może mieć odniesienieD2
.Group 2
- Druga odpowiedzialność potrzebuje jednego obiektu do wywołania drugiego. Nie możesz sobieD4
poradzićD3
zamiast swojej klasy? Wtedy prawdopodobnie możemy wyeliminowaćD3
z klasyC
, pozwalającD4
zamiast tego wykonywać połączenia.Nie bierz mojej odpowiedzi tak mocno osadzonej w kamieniu, ponieważ przykład jest bardzo abstrakcyjny i zawiera wiele założeń. Jestem prawie pewien, że istnieje więcej sposobów, aby to zmienić, ale przynajmniej te kroki mogą pomóc ci uzyskać jakiś proces przenoszenia obowiązków zamiast podziału klas.
Edytować:
Wśród komentarzy @Emmad Karem mówi:
Prawdą jest, że obiekty DAO mają zwykle wiele parametrów, które należy ustawić w konstruktorze, a parametry są zwykle prostymi typami, takimi jak łańcuch znaków. Jednak w przykładzie
Customer
klasy nadal można grupować jej właściwości w innych klasach, aby uprościć sprawę. Na przykład, mającAddress
klasę z ulicami iZipcode
klasę, która zawiera kod pocztowy i obsługuje logikę biznesową, taką jak sprawdzanie poprawności danych:Zagadnienie to zostało omówione w poście na blogu „Nigdy, nigdy, nigdy nie używaj Stringa w Javie (lub przynajmniej często)” . Alternatywą dla użycia konstruktorów lub metod statycznych w celu ułatwienia tworzenia obiektów podrzędnych jest użycie wzorca konstruktora płynów .
źródło
Zgadzam się ze wszystkimi odpowiedziami na temat SRP i tego, jak można go posunąć za daleko. W swoim poście wspominasz, że z powodu „nadmiernego refaktoryzacji”, aby zastosować się do SRP, znalazłeś łamanie lub modyfikowanie enkapsulacji. Jedyną rzeczą, która mi się sprawdziła, jest trzymanie się podstaw i robienie dokładnie tego, co jest wymagane do osiągnięcia celu.
Podczas pracy z systemami Legacy „entuzjazm” do naprawiania wszystkiego, aby było lepiej, jest zwykle dość wysoki w zespołach kierowniczych, szczególnie tych, którzy są nowi w tej roli. SOLID, po prostu nie ma SRP - to tylko S. Upewnij się, że jeśli podążasz za SOLID, nie zapomnisz również OLID.
Obecnie pracuję nad systemem Legacy i na początku zaczęliśmy podążać podobną ścieżką. Dla nas zadziałała wspólna decyzja zespołu, aby jak najlepiej wykorzystać oba światy - SOLID i KISS (Keep It Simple Stupid). Wspólnie dyskutowaliśmy o najważniejszych zmianach w strukturze kodu i stosowaliśmy zdrowy rozsądek w stosowaniu różnych zasad programistycznych. Są świetne jako wytyczne, a nie „Prawa rozwoju S / W”. W zespole nie chodzi tylko o kierownika zespołu - chodzi o wszystkich programistów w zespole. Rzeczą, która zawsze działała dla mnie, jest doprowadzenie wszystkich do pokoju i przedstawienie wspólnego zestawu wytycznych, których cały zespół zgodzi się przestrzegać.
Jeśli korzystasz z VCS i nie dodajesz zbyt wielu nowych funkcji do swojej aplikacji, jeśli chcesz naprawić swoją obecną sytuację, zawsze możesz wrócić do wersji kodu, którą cały zespół uważa za zrozumiały, czytelny i łatwy do utrzymania. Tak! Proszę cię, odrzuć pracę i zacznij od zera. Jest to lepsze niż próba „naprawienia” czegoś, co zostało zepsute i przeniesienie go z powrotem do czegoś, co już istniało.
źródło
Odpowiedzią jest przede wszystkim łatwość konserwacji i przejrzystość kodu. Dla mnie oznacza to pisanie mniej kodu , a nie więcej. Mniej abstrakcji, mniej interfejsów, mniej opcji, mniej parametrów.
Ilekroć oceniam restrukturyzację kodu lub dodając nową funkcję, myślę o tym, ile będzie wymagało płyta podstawowa w porównaniu do rzeczywistej logiki. Jeśli odpowiedź jest większa niż 50%, prawdopodobnie oznacza to, że zastanawiam się nad tym.
Oprócz SRP istnieje wiele innych stylów programowania. W twoim przypadku zdecydowanie brakuje brzmień YAGNI.
źródło
Wiele odpowiedzi tutaj jest naprawdę dobrych, ale koncentruje się na technicznej stronie tego problemu. Dodam po prostu, że brzmi to tak, jakby deweloper próbował podążać za dźwiękiem SRP, jakby faktycznie naruszał SRP.
Możesz zobaczyć blog Boba tutaj na temat tej sytuacji, ale twierdzi on, że jeśli odpowiedzialność jest rozmazana w wielu klasach, odpowiedzialność SRP zostaje naruszona, ponieważ klasy te zmieniają się równolegle. Podejrzewam, że twój twórca naprawdę spodobałby się projektowi na szczycie bloga Boba i może być trochę rozczarowany widząc, że został on rozerwany. W szczególności dlatego, że narusza „wspólną zasadę zamknięcia” - rzeczy, które zmieniają się razem, pozostają razem.
Pamiętaj, że SRP odnosi się do „przyczyny zmiany”, a nie „rób jedną rzecz”, i że nie musisz się martwić tym powodem zmiany, dopóki zmiana faktycznie nie nastąpi. Drugi facet płaci za abstrakcję.
Teraz jest drugi problem - „zjadliwy zwolennik rozwoju SOLID”. Z pewnością nie brzmi to tak, jakbyś miał świetne relacje z tym deweloperem, więc wszelkie próby przekonania go o problemach w bazie kodu są zakłócone. Musisz naprawić relację, aby móc prawdziwie omówić problemy. Polecam piwo.
Nie poważnie - jeśli nie pijesz, udaj się do kawiarni. Wyjdź z biura i zrelaksuj się, gdzie możesz porozmawiać o tym nieformalnie. Zamiast próbować wygrać kłótnię na spotkaniu, czego nie chcesz, dyskutuj gdzieś zabawnie. Spróbuj rozpoznać, że ten deweloper, który doprowadza cię do szału, jest faktycznie działającym człowiekiem, który próbuje wydostać się z oprogramowania i nie chce wysyłać bzdur. Ponieważ prawdopodobnie podzielasz tę wspólną płaszczyznę, możesz zacząć dyskutować, jak ulepszyć projekt, zachowując jednocześnie zgodność z SRP.
Jeśli oboje możecie uznać, że SRP jest dobrą rzeczą, że po prostu interpretujecie aspekty inaczej, prawdopodobnie możecie zacząć produktywne rozmowy.
źródło
Zgadzam się z twoją decyzją lidera zespołu [aktualizacja = 2012.05.31], że SRP jest ogólnie dobrym pomysłem. Ale całkowicie zgadzam się z komentarzem @ Spoike -s, że konstruktor z 20 argumentami interfejsu jest zbyt wiele. [/ Update]:
Wprowadzenie SRP z IoC przenosi złożoność z jednej „klasy wielu odpowiedzialnych” do wielu klas srp i znacznie bardziej skomplikowaną inicjalizację z korzyścią dla
Obawiam się, że nie można zmniejszyć defragmentacji kodu bez poświęcenia srp.
Ale możesz „złagodzić ból” inicjalizacji kodu poprzez zaimplementowanie syntaktycznej klasy cukru, która ukrywa złożoność inicjalizacji w konstruktorze.
źródło