Czy luźne sprzęgło bez skrzynek nie stanowi wzoru?


22

Luźne sprzężenie jest dla niektórych deweloperów świętym graalem dobrze zaprojektowanego oprogramowania. Z pewnością dobrą rzeczą jest uelastycznienie kodu w obliczu zmian, które mogą nastąpić w dającej się przewidzieć przyszłości, lub uniknięcie powielania kodu.

Z drugiej strony wysiłki na rzecz luźnego łączenia komponentów zwiększają stopień pośredniości w programie, zwiększając w ten sposób jego złożoność, często utrudniając zrozumienie i często zmniejszając jego wydajność.

Czy uważasz, że skupianie się na luźnym sprzężeniu bez żadnych przypadków użycia luźnego sprzężenia (takich jak unikanie duplikacji kodu lub planowanie zmian, które mogą wystąpić w dającej się przewidzieć przyszłości) jest anty-wzorcem? Czy luźne złącze może wpaść pod parasol YAGNI?


1
Większość korzyści wiąże się z pewnym kosztem. Użyj, jeśli korzyść przewyższa koszt.
LennyProgrammers,

4
zapomniałeś drugiej strony luźnej monety sprzęgającej, a ta high cohesionjedna bez drugiej to strata wysiłku i ilustracji, podstawowy brak zrozumienia jednego z nich.

Odpowiedzi:


13

Nieco.

Czasami luźne sprzężenie, które przychodzi bez większego wysiłku, jest w porządku, nawet jeśli nie masz specyficznych wymagań wymagających odłączenia modułu. Nisko wiszące owoce.

Z drugiej strony nadużywanie inżynierii w celu uzyskania absurdalnych zmian będzie wymagało niepotrzebnej złożoności i wysiłku. YAGNI, jak mówisz, uderza go w głowę.


22

Czy praktyka programowania X jest dobra czy zła? Oczywiście, odpowiedź jest zawsze „to zależy”.

Jeśli patrzysz na swój kod, zastanawiasz się, jakie „wzorce” możesz wprowadzić, to robisz to źle.

Jeśli budujesz swoje oprogramowanie, aby niepowiązane ze sobą obiekty nie bawiły się ze sobą, to robisz to dobrze.

Jeśli „projektujesz” swoje rozwiązanie, aby można je było nieskończenie rozszerzać i zmieniać, to w rzeczywistości komplikujesz je.

Myślę, że pod koniec dnia pozostaje ci jedna prawda: czy mniej lub bardziej skomplikowane jest rozłączenie obiektów? Jeśli ich połączenie jest mniej skomplikowane, jest to właściwe rozwiązanie. Jeśli ich rozdzielenie jest mniej skomplikowane, jest to właściwe rozwiązanie.

(Obecnie pracuję w dość małej bazie kodu, która wykonuje prostą pracę w bardzo skomplikowany sposób, a częścią tego, co czyni ją tak skomplikowaną, jest brak zrozumienia terminów „łączenie” i „spójność” ze strony oryginału deweloperzy).


4
Zrobiłem to tylko innego dnia: pomyślałem, że będę potrzebował pewnej pośredniczości między dwiema klasami „na wszelki wypadek”. Potem zraniłem się w głowę, próbując coś debugować, wyrwałem się z pośrednictwa i byłem znacznie szczęśliwszy.
Frank Shearar

3

Myślę, że chodzi tu o spójność . Czy ten kod ma dobry cel? Czy mogę zinternalizować ten cel i zrozumieć „duży obraz” tego, co się dzieje?

Może to prowadzić do trudnego do naśladowania kodu, nie tylko dlatego, że istnieje wiele innych plików źródłowych (zakładając, że są to osobne klasy), ale również dlatego, że żadna pojedyncza klasa nie ma celu.

Z zwinnego punktu widzenia mogę zasugerować, że takie luźne połączenie byłoby anty-wzorem. Bez spójności, a nawet przypadków użycia, nie można napisać sensownych testów jednostkowych i nie można zweryfikować celu kodu. Teraz zwinny kod może prowadzić do luźnego sprzężenia, na przykład w przypadku programowania opartego na testach. Ale jeśli stworzono odpowiednie testy, we właściwej kolejności, prawdopodobnie istnieje zarówno dobra kohezja, jak i luźne połączenie. Mówisz tylko o przypadkach, w których wyraźnie nie ma spójności.

