Lubię SOLID i staram się go używać i stosować podczas programowania. Ale nie mogę się oprzeć wrażeniu, że podejście SOLID zmienia kod w kod „framework” - tj. Kod, który zaprojektowałbyś, gdybyś tworzył strukturę lub bibliotekę dla innych programistów.
Zazwyczaj ćwiczyłem 2 tryby programowania - tworzenie mniej więcej dokładnie tego, o co pytamy za pomocą wymagań i KISS (typowe programowanie), lub tworzenie bardzo ogólnej i logiki wielokrotnego użytku, usług itp., Które zapewniają elastyczność, jakiej inni programiści mogą potrzebować (programowanie ramowe) .
Jeśli użytkownik naprawdę chce, aby aplikacja wykonywała x i y rzeczy, czy warto podążać za SOLID i dodawać całą masę punktów wejścia do abstrakcji, skoro nawet nie wiesz, czy to w ogóle prawidłowy problem na początek z? Jeśli dodasz te punkty wejścia do abstrakcji, czy naprawdę spełniasz wymagania użytkowników, czy tworzysz strukturę na podstawie istniejącej struktury i stosu technologicznego, aby ułatwić przyszłe dodawanie? W którym przypadku służysz interesom klienta lub programisty?
Jest to coś, co wydaje się powszechne w świecie Java Enterprise, gdzie wydaje się, że projektujesz własną platformę na J2EE lub Spring, aby był to lepszy UX dla programisty, zamiast skupiać się na UX dla użytkownika?
źródło
Odpowiedzi:
Twoja obserwacja jest prawidłowa, zasady SOLID są opracowane przez IMHO z myślą o bibliotekach wielokrotnego użytku lub kodzie szkieletowym. Kiedy śledzisz je wszystkie na ślepo, nie pytając, czy ma to sens, czy nie, ryzykujesz nadmierną generalizacją i zainwestuje w swój system więcej wysiłku, niż to prawdopodobnie konieczne.
Jest to kompromis i wymaga pewnego doświadczenia, aby podejmować właściwe decyzje dotyczące tego, kiedy uogólnić, a kiedy nie. Możliwym podejściem jest trzymanie się zasady YAGNI - nie rób z kodu SOLID „na wszelki wypadek” - lub, używając słów: nie
Zamiast zapewnić elastyczność inni deweloperzy faktycznie potrzeba , jak tylko jest to potrzebne , ale nie wcześniej.
Tak więc, ilekroć masz w kodzie jedną funkcję lub klasę, nie masz pewności, czy można ją ponownie wykorzystać, nie umieszczaj jej teraz w swoim środowisku. Poczekaj, aż będziesz mieć rzeczywistą skrzynkę do ponownego użycia, i przeprowadź refakturę do „SOLID wystarczającej ilości dla tej skrzynki”. Nie wdrażaj większej konfigurowalności (zgodnie z OCP) lub punktów wejścia abstrakcji (za pomocą DIP) do takiej klasy, jakiej naprawdę potrzebujesz w przypadku rzeczywistego ponownego użycia. Dodaj kolejną elastyczność, gdy faktycznie istnieje kolejne wymaganie ponownego użycia.
Oczywiście ten sposób pracy zawsze będzie wymagał pewnej refaktoryzacji w istniejącej, działającej bazie kodu. Dlatego ważne są tutaj testy automatyczne. Zatem sprawienie, by Twój kod był wystarczająco SOLIDNY od samego początku, aby mógł być testowany jednostkowo, nie jest stratą czasu, a robienie tego nie jest sprzeczne z YAGNI. Testy automatyczne są ważnym przypadkiem „ponownego użycia kodu”, ponieważ dany kod jest używany zarówno z kodu produkcyjnego, jak i z testów. Ale pamiętaj, po prostu dodaj elastyczność, której potrzebujesz, aby testy działały, nie mniej, nie więcej.
To właściwie stara mądrość. Dawno temu, zanim termin SOLID dostał popularne, ktoś powiedział mi, zanim spróbujemy napisać ponownie kod użytkową, powinniśmy napisać użytkowej kod. I nadal uważam, że to dobra rekomendacja.
źródło
Z mojego doświadczenia, pisząc aplikację, masz trzy możliwości:
W pierwszym przypadku często występuje ścisły kod, który nie ma testów jednostkowych. Jasne, że napisanie jest szybkie, ale trudne do przetestowania. A właściwym jest królewski ból, aby zmienić go później, gdy zmienią się wymagania.
W drugim przypadku spędza się mnóstwo czasu, próbując przewidzieć przyszłe potrzeby. A nazbyt często te przewidywane przyszłe wymagania nigdy się nie spełniają. To wydaje się scenariusz, który opisujesz. Jest to strata wysiłku przez większość czasu i skutkuje niepotrzebnie złożonym kodem, który wciąż trudno zmienić, gdy pojawi się wymaganie, którego nie przewidywano.
Moim zdaniem celem jest ten ostatni przypadek. Użyj TDD lub podobnych technik, aby przetestować kod w miarę upływu czasu, a skończysz z luźno sprzężonym kodem, który jest łatwy do modyfikacji, ale wciąż szybki do napisania. Rzecz w tym, że postępując w ten sposób, naturalnie przestrzegasz wielu zasad SOLID: małych klas i funkcji; interfejsy i wstrzykiwane zależności. I pani Liskov jest również ogólnie szczęśliwa, ponieważ proste zajęcia z pojedynczymi obowiązkami rzadko są sprzeczne z jej zasadą zastępowania.
Jedynym aspektem SOLID, który tak naprawdę nie ma zastosowania, jest zasada otwartego / zamkniętego. Jest to ważne dla bibliotek i frameworków. W przypadku samodzielnej aplikacji, nie tyle. Naprawdę jest to przypadek pisania kodu zgodnego z „ SLID ”: łatwy do napisania (i odczytu), łatwy do przetestowania i łatwy w utrzymaniu.
źródło
Perspektywa, którą masz, może być wypaczona przez osobiste doświadczenie. Jest to śliskie zbocze faktów, które są indywidualnie poprawne, ale wynikowe wnioskowanie nie jest, nawet jeśli na pierwszy rzut oka wydaje się poprawne.
Oznacza to, że podczas interakcji z frameworkami i mniejszymi bibliotekami kod dobrej praktyki, z którym będziesz wchodzić w interakcje, będzie częściej znajdowany w większych frameworkach.
Ten błąd jest bardzo powszechny, np. Każdy lekarz, u którego byłem leczony, był arogancki. Dlatego dochodzę do wniosku, że wszyscy lekarze są aroganccy. Te błędy zawsze cierpią z powodu wyciągania wniosków na podstawie osobistych doświadczeń.
W twoim przypadku możliwe jest, że przeszedłeś dobrą praktykę w większych środowiskach, a nie w mniejszych bibliotekach. Twoje osobiste obserwacje nie są złe, ale są to niepotwierdzone dowody i nie mają uniwersalnego zastosowania.
Trochę to potwierdzasz. Pomyśl o tym, czym jest framework. To nie jest aplikacja. Jest to uogólniony „szablon”, którego inni mogą używać do tworzenia wszelkiego rodzaju aplikacji. Logicznie oznacza to, że struktura jest zbudowana w znacznie bardziej abstrakcyjnej logice, aby wszyscy mogli z niej korzystać.
Konstruktorzy frameworków nie są w stanie korzystać ze skrótów, ponieważ nawet nie wiedzą, jakie są wymagania kolejnych aplikacji. Zbudowanie frameworku z natury zachęca ich do używania kodu dla innych.
Twórcy aplikacji mają jednak możliwość kompromisu w zakresie wydajności logicznej, ponieważ koncentrują się na dostarczaniu produktu. Ich głównym celem nie jest działanie kodu, ale raczej doświadczenie użytkownika.
W przypadku frameworka końcowym użytkownikiem jest inny programista, który będzie wchodził w interakcje z Twoim kodem. Jakość kodu ma znaczenie dla użytkownika końcowego.
W przypadku aplikacji użytkownik końcowy nie jest programistą, który nie będzie wchodził w interakcje z Twoim kodem. Jakość twojego kodu nie ma dla nich znaczenia.
Właśnie dlatego architekci zespołu programistycznego często pełnią rolę strażników dobrych praktyk. Są o jeden krok od dostarczenia produktu, co oznacza, że mają tendencję do obiektywnego patrzenia na kod, zamiast skupiania się na dostarczaniu samej aplikacji.
Jest to interesujący punkt i jest (z mojego doświadczenia) głównym powodem, dla którego ludzie nadal próbują uzasadnić unikanie dobrych praktyk.
Podsumowując poniższe punkty: Pominięcie dobrej praktyki może być uzasadnione tylko wtedy, gdy twoje wymagania (jak obecnie znane) są niezmienne i nigdy nie będzie żadnych zmian / dodatków do bazy kodu. Ostrzeżenie o spoilerze: rzadko się to zdarza.
Na przykład, gdy piszę 5-minutową aplikację konsoli do przetwarzania określonego pliku, nie stosuję dobrych praktyk. Ponieważ zamierzam korzystać z aplikacji tylko dzisiaj i nie będzie trzeba jej aktualizować w przyszłości (łatwiej byłoby napisać inną aplikację, gdybym jej potrzebował ponownie).
Załóżmy, że możesz tandetnie zbudować aplikację w 4 tygodnie i poprawnie ją zbudować w 6 tygodni. Na pierwszy rzut oka tandetne budowanie wydaje się lepsze. Klient otrzymuje aplikację szybciej, a firma musi poświęcać mniej czasu na płace programistów. Wygrana / wygrana, prawda?
Jest to jednak decyzja podjęta bez wybiegania w przyszłość. Z powodu jakości bazy kodu dokonanie poważnej zmiany w tandetnie zbudowanym zajmie 2 tygodnie, a wprowadzenie tych samych zmian w poprawnie zbudowanej zajmie 1 tydzień. W przyszłości może pojawić się wiele z tych zmian.
Co więcej, istnieje tendencja do nieoczekiwanych zmian, które wymagają więcej pracy, niż początkowo sądzono, w tandetnie zbudowanych bazach kodu, co prawdopodobnie skraca czas programowania do trzech tygodni zamiast dwóch.
A także tendencja do marnowania czasu na poszukiwanie błędów. Dzieje się tak często w projektach, w których rejestrowanie zostało zignorowane z powodu ograniczeń czasowych lub zwykłej niechęci do jego wdrożenia, ponieważ bezmyślnie pracujesz przy założeniu, że produkt końcowy będzie działał zgodnie z oczekiwaniami.
To nawet nie musi być poważną aktualizacją. U mojego obecnego pracodawcy widziałem kilka projektów, które zostały zbudowane szybko i brudno, a kiedy najmniejszy błąd / zmiana musiała zostać wykonana z powodu nieporozumienia w wymaganiach, które prowadziły do reakcji łańcuchowej z koniecznością refaktoryzacji moduł po module . Niektóre z tych projektów upadały (pozostawiając nie do utrzymania bałagan), zanim nawet wydali swoją pierwszą wersję.
Decyzje skrótowe (szybkie i nieprzyzwoite programowanie) są korzystne tylko wtedy, gdy można jednoznacznie zagwarantować, że wymagania są dokładnie poprawne i nigdy nie trzeba ich zmieniać. Z mojego doświadczenia nigdy nie spotkałem się z projektem, w którym to prawda.
Inwestowanie dodatkowego czasu w dobrą praktykę to inwestowanie w przyszłość. Przyszłe błędy i zmiany będą o wiele łatwiejsze, gdy istniejąca baza kodów będzie oparta na dobrych praktykach. Będzie już wypłacał dywidendy po wprowadzeniu tylko dwóch lub trzech zmian.
źródło
W jaki sposób SOLID zamienia prosty kod w kod frameworka? W żadnym wypadku nie jestem stanem na SOLID, ale tak naprawdę nie jest oczywiste, co masz na myśli.
Przyznaję, że sam nie myślę w kategoriach SOLID, ponieważ wymyśliłem szkoły programowania Gang of Four i Josh Bloch , a nie szkołę Boba Martina. Ale naprawdę uważam, że jeśli myślisz „SOLID” = „dodawanie kolejnych warstw do stosu technologicznego”, źle to czytasz.
PS Nie sprzedawaj korzyści „lepszego UX dla programisty” w skrócie. Kod spędza większość swojego życia na utrzymaniu. Deweloper to ty .
źródło
class A{ int X; int Y; } class A_setX{ f(A a, int N) { a.X = N; }} class A_getX{ int f(A a) { return X; }} class A_setY ... etc.
Myślę, że patrzysz na to ze zbyt meta punktu widzenia w odniesieniu do roszczenia fabryki. Inicjalizacja nie jest aspektem problemu z domeną.