Jak zapobiec nieznanemu powielaniu kodu?

33

Pracuję na dość dużej podstawie kodu. Setki klas, mnóstwo różnych plików, wiele funkcji, potrzeba więcej niż 15 minut, aby pobrać nową kopię itp.

Dużym problemem związanym z tak dużą bazą kodu jest to, że ma całkiem sporo metod narzędziowych i takich, które robią to samo, lub kod, który nie używa tych metod narzędziowych, kiedy może. A także metody użytkowe nie są tylko jedną klasą (ponieważ byłby to ogromny pomieszany bałagan).

Jestem raczej nowy w bazie kodu, ale lider zespołu, który pracował nad nim od lat, wydaje się mieć ten sam problem. Prowadzi to do dużej ilości kodu i powielania pracy, i jako takie, gdy coś się psuje, zwykle jest dzielone na 4 kopie zasadniczo tego samego kodu

Jak możemy ograniczyć ten wzór? Podobnie jak w przypadku większości dużych projektów, nie cały kod jest udokumentowany (choć niektóre są) i nie cały kod jest ... cóż, czysty. Ale w gruncie rzeczy byłoby naprawdę miło, gdybyśmy mogli popracować nad poprawą jakości pod tym względem, aby w przyszłości mieliśmy mniej powielania kodu, a takie funkcje, jak funkcje użytkowe były łatwiejsze do wykrycia.

Ponadto funkcje narzędziowe są zwykle albo w statycznej klasie pomocniczej, w innej niestatycznej klasie pomocniczej, która działa na jednym obiekcie, lub jest statyczną metodą w klasie, z którą głównie „pomaga”.

Miałem jeden eksperyment z dodawaniem funkcji narzędziowych jako metod rozszerzenia (nie potrzebowałem żadnych elementów wewnętrznych klasy, a na pewno było to wymagane tylko w bardzo specyficznych scenariuszach). Skutkowało to zapobieganiem zaśmiecaniu klasy podstawowej i tym podobnych, ale tak naprawdę nie jest już możliwe do odkrycia, chyba że już o tym wiesz

Earlz
źródło

Odpowiedzi:

30

Prosta odpowiedź jest taka, że ​​tak naprawdę nie można zapobiec duplikacji kodu. Możesz jednak „naprawić” trudny, ciągły, powtarzalny proces przyrostowy, który sprowadza się do dwóch etapów:

Krok 1. Rozpocznij pisanie testów na starszym kodzie (najlepiej przy użyciu środowiska testowego)

Krok 2. Przepisz / przeredaguj kod, który jest zduplikowany, korzystając z tego, czego nauczyłeś się z testów

Możesz użyć narzędzi do analizy statycznej, aby wykryć zduplikowany kod, a dla języka C # istnieje mnóstwo narzędzi, które mogą to zrobić za Ciebie:

Takie narzędzia pomogą ci znaleźć punkty w kodzie, które robią podobne rzeczy. Kontynuuj pisanie testów, aby ustalić, czy naprawdę tak robią; użyj tych samych testów, aby uprościć korzystanie ze zduplikowanego kodu. To „refaktoryzacja” można wykonać na wiele sposobów i możesz użyć tej listy do ustalenia poprawnej:

Co więcej, jest też cała książka na ten temat autorstwa Michaela C. Feathersa, działającego skutecznie z Legacy Code . Omówiono różne strategie, które można zastosować, aby zmienić kod na lepszy. Ma „starszy algorytm zmiany kodu”, który nie jest daleko od dwustopniowego procesu powyżej:

  1. Zidentyfikuj punkty zmian
  2. Znajdź punkty testowe
  3. Przerwij zależności
  4. Napisz testy
  5. Dokonaj zmian i refaktoryzuj

Książka jest dobra do przeczytania, jeśli masz do czynienia z rozwojem brązowych pól, tj. Starszego kodu, który należy zmienić.

