Czy istnieją wyjątkowe przypadki, w których możemy zaakceptować duplikat kodu?

57

Pracuję nad projektem oprogramowania, w którym musimy zbudować trzy interfejsy API. Jeden dla kanału bankowości domowej , jeden dla kanału agencji i trzeci dla kanału mobilnego .

Interfejs API agencji jest najbardziej kompletny, ponieważ ma wszystkie funkcje .. następnie nieco mniej Home API, a następnie API mobilne.

Architekci tutaj stworzyli wspólną warstwę (międzykanałowe usługi EJB wspólne dla wszystkich interfejsów API). Ale interfejsy API są różne.

Na razie nie ma dużej różnicy między interfejsami API. Duży zespół zaczął od kanału agencji, a my dostosowujemy go teraz do kanału domowego. Po prostu wzbogacamy obiekty specjalnie do naszej aplikacji domowej. W przeciwnym razie kod jest 95% podobny między interfejsami API. Interfejsy API są oparte na Spring MVC i mają (kontrolery, modele i niektóre narzędzia).

Zasadniczo kontrolery wykonują mapowanie BO do ChannelObject (wydaje mi się, że nie jest to właściwe miejsce do zrobienia tego), a także dodatkowe narzędzia i serializatory. Na razie wszystko jest zduplikowane. Mówią, że powodem duplikacji jest to, że chcą niezależnych interfejsów API. „Jeśli jutro chcemy innego zachowania dla domu niż agencji czy telefonu komórkowego, nie będziemy walczyć !!”

Czy istnieje przypadek, w którym powinniśmy zaakceptować duplikat kodu?

Mohamed Amine Mrad
źródło
22
A jeśli trzy jutro w dół drogi zdecydują, że chcą spójnego dostępu do danych i reprezentacji między wszystkimi interfejsami API ... cóż ... „Walka!”
Becuzz,
26
Duplikat kodu niekoniecznie jest złą rzeczą. Nie można wystarczająco podkreślić powiedzenia „DRY jest wrogiem oddzielania się”. Powiedziawszy to, projektowanie na przyszłość, a nie teraz, naprawdę jest naprawdę bardzo złe. Ta przyszłość prawie nigdy się nie spełnia. Zamiast tego zaprojektuj wysoce oddzielone rozwiązanie, objęte dobrymi automatycznymi testami tego, co jest teraz potrzebne. W przyszłości, jeśli coś innego będzie potrzebne, łatwiej będzie to zmienić.
David Arno,
2
Myślę, że dwa przypadki, w których nigdy nie żałowałem, że powielony kod to (a) w których powielony kod jest bardzo małą i niezbyt ważną częścią całości, oraz (b) gdy kopiuję kod z systemu, który już umiera w nowy system zaprojektowany z myślą o długowieczności. Jest wiele innych przypadków, w których płakałem gorzkimi łzami po rozwidleniu kodu.
Michael Kay,
14
Tam, gdzie pracowałem, często zauważano, że należy (często) powielić raz, a po raz trzeci wyodrębnić wspólność. Usuwa to zeitgeist wczesnej, być może niewłaściwej abstrakcji, która zwiększa sprzężenie zamiast spójności. Kiedy przyszłe wymagania są właściwie dobrze zrozumiane, można oczywiście zrobić wyjątek.
Pieter Geerkens,
3
Jednym trywialnym przypadkiem, w którym można zaakceptować zduplikowany kod, jest wygenerowanie go automatycznie
samgak,

Odpowiedzi:

70

Powielanie może być właściwe, ale nie z tego powodu.

Powiedzenie „możemy chcieć, aby te trzy miejsca w bazie kodu zachowywały się inaczej, chociaż w tej chwili są identyczne”, nie jest dobrym powodem do powielania na dużą skalę. Pojęcie to może mieć zastosowanie do każdego systemu i może być wykorzystane do uzasadnienia jakiegokolwiek powielania, co oczywiście nie jest uzasadnione.

Powielanie powinny być tolerowane tylko przy usuwaniu byłoby ogólnie bardziej kosztowne teraz z jakiegoś innego powodu (nie mogę myśleć o dobry teraz, ale mieć pewność, że mogą być one - praktycznie wszystko w programowaniu to raczej kompromis niż prawo).

W przypadku tego, co robisz, właściwym rozwiązaniem może być np. Wyodrębnienie zachowania, które jest teraz duplikowane, do Strategii lub innego wzorca modelującego zachowanie jako klasy, a następnie użycie trzech wystąpień tej samej klasy. W ten sposób, jeśli nie chcesz, aby zmienić zachowanie w jednym z trzech miejsc, trzeba tylko stworzyć nową strategię i instancji, że w jednym miejscu. W ten sposób musisz tylko dodać niektóre klasy i pozostawić resztę bazy kodu prawie całkowicie nietkniętą.

