Czy można zastosować OSUSZANIE bez zwiększania sprzężenia?


14

Załóżmy, że mamy moduł oprogramowania A, który implementuje funkcję F. Inny moduł B implementuje tę samą funkcję co F '.

Istnieje wiele sposobów na pozbycie się duplikatu kodu:

  1. Niech A użyje F 'z B.
  2. Niech B użyje F z A.
  3. Umieść F we własnym module C i pozwól, aby korzystały z niego zarówno A, jak i B.

Wszystkie te opcje generują dodatkowe zależności między modułami. Stosują zasadę OSUSZANIA kosztem rosnącego sprzężenia.

O ile widzę, sprzężenie jest zawsze zwiększane lub w leasingu przenoszone na wyższy poziom podczas stosowania DRY. Wydaje się, że istnieje konflikt między dwiema najbardziej podstawowymi zasadami projektowania oprogramowania.

(Właściwie nie wydaje mi się zaskakujące, że takie konflikty są takie. Prawdopodobnie to sprawia, że ​​dobry projekt oprogramowania jest tak trudny. Zaskakujące jest to, że konflikty te zwykle nie są omawiane w tekstach wprowadzających.)

Edycja (dla wyjaśnienia): Zakładam, że równość F i F 'to nie tylko przypadek. Jeśli F będzie musiał zostać zmodyfikowany, F 'prawdopodobnie będzie musiał zostać zmodyfikowany w ten sam sposób.


2
... Myślę, że DRY może być bardzo przydatną strategią, ale to pytanie ilustruje nieskuteczność DRY. Niektórzy (np. Entuzjaści OOP) mogą argumentować, że powinieneś skopiować / wkleić F do B tylko ze względu na utrzymanie koncepcyjnej autonomii A i B, ale nie wpadłem na scenariusz, w którym to zrobiłbym. Myślę, że kopiowanie / wklejanie kodu jest tylko najgorszą opcją, nie mogę znieść tego „krótkotrwałego zaniku pamięci”, kiedy byłem tak pewien, że już napisałem metodę / funkcję, aby coś zrobić; naprawienie błędu w jednej funkcji i zapomnienie o aktualizacji innej może być kolejnym poważnym problemem.
jrh

3
Istnieją takie sprzeczne ze sobą zasady OO. W większości przypadków musisz znaleźć rozsądny kompromis. Ale zasada IMHO DRY jest najcenniejsza. Jak napisał @jrh: posiadanie tego samego zachowania w wielu miejscach jest koszmarem konserwacyjnym, którego należy unikać za wszelką cenę. Stwierdzenie, że zapomniałeś zaktualizować jedną ze zbędnych kopii w produkcji, może zniszczyć Twój biznes.
Timothy Truckle,

2
@TimothyTruckle: Nie powinniśmy nazywać ich zasadami OO, ponieważ dotyczą one również innych paradygmatów programowania. I tak, SUCHO jest cenne, ale również niebezpieczne, jeśli zostanie przedawnione. Nie tylko dlatego, że ma tendencję do tworzenia zależności, a tym samym złożoności. Często stosuje się go również do duplikatów spowodowanych przypadkiem, które mają różne powody do zmiany.
Frank Puffer

1
... czasami też, gdy natknąłem się na ten scenariusz, byłem w stanie rozbić F na części, które można wykorzystać do tego, czego potrzebuje A i czego B.
jrh

1
Sprzężenie nie jest z natury złe i często jest konieczne, aby zmniejszyć liczbę błędów i zwiększyć wydajność. Gdybyś użył funkcji analizy składni ze standardowej biblioteki swojego języka w swojej funkcji, powiązałbyś swoją funkcję ze standardową biblioteką. Nie widziałem programu, który nie robi tego od wielu lat. Kluczem jest, aby nie tworzyć niepotrzebnych połączeń. Najczęściej interfejs służy do unikania / usuwania takiego sprzężenia. Na przykład moja funkcja może zaakceptować implementację parseInt jako argumentu. Nie zawsze jest to jednak konieczne i nie zawsze jest mądre.
Joshua Jones