W tym przypadku

W przypadku OP mogę sobie wyobrazić, że nieuleczalny kod jest powodowany przez garnek miodu dla „metod użytkowych i sztuczek”, które przybierają różne formy:

Zauważ, że nie ma w tym nic złego, ale z drugiej strony są one zazwyczaj trudne do utrzymania i zmiany. Metody rozszerzeń w .NET są metodami statycznymi, ale są również stosunkowo łatwe do przetestowania.

Zanim przejdziesz do refaktoryzacji, porozmawiaj o tym ze swoim zespołem. Muszą one znajdować się na tej samej stronie co Ty, zanim zaczniesz cokolwiek robić. Wynika to z faktu, że jeśli coś refaktoryzujesz, wówczas szanse są duże, będziesz powodować konflikty scalania. Zanim coś przerobisz, sprawdź to i powiedz swojemu zespołowi, aby przez pewien czas pracował nad tymi punktami kodu, dopóki nie skończysz.

Ponieważ OP jest nowością w kodzie, jest kilka rzeczy do zrobienia, zanim powinieneś cokolwiek zrobić:

  • Poświęć czas na naukę w bazie kodu, tj. Przełam „wszystko”, przetestuj „wszystko”, cofnij.
  • Poproś kogoś z zespołu o sprawdzenie kodu przed jego zatwierdzeniem. ;-)

Powodzenia!

Łup
źródło
W rzeczywistości mamy sporo testów jednostkowych i integracyjnych. Nie 100% pokrycia, ale niektóre rzeczy, które robimy, są prawie niemożliwe do przetestowania jednostkowego bez radykalnych zmian w naszej bazie kodu. Nigdy nie rozważałem zastosowania analizy statycznej w celu znalezienia duplikacji. Będę musiał spróbować później.
Earlz
@Ellz: Analiza kodu statycznego jest niesamowita! ;-) Ponadto, ilekroć musisz dokonać zmiany, pomyśl o rozwiązaniach, które ułatwią wprowadzanie zmian (w tym celu zapoznaj się z katalogiem wzorców)
Spoike
+1 Zrozumiałbym, gdyby ktoś naliczył nagrodę za to Q, aby udzielić tej odpowiedzi jako „dodatkowej pomocy”. Katalog Refactor to Patterns jest złoty, takie rzeczy jak GuidanceExplorer.codeplex.com są świetnymi pomocami programistycznymi.
Jeremy Thompson
2

Możemy także spróbować spojrzeć na problem z innej strony. Zamiast myśleć, że problemem jest duplikacja kodu, możemy rozważyć, czy przyczyną problemu jest brak zasad ponownego użycia kodu.

Niedawno przeczytałem książkę Inżynieria oprogramowania z komponentami wielokrotnego użytku i rzeczywiście zawiera ona zestaw bardzo interesujących pomysłów, w jaki sposób promować wielokrotne użycie kodu na poziomie organizacji.

Autor tej książki, Johannes Sametinger, opisuje zestaw barier w ponownym użyciu kodu, niektóre koncepcyjne, a niektóre techniczne. Na przykład:

