Rozumiem, co SOLID ma osiągnąć i używać go regularnie w sytuacjach, w których modułowość jest ważna, a jej cele są wyraźnie przydatne. Jednak dwie rzeczy uniemożliwiają mi konsekwentne stosowanie go w mojej bazie kodu:
Chcę uniknąć przedwczesnej abstrakcji. Z mojego doświadczenia wynika, że rysowanie linii abstrakcji bez konkretnych przypadków użycia (takich, które istnieją obecnie lub w dającej się przewidzieć przyszłości) prowadzi do ich narysowania w niewłaściwych miejscach. Kiedy próbuję zmodyfikować taki kod, linie abstrakcji przeszkadzają, a nie pomagają. Dlatego mam tendencję do błądzenia po stronie nie rysowania żadnych linii abstrakcji, dopóki nie mam pojęcia, gdzie byłyby przydatne.
Trudno mi usprawiedliwić zwiększenie modułowości dla samego siebie, jeśli sprawia, że mój kod jest bardziej szczegółowy, trudniejszy do zrozumienia itp. I nie eliminuje żadnego powielania. Uważam, że prosty, ściśle sprzężony kod proceduralny lub obiektowy Boga jest czasem łatwiejszy do zrozumienia niż bardzo dobrze skonstruowany kod ravioli, ponieważ przepływ jest prosty i liniowy. O wiele łatwiej jest pisać.
Z drugiej strony ten sposób myślenia często prowadzi do obiektów Boga. Ogólnie zachowuję je ostrożnie, dodając wyraźne linie abstrakcji tylko wtedy, gdy widzę pojawiające się wyraźne wzory. Co, jeśli w ogóle, jest nie tak z obiektami Boga i ściśle powiązanym kodem, jeśli nie potrzebujesz wyraźnie modułowości, nie ma znaczącego powielania, a kod jest czytelny?
EDYCJA: Jeśli chodzi o indywidualne zasady SOLID, chciałem podkreślić, że Zastępstwo Liskowa jest IMHO formalizacją zdrowego rozsądku i powinno być stosowane wszędzie, ponieważ abstrakcje nie mają sensu, jeśli nie są. Ponadto, każda klasa powinna ponosić jedną odpowiedzialność na pewnym poziomie abstrakcji, chociaż może to być bardzo wysoki poziom ze szczegółami implementacji zatłoczonymi w jedną ogromną klasę 2000 linii. Zasadniczo twoje abstrakcje powinny mieć sens tam, gdzie wybierzesz abstrakty. Zasady, które kwestionuję w przypadkach, w których modułowość nie jest wyraźnie użyteczna, to otwarte-zamknięte, segregacja interfejsu, a zwłaszcza inwersja zależności, ponieważ dotyczą one modułowości, a nie tylko abstrakcji mają sens.
źródło
Odpowiedzi:
Oto proste zasady, które możesz zastosować, aby pomóc Ci zrozumieć, jak zrównoważyć projekt systemu:
S
w SOLID. Możesz mieć bardzo duży obiekt pod względem liczby metod lub ilości danych i nadal przestrzegać tej zasady. Weźmy na przykład obiekt Ruby String. Ten obiekt ma więcej metod, niż można potrząsnąć kijem, ale nadal ma tylko jedną odpowiedzialność: przytrzymaj ciąg tekstu dla aplikacji. Gdy tylko twój obiekt zacznie przyjmować na siebie nową odpowiedzialność, zacznij się nad tym intensywnie zastanawiać. Problem z utrzymaniem dotyczy pytania „gdzie miałbym znaleźć ten kod, gdybym miał z nim później problemy?”Zasadniczo próbujesz zrobić wszystko, aby nie zastrzelić się w stopę, jeśli przyjdzie czas na utrzymanie oprogramowania. Jeśli twoje duże obiekty są rozsądnymi abstrakcjami, to nie ma powodu, by je dzielić tylko dlatego, że ktoś wymyślił metrykę, która mówi, że klasa nie powinna być większa niż X linii / metod / właściwości / itp. Jeśli tak, to są wytyczne, a nie twarde i szybkie reguły.
źródło
Myślę, że w większości odpowiedziałeś na własne pytanie. SOLID to zestaw wskazówek, jak refaktoryzować kod, gdy koncepcje muszą być przenoszone na wyższy poziom abstrakcji.
Jak wszystkie inne dyscypliny twórcze, nie ma absolutów - po prostu kompromisy. Im częściej to robisz, tym „łatwiej” staje się decydowanie, kiedy jest wystarczająca dla bieżącej problematycznej domeny lub wymagań.
Powiedziawszy to - abstrakcja jest sercem rozwoju oprogramowania - więc jeśli nie ma dobrego powodu, aby tego nie robić, to praktyka sprawi, że będziesz w tym lepszy, a ty poczujesz odwagę za kompromisy. Więc faworyzuj to, chyba że stanie się szkodliwe.
źródło
Jest to częściowo słuszne, a częściowo błędne.
Źle
To nie jest powód, aby uniemożliwiać stosowanie zasad OO / SOLID. To tylko zapobiega ich stosowaniu zbyt wcześnie.
Który jest...
Prawo
Nie refaktoryzuj kodu, dopóki nie można go refaktoryzować; kiedy wymagania są spełnione. Lub, gdy „przypadki użycia” są dostępne, jak mówisz.
Podczas pracy w pojedynkę lub w silosie
Problem z nie robieniem OO nie jest od razu oczywisty. Po napisaniu pierwszej wersji programu:
3/4 z tych dobrych rzeczy szybko umiera w krótkim czasie.
Wreszcie rozpoznajesz, że masz obiekty Boga (obiekty z wieloma funkcjami), więc rozpoznajesz poszczególne funkcje. Hermetyzować. Umieść je w folderach. Raz na jakiś czas korzystaj z interfejsów. Zrób sobie przysługę za długoterminową konserwację. Zwłaszcza, że metody nierefrakcjonowane mają tendencję do wzdęcia w nieskończoność i stają się metodami Boga.
W zespole
Problemy z nie robieniem OO są natychmiast zauważalne. Dobry kod OO jest co najmniej w pewnym stopniu samodokumentujący i czytelny. Zwłaszcza w połączeniu z dobrym zielonym kodem. Ponadto brak struktury utrudnia rozdzielenie zadań i integracji o wiele bardziej skomplikowane.
źródło
Nie ma nic złego w dużych obiektach i ściśle sprzężonym kodzie, gdy są one odpowiednie i kiedy nie jest wymagane nic lepiej zaprojektowanego . To tylko kolejny przejaw reguły kciuka zmieniającej się w dogmat.
Luźne sprzężenie i małe, proste obiekty zwykle zapewniają określone korzyści w wielu typowych przypadkach, więc ogólnie warto z nich korzystać. Problem leży w tym, że ludzie, którzy nie rozumieją zasad leżących u podstaw zasad, ślepo próbują zastosować je uniwersalnie, nawet tam, gdzie nie są stosowane.
źródło
Podchodzę do tego bardziej z punktu widzenia You Ain't Gonna Need It. Ten post skupi się w szczególności na jednym elemencie: dziedziczeniu.
W moim początkowym projekcie tworzę hierarchię rzeczy, o których wiem, że będą ich dwie lub więcej. Są szanse, że będą potrzebować dużo tego samego kodu, więc warto zaprojektować go od samego początku. Po wprowadzeniu początkowego kodu i konieczności dodania kolejnych funkcji / funkcji, patrzę na to, co mam i myślę: „Czy coś, co już mam, implementuje tę samą lub podobną funkcję?” Jeśli tak, to najprawdopodobniej pojawi się nowa abstrakcja.
Dobrym przykładem tego jest framework MVC. Zaczynając od zera, tworzysz jedną dużą stronę z kodem z tyłu. Ale potem chcesz dodać kolejną stronę. Jednak jeden kod już zaimplementował dużą logikę wymaganą przez nową stronę. Tak więc wyodrębniasz
Controller
klasę / interfejs, który zaimplementuje logikę specyficzną dla tej strony, pozostawiając wspólne elementy w oryginalnym „bogu” za kodem.źródło
Jeśli jako programista wiesz, kiedy NIE stosować zasad z powodu przyszłości kodu, to dobrze dla Ciebie.
Mam nadzieję, że SOLID pozostaje w twojej głowie, aby wiedzieć, kiedy trzeba wyodrębnić rzeczy, aby uniknąć złożoności i poprawić jakość kodu, jeśli zacznie on obniżać się do podanych przez ciebie wartości.
Co ważniejsze, pamiętaj, że większość programistów wykonuje codzienną pracę i nie dba o ciebie tak bardzo jak ty. Jeśli początkowo wybierzesz metody działające długo, inni programiści, którzy przyjdą do kodu, pomyślą, kiedy go utrzymają lub rozszerzą? ... Tak, zgadłeś, WIĘKSZE funkcje, DŁUŻSZE klasy uruchomieniowe i WIĘCEJ Bóg obiektów, i nie mają na myśli zasad, aby móc złapać rosnący kod i poprawnie go zamknąć. Twój „czytelny” kod staje się teraz gnijącym bałaganem.
Możesz więc argumentować, że zły programista zrobi to bez względu na to, ale przynajmniej będziesz miał większą przewagę, jeśli wszystko będzie proste i dobrze zorganizowane od samego początku.
Nie mam specjalnie na myśli globalnej „mapy rejestru” lub „Boskiego obiektu”, jeśli są one proste. To naprawdę zależy od projektu systemu, czasem można go uciec i zachować prostotę, a czasem nie.
Nie zapominajmy również, że duże funkcje i Boskie Przedmioty mogą okazać się niezwykle trudne do przetestowania. Jeśli chcesz pozostać zwinny i czuć się „bezpiecznie” podczas przefaktoryzowania i zmiany kodu, potrzebujesz testów. Testy są trudne do napisania, gdy funkcje lub obiekty wykonują wiele czynności.
źródło
Problem z boskim przedmiotem polega na tym, że zazwyczaj możesz z zyskiem rozbić go na kawałki. Z definicji robi więcej niż jedną rzecz. Cały cel podzielenia go na mniejsze klasy polega na tym, abyś mógł mieć klasę, która robi jedną rzecz dobrze (i nie powinieneś również rozszerzać definicji „jednej rzeczy”). Oznacza to, że dla każdej klasy, gdy wiesz, co należy zrobić, powinieneś być w stanie dość łatwo ją przeczytać i powiedzieć, czy robi to poprawnie.
Myślę, że jest coś takiego jak zbyt wiele modułowość i zbyt dużą elastyczność, ale to bardziej z nad-projektowej i ponad-inżynierskiej problem, gdzie jesteś rachunkowości dla wymagań, które nawet nie wiedzą, że klient chce. Wiele pomysłów projektowych ma na celu ułatwienie kontrolowania zmian w sposobie działania kodu, ale jeśli nikt nie spodziewa się zmiany, to nie ma sensu uwzględniać elastyczności.
źródło
Queryer
który optymalizuje zapytania do bazy danych, wysyła zapytania do RDBMS i analizuje wyniki w obiekty do zwrócenia. Jeśli jest tylko jeden sposób, aby to zrobić w aplikacji iQueryer
jest on hermetycznie zamknięty i hermetyzuje ten jeden sposób, to robi tylko jedną rzecz. Jeśli istnieje wiele sposobów, aby to zrobić, a ktoś może dbać o szczegóły jednej jego części, robi to wiele rzeczy i możesz chcieć to rozdzielić.Używam prostszej wytycznej: jeśli potrafisz napisać testy jednostkowe, a nie masz duplikacji kodu, jest to wystarczająco abstrakcyjne. Ponadto masz dobrą pozycję do późniejszego refaktoryzacji.
Poza tym powinieneś pamiętać o SOLID, ale raczej jako wytyczna niż zasada.
źródło
Jeśli aplikacja jest wystarczająco mała, wszystko można utrzymać. W większych aplikacjach obiekty Boga szybko stają się problemem. Ostatecznie wdrożenie nowej funkcji wymaga modyfikacji 17 obiektów Boga. Ludzie nie radzą sobie zbyt dobrze po 17-etapowych procedurach. Obiekty Boga są ciągle modyfikowane przez wielu programistów i zmiany te muszą być wielokrotnie łączone. Nie chcesz tam iść.
źródło
Podzielam wasze obawy dotyczące nadmiernej i niewłaściwej abstrakcji, ale niekoniecznie jestem tak zaniepokojony przedwczesną abstrakcją.
To prawdopodobnie brzmi jak sprzeczność, ale jest mało prawdopodobne, aby użycie abstrakcji spowodowało problem, o ile nie przesadzisz z nim zbyt wcześnie - tj. O ile będziesz gotów i będziesz mógł dokonać refaktoryzacji w razie potrzeby.
Oznacza to pewien stopień prototypowania, eksperymentowania i cofania w kodzie.
To powiedziawszy, nie ma prostej reguły deterministycznej, która mogłaby postępować wszystkie problemy. Doświadczenie się liczy, ale musisz je zdobyć, popełniając błędy. I zawsze jest więcej błędów, aby poprzednie błędy nie przygotowały cię na to.
Mimo to potraktuj zasady podręcznika jako punkt wyjścia, a następnie naucz się dużo programowania i zobacz, jak te zasady się sprawdzają. Gdyby można było podać lepsze, bardziej precyzyjne i wiarygodne wytyczne niż SOLID, ktoś prawdopodobnie zrobiłby to teraz - a nawet gdyby tak było, te ulepszone zasady byłyby nadal niedoskonałe, a ludzie pytaliby o ograniczenia w tych .
Dobra robota - jeśli ktokolwiek mógłby zapewnić jasny, deterministyczny algorytm do projektowania i kodowania programów, istniałby tylko jeden ostatni program do napisania przez ludzi - program, który automatycznie zapisywałby wszystkie przyszłe programy bez interwencji człowieka.
źródło
Rysowanie odpowiednich linii abstrakcji jest czymś, co czerpie się z doświadczenia. Popełnisz przy tym błędy, co jest niezaprzeczalne.
SOLID jest skoncentrowaną formą tego doświadczenia, którą otrzymują ci ludzie, którzy zdobyli to doświadczenie na własnej skórze. Prawie go przybiłeś, kiedy mówisz, że powstrzymasz się od tworzenia abstrakcji, zanim zobaczysz jej potrzebę. Cóż, doświadczenie SOLID zapewnia pomoc w rozwiązywaniu problemów, których nigdy wcześniej nie istniałeś . Ale zaufaj mi ... są bardzo prawdziwe.
SOLID to modułowość, modułowość to łatwość konserwacji, a łatwość utrzymania pozwala na zachowanie humanitarnego harmonogramu pracy, gdy oprogramowanie osiągnie produkcję i klient zacznie zauważać błędy, których nie masz.
Największą zaletą modułowości jest testowalność. Im bardziej modułowy jest system, tym łatwiej jest go testować i tym szybciej można zbudować solidne fundamenty, aby poradzić sobie z trudniejszymi aspektami problemu. Twój problem może nie wymagać tej modułowości, ale sam fakt, że pozwoli ci szybciej tworzyć lepszy kod testowy, nie stanowi problemu, aby dążyć do modułowości.
Zwinność polega na próbie znalezienia delikatnej równowagi między szybką wysyłką a zapewnieniem dobrej jakości. Zwinność nie oznacza, że powinniśmy skrócić narożnik, w rzeczywistości najbardziej udane projekty zwinne, w które byłem zaangażowany, to te, które zwróciły szczególną uwagę na takie wytyczne.
źródło
Ważnym punktem, który wydaje się być przeoczony, jest to, że SOLID jest praktykowany wraz z TDD. TDD ma tendencję do podkreślania tego, co jest „odpowiednie” w twoim konkretnym kontekście i pomaga złagodzić wiele niejednoznaczności, które wydają się być w radzie.
źródło