Odpowiedzi:


14

Wszystkie te opcje generują dodatkowe zależności między modułami. Stosują zasadę OSUSZANIA kosztem rosnącego sprzężenia.

Dlaczego tak. Ale zmniejszają sprzężenie między liniami. Otrzymujesz moc zmiany sprzęgła. Sprzężenie występuje w wielu formach. Wyodrębnianie kodu zwiększa pośrednie i abstrakcyjne. Zwiększenie, które może być dobre lub złe. Najważniejszą rzeczą, która decyduje o tym, którą masz, jest nazwa, której używasz. Jeśli patrzenie na to nazwisko mnie zaskakuje, kiedy zajrzę do środka, to nikomu nie wyświadczyłeś przysług.

Ponadto, nie podążaj za OSUSZANIEM w próżni. Jeśli zabijesz duplikat, bierzesz odpowiedzialność za przewidywanie, że te dwa zastosowania tego kodu zmienią się razem. Jeśli prawdopodobnie zmienią się one niezależnie, spowodowałeś zamieszanie i dodatkową pracę przynoszącą niewielkie korzyści. Ale naprawdę dobre imię może sprawić, że będzie to smaczniejsze. Jeśli wszystko, co możesz wymyślić, to złe imię, proszę, po prostu przestań.

Łączenie zawsze będzie istnieć, chyba że system jest tak odizolowany, że nikt nigdy nie będzie wiedział, czy działa. Sprzężenie refaktoryzacyjne to gra polegająca na wyborze trucizny. Postępowanie w trybie DRY może się opłacić, minimalizując sprzężenie powstałe w wyniku wielokrotnego wyrażania tej samej decyzji projektowej w wielu miejscach, dopóki zmiana nie będzie bardzo trudna. Ale DRY może uniemożliwić zrozumienie twojego kodu. Najlepszym sposobem na uratowanie tej sytuacji jest znalezienie naprawdę dobrego imienia. Jeśli nie potrafisz wymyślić dobrego imienia, mam nadzieję, że potrafisz unikać imion bez znaczenia


Aby upewnić się, że poprawnie rozumiem twój punkt, pozwól, że powiem to nieco inaczej: jeśli nadasz wyodrębnionemu kodowi dobrą nazwę, wyodrębniony kod nie będzie już istotny dla zrozumienia oprogramowania, ponieważ nazwa mówi wszystko (idealnie). Sprzężenie wciąż istnieje na poziomie technicznym, ale nie na poziomie poznawczym. Dlatego jest względnie nieszkodliwy, prawda?
Frank Puffer,


@FrankPuffer lepiej?
candied_orange

3

Istnieją sposoby na przełamanie jawnych zależności. Popularnym jest wstrzykiwanie zależności w czasie wykonywania. W ten sposób uzyskuje się OSUSZANIE, zdejmowanie sprzęgła kosztem bezpieczeństwa statycznego. Obecnie jest tak popularny, że ludzie nawet nie rozumieją, że jest to kompromis. Na przykład kontenery aplikacji rutynowo zapewniają zarządzanie zależnościami, co znacznie komplikuje oprogramowanie, ukrywając złożoność. Nawet zwykły zastrzyk starego konstruktora nie gwarantuje niektórych kontraktów z powodu braku systemu typów.

Aby odpowiedzieć na tytuł - tak, jest to możliwe, ale bądź przygotowany na konsekwencje wysyłki środowiska uruchomieniowego.

  • Zdefiniuj interfejs F A w A, zapewniając funkcjonalność F
  • Zdefiniuj interfejs F B w B
  • Umieść F w C.
  • Utwórz moduł D, aby zarządzać wszystkimi zależnościami (zależy to od A, B i C)
  • Dostosuj F do F A i F B w D.
  • Wstrzyknąć (przekazać) owijki do A i B.

W ten sposób jedynym typem zależności, jakie można mieć, jest D w zależności od każdego modułu.