Kilian Foth
źródło
1
Jeśli dwie rzeczy zachowują się tak samo, jest to powielanie. Chociaż z jakiegoś powodu nie zachowują się tak samo, nie należy ich scalać. Czy można wyodrębnić jakąś wspólną część wdrożeń? Czasami są elementy, które nie są jeszcze abstrakcyjne, kto wie.
Deduplicator,
32
Na przykład mieliśmy fragment kodu używany przez trzy części naszej aplikacji i był on okropnie napisany z mnóstwem małych gałęzi warunkowych na całym świecie, aby uwzględnić wyjątki dla każdej z tych trzech. Ale i to był ważny element, był intensywnie używany przez dwa lata bez żadnych znaczących zgłoszeń błędów. Więc kiedy czwarta część aplikacji musiała coś z tym zrobić, ale znowu nieco inna, podjęto decyzję, aby nie dotykać wadliwego kodu, który działał bezbłędnie, i po prostu skopiować go i stworzyć lepiej napisaną, elastyczną bazę dla przyszłość.
KRyan,
2
Powielanie jest właściwym rozwiązaniem, gdy koszty czytelności zbytnio rosną w porównaniu z kosztami konserwacji, które ostatecznie są ze sobą powiązane. Nie sądzę, aby miało to w ogóle zastosowanie w tym scenariuszu i często jest postrzegane w mniejszej skali w projekcie, a nie coś takiego. Nawet wtedy bardzo rzadko zdarza się, że tak się dzieje, dzieje się tak, jeśli masz niszowe zagnieżdżenie, które zmusiłoby cię do użycia wzorca metody szablonu lub podobnego, ale w małych miejscach. Zwykle kończy się to zaciemnianiem kodu.
opa
Przykładem gdzie kopiowanie jest przydatny (i „usunięcie go yould być ogólnie bardziej kosztowne teraz ”) jest wejście valdation w aplikacjach internetowych: Zatrudniamy pierwszy (ewentualnie uproszczony) walidacji po stronie klienta, dzięki czemu użytkownik otrzymuje natychmiastowe informacje zwrotne o problemach; a następnie przeprowadzamy tę samą (lub dokładniejszą) weryfikację na serwerze, ponieważ nie można ufać klientom.
Hagen von Eitzen,
3
@HagenvonEitzen Ale nie musi to być nawet powielanie, w zależności od stosu technologii. Na przykład, możesz mieć sprawdzanie JavaScript w przeglądarce, a następnie sprawdzanie Java na serwerze, więc masz zduplikowaną funkcjonalność. Ale jeśli miałbyś uruchomić Node.js na serwerze, możesz użyć tej samej weryfikacji JavaScript w przeglądarce i na serwerze, eliminując duplikację. Nadal chcesz, aby kod działał w wielu miejscach (środowisko zaufane i niezaufane), ale kod nie musi być koniecznie powielany.
Joshua Taylor,
87

Sandi Metz, uznana inżynierka oprogramowania i autorka ekosystemu Ruby, ma świetny post na blogu i przemówienie, w którym mówi także o związku między powielaniem a abstrakcją. Dochodzi do następującego wniosku

powielanie jest znacznie tańsze niż zła abstrakcja

I całkowicie się z nią zgadzam. Pozwól, że dam ci więcej kontekstu do tego cytatu. Czasami znalezienie właściwej abstrakcji jest bardzo trudne. W takich przypadkach kuszące jest po prostu skorzystanie z jakiejkolwiek abstrakcji w celu ograniczenia powielania. Ale później może się okazać, że twoja abstrakcja nie dotyczy wszystkich przypadków. Jednak ponowna zmiana wszystkiego i wybranie innej drogi jest kosztowne (dla lepszego wyjaśnienia obserwuj, jak mówi!).

Tak więc dla mnie istnieją wyjątkowe przypadki, w których akceptacja duplikacji jest właściwą decyzją, szczególnie gdy nie jesteś pewien, co ma nadejść i wymagania mogą się zmienić. Z twojego postu zakładam, że jest teraz wiele powielania, ale twoi koledzy sugerują, że może się to zmienić i nie łączyć obu aplikacji ze sobą. Moim zdaniem jest to uzasadniony argument i nie można go zlekceważyć.

larsbe
źródło
23
Ach tak, jeśli nie możesz wydedukować uogólnionych reguł, próba ich streszczenia może się nie powieść. A jeśli korespondencja jest przypadkowa, może być krótkotrwała.
Deduplicator,
5
Zmiana abstrakcji, gdy jest już na miejscu, może być kosztowna, ale pozbycie się duplikacji po jej umieszczeniu może być jeszcze większym problemem. Oczywiście zależy to w dużej mierze od języka itp. - nowoczesne systemy typu o silnej statyczności mogą zrobić dobrą robotę, pomagając w uzyskaniu nawet dużych zmian niektórych poprawnych abstrakcji. Utrzymywanie synchronizacji zduplikowanych funkcji nie jest jednak czymś, w czym system typów może ci bardzo pomóc (ponieważ duplikaty różnią się od systemu typów ). Ale przypuszczam, że w dynamicznych językach pisanych kaczką jest raczej na odwrót; więc może mieć sens dla Ruby.
leftaroundabout
34