Koncepcyjne i techniczne

  • Trudności ze znalezieniem oprogramowania wielokrotnego użytku : oprogramowania nie można ponownie użyć, chyba że można je znaleźć. Ponowne użycie jest mało prawdopodobne, gdy repozytorium nie ma wystarczających informacji o komponentach lub gdy komponenty są źle sklasyfikowane.
  • Brak możliwości ponownego użycia znalezionego oprogramowania : łatwy dostęp do istniejącego oprogramowania niekoniecznie zwiększa jego ponowne wykorzystanie. Nieumyślnie oprogramowanie jest pisane rzadko, aby inni mogli go ponownie wykorzystać. Modyfikowanie i dostosowywanie oprogramowania innego użytkownika może stać się jeszcze droższe niż zaprogramowanie niezbędnej funkcjonalności od zera.
  • Starsze komponenty nie nadają się do ponownego użycia : Ponowne użycie komponentów jest trudne lub niemożliwe, chyba że zostały zaprojektowane i opracowane do ponownego użycia. Po prostu zebranie istniejących komponentów z różnych starszych systemów oprogramowania i próba ponownego wykorzystania ich do nowych rozwiązań nie jest wystarczająca do systematycznego ponownego użycia. Przeprojektowanie może pomóc w wydobyciu komponentów wielokrotnego użytku, jednak wysiłek może być znaczny.
  • Technologia obiektowa : Powszechnie uważa się, że technologia obiektowa ma pozytywny wpływ na ponowne wykorzystanie oprogramowania. Niestety i niesłusznie wielu uważa, że ​​ponowne użycie zależy od tej technologii lub że przyjęcie technologii obiektowej wystarcza do ponownego wykorzystania oprogramowania.
  • Modyfikacja : komponenty nie zawsze będą dokładnie tak, jak chcemy. Jeśli konieczne są modyfikacje, powinniśmy być w stanie określić ich wpływ na komponent i jego wcześniejsze wyniki weryfikacji.
  • Ponowne użycie śmieci : Certyfikacja komponentów wielokrotnego użytku do określonych poziomów jakości pomaga zminimalizować możliwe usterki. Słaba kontrola jakości jest jedną z głównych barier w ponownym użyciu. Potrzebujemy pewnych sposobów oceny, czy wymagane funkcje odpowiadają funkcjom zapewnianym przez komponent.

Inne podstawowe trudności techniczne obejmują

  • Uzgodnienie, co składa się na element wielokrotnego użytku.
  • Zrozumienie, co robi składnik i jak go używać.
  • Zrozumienie sposobu łączenia komponentów wielokrotnego użytku z resztą projektu.
  • Projektowanie komponentów wielokrotnego użytku, tak aby można je było łatwo adaptować i modyfikować w kontrolowany sposób.
  • Zorganizowanie repozytorium, aby programiści mogli znaleźć i wykorzystać to, czego potrzebują.

Według autora różne poziomy ponownego wykorzystania zdarzają się w zależności od dojrzałości organizacji.

  • Ponowne wykorzystanie doraźne wśród grup aplikacji : jeśli nie ma wyraźnego zobowiązania do ponownego użycia, ponowne użycie może nastąpić w najlepszym razie w sposób nieformalny i przypadkowy. Większość ponownego wykorzystania, jeśli w ogóle, nastąpi w ramach projektów. Prowadzi to również do oczyszczania kodu i kończy się powieleniem kodu.
  • Ponowne użycie oparte na repozytorium między grupami aplikacji : sytuacja nieznacznie się poprawia, gdy używane jest repozytorium komponentów i mogą być dostępne dla różnych grup aplikacji. Jednak nie istnieje wyraźny mechanizm wprowadzania komponentów do repozytorium i nikt nie jest odpowiedzialny za jakość komponentów w repozytorium. Może to prowadzić do wielu problemów i utrudniać ponowne wykorzystanie oprogramowania.
  • Scentralizowane ponowne użycie z grupą komponentów: W tym scenariuszu grupa komponentów jest wyraźnie odpowiedzialna za repozytorium. Grupa określa, które komponenty mają być przechowywane w repozytorium i zapewnia jakość tych komponentów oraz dostępność niezbędnej dokumentacji, a także pomaga w wyszukiwaniu odpowiednich komponentów w konkretnym scenariuszu ponownego użycia. Grupy aplikacji są oddzielone od grupy komponentów, która działa jako rodzaj podwykonawcy dla każdej grupy aplikacji. Celem grupy komponentów jest zminimalizowanie redundancji. W niektórych modelach członkowie tej grupy mogą również pracować nad konkretnymi projektami. Podczas uruchamiania projektów ich wiedza jest cenna dla wspierania ponownego wykorzystania, a dzięki ich zaangażowaniu w konkretny projekt mogą zidentyfikować potencjalnych kandydatów do włączenia do repozytorium.
  • Ponowne wykorzystanie oparte na domenie : Specjalizacja grup komponentów oznacza ponowne użycie oparte na domenie. Każda grupa domen jest odpowiedzialna za komponenty w swojej domenie, np. Komponenty sieciowe, komponenty interfejsu użytkownika, komponenty bazy danych.

