Co by pomogło przy refaktoryzacji dużej metody, aby upewnić się, że nic nie zepsuję?

10

Obecnie refaktoryzuję część dużej bazy kodu bez żadnych testów jednostkowych. Próbowałem zmienić kod na brutalny, tzn. Próbując odgadnąć, co robi kod i jakie zmiany nie zmieniłyby jego znaczenia, ale bez powodzenia: losowo psuje funkcje w całej bazie kodu.

Należy pamiętać, że refaktoryzacja obejmuje przeniesienie starszego kodu C # do bardziej funkcjonalnego stylu (starszy kod nie korzysta z żadnych funkcji .NET Framework 3 i nowszych, w tym LINQ), dodając ogólne, w których kod może z nich skorzystać itp.

Nie mogę korzystać z metod formalnych , biorąc pod uwagę, ile by to kosztowały.

Z drugiej strony, zakładam, że należy ściśle przestrzegać zasady „Każdy zrekonstruowany starszy kod będzie zawierał testy jednostkowe” , niezależnie od tego, ile by to kosztowało. Problem polega na tym, że kiedy refaktoryzuję niewielką część prywatnej metody 500 LOC, dodawanie testów jednostkowych wydaje się trudnym zadaniem.

Co może mi pomóc w ustaleniu, które testy jednostkowe są odpowiednie dla danego fragmentu kodu? Domyślam się, że statyczna analiza kodu byłaby w jakiś sposób pomocna, ale jakich narzędzi i technik mogę użyć do:

  • Wiedz dokładnie, jakie testy jednostkowe powinienem utworzyć,

  • I / lub wiesz, czy dokonana przeze mnie zmiana wpłynęła na oryginalny kod w sposób, który wykonuje inaczej niż teraz?

Arseni Mourzenko
źródło
Jakie jest twoje uzasadnienie, że pisanie testów jednostkowych wydłuży czas trwania tego projektu? Wielu zwolenników by się nie zgodziło, ale zależy to również od umiejętności ich napisania.
JeffO
Nie twierdzę, że zwiększy to ogólny czas trwania projektu. Chciałem powiedzieć, że wydłuży czas krótkoterminowy (tj. Natychmiastowy czas, który spędzam teraz przy refaktoryzacji kodu).
Arseni Mourzenko
1
I tak byś nie używał, formal methods in software developmentponieważ służy on do udowodnienia poprawności programu przy użyciu logiki predykatów i nie miałby zastosowania do refaktoryzacji dużej bazy kodu. Formalne metody zwykle stosowane w celu udowodnienia, że ​​kod działa poprawnie w obszarach takich jak zastosowania medyczne. Masz rację, jest to kosztowne, dlatego nie jest często używane.
Mushy,
Dobre narzędzie, takie jak opcje refaktora w ReSharper , znacznie ułatwia takie zadanie. W takich sytuacjach jest to warte swojej ceny.
billy.bob
1
Nie pełna odpowiedź, ale głupia technika, która wydaje mi się zaskakująco skuteczna, gdy zawodzą mnie wszystkie inne techniki refaktoryzacji: Stwórz nową klasę, podziel funkcję na osobne funkcje z dokładnie tam zawartym kodem, po prostu zepsuty co około 50 wierszy, promuj dowolne miejscowi, którzy są współdzieleni przez funkcje dla członków, wtedy poszczególne funkcje lepiej pasują do mojej głowy i dają mi możliwość zobaczenia w elementach, które elementy są przewleczone przez całą logikę. To nie jest cel końcowy, po prostu bezpieczny sposób na uzyskanie starszego bałaganu, aby był gotowy do bezpiecznego refaktoryzacji.
Jimmy Hoffa

Odpowiedzi:

12

Miałem podobne wyzwania. Książka Working with Legacy Code to świetny zasób, ale można założyć, że w testach jednostkowych możesz walić stopą, aby wesprzeć swoją pracę. Czasami to po prostu niemożliwe.

W mojej pracy z zakresu archeologii (mój termin na utrzymanie takiego kodu starszego typu) stosuję podobne podejście do tego, co nakreśliłeś.

  • Zacznij od dokładnego zrozumienia tego, co obecnie robi rutyna.
  • Jednocześnie określ, co powinna robić rutyna . Wielu uważa, że ​​ten i poprzedni pocisk są takie same, ale istnieje subtelna różnica. Często, jeśli rutyna robi to, co powinna, to nie wprowadzasz zmian konserwacyjnych.
  • Przeprowadź kilka próbek przez procedurę i upewnij się, że trafiłeś w przypadki graniczne, odpowiednie ścieżki błędów wraz ze ścieżką linii głównej. Z mojego doświadczenia wynika, że ​​uszkodzenie dodatkowe (uszkodzenie funkcji) wynika z warunków brzegowych, które nie są realizowane w dokładnie taki sam sposób.
  • Po tych przykładowych przypadkach określ, co jest utrwalane, co niekoniecznie musi być utrwalone. Znów stwierdziłem, że takie skutki uboczne prowadzą do innych szkód ubocznych.