Jeśli ludzie zaczną rozumować na temat projektu słowami „jeśli jutro” , to często jest to dla mnie duży znak ostrzegawczy, zwłaszcza gdy argument ten jest uzasadniany decyzją obejmującą dodatkową pracę i wysiłek, o której nikt tak naprawdę nie wie, czy to się kiedykolwiek wydarzy spłacić, a trudniejsze do zmiany lub cofnięcia niż decyzja przeciwna.

Powielanie kodu zmniejsza wysiłek tylko na krótki czas, ale zwiększy wysiłki związane z utrzymaniem prawie natychmiast, proporcjonalnie do liczby zduplikowanych linii kodu. Zauważ też, że gdy kod zostanie zduplikowany, trudno będzie usunąć duplikację, gdy okaże się, że była to zła decyzja, podczas gdy jeśli nie powiela się teraz kodu, nadal łatwo jest wprowadzić duplikację później, jeśli okaże się, że trzymasz się DRY była zła decyzja.

Mówi się, że w większych organizacjach czasem korzystne jest faworyzowanie niezależności różnych zespołów w stosunku do zasady DRY. Jeśli usunięcie duplikacji poprzez wyodrębnienie 95% wspólnych części interfejsów API dwa nowe komponenty prowadzą do połączenia dwóch niezależnych zespołów, może to nie być najmądrzejsza decyzja. Z drugiej strony, jeśli masz ograniczone zasoby i będzie tylko jeden zespół utrzymujący oba interfejsy API, jestem pewien, że w ich własnym interesie nie będzie tworzenie podwójnego wysiłku i unikanie niepotrzebnego powielania kodu.

Zauważ też, że robi to różnicę, jeśli interfejsy API „Strona główna” i „Agencja” są używane wyłącznie przez całkowicie różne aplikacje, lub jeśli ktoś może spróbować napisać kompilację komponentu na podstawie tych interfejsów API, których można również użyć w kontekście „Strona główna” jak w kontekście „Agencji”. W tej sytuacji posiadanie dokładnie identycznych części wspólnych interfejsów API (co można zagwarantować tylko wtedy, gdy części wspólne nie są powielone), znacznie ułatwi opracowanie takiego komponentu.

Jeśli więc okaże się, że naprawdę będą istniały różne podgrupy, każda odpowiedzialna za każdy z interfejsów API, każda z innym harmonogramem i zasobami, nadszedł czas na skopiowanie kodu, ale nie „na wszelki wypadek”.

Doktor Brown
źródło
3
Jeszcze większy znak ostrzegawczy niż „jeśli jutro” jest dla mnie „To nigdy się nie zmieni”.
abuzittin gillifirca
@abuzittingillifirca: a co to ma wspólnego z pytaniem lub moją odpowiedzią?
Doc Brown
1
Podważam twój pierwszy akapit.
abuzittin gillifirca
2
@abuzittingillifirca: cóż, przeczytaj jeszcze raz pytanie: chodzi o rozumowanie z argumentem „jeśli jutro” uzasadniającym decyzję o powieleniu, która jest trudna do cofnięcia , więc w niektórych przypadkach oprogramowanie jest trudniejsze do zmiany. Może to być trochę sprzeczne z intuicją, ale najlepszym sposobem na zmianę oprogramowania na przyszłość nie jest przyjmowanie (prawdopodobnie błędnych) założeń dotyczących przyszłości, ale utrzymanie oprogramowania tak małego i SOLIDNEGO, jak to możliwe.
Doc Brown
13

Powielanie w celu uniknięcia sprzężenia . Powiedzmy, że masz dwa duże systemy i zmuszasz je do korzystania z tej samej biblioteki. Być może łączysz cykl uwalniania obu systemów. Może nie jest tak źle, ale powiedzmy, że jeden system musi wprowadzić zmianę. Druga musi przeanalizować zmianę i może to mieć wpływ. Czasami może to popsuć. Nawet jeśli obie strony są w stanie koordynować zmiany, może to być wiele spotkań, przez menedżerów, testowanie, zależności i koniec małego autonomicznego zespołu.

Więc płacisz cenę za powielony kod, aby uzyskać autonomię i niezależność.

Borjab
źródło
Jest to więc duplikacja między komponentami, aby uniknąć sprzężenia. Ale nadal chcesz uniknąć powielania elementów, prawda?
TemplateRex,
Cóż, tak, chcesz zminimalizować zduplikowany kod, ponieważ ułatwi to zrozumienie i modyfikację kodu. Pamiętaj jednak, że istnieją dobre odpowiedzi z realnymi wyjątkami. Właściwa abstrakcja pozwalająca uniknąć powielania może być trudna do znalezienia.
Borjab,