Być może więc, oprócz wszystkich sugestii podanych w innych odpowiedziach, możesz pracować nad projektem programu wielokrotnego użytku, obejmować zarządzanie, utworzyć grupę komponentów odpowiedzialną za identyfikację komponentów wielokrotnego użytku poprzez analizę domeny i zdefiniować repozytorium komponentów wielokrotnego użytku, które inni programiści mogą łatwo odpytuj i szukaj gotowych rozwiązań ich problemów.

edalorzo
źródło
1

Istnieją 2 możliwe rozwiązania:

Zapobieganie - staraj się mieć jak najlepszą dokumentację. Spraw, aby każda funkcja była odpowiednio udokumentowana i łatwa do przeszukania całej dokumentacji. Pisząc kod, wyraźnie zaznacz, gdzie powinien się znaleźć kod, aby było oczywiste, gdzie szukać. Ograniczenie ilości kodu „użyteczności” jest jednym z kluczowych punktów tego. Za każdym razem, gdy słyszę „zróbmy klasę użytkową”, moje włosy podnoszą się, a krew zamarza, ponieważ to oczywiście problem. Zawsze miej szybki i łatwy sposób, aby poprosić ludzi o zapoznanie się z bazą kodu, gdy tylko jakaś funkcja już istnieje.

Rozwiązanie - Jeśli zapobieganie się nie powiedzie, powinieneś być w stanie szybko i skutecznie rozwiązać problematyczną część kodu. Twój proces programowania powinien umożliwić szybkie naprawienie duplikatu kodu. Testowanie jednostkowe jest do tego idealne, ponieważ można efektywnie modyfikować kod bez obawy jego uszkodzenia. Jeśli więc znajdziesz 2 podobne fragmenty kodu, wyodrębnienie ich do funkcji lub klasy powinno być łatwe przy odrobinie refaktoryzacji.

Osobiście uważam, że zapobieganie nie jest możliwe. Im więcej spróbujesz, tym trudniej będzie znaleźć już istniejące funkcje.

Euforyk
źródło
0

Nie sądzę, że tego rodzaju problemy mają ogólne rozwiązanie. Duplikat kodu nie zostanie utworzony, jeśli programiści będą mieli wystarczającą ochotę wyszukać istniejący kod. Również programiści mogą naprawić problemy na miejscu, jeśli chcą.

Jeśli językiem jest C / C ++, scalanie będzie łatwiejsze dzięki elastyczności łączenia (można wywoływać dowolne externfunkcje bez uprzedniej informacji). W przypadku Java lub .NET może być konieczne opracowanie klas pomocniczych i / lub składników narzędziowych.

Zwykle zaczynam od kopiowania istniejącego kodu tylko wtedy, gdy główne błędy wynikają z powielonych części.

9dan
źródło
0

Jest to typowy problem większego projektu, którym zajmowało się wielu programistów, którzy wnieśli swój wkład pod niekiedy dużą presją ze strony otoczenia. Bardzo kuszące jest tworzenie kopii klasy i dostosowanie jej do tej konkretnej klasy. Jednak gdy problem został znaleziony w klasie inicjującej, należy go również rozwiązać w przyzwoitkach, o których często się zapomina.

Istnieje na to rozwiązanie, które nazywa się Generics, które zostało wprowadzone w Javie 6. Jest to odpowiednik C ++ o nazwie Szablon. Kod, którego dokładna klasa nie jest jeszcze znana w klasie ogólnej. Sprawdź Java Generics, a znajdziesz mnóstwo ton dokumentacji.

