Byłem dzisiaj zaangażowany w dyskusję programistyczną, w której wydałem kilka stwierdzeń, które zasadniczo przyjmowały aksjomatycznie, że odwołania cykliczne (między modułami, klasami, cokolwiek) są ogólnie złe. Kiedy skończyłem z boiskiem, mój współpracownik zapytał: „Co jest nie tak z okólnikami?”.
Mam w tej sprawie silne uczucia, ale trudno mi wyrazić zwięźle i konkretnie. Wszelkie wyjaśnienia, które mogę wymyślić, polegają zwykle na innych elementach, które również uważam za aksjomaty („nie mogę używać w izolacji, więc nie mogę przetestować”, „nieznane / niezdefiniowane zachowanie jako stan mutuje w uczestniczących obiektach” itp. .), ale chciałbym usłyszeć zwięzły powód, dla którego błędne są okólniki, które nie wykorzystują tego rodzaju skoków wiary, jakie robi mój własny mózg, spędzając wiele godzin w ciągu lat na rozwikłaniu ich, aby zrozumieć, naprawić, i rozszerz różne bity kodu.
Edycja: Nie pytam o homogeniczne odwołania cykliczne, takie jak te na podwójnie połączonej liście lub wskaźnik do rodzica. To pytanie naprawdę dotyczy pytań o „szerszym zakresie”, takich jak wywołanie libA libB, które wywołuje z powrotem libA. Jeśli chcesz, zamień „moduł” na „lib”. Dziękujemy za wszystkie dotychczasowe odpowiedzi!
źródło
Odpowiedzi:
W okólnikach jest wiele błędów:
Referencje klas okrągłych tworzą wysokie sprzężenie ; obie klasy muszą zostać ponownie skompilowane za każdym razem, gdy jedna z nich zostanie zmieniona.
Okrągłe montażowe odniesienia zapobieżenia sieciowania statyczne , ponieważ zależy od B, ale nie może być zmontowana, aż B jest zakończona.
Odwołania do obiektów okrągłych mogą powodować awarie naiwnych algorytmów rekurencyjnych (takich jak serializatory, osoby odwiedzające i ładne drukarki) z przepełnieniem stosu. Bardziej zaawansowane algorytmy będą miały wykrywanie cyklu i po prostu zawiodą z bardziej opisowym komunikatem o wyjątku / błędzie.
Odwołania do obiektów okrągłych uniemożliwiają także wstrzykiwanie zależności , co znacznie zmniejsza testowalność systemu.
Obiekty z bardzo dużą liczbą okrągłych odniesień są często obiektami Boga . Nawet jeśli nie są, mają tendencję do wprowadzania kodu spaghetti .
Okrągłe jednostka odniesienia (zwłaszcza w bazach danych, ale również w modelach domen) uniemożliwić korzystanie z ograniczeniami bez wartości null , które mogą ostatecznie doprowadzić do utraty danych lub co najmniej niespójność.
Odniesienia kołowe zwykle są po prostu mylące i drastycznie zwiększają obciążenie poznawcze, gdy próbuje się zrozumieć, jak działa program.
Proszę, pomyśl o dzieciach; w miarę możliwości unikaj okrągłych odniesień.
źródło
Referencja kołowa to dwukrotne sprzężenie referencji nieokrągłej.
Jeśli Foo wie o Baru, a Bar wie o Foo, masz dwie rzeczy, które wymagają zmiany (gdy pojawi się wymaganie, że Foos i Bary nie mogą się już więcej o sobie wiedzieć). Jeśli Foo wie o Baru, ale Bar nie wie o Foo, możesz zmienić Foo bez dotykania Bar.
Cykliczne odniesienia mogą również powodować problemy z ładowaniem, przynajmniej w środowiskach, które trwają długo (wdrożone usługi, środowiska programistyczne oparte na obrazach), gdzie Foo zależy od Bar działającego w celu załadowania, ale Bar zależy również od Foo działającego w celu obciążenie.
źródło
Gdy połączysz dwa bity kodu, masz jeden duży fragment kodu. Trudność utrzymania odrobiny kodu to co najmniej kwadrat jego wielkości, a być może nawet większy.
Ludzie często patrzą na złożoność pojedynczej klasy (/ function / file / etc.) I zapominają, że naprawdę powinieneś wziąć pod uwagę złożoność najmniejszej możliwej do rozdzielenia (kapsułkowania) jednostki. Posiadanie zależności cyklicznej zwiększa rozmiar tej jednostki, być może niewidocznie (dopóki nie zaczniesz próbować zmienić pliku 1 i nie zauważysz, że wymaga to również zmian w plikach 2-127).
źródło
Mogą być złe nie same w sobie, ale jako wskaźnik możliwego złego projektu. Jeśli Foo zależy od Bar, a Bar zależy od Foo, uzasadnione jest pytanie, dlaczego są to dwa zamiast unikalnego FooBar.
źródło
Hmm ... to zależy od tego, co rozumiesz przez zależność cykliczną, ponieważ w rzeczywistości istnieją pewne zależności cykliczne, które moim zdaniem są bardzo korzystne.
Rozważmy DOM XML - sensowne jest, aby każdy węzeł miał odwołanie do swojego rodzica, a każdy rodzic miał listę swoich dzieci. Struktura jest logicznie drzewem, ale z punktu widzenia algorytmu wyrzucania elementów bezużytecznych lub podobnego struktura jest okrągła.
źródło
Node
jest to klasa, która zawiera w sobie inne odniesienia doNode
dzieci. Ponieważ odwołuje się tylko do siebie, klasa jest całkowicie samodzielna i nie jest sprzężona z niczym innym. --- Za pomocą tego argumentu można argumentować, że funkcja rekurencyjna jest zależnością cykliczną. To jest (bez przerwy), ale nie w złym tego słowa znaczeniu.Jest jak problem z kurczakiem lub jajkiem .
W wielu przypadkach odwołanie cykliczne jest nieuniknione i przydatne, ale na przykład w następującym przypadku nie działa:
Projekt A zależy od projektu B, a B zależy od A. Kompilacja A wymaga użycia w B, która wymaga kompilacji B przed A, która wymaga kompilacji B przed A, która ...
źródło
Chociaż zgadzam się z większością komentarzy tutaj, chciałbym przedstawić specjalny przypadek dotyczący okólnika „rodzic” / „dziecko”.
Klasa często musi wiedzieć coś o swojej klasie nadrzędnej lub właścicielskiej, być może domyślnym zachowaniu, nazwie pliku, z którego pochodzą dane, instrukcji sql, która wybrała kolumnę, lub lokalizacji pliku dziennika itp.
Możesz to zrobić bez odwołania cyklicznego, posiadając klasę zawierającą, dzięki czemu to, co wcześniej było „rodzicem”, jest teraz rodzeństwem, ale nie zawsze jest to możliwe, aby ponownie uwzględnić istniejący kod, aby to zrobić.
Inną alternatywą jest przekazanie wszystkich danych, których dziecko może potrzebować w swoim konstruktorze, co w końcu jest po prostu okropne.
źródło
W kategoriach bazy danych cykliczne odwołania z odpowiednimi relacjami PK / FK uniemożliwiają wstawianie lub usuwanie danych. Jeśli nie możesz usunąć z tabeli a, chyba że rekord zniknie z tabeli b i nie możesz usunąć z tabeli b, chyba że rekord zniknie z tabeli A, nie możesz usunąć. To samo z wkładkami. dlatego wiele baz danych nie pozwala na konfigurowanie kaskadowych aktualizacji lub usuwanie, jeśli istnieje cykliczne odwołanie, ponieważ w pewnym momencie staje się to niemożliwe. Tak, możesz ustanowić tego rodzaju relacje bez formalnego zadeklarowania PK / Fk, ale wtedy (według mojego doświadczenia) będziesz miał problemy z integralnością danych. To po prostu zły projekt.
źródło
Przyjmę to pytanie z punktu widzenia modelowania.
Dopóki nie dodasz żadnych relacji, których tak naprawdę nie ma, jesteś bezpieczny. Jeśli je dodasz, uzyskasz mniej integralności danych (ponieważ występuje nadmiarowość) i ściślej powiązany kod.
W przypadku odnośników cyklicznych chodzi konkretnie o to, że nie widziałem przypadku, w którym byłyby one faktycznie potrzebne, z wyjątkiem jednego odniesienia do siebie. Jeśli modelujesz drzewa lub wykresy, potrzebujesz tego i wszystko jest w porządku, ponieważ samoodniesienie jest nieszkodliwe z punktu widzenia jakości kodu (bez dodanej zależności).
Uważam, że w chwili, gdy zaczynasz potrzebować odniesienia innego niż ja, natychmiast powinieneś zapytać, czy nie możesz modelować go jako wykresu (zwinąć wiele bytów w jeden - węzeł). Być może jest jakiś przypadek, w którym tworzysz odniesienie kołowe, ale modelowanie go jako wykresu jest nieodpowiednie, ale bardzo w to wątpię.
Istnieje niebezpieczeństwo, że ludzie myślą, że potrzebują okólnika, ale w rzeczywistości nie potrzebują. Najczęstszym przypadkiem jest „przypadek jednego z wielu”. Na przykład masz klienta z wieloma adresami, z których jeden powinien być oznaczony jako adres podstawowy. Modelowanie tej sytuacji jest bardzo kuszące, ponieważ dwie osobne relacje has_address i is_primary_address_of, ale nie są poprawne. Powodem jest to, że bycie adresem podstawowym nie jest oddzielną relacją między użytkownikami a adresami, ale jest atrybutem relacji ma adres. Dlaczego? Ponieważ jego domena jest ograniczona do adresów użytkowników, a nie do wszystkich dostępnych adresów. Wybierz jeden z linków i oznacz go jako najsilniejszy (główny).
(Zamierzam teraz mówić o bazach danych) Wiele osób wybiera rozwiązanie dwóch relacji, ponieważ rozumie, że „podstawowy” jest unikalnym wskaźnikiem, a klucz obcy jest rodzajem wskaźnika. Więc klucz obcy powinien być odpowiedni, prawda? Źle. Klucze obce reprezentują relacje, ale „pierwotny” nie jest relacją. Jest to zdegenerowany przypadek zamówienia, w którym jeden element jest przede wszystkim, a reszta nie jest uporządkowana. Gdybyś musiał modelować całkowite uporządkowanie, oczywiście wziąłbyś to za atrybut relacji, ponieważ w zasadzie nie ma innego wyboru. Ale w chwili, gdy go degenerujesz, istnieje wybór - i to okropny - modelowanie czegoś, co nie jest relacją jako relacją. Nadchodzi więc - nadmiar związku, którego z pewnością nie należy lekceważyć.
Nie pozwoliłbym więc na pojawienie się cyklicznego odniesienia, chyba że jest absolutnie jasne, że pochodzi ono z tego, co modeluję.
(uwaga: jest to nieco stronnicze w stosunku do projektu bazy danych, ale założę się, że ma to również zastosowanie w innych obszarach)
źródło
Odpowiem na to pytanie innym pytaniem:
Jaką sytuację możesz mi podać, gdzie utrzymanie kołowego modelu odniesienia jest najlepszym modelem tego, co próbujesz zbudować?
Z mojego doświadczenia wynika, że najlepszy model prawie nigdy nie będzie zawierał okrągłych odniesień w sposób, który według mnie masz na myśli. To powiedziawszy, istnieje wiele modeli, w których przez cały czas używasz referencji okrągłych, jest to po prostu bardzo podstawowe. Rodzic -> Relacje podrzędne, dowolny model wykresu itp., Ale są to dobrze znane modele i myślę, że odnosisz się do czegoś zupełnie innego.
źródło
Odnośniki cykliczne w strukturach danych są czasem naturalnym sposobem wyrażania modelu danych. Pod względem kodowania zdecydowanie nie jest idealny i może być (do pewnego stopnia) rozwiązany przez wstrzyknięcie zależności, przenosząc problem z kodu na dane.
źródło
Okrągła konstrukcja odniesienia jest problematyczna, nie tylko z punktu widzenia projektu, ale także z punktu widzenia wychwytywania błędów.
Rozważ możliwość awarii kodu. Nie umieściłeś właściwego wychwytywania błędów w żadnej z klas, albo dlatego, że nie opracowałeś jeszcze swoich metod, albo jesteś leniwy. Tak czy inaczej, nie masz komunikatu o błędzie informującego o tym, co się wydarzyło, i musisz go debugować. Jako dobry projektant programów wiesz, jakie metody są powiązane z procesami, więc możesz zawęzić je do metod związanych z procesem, który spowodował błąd.
Dzięki referencjom cyklicznym Twoje problemy się podwoiły. Ponieważ twoje procesy są ściśle powiązane, nie masz możliwości dowiedzenia się, która metoda mogła spowodować błąd lub skąd wziął się błąd, ponieważ jedna klasa jest zależna od drugiej, jest zależna od drugiej. Musisz teraz poświęcić czas na przetestowanie obu klas w celu ustalenia, która z nich jest naprawdę odpowiedzialna za błąd.
Oczywiście prawidłowe wychwytywanie błędów rozwiązuje ten problem, ale tylko wtedy, gdy wiadomo, kiedy wystąpi błąd. A jeśli używasz ogólnych komunikatów o błędach, nadal nie ma się o wiele lepiej.
źródło
Niektóre śmieciarki mają problemy z ich czyszczeniem, ponieważ do każdego obiektu odwołuje się inny.
EDYCJA: Jak zauważono w komentarzach poniżej, jest to prawdą tylko w przypadku wyjątkowo naiwnej próby wyrzucania śmieci, a nie takiej, którą można spotkać w praktyce.
źródło
Moim zdaniem posiadanie nieograniczonych referencji ułatwia projektowanie programów, ale wszyscy wiemy, że niektóre języki programowania nie obsługują ich w niektórych kontekstach.
Wspomniałeś o referencjach między modułami lub klasami. W takim przypadku jest to rzecz statyczna, predefiniowana przez programistę, i programista może wyraźnie szukać struktury, która nie ma okrągłości, chociaż może nie pasować do problemu.
Prawdziwy problem pojawia się w postaci cykliczności w strukturach danych w czasie wykonywania, gdzie niektórych problemów nie można tak naprawdę zdefiniować w sposób, który eliminuje cykliczność. W końcu - to problem, który powinien dyktować i wymagać czegoś innego, zmusza programistę do rozwiązania niepotrzebnej układanki.
Powiedziałbym, że to problem z narzędziami, a nie zasada.
źródło