Lub zarejestruj C w kontenerze aplikacji z wbudowanym wstrzykiwaniem zależności i ciesz się fortunami autowired wolno rosnących pętli i zakleszczeń klas środowiska wykonawczego.


1
+1, ale z wyraźnym zastrzeżeniem, że jest to zwykle zły kompromis. To bezpieczeństwo statyczne służy twojej ochronie, a obchodzenie go za pomocą upiornej akcji na odległość wymaga tylko trudnych do wyśledzenia błędów w dalszej linii, gdy złożoność projektu nieco wzrasta ...
Mason Wheeler

1
Czy naprawdę można powiedzieć, że DI przełamuje zależność? Nawet formalnie potrzebujesz interfejsu z podpisem F. Ale mimo to, jeśli moduł A używa Rw F z R, to się na nim rozłącza, bez względu na to, że C jest wstrzykiwany w czasie wykonywania lub bezpośrednio połączony. Nie przerywa zależności, błąd tylko odkłada awarię, jeśli zależność nie jest zapewniona
max630

@ max630 zastępuje zależność implementacyjną od umownej, która jest słabsza.
Basilevs,

@ max630 Masz rację. Nie można powiedzieć, że DI przerywa zależność. DI jest w rzeczywistości metodą wprowadzania zależności i jest naprawdę ortogonalna w stosunku do pytania zadawanego w odniesieniu do łączenia. Zmiana F (lub interfejs go enkapsulujący) nadal wymagałaby zmiany zarówno A, jak i B.
king-side-slide

1

Nie jestem pewien, czy odpowiedź bez dalszego kontekstu ma sens.

Czy Ajuż zależy Blub odwrotnie? - w takim przypadku możemy mieć oczywisty wybór domu F.

Czy Ai Bjuż udostępniać żadnych wspólnych zależności, które mogą być dobre do domu F?

Jak duży / złożony jest F? Od czego jeszcze Fzależy?

Moduły są Ai Bstosowane w tym samym projekcie?

Czy Ai tak Bostatecznie skończy się wspólna zależność?

Jaki system językowy / modułowy jest używany: Jak bolesny jest nowy moduł, w bólu programisty, w nakładzie wydajności? Na przykład, jeśli piszesz w C / C ++ z systemem modułowym COM, co powoduje ból w kodzie źródłowym, wymaga alternatywnego oprzyrządowania, ma wpływ na debugowanie i ma wpływ na wydajność (w przypadku wywołań między modułami), mógłbym zrób poważną przerwę.

Z drugiej strony, jeśli mówisz o bibliotekach Java lub C # DLL, które łączą się dość płynnie w środowisku pojedynczego wykonania, to inna sprawa.


Funkcja jest abstrakcją i obsługuje DRY.

Jednak dobre abstrakty muszą być kompletne - niekompletne abstrakty mogą bardzo dobrze powodować, że konsumujący klient (programista) uzupełnia niedobór, wykorzystując wiedzę o podstawowej implementacji: skutkuje to ściślejszym sprzężeniem, niż gdyby abstrakcja była oferowana jako pełniejsza.

Tak więc, argumentowałbym za stworzeniem lepszej abstrakcji Ai Bpolegania na niej niż po prostu przeniesieniem jednej funkcji do nowego modułu C

Szukałbym zestawu funkcji, które dałyby początek nowej abstrakcji, co oznacza, że ​​mógłbym poczekać, aż baza kodu będzie dalej, aby zidentyfikować pełniejsze / bardziej kompletne refaktoryzowanie abstrakcji zamiast jednego opartego na jeden kod funkcji powiedzieć.


2
Czy łączenie abstrakcji i wykresu zależności nie jest niebezpieczne?
Basilevs,

Does A already depend on B or vice versa? — in which case we might have an obvious choice of home for F.Zakłada się, że A zawsze będzie polegać na B (lub odwrotnie), co jest bardzo niebezpiecznym założeniem. Fakt, że OP postrzega F jako nieodłączną część A (lub B) sugeruje, że F istnieje bez bycia nieodłącznym dla żadnej biblioteki. Jeśli F należy do jednej biblioteki (np. Metody rozszerzenia DbContext (F) i biblioteki opakowującej Entity Framework (A lub B)), pytanie OP byłoby dyskusyjne.
Flater