Ponownie, z punktu widzenia zwinności, nie chcesz tego sztucznego poziomu pośrednictwa, ponieważ jest to zmarnowany wysiłek na coś, co prawdopodobnie i tak nie będzie potrzebne. O wiele łatwiej jest refaktoryzować, gdy potrzeba jest prawdziwa.

Ogólnie rzecz biorąc, potrzebujesz wysokiego sprzężenia w swoich modułach i luźnego sprzężenia między nimi. Bez wysokiego sprzężenia prawdopodobnie nie masz spójności.


1

W przypadku większości takich pytań odpowiedź brzmi „to zależy”. Ogólnie rzecz biorąc, jeśli uda mi się stworzyć projekt logicznie luźno sprzężony w sposób, który ma sens, bez dużego narzutu, to zrobię. Unikanie niepotrzebnego łączenia w kodzie jest moim zdaniem całkowicie wartościowym celem projektowym.

Gdy dojdzie do sytuacji, w której wydaje się, że komponenty powinny być logicznie ściśle ze sobą powiązane, będę szukał przekonującego argumentu, zanim zacznę je rozbijać.

Wydaje mi się, że zasadą, nad którą pracuję z większością tego rodzaju praktyk, jest zasada bezwładności. Mam pojęcie o tym, jak chciałbym, aby mój kod działał i jeśli mogę to zrobić bez utrudniania życia, to zrobię to. Jeśli to sprawi, że rozwój będzie trudniejszy, ale utrzymanie i przyszłe prace będą łatwiejsze, postaram się zgadnąć, czy będzie to więcej pracy w całym okresie istnienia kodu i wykorzystam to jako mój przewodnik. W przeciwnym razie warto wybrać celowy projekt.


1

Prosta odpowiedź brzmi: luźne sprzęganie jest dobre, jeśli jest wykonane poprawnie.

Jeśli przestrzegana jest zasada jednej funkcji, jeden cel, to powinno być wystarczająco łatwe śledzenie tego, co się dzieje. Również luźny kod pary następuje oczywiście bez żadnego wysiłku.

Proste zasady projektowania: 1. nie buduj wiedzy o wielu przedmiotach w jednym punkcie (jak zaznaczono wszędzie, zależy), chyba że budujesz interfejs fasady. 2. jedna funkcja - jeden cel (ten cel może być wieloaspektowy, np. W elewacji) 3. jeden moduł - jeden przejrzysty zestaw powiązanych ze sobą funkcji - jeden wyraźny cel 4. jeśli nie można po prostu przetestować go jednostkowo, to nie ma prosty cel

Wszystkie te komentarze na temat łatwiejszego późniejszego refaktoryzacji to mnóstwo dorszy. Gdy wiedza zostanie wbudowana w wiele miejsc, szczególnie w systemach rozproszonych, koszt refaktoryzacji, synchronizacji wdrażania i niemal każdego innego koszta powoduje, że do tej pory nie bierze się pod uwagę, że w większości przypadków system zostaje z tego powodu zniszczony.

Smutne jest, że w dzisiejszych czasach tworzenie oprogramowania jest 90% ludzi, którzy opracowują nowe systemy i nie mają możliwości zrozumienia starych systemów, i nigdy nie są w pobliżu, gdy system jest w tak złym stanie zdrowia z powodu ciągłego refaktoryzacji części.


0

Nie ma znaczenia, jak ściśle jedna rzecz jest połączona z drugą, jeśli ta druga rzecz nigdy się nie zmienia. Przez lata uważałem, że bardziej produktywne jest skupianie się na szukaniu mniejszej liczby powodów do zmiany rzeczy, dążeniu do stabilności, niż ułatwianie ich zmiany poprzez próbę uzyskania możliwie najlżejszej formy sprzężenia. Odsprzęganie okazało się bardzo przydatne, do tego stopnia, że ​​czasami preferuję skromne powielanie kodu w celu rozłączania pakietów. Jako podstawowy przykład mogłem użyć biblioteki matematycznej do zaimplementowania biblioteki obrazów. Nie skopiowałem i po prostu skopiowałem kilka podstawowych funkcji matematycznych, których kopiowanie było banalne.