W tym momencie powinieneś mieć listę kandydatów na to, co zostało ujawnione i / lub zmanipulowane przez tę procedurę. Niektóre z tych manipulacji mogą być nieumyślne. Teraz używam findstri IDE, aby zrozumieć, jakie inne obszary mogą odnosić się do pozycji na liście kandydatów. Poświęcę trochę czasu na zrozumienie, jak działają te referencje i jaka jest ich natura.

Wreszcie, kiedy już się łudzę, że rozumiem wpływ pierwotnej rutyny, wprowadzę zmiany pojedynczo i ponownie uruchomię opisane powyżej analizy, aby sprawdzić, czy zmiana działa zgodnie z oczekiwaniami to działa. W szczególności staram się unikać zmiany wielu rzeczy na raz, ponieważ stwierdziłem, że to mnie wysadza, gdy próbuję zweryfikować wpływ. Czasami można uniknąć wielu zmian, ale jeśli mogę podążać pojedynczą trasą, to moje preferencje.

Krótko mówiąc, moje podejście jest podobne do tego, co przedstawiłeś. To dużo pracy przygotowawczej; następnie dokonaj rozważnych, indywidualnych zmian; a następnie zweryfikuj, zweryfikuj, zweryfikuj.


źródło
2
+1 za użycie samej „archeologii”. Tego samego terminu używam do opisania tej aktywności i myślę, że to świetny sposób, aby to ująć (również pomyślałem, że odpowiedź była dobra - nie jestem tak płytka)
Erik Dietrich
10

Co by pomogło przy refaktoryzacji dużej metody, aby upewnić się, że nic nie zepsuję?

Krótka odpowiedź: małe kroki.

Problem polega na tym, że kiedy refaktoryzuję niewielką część prywatnej metody 500 LOC, dodawanie testów jednostkowych wydaje się trudnym zadaniem.

Rozważ następujące kroki:

  1. Przenieś implementację do innej (prywatnej) funkcji i przekaż połączenie.

    // old:
    private int ugly500loc(int parameters) {
        // 500 LOC here
    }
    
    // new:    
    private int ugly500loc_old(int parameters) {
        // 500 LOC here
    }
    
    private void ugly500loc(int parameters) {
        return ugly500loc_old(parameters);
    }
    
  2. Dodaj kod rejestrujący (upewnij się, że rejestracja nie powiedzie się) w oryginalnej funkcji dla wszystkich danych wejściowych i wyjściowych.

    private void ugly500loc(int parameters) {
        static int call_count = 0;
        int current = ++call_count;
        save_to_file(current, parameters);
        int result = ugly500loc_old(parameters);
        save_to_file(current, result); // result, any exceptions, etc.
        return result;
    }
    

    Uruchom aplikację i rób z nią, co możesz (prawidłowe użycie, nieprawidłowe użycie, typowe użycie, nietypowe użycie itp.).

  3. Masz teraz max(call_count)zestawy danych wejściowych i wyjściowych do pisania testów; Możesz napisać pojedynczy test, który będzie powtarzał wszystkie posiadane parametry / zestawy wyników i wykonywał je w pętli. Możesz także napisać dodatkowy test, który uruchamia określoną kombinację (do użycia, aby szybko sprawdzić przekazywanie określonego zestawu we / wy).

  4. Przenieść // 500 LOC herez powrotem do ugly500locfunkcji (i usunąć z zalogowaniem funkcjonalność).

  5. Zacznij wyodrębniać funkcje z dużej funkcji (nie rób nic więcej, po prostu wyodrębniaj funkcje) i uruchamiaj testy. Po tym powinieneś mieć więcej małych funkcji do refaktoryzacji, zamiast 500LOC.

  6. Żyj długo i szczęśliwie.

utnapistim
źródło
3

Zwykle są to testy jednostkowe.

Wykonaj niezbędne testy, które udowodnią, że prąd działa zgodnie z oczekiwaniami. Nie spiesz się, a ostatni test sprawi, że będziesz pewny wyników.

Co może mi pomóc w ustaleniu, które testy jednostkowe są odpowiednie dla danego fragmentu kodu?

Jesteś w trakcie refaktoryzacji fragmentu kodu, musisz dokładnie wiedzieć, co robi i na co wpływa. Zasadniczo musisz przetestować wszystkie dotknięte strefy. Zajmie ci to dużo czasu ... ale jest to oczekiwany wynik każdego procesu refaktoryzacji.

Następnie możesz bez problemu rozerwać wszystko na strzępy.

AFAIK, nie ma na to techniki kuloodpornej ... musisz być metodyczny (na jakiejkolwiek metodzie, na której czujesz się komfortowo), dużo czasu i dużo cierpliwości! :)

Pozdrawiam i powodzenia!

Alex

AlexCode
źródło
Narzędzia pokrycia kodu są tutaj niezbędne. Potwierdzenie, że obejrzałeś każdą ścieżkę za pomocą dużej złożonej metody inspekcji, jest trudne. Narzędzie, które pokazuje, że łącznie KitchenSinkMethodTest01 () ... KitchenSinkMethodTest17 () obejmuje linie 1-45, 48-220, 245-399 i 488-500, ale nie zmienia kodu między; sprawi, że ustalenie, jakie dodatkowe testy potrzebujesz napisać, jest znacznie prostsze.
Dan Is Fiddling By Firelight