0

Odpowiedzi, które koncentrują się na wszystkich sposobach „zminimalizowania” tego problemu, wyrządzają ci krzywdę. A „rozwiązania” po prostu oferujące różne sposoby tworzenia sprzężenia wcale nie są naprawdę rozwiązaniami.

Prawda jest taka, że ​​nie rozumieją samego problemu, który stworzyłeś. Problem z twoim przykładem nie ma nic wspólnego z DRY, a raczej (szerzej) z projektowaniem aplikacji.

Zadaj sobie pytanie, dlaczego moduły A i B są oddzielne, jeśli oba zależą od tej samej funkcji F? Oczywiście będziesz mieć problemy z zarządzaniem zależnościami / abstrakcją / sprzężeniem / you-name-it, jeśli popełnisz zły projekt.

Właściwe modelowanie aplikacji odbywa się zgodnie z zachowaniem. Jako takie, części A i B, które są zależne od F, należy wyodrębnić do ich własnego, niezależnego modułu. Jeśli nie jest to możliwe, należy połączyć A i B. W obu przypadkach A i B nie są już użyteczne dla systemu i powinny przestać istnieć.

DRY to zasada, której można użyć do ujawnienia złego projektu, a nie do jego spowodowania. Jeśli nie możesz osiągnąć OSUSZANIA ( gdy naprawdę ma to zastosowanie - zwracając uwagę na edycję) ze względu na strukturę aplikacji, jest to wyraźny znak, że struktura stała się zobowiązaniem. Dlatego „ciągłe refaktoryzowanie” jest również zasadą, której należy przestrzegać.

ABC innych zleceniodawców projektowych (mocne, suche, itp) tam są wszystkie używane do zmieniania (włączając refactoring) wniosek bardziej bezbolesne. Skup się na tym , a wszystkie inne problemy zaczną znikać.


Czy sugerujesz mieć dokładnie jeden moduł w każdej aplikacji?
Basilevs,

@Basilevs Absolutnie nie (chyba że jest to uzasadnione). Proponuję mieć jak najwięcej całkowicie oddzielonych modułów. W końcu taki jest cel modułu.
king-side-slide

Cóż, pytanie sugeruje, że moduły A i B zawierają niepowiązane funkcje i należy je odpowiednio wyodrębnić, dlaczego i jak powinny przestać istnieć? Jakie jest twoje rozwiązanie problemu, jak stwierdzono?
Basilevs,

@Basilevs Pytanie sugeruje, że A i B nie zostały odpowiednio modelowane. To właśnie ten nieodłączny niedobór powoduje problem. Z pewnością sam fakt, że oni zrobić istnienia nie jest dowodem, że powinien istnieć. Właśnie o tym mówię powyżej. Oczywiście konieczne jest zastosowanie alternatywnego projektu, aby uniknąć zepsucia SUSZENIA. Ważne jest, aby zrozumieć, że głównym celem wszystkich tych „zasad projektowania” jest ułatwienie zmiany aplikacji.
king-side-slide

Czy mnóstwo innych metod, z całkowicie odmiennymi zależnościami, zostało wymodelowanych źle i trzeba było je przerobić tylko z powodu tej jednej nieprzypadkowej? A może zakładasz, że moduły A i B zawierają po jednej metodzie?
Basilevs,

0

Wszystkie te opcje generują dodatkowe zależności między modułami. Stosują zasadę OSUSZANIA kosztem rosnącego sprzężenia.

Mam inną opinię, przynajmniej w przypadku trzeciej opcji:

Z twojego opisu:

  • A potrzebuje F.
  • B potrzebuje F.
  • Ani A, ani B nie potrzebują się nawzajem.

Umieszczenie F w module C nie zwiększa sprzężenia, ponieważ zarówno A, jak i B już potrzebują funkcji C.

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.