Teraz moja biblioteka obrazów jest całkowicie niezależna od biblioteki matematycznej w taki sposób, że bez względu na to, jakie zmiany wprowadzę do biblioteki matematycznej, nie wpłynie to na bibliotekę obrazów. To stawia stabilność przede wszystkim. Biblioteka obrazów jest teraz bardziej stabilna, ponieważ ma drastycznie mniej powodów do zmiany, ponieważ jest oddzielona od dowolnej innej biblioteki, która mogłaby się zmienić (oprócz standardowej biblioteki C, która, mam nadzieję, nigdy nie powinna się zmienić). Jako bonus można go również łatwo wdrożyć, gdy jest to samodzielna biblioteka, która nie wymaga pobierania wielu innych bibliotek, aby ją zbudować i używać.

Stabilność jest dla mnie bardzo pomocna. Lubię budować kolekcję dobrze przetestowanego kodu, który ma coraz mniej powodów, aby kiedykolwiek zmieniać się w przyszłości. To nie marzenie z fajki; Mam kod C, którego używałem i używam ponownie od późnych lat 80-tych, które od tamtej pory wcale się nie zmieniły. To wprawdzie niskopoziomowe rzeczy, takie jak kod zorientowany na piksele i geometria, podczas gdy wiele moich rzeczy na wyższym poziomie stało się przestarzałych, ale jest to coś, co nadal bardzo pomaga w utrzymaniu. To prawie zawsze oznacza bibliotekę, która polega na coraz mniejszej liczbie rzeczy, jeśli w ogóle nic zewnętrznego. Niezawodność rośnie w górę, jeśli oprogramowanie w coraz większym stopniu zależy od stabilnych podstaw, które nie mają lub nie mają powodów do zmiany. Mniej ruchomych części jest naprawdę fajnych, nawet jeśli w praktyce ruchome części są znacznie większe niż części stabilne.

Luźne sprzężenie jest w tym samym stylu, ale często uważam, że luźne sprzężenie jest o wiele mniej stabilne niż brak sprzężenia. O ile nie pracujesz w zespole z znacznie lepszymi projektantami interfejsów i klientami, którzy nie zmieniają zdania, z którymi kiedykolwiek pracowałem, nawet czyste interfejsy często znajdują powody do zmiany w sposób, który wciąż powoduje kaskadowe awarie w całym kodzie. Pomysł, że stabilność można osiągnąć kierując zależności w stronę abstraktu, a nie konkretów, jest użyteczny tylko wtedy, gdy projekt interfejsu jest łatwiejszy do poprawienia za pierwszym razem niż implementacja. Często zdarza mi się, że jest odwrotnie, gdy deweloper mógł stworzyć bardzo dobrą, jeśli nie cudowną implementację, biorąc pod uwagę wymagania projektowe, które według nich powinny spełnić, ale w przyszłości okazuje się, że wymagania projektowe całkowicie się zmieniają.

Dlatego lubię stabilność i całkowite oddzielenie, aby móc przynajmniej śmiało powiedzieć: „Ta mała izolowana biblioteka, która była używana od lat i zabezpieczona dokładnymi testami, nie ma prawie żadnych szans na konieczność wprowadzenia zmian bez względu na to, co dzieje się w chaotycznym świecie zewnętrznym . ” Daje mi to odrobinę rozsądku bez względu na to, jakie zmiany projektowe są wymagane na zewnątrz.

Sprzężenie i stabilność, przykład ECS

Uwielbiam także systemy encji-komponentów, które wprowadzają wiele ścisłych powiązań, ponieważ wszystkie zależności od systemu do komponentu uzyskują bezpośredni dostęp do surowych danych i manipulują nimi, w następujący sposób:

wprowadź opis zdjęcia tutaj

Wszystkie zależności tutaj są dość ścisłe, ponieważ komponenty po prostu ujawniają surowe dane. Zależności nie płyną w kierunku abstrakcji, płyną w kierunku nieprzetworzonych danych, co oznacza, że ​​każdy system ma maksymalną możliwą wiedzę na temat każdego rodzaju komponentu, o który prosi o dostęp. Komponenty nie mają żadnej funkcjonalności, ponieważ wszystkie systemy uzyskują dostęp do surowych danych i manipulują nimi. Jednak bardzo łatwo jest uzasadnić taki system, ponieważ jest tak płaski. Jeśli tekstura wyjdzie paskudnie, od razu wiesz z tym systemem, że tylko system renderowania i malowania ma dostęp do składników tekstury i prawdopodobnie możesz szybko wykluczyć system renderowania, ponieważ czyta tylko z tekstur koncepcyjnie.

