Jak usunąć funkcję lub funkcję podczas korzystania z TDD

20

W tekstach o TDD często czytam o „usunięciu duplikacji” lub „poprawie czytelności” podczas etapu refaktoryzacji. Ale co sprawia, że ​​usuwam nieużywaną funkcję?

Powiedzmy na przykład, że istnieje klasa Cz metodami a()i b(). Teraz myślę, że byłoby miło mieć metodę, w f()którą się zaangażuje C. W rzeczywistości f()zastępuje wszystkie wywołania do, b()z wyjątkiem testów jednostkowych, które zdefiniowały / opisały b(). Nie jest już potrzebny - z wyjątkiem testów.

Czy wystarczy zapisać b()i usunąć wszystkie testy, które go wykorzystały? Czy to część „poprawy czytelności”?

TobiMcNamobi
źródło
3
Wystarczy dodać kolejny test, aby sprawdzić, czy funkcja nie istnieje, a następnie naprawić wadliwe przypadki testowe;)
Filip Haglund
@FilipHaglund W zależności od języka może to być możliwe. Ale jako dokumentacja kodu wyglądałoby to dziwnie.
TobiMcNamobi,
1
Dla każdego, kto może nie wiedzieć lepiej: komentarz @ FilipHaglund to oczywiście żart. Nie rób tego
jhyot

Odpowiedzi:

16

Tak oczywiście. Najłatwiejszy do odczytania kod to ten, którego nie ma.

To powiedziawszy, refaktoryzacja ogólnie oznacza ulepszenie kodu bez zmiany jego zachowania. Jeśli myślisz o czymś, co poprawia kod, po prostu zrób to. Nie musisz wpasowywać go w jakąś dziurę gołębia, zanim będziesz mógł to zrobić.

Sebastian Redl
źródło
@ jpmc26 Zwinny, stary, zwinny! :-)
TobiMcNamobi
1
„Najłatwiejszy do odczytania kod to ten, którego nie ma”. -
Wiszę
27

Usunięcie metody publicznej nie oznacza „refaktoryzacji” - refaktoryzacja zmienia implementację przy jednoczesnym przejściu istniejących testów.

Jednak usunięcie niepotrzebnej metody jest całkowicie uzasadnioną zmianą projektu.

TDD wyciąga to w pewnym stopniu, ponieważ podczas przeglądu testów można zauważyć, że testuje niepotrzebną metodę. Testy napędzają Twój projekt, ponieważ możesz przejść „Spójrz, ten test nie ma nic wspólnego z moim celem”.

Może ujawnić się więcej na wyższych poziomach testowania, w połączeniu z narzędziami do pokrywania kodu. Jeśli uruchomisz testy integracyjne z pokryciem kodu i zobaczysz, że metody nie są wywoływane, to wskazówka, że ​​metoda nie jest używana. Analiza kodu statycznego może również wskazywać, że metody nie są używane.

Istnieją dwa podejścia do usuwania metody; oba działają w różnych okolicznościach:

  1. Usuń metodę. Postępuj zgodnie z błędami kompilacji, aby usunąć zależny kod i testy. Jeśli masz pewność, że testy, których dotyczy problem, są jednorazowe, zatwierdź zmiany. Jeśli nie, cofnij.

  2. Usuń testy, które uważasz za przestarzałe. Uruchom cały zestaw testów z pokryciem kodu. Usuń metody, które nie zostały wykonane przez zestaw testów.

(To zakłada, że ​​Twój zestaw testów ma dobry zasięg na początek).

szczupły
źródło
10

W rzeczywistości f () zastępuje wszystkie wywołania b (), z wyjątkiem testów jednostkowych, które zdefiniowały / opisały b ()

IMHO typowy cykl TDD będzie wyglądał następująco:

  • napisz testy negatywne dla f () (prawdopodobnie oparte na testach dla b ()): testy stają się czerwone

  • zaimplementuj f () -> testy stają się zielone

  • refaktor : -> usuń b () i wszystkie testy dla b ()

W ostatnim kroku możesz najpierw rozważyć usunięcie b () i zobacz, co się stanie (w przypadku korzystania z języka skompilowanego kompilator powinien narzekać tylko na istniejące testy, w przeciwnym razie stare testy jednostkowe dla b nie powiodą się, więc jest jasne, że musisz je również usunąć).

Doktor Brown
źródło
4

Tak to jest.

Najlepszym, najbardziej wolnym od błędów i najbardziej czytelnym kodem jest kod, który nie istnieje. Staraj się pisać tak dużo kodu, jak to możliwe, spełniając Twoje wymagania.

Kilian Foth
źródło
9
Przegapiłeś część „jak”.
JeffO
2