Dobrym podejściem jest przepisywanie kodu, który wydaje się być kopiowany / wklejany w wielu miejscach, przepisując pierwszy, który musisz np. Naprawić z powodu określonego błędu. Przepisz go, aby używać Generics, a także pisać bardzo rygorystyczny kod testowy.

Upewnij się, że wywoływana jest każda metoda klasy Generic. Możesz także wprowadzić narzędzia do pokrycia kodu: kod ogólny powinien obejmować cały kod, ponieważ będzie używany w kilku miejscach.

Napisz także kod testowy, tj. Używając JUnit lub podobnego dla pierwszej wyznaczonej klasy, która będzie używana w połączeniu z częścią Ogólną.

Zacznij korzystać z kodu Generic dla drugiej (najczęściej) skopiowanej wersji, gdy cały poprzedni kod działa i jest w pełni przetestowany. Zobaczysz, że istnieją pewne wiersze kodu, które są specyficzne dla tej wyznaczonej klasy. Możesz wywołać te wiersze zakodowane w abstrakcyjnej metodzie chronionej, która musi zostać zaimplementowana przez klasę pochodną, ​​która używa ogólnej klasy bazowej.

Tak, to żmudna praca, ale w miarę postępów będzie coraz lepiej wyrywać podobne klasy i zastępować ją czymś, co jest bardzo czyste, dobrze napisane i o wiele łatwiejsze w utrzymaniu.

Miałem podobną sytuację, gdy w klasie generycznej ostatecznie zastąpiłem coś w rodzaju 6 lub 7 innych prawie identycznych klas, które były prawie prawie identyczne, ale zostały skopiowane i wklejone przez różnych programistów w pewnym okresie czasu.

I tak, jestem bardzo za automatycznym testowaniem kodu. Na początku będzie to kosztować więcej, ale zdecydowanie zaoszczędzi Ci mnóstwo czasu. I spróbuj osiągnąć zasięg kodu wynoszący co najmniej 80% i 100% dla kodu ogólnego.

Mam nadzieję, że to pomoże i powodzenia.

André van Kouwen
źródło
0

Mam zamiar powtórzyć najmniej popularną opinię tutaj i po stronie Gangnusi zasugerować, że duplikacja kodu nie zawsze jest szkodliwa i czasami może być mniejszym złem.

Jeśli powiesz mi, że możesz użyć:

A) Stabilna (niezmienna) i niewielka biblioteka obrazów, dobrze przetestowana , która powiela kilkadziesiąt wierszy trywialnego kodu matematycznego dla matematyki wektorowej, takich jak produkty kropkowe, wyrywki i klamry, ale jest całkowicie oddzielona od wszystkiego innego i tworzy ułamek sekunda.

B) Niestabilna (szybko zmieniająca się) biblioteka obrazów, która zależy od epickiej biblioteki matematycznej, aby uniknąć wspomnianych kilku tuzinów linii kodu, przy czym biblioteka matematyczna jest niestabilna i stale otrzymuje nowe aktualizacje i zmiany, a zatem biblioteka obrazów musi również zostać przebudowany, jeśli nie całkowicie również zmieniony. Zbudowanie całości zajmuje 15 minut.

... więc dla większości ludzi powinno być oczywiste, że A, a właściwie właśnie ze względu na niewielkie powielanie kodu, jest preferowane. Najważniejszym akcentem, który muszę zrobić, jest dobrze przetestowana część. Oczywiście nie ma nic gorszego niż zduplikowany kod, który nawet nie działa, w tym momencie powielają błędy.

Ale trzeba też pomyśleć o sprzężeniu i stabilności, a także o niewielkim stopniu powielania tu i tam może służyć jako mechanizm odsprzęgający, który również zwiększa stabilność (niezmienną naturę) pakietu.