Tymczasem luźno powiązana alternatywa może być następująca:

wprowadź opis zdjęcia tutaj

... z wszystkimi zależnościami płynącymi w kierunku funkcji abstrakcyjnych, a nie danych, i każda pojedyncza rzecz na tym schemacie ujawnia własny interfejs i własną funkcjonalność. Tutaj wszystkie zależności mogą być bardzo luźne. Obiekty mogą nawet nie zależeć bezpośrednio od siebie i oddziaływać ze sobą za pośrednictwem czystych interfejsów. Nadal bardzo trudno jest uzasadnić ten system, szczególnie jeśli coś pójdzie nie tak, biorąc pod uwagę złożoną plątaninę interakcji. Będzie też więcej interakcji (więcej sprzężeń, choć luźniejszych) niż ECS, ponieważ podmioty muszą wiedzieć o agregowanych przez siebie komponentach, nawet jeśli wiedzą tylko o swoim abstrakcyjnym interfejsie publicznym.

Również jeśli coś zmieni się w projekcie, otrzymasz więcej kaskadowych uszkodzeń niż ECS, i zwykle będzie więcej powodów i pokus dla zmian w projekcie, ponieważ każda rzecz stara się zapewnić ładny obiektowy interfejs i abstrakcję. To natychmiast przychodzi z pomysłem, że każda drobiazg będzie próbować nałożyć ograniczenia i ograniczenia na projekt, a te ograniczenia są często tym, co uzasadnia zmiany w projekcie. Funkcjonalność jest znacznie bardziej ograniczona i musi przyjmować o wiele więcej założeń projektowych niż surowe dane.

Odkryłem w praktyce, że powyższy typ „płaskiego” systemu ECS jest o wiele łatwiejszy do uzasadnienia niż nawet najbardziej luźno sprzężone systemy ze złożoną siecią luźnych zależności i, co najważniejsze, mam tak mało powodów aby wersja ECS musiała kiedykolwiek zmieniać istniejące komponenty, ponieważ te komponenty nie były odpowiedzialne, poza dostarczeniem odpowiednich danych potrzebnych do funkcjonowania systemów. Porównaj trudność zaprojektowania czystego IMotioninterfejsu i konkretnego obiektu ruchu implementującego ten interfejs, który zapewnia wyrafinowaną funkcjonalność, jednocześnie próbując utrzymać niezmienniki danych prywatnych w porównaniu z komponentem ruchu, który musi tylko dostarczyć surowe dane istotne dla rozwiązania problemu i nie przeszkadza funkcjonalność.

Funkcjonalność jest o wiele trudniejsza do uzyskania niż dane, dlatego myślę, że często lepiej jest kierować przepływ zależności w kierunku danych. W końcu ile jest bibliotek wektorów / macierzy? Ilu z nich używa dokładnie tej samej reprezentacji danych i tylko nieznacznie różni się funkcjonalnością? Niezliczone, a jednak wciąż mamy ich tyle, pomimo identycznych reprezentacji danych, ponieważ chcemy subtelnych różnic w funkcjonalności. Ile jest bibliotek obrazów? Ile z nich reprezentuje piksele w inny i niepowtarzalny sposób? Prawie żaden, i ponownie pokazując, że funkcjonalność jest znacznie bardziej niestabilna i podatna na zmiany projektowe niż dane w wielu scenariuszach. Oczywiście w pewnym momencie potrzebujemy funkcjonalności, ale możesz zaprojektować systemy, w których większość zależności płynie w kierunku danych, a nie ogólnie abstrakcje lub funkcjonalność. Taki priorytet nadałby stabilności ponad sprzężenie.

Najbardziej stabilne funkcje, jakie kiedykolwiek napisałem (takie, których używałem i których używałem od późnych lat 80. bez konieczności ich zmiany), wszystkie polegały na surowych danych, takie jak funkcja geometrii, która akceptuje tablicę liczba zmiennoprzecinkowa i liczby całkowite, nie te zależne od złożonego Meshobiektu lub IMeshinterfejsu, lub mnożenie wektora / macierzy, które zależały od float[]lub double[]nie, od którego zależało FancyMatrixObjectWhichWillRequireDesignChangesNextYearAndDeprecateWhatWeUse.

Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.