Pożądane jest usunięcie, b()gdy nie będzie już używane, z tego samego powodu, dla którego pożądane jest, aby nie dodawać nieużywanych funkcji w pierwszej kolejności. Niezależnie od tego, czy nazywasz to „czytelnością”, czy czymś innym, wszystko inne jest równe, jest to ulepszenie kodu, które nie zawiera niczego, do czego nie ma pożytku. Ze względu na co najmniej jeden konkretny środek, dzięki któremu lepiej go nie mieć, usunięcie go gwarantuje, że jego przyszłe koszty utrzymania po tej zmianie wynoszą zero!

Nie znalazłem żadnej specjalnej techniki, która byłaby potrzebna do faktycznego usunięcia go za pomocą jego testów, ponieważ każdej myśli o zamianie na b()coś nowego musi oczywiście towarzyszyć rozpatrzenie całego aktualnie wywoływanego kodu b(), a testy stanowią podzbiór „całego kodu „.

Argumentacja, która ogólnie dla mnie działa, polega na tym, że w momencie, w którym zauważyłem, że f()stało się to b()przestarzałe, dlatego b()powinienem być przynajmniej przestarzały, a ja szukam wszystkich połączeń b()z zamiarem zastąpienia ich wezwaniami do f(), ja rozważ również kod testowy . W szczególności, jeśli b()nie jest już wymagany, mogę i powinienem usunąć jego testy jednostkowe.

Masz całkowitą rację, że nic nie zmusza mnie do zauważenia, że b()nie jest już wymagane. Jest to kwestia umiejętności (i, jak mówi Slim, raportów dotyczących pokrycia kodu w testach wyższego poziomu). Jeśli odwołują się tylko testy jednostkowe, a nie testy funkcjonalne, b()mogę być ostrożnie optymistyczny, że nie jest on częścią żadnego opublikowanego interfejsu, a zatem usunięcie go nie jest przełomową zmianą dla żadnego kodu, który nie jest pod moją bezpośrednią kontrolą.

Cykl czerwony / zielony / refaktor nie wspomina wyraźnie o usunięciu testów. Co więcej, usunięcie b()narusza zasadę otwartego / zamkniętego, ponieważ oczywiście twój komponent jest otwarty na modyfikacje. Więc jeśli chcesz myśleć o tym kroku jako o czymś poza prostym TDD, śmiało. Na przykład możesz mieć jakiś proces deklarowania testu jako „zły”, który można zastosować w tym przypadku, aby usunąć test, ponieważ testuje on coś, czego nie powinno być (niepotrzebna funkcja b()).

Myślę, że w praktyce większość ludzi prawdopodobnie dopuszcza przeprowadzenie pewnej ilości przeprojektowania wraz z cyklem czerwony / zielony / refaktor, lub uważają usunięcie zbędnych testów jednostkowych za ważną część „refaktora”, choć ściśle mówiąc to nie jest refaktoryzacja. Twój zespół może zdecydować, ile dramatu i formalności powinno być zaangażowanych w uzasadnienie tej decyzji.

W każdym razie, jeśli b()było to ważne, byłyby testy funkcjonalne, które nie zostałyby lekko usunięte, ale już powiedziałeś, że są tylko testy jednostkowe. Jeśli nie rozróżniasz odpowiednio testów jednostkowych (zapisanych w bieżącym projekcie wewnętrznym kodu, który zmieniłeś) i testów funkcjonalnych (napisanych w opublikowanych interfejsach, których być może nie chcesz zmieniać), musisz być bardziej ostrożny na temat usuwania testów jednostkowych.

Steve Jessop
źródło
2

Jedną rzeczą, o której zawsze należy pamiętać, jest to, że korzystamy teraz z REPOZYTORII KODÓW z KONTROLĄ WERSJI. Ten usunięty kod tak naprawdę nie zniknął ... wciąż istnieje gdzieś w poprzedniej iteracji. Więc wysadź to! Bądź liberalny dzięki klawiszowi usuwania, ponieważ zawsze możesz wrócić i odzyskać tę cenną, elegancką metodę, która Twoim zdaniem może kiedyś się przydać ... jeśli to kiedyś nadejdzie. Jest tutaj.

Oczywiście idzie to w parze z problemami i niebezpieczeństwem niekompatybilnych wersji ... zewnętrznych aplikacji opartych na implementacji interfejsu, które są teraz osierocone przez (nagle) przestarzały kod.

dwoz
źródło
Usunięcie kodu ogólnie nie stanowi problemu. Ja kocham usunąć, aby usuwać wierszy, funkcji, albo ... no, całych modułów! Jeśli to możliwe. I robię to wszystko z TDD lub bez. Ale: Czy w przepływie pracy TDD jest punkt, w którym usuwany jest kod (inny niż dupli)? Jeśli nie, i tak zostanie usunięty. Ale czy jest? To było moje pytanie.
TobiMcNamobi,