Tak więc moją propozycją będzie skupienie się bardziej na testowaniu i próbie znalezienia czegoś naprawdę stabilnego (jak w przypadku niezmienności, znajdowania kilku powodów do zmiany w przyszłości) i niezawodnego, którego zależności od źródeł zewnętrznych, jeśli takie istnieją, są bardzo stabilny, ponad próbę usunięcia wszelkich form duplikacji w bazie kodu. W środowisku dużego zespołu ten ostatni jest niepraktycznym celem, nie wspominając o tym, że może zwiększyć sprzężenie i ilość niestabilnego kodu, który masz w bazie kodu.


źródło
-2

Nie zapominaj, że powielanie kodu nie zawsze jest szkodliwe. Wyobraź sobie: teraz masz jakieś zadanie do rozwiązania w absolutnie różnych modułach swojego projektu. Właśnie teraz jest to to samo zadanie.

Mogą to być trzy przyczyny:

  1. Niektóre motywy wokół tego zadania są takie same dla obu modułów. W takim przypadku duplikacja kodu jest zła i powinna zostać zlikwidowana. Byłoby sprytnie stworzyć klasę lub moduł do obsługi tego tematu i używać jego metod w obu modułach.

  2. Zadanie jest teoretyczne z punktu widzenia twojego projektu. Na przykład pochodzi z fizyki lub matematyki itp. Zadanie istnieje niezależnie od twojego projektu. W takim przypadku duplikacja kodu jest zła i również powinna zostać zlikwidowana. Stworzyłbym specjalną klasę dla takich funkcji. I użyj takiej funkcji w dowolnym module, w którym jej potrzebujesz.

  3. Ale w innych przypadkach zbieżność zadań jest zbiegiem okoliczności i niczym więcej. Niebezpiecznie byłoby wierzyć, że zadania te pozostaną takie same podczas zmian w projekcie z powodu refaktoryzacji, a nawet debugowania. W takim przypadku lepiej byłoby utworzyć dwie takie same funkcje / fragmenty kodu w różnych miejscach. A przyszłe zmiany w jednym z nich nie dotkną drugiego.

I ten trzeci przypadek zdarza się bardzo często. Jeśli powielasz „nieświadomie”, głównie z tego właśnie powodu - to nie jest prawdziwe powielanie!

Staraj się więc utrzymywać go w czystości, gdy jest to naprawdę konieczne, i nie bój się powielania, jeśli nie jest to konieczne.

Gangnus
źródło
2
code duplication is not always harmfuljest jedna kiepska rada.
Tulains Córdova,
1
Czy powinienem pokłonić się waszemu autorytetowi? Podałem tutaj swoje powody. Jeśli się mylę, pokaż, gdzie jest błąd. Teraz wydaje się raczej słabą umiejętnością prowadzenia dyskusji.
Gangnus
3
Duplikacja kodu jest jednym z podstawowych problemów w tworzeniu oprogramowania, a wielu naukowców i teoretyków obliczeń opracowało paradygmaty i metodologie, aby uniknąć duplikacji kodu jako głównego źródła problemów związanych z utrzymywalnością w rozwoju oprogramowania. To tak, jakby powiedzieć „pisanie złego kodu nie zawsze jest złe”, w ten sposób wszystko może być retorycznie uzasadnione. Być może masz rację, ale unikanie powielania kodu jest zbyt dobrą zasadą, aby żyć, aby zachęcać do
czegoś
I nie umieścić tutaj argumenty. Nie masz Odniesienie do władz nie będzie działać od XVI wieku. Nie możesz zagwarantować, że dobrze je zrozumiałeś i że są one również dla mnie autorytetem.
Gangnus,
Masz rację, duplikacja kodu nie jest jednym z podstawowych problemów w rozwoju oprogramowania i nie opracowano żadnych paradygmatów i metodologii, aby tego uniknąć.
Tulains Córdova,