Według Martina Fowlera refaktoryzacja kodu jest (wyróżnienie moje):
Refaktoryzacja to zdyscyplinowana technika restrukturyzacji istniejącego kodu, zmieniająca jego wewnętrzną strukturę bez zmiany zewnętrznych zachowań . Jego sercem jest seria drobnych zachowań zachowujących transformacje. Każda transformacja (zwana „refaktoryzacją”) niewiele robi, ale sekwencja transformacji może spowodować znaczną restrukturyzację. Ponieważ każde refaktoryzacja jest niewielka, prawdopodobieństwo niepowodzenia jest mniejsze. System jest również w pełni sprawny po każdym małym refaktoryzacji, co zmniejsza ryzyko poważnego uszkodzenia systemu podczas restrukturyzacji.
Co to jest „zachowanie zewnętrzne” w tym kontekście? Na przykład, jeśli zastosuję refaktoryzację metody przenoszenia i przeniosę jakąś metodę do innej klasy, wygląda to tak, jakbym zmienił zachowanie zewnętrzne, prawda?
Chciałbym więc dowiedzieć się, w którym momencie zmiana przestaje być refaktorem i staje się czymś więcej. Termin „refaktoryzacja” może być niewłaściwie używany w przypadku większych zmian: czy jest na to inne słowo?
Aktualizacja. Wiele ciekawych odpowiedzi na temat interfejsu, ale czy przeniesienie metody refaktoryzacji nie zmieni interfejsu?
źródło
Odpowiedzi:
„Zewnętrzne” w tym kontekście oznacza „obserwowalne dla użytkowników”. Użytkownicy mogą być ludźmi w przypadku aplikacji lub innych programów w przypadku publicznego API.
Jeśli więc przeniesiesz metodę M z klasy A do klasy B, a obie klasy znajdują się głęboko w aplikacji, a żaden użytkownik nie zauważy żadnej zmiany w zachowaniu aplikacji z powodu tej zmiany, możesz słusznie nazwać to refaktoryzacją.
Jeśli, OTOH, jakiś inny podsystem / komponent wyższego poziomu zmieni swoje zachowanie lub ulegnie awarii z powodu zmiany, jest to rzeczywiście (zwykle) obserwowalne dla użytkowników (lub przynajmniej dla sysadminów sprawdzających logi). Lub jeśli twoje klasy były częścią publicznego API, może istnieć kod innej firmy, który zależy od tego, czy M jest częścią klasy A, a nie B. Więc żaden z tych przypadków nie jest refaktoryzujący w ścisłym tego słowa znaczeniu.
Rzeczywiście, jest to smutna, ale oczekiwana konsekwencja modnego refaktoryzacji. Deweloperzy od dawna wykonują przeróbki kodu w sposób ad hoc iz pewnością łatwiej jest nauczyć się nowego modnego hasła niż analizować i zmieniać zakorzenione nawyki.
Nazwałbym to przeprojektowaniem .
Aktualizacja
Czego? Konkretne klasy, tak. Ale czy te klasy są w jakikolwiek sposób bezpośrednio widoczne dla świata zewnętrznego? Jeśli nie - ponieważ są one w twoim programie i nie są częścią zewnętrznego interfejsu (API / GUI) programu - żadna dokonana tam zmiana nie jest obserwowana przez podmioty zewnętrzne (chyba że zmiana coś oczywiście psuje).
Wydaje mi się, że istnieje głębsze pytanie: czy konkretna klasa istnieje jako samodzielna jednostka? W większości przypadków odpowiedź brzmi „ nie” : klasa istnieje tylko jako część większego komponentu, ekosystemu klas i obiektów, bez których nie można utworzyć instancji i / lub jest bezużyteczna. Ekosystem ten obejmuje nie tylko jego (bezpośrednie / pośrednie) zależności, ale także inne klasy / obiekty, które od niego zależą. Wynika to z faktu, że bez tych klas wyższego poziomu odpowiedzialność związana z naszą klasą może być bez znaczenia / bezużyteczna dla użytkowników systemu.
Np. W naszym projekcie dotyczącym wynajmu samochodów jest
Charge
klasa. Ta klasa sama w sobie nie jest użyteczna dla użytkowników systemu, ponieważ agenci wynajmu stacji i klienci nie mogą wiele zrobić z indywidualną opłatą: zajmują się umowami najmu jako całością (które obejmują wiele różnych rodzajów opłat) . Użytkownicy są najbardziej zainteresowani sumą tych opłat, które ostatecznie mają zapłacić; agent jest zainteresowany różnymi opcjami umowy, długością wynajmu, grupą pojazdów, pakietem ubezpieczenia, dodatkowymi przedmiotami itp. itd., które (na podstawie wyrafinowanych reguł biznesowych) regulują obecne opłaty i sposób obliczania płatności końcowej z tych. Przedstawiciele krajów / analitycy biznesowi dbają o szczegółowe reguły biznesowe, ich synergię i efekty (na dochody firmy itp.).Niedawno zreorganizowałem tę klasę, zmieniając nazwę większości jej pól i metod (zgodnie ze standardową konwencją nazewnictwa Java, która została całkowicie zaniedbana przez naszych poprzedników). Planuję również dalsze refaktoryzacje, aby zastąpić
String
ichar
pola bardziej odpowiednieenum
iboolean
typy. Wszystko to z pewnością zmieni interfejs klasy, ale (jeśli wykonam swoją pracę poprawnie) żadne z nich nie będzie widoczne dla użytkowników naszej aplikacji. Żadnemu z nich nie zależy na tym, w jaki sposób reprezentowane są poszczególne ładunki, mimo że na pewno znają pojęcie ładunku. Mógłbym wybrać jako przykład sto innych klas nie reprezentujących żadnego pojęcia domeny, więc będąc nawet koncepcyjnie niewidocznym dla użytkowników końcowych, ale pomyślałem, że bardziej interesujące jest wybranie przykładu, w którym przynajmniej widoczność jest na poziomie koncepcji. Ładnie pokazuje to, że interfejsy klasowe są jedynie reprezentacjami pojęć domenowych (w najlepszym wypadku), a nie rzeczywistością *. Reprezentację można zmienić bez wpływu na koncepcję. Użytkownicy mają tylko i rozumieją tę koncepcję; naszym zadaniem jest mapowanie koncepcji i reprezentacji.* I można łatwo dodać, że model domeny, który reprezentuje nasza klasa, sam w sobie jest jedynie przybliżoną reprezentacją jakiejś „prawdziwej rzeczy” ...
źródło
Zewnętrzne oznacza po prostu interfejs w jego prawdziwym znaczeniu językowym. Rozważ krowę w tym przykładzie. Tak długo, jak karmisz warzywa i dostajesz mleko jako wartość zwrotną, nie obchodzi cię, jak działają narządy wewnętrzne. Teraz, jeśli Bóg zmieni krowy narządów wewnętrznych, tak że jego krew stanie się niebieska, o ile punkt wejścia i punkt wyjścia (usta i mleko) nie zmienią się, można to uznać za refaktoryzację.
źródło
Dla mnie refaktoryzacja była najbardziej produktywna / wygodna, gdy granice zostały ustalone przez testy i / lub formalną specyfikację.
Granice te są wystarczająco sztywne, aby czuć się bezpiecznie wiedząc, że jeśli od czasu do czasu przekroczę, zostanie to wykryte na tyle szybko, że nie będę musiał cofać wielu zmian, aby odzyskać. Z drugiej strony dają one wystarczającą swobodę w ulepszaniu kodu bez obawy o zmianę nieistotnego zachowania.
Szczególnie podoba mi się to, że tego rodzaju granice są adaptacyjne, że tak powiem. Mam na myśli: 1) Wprowadzam zmiany i sprawdzam, czy są zgodne ze specyfikacją / testami. Następnie 2) jest przekazywany do kontroli jakości lub testów użytkownika - zwróć uwagę, że nadal może się nie powieść, ponieważ brakuje czegoś w specyfikacjach / testach. OK, jeśli 3a) testy zakończą się pomyślnie, jestem skończony. W przeciwnym razie, jeśli 3b) testowanie się nie powiedzie, to 4) wycofam zmianę i 5) dodaję testy lub wyjaśnij specyfikację, aby następnym razem ten błąd się nie powtórzył. Należy pamiętać, że bez względu na to, czy testowanie przepustki lub nie, ja zdobyć coś - albo kodu / testy / specyfikacji zostanie poprawiona - moje wysiłki nie zamieni się w ogólnej ilości odpadów.
Jeśli chodzi o granice innego rodzaju - jak dotąd nie miałem szczęścia.
„Obserwowalne dla użytkowników” to bezpieczny zakład, jeśli ktoś ma dyscyplinę, którą należy stosować - co w jakiś sposób zawsze wymagało dużego wysiłku w analizie istniejących / tworzenia nowych testów - może zbyt dużego wysiłku. Inną rzeczą, której nie lubię w tym podejściu, jest to, że ślepe jego stosowanie może okazać się zbyt restrykcyjne. - Ta zmiana jest zabroniona, ponieważ wraz z nią ładowanie danych zajmie 3 sekundy zamiast 2. - Uhm, a co powiesz na sprawdzenie u użytkowników / ekspertów UX, czy jest to istotne, czy nie? - Nie ma mowy, żadna zmiana w obserwowalnym zachowaniu jest zabroniona, kropka. Bezpieczny? obstawiasz! produktywny? nie całkiem.
Innym, którego próbowałem, jest zachowanie logiki kodu (sposób, w jaki to rozumiem podczas czytania). Z wyjątkiem najbardziej elementarnych (i zazwyczaj niezbyt owocnych) zmian, ta zawsze była puszką robaków ... a może powinienem powiedzieć puszkę błędów? Mam na myśli błędy regresji. Po prostu zbyt łatwo jest złamać coś ważnego podczas pracy z kodem spaghetti.
źródło
Refaktoryzacja książka jest dość silny w swojej wiadomości, które można wykonywać tylko refactoring gdy masz zasięg testów jednostkowych .
Można więc powiedzieć, że dopóki nie łamie się żadnych testów jednostkowych, refaktoryzuje się . Kiedy przełamujesz testy, nie dokonujesz już refaktoryzacji.
Ale: co powiesz na takie proste refaktoryzacje, takie jak zmiana nazw klas lub członków? Czy oni nie łamią testów?
Tak, robią to i za każdym razem musisz rozważyć, czy przerwa jest znacząca. Jeśli twój SUT jest publicznym API / SDK, wówczas zmiana nazwy jest rzeczywiście przełomową zmianą. Jeśli nie, to prawdopodobnie OK.
Pamiętaj jednak, że często testy nie są przerywane, ponieważ zmieniłeś zachowanie, na którym ci zależy, ale dlatego, że są to testy kruche .
źródło
Najlepszym sposobem zdefiniowania „zachowania zewnętrznego” w tym kontekście mogą być „przypadki testowe”.
Jeśli zrefaktoryzujesz kod i nadal będzie on przekazywał przypadki testowe (zdefiniowane przed refaktoryzacją), wówczas refaktoryzacja nie zmieniła zachowania zewnętrznego. Jeśli jeden lub więcej przypadków testowych nie, to nie zmienił zachowanie zewnętrznego.
Tak przynajmniej rozumiem różne książki opublikowane na ten temat (np. Fowler).
źródło
Granicą byłaby linia, która mówi między tymi, którzy opracowują, utrzymują, wspierają projekt i tymi, którzy są jego użytkownikami innymi niż osoby wspierające, opiekunów, programistów. Zatem w świecie zewnętrznym zachowanie wygląda tak samo, podczas gdy wewnętrzne struktury stojące za tym zachowaniem uległy zmianie.
Dlatego przenoszenie funkcji między klasami powinno być OK, o ile nie są to te, które widzą użytkownicy.
Tak długo, jak przerobienie kodu nie zmienia zachowań zewnętrznych, nie dodaje nowych funkcji ani nie usuwa istniejących, myślę, że można nazwać przerobienie refaktoryzacją.
źródło
Z całym szacunkiem musimy pamiętać, że użytkownicy klasy nie są użytkownikami końcowymi aplikacji zbudowanych z tą klasą, ale raczej klasami, które są implementowane poprzez wykorzystanie - albo wywołanie, albo dziedziczenie - z klasy, która jest refaktoryzowana .
Kiedy mówisz, że „zachowanie zewnętrzne nie powinno się zmieniać”, masz na myśli, że jeśli chodzi o użytkowników, klasa zachowuje się dokładnie tak, jak wcześniej. Może się zdarzyć, że pierwotna (nierefaktoryzowana) implementacja była jedną klasą, a nowa (refaktoryzowana) implementacja ma jedną lub więcej superklas, na których zbudowana jest klasa, ale użytkownicy nigdy nie widzą wnętrza (implementacji) zobacz tylko interfejs.
Więc jeśli klasa ma metodę o nazwie „doSomethingAmazing”, nie ma znaczenia dla użytkownika, czy jest ona implementowana przez klasę, do której się odnoszą, lub przez nadklasę, na której zbudowana jest ta klasa. Jedyne, co liczy się dla użytkownika, to to, że nowy (doładowany) „doSomethingAmazing” ma ten sam wynik, co do starego (bezrefrakcjonowanego) „doSomethingAmazing”.
Jednak to, co nazywa się refaktoryzacją w wielu przypadkach, nie jest prawdziwym refaktoryzowaniem, ale być może ponownym wdrożeniem, które ma na celu ułatwienie modyfikacji kodu i dodanie nowej funkcji. Tak więc w tym późniejszym przypadku (pseudo) refaktoryzacji nowy (refaktoryzowany) kod faktycznie robi coś innego, a może coś więcej niż stary.
źródło
Przez „zachowanie zewnętrzne” mówi przede wszystkim o interfejsie publicznym, ale dotyczy to również wyników / artefaktów systemu. (tzn. masz metodę generującą plik, zmiana formatu pliku zmieniłaby zachowanie zewnętrzne)
e: Uważam, że „metoda przenoszenia” jest zmianą w zachowaniu zewnętrznym. Pamiętaj, że Fowler mówi o istniejących bazach kodów, które zostały wypuszczone na wolność. W zależności od twojej sytuacji możesz być w stanie sprawdzić, czy twoja zmiana nie psuje żadnych zewnętrznych klientów i kontynuować swoją drogę.
e2: „Więc jakie jest właściwe słowo dla przeróbek, które zmieniają zachowanie zewnętrzne?” - Refaktoryzacja API, Breaking Change itp. ... wciąż refaktoryzuje, po prostu nie przestrzega najlepszych praktyk refaktoryzacji publicznego interfejsu, który jest już na wolności z klientami.
źródło
„Metoda przenoszenia” jest techniką refaktoryzacji, a nie samą refaktoryzacją. Refaktoryzacja to proces stosowania kilku technik refaktoryzacji do klas. Tam, gdzie mówisz, zastosowałem „metodę przenoszenia” do klasy, tak naprawdę nie masz na myśli „przebudowałem (klasę)”, masz na myśli „zastosowałem technikę refaktoryzacji w tej klasie”. Refaktoryzacja, w tym sensie, najczystsze znaczenie ma zastosowanie do projektu, a dokładniej, do pewnej części projektu aplikacji, którą można postrzegać jako czarną skrzynkę.
Można powiedzieć, że „refaktoryzacja” stosowana w kontekście klas oznacza „technikę refaktoryzacji”, a zatem „metoda przenoszenia” nie łamie definicji refaktoryzacji procesu. Na tej samej stronie „refaktoryzacja” w kontekście projektu nie psuje istniejących funkcji w kodzie, tylko „psuje” projekt (co i tak jest jego celem).
Podsumowując, „granica”, o której mowa w pytaniu, zostaje przekroczona, jeśli pomylisz (mix: D) refaktoryzację techniki z refaktoryzacją procesu.
PS: Dobre pytanie, przy okazji.
źródło
jeśli mówimy o faktorowaniu liczb, to opisujemy grupę liczb całkowitych, które po pomnożeniu równą się liczbie początkowej. Jeśli weźmiemy tę definicję do faktorowania i zastosujemy ją do programistycznego terminu refactor, wówczas refaktoryzacja rozbije program na najmniejsze jednostki logiczne, tak że gdy zostaną uruchomione jako program, wygenerują takie same dane wyjściowe (przy tych samych danych wejściowych ) jako oryginalny program.
źródło
Granice refaktoryzacji są specyficzne dla danego refaktoryzacji. Po prostu nie może być jednej odpowiedzi i obejmuje wszystko. Jednym z powodów jest to, że sam fakt refaktoryzacji jest niespecyficzny.
Możesz refaktoryzować:
i jestem pewien, że kilka innych rzeczy.
Być może refaktor można zdefiniować jako
źródło
Zdecydowanie są pewne granice, jak daleko można refaktoryzować. Zobacz: kiedy dwa algorytmy są takie same?
Więc nie idź za daleko, bo nie będziesz mieć zaufania do wyniku. Z drugiej strony doświadczenie wskazuje, że często można zastąpić jeden algorytm innym i uzyskać te same odpowiedzi, czasem szybciej. To jest jego piękno, prawda?
źródło
Możesz myśleć o znaczeniu „zewnętrznym”
Tak więc każdą zmianę w systemie, która nie ma wpływu na żadne świnie, można uznać za refaktoryzację.
Zmiana interfejsu klasy nie stanowi problemu, jeśli z klasy korzysta tylko jeden system zbudowany i utrzymywany przez zespół. Jeśli jednak klasa jest klasą publiczną w środowisku .net używanym przez każdego programistę .net, jest to zupełnie inna sprawa.
źródło