Mam dużą metodę, która wykonuje 3 zadania, z których każde można wyodrębnić do osobnej funkcji. Jeśli stworzę dodatkowe funkcje dla każdego z tych zadań, czy poprawi to lub pogorszy mój kod i dlaczego?
Oczywiście spowoduje to zmniejszenie liczby wierszy kodu w funkcji głównej, ale pojawią się dodatkowe deklaracje funkcji, więc moja klasa będzie miała dodatkowe metody, które moim zdaniem nie są dobre, ponieważ sprawią, że klasa będzie bardziej złożona.
Czy powinienem to zrobić, zanim napisałem cały kod, czy powinienem go zostawić, aż wszystko się skończy, a następnie wyodrębnić funkcje?
java
design-patterns
dhblah
źródło
źródło
Odpowiedzi:
Jest to książka, do której często prowadzę odnośniki, ale tutaj ponownie: Robert C Martin's Clean Code , rozdział 3, „Funkcje”.
Czy wolisz czytać funkcję z liniami +150, czy funkcję wywołującą 3 funkcje linii +50? Myślę, że wolę drugą opcję.
Tak , poprawi Twój kod w tym sensie, że będzie bardziej „czytelny”. Twórz funkcje, które wykonują jedną i tylko jedną rzecz, będą łatwiejsze w utrzymaniu i stworzeniu przypadku testowego.
Bardzo ważna rzecz, której nauczyłem się ze wspomnianej książki: wybierz dobre i precyzyjne nazwy dla swoich funkcji. Im ważniejsza jest ta funkcja, tym bardziej precyzyjna powinna być nazwa. Nie przejmuj się długością nazwy, jeśli trzeba ją nazwać
FunctionThatDoesThisOneParticularThingOnly
, nazwij ją w ten sposób.Przed wykonaniem refaktora napisz jeden lub więcej przypadków testowych. Upewnij się, że działają. Po zakończeniu refaktoryzacji będziesz mógł uruchomić te przypadki testowe, aby upewnić się, że nowy kod działa poprawnie. Możesz napisać dodatkowe „mniejsze” testy, aby upewnić się, że nowe funkcje działają dobrze oddzielnie.
Wreszcie, i to nie jest sprzeczne z tym, co właśnie napisałem, zadaj sobie pytanie, czy naprawdę musisz dokonać tego refaktoryzacji, sprawdź odpowiedzi na „ Kiedy refaktoryzować ?” (również szukaj SO pytania dotyczące „refaktoryzacji”, jest ich więcej i odpowiedzi są interesujące do przeczytania)
Jeśli kod już istnieje i działa, a brakuje ci czasu na następne wydanie, nie dotykaj go. W przeciwnym razie uważam, że w miarę możliwości należy wykonywać małe funkcje i jako takie refaktoryzować, gdy tylko będzie trochę czasu, upewniając się, że wszystko działa jak wcześniej (przypadki testowe).
źródło
Tak, oczywiście. Jeśli łatwo jest zobaczyć i oddzielić różne „zadania” jednej funkcji.
Ale mogą być z tym problemy:
źródło
Problem, który stawiasz, nie jest problemem kodowania, konwencji lub praktyki kodowania, a raczej problemem czytelności i sposobu, w jaki redaktorzy tekstowi pokazują napisany kod. Ten sam problem pojawia się również w poście:
Czy podzielenie długich funkcji i metod na mniejsze jest w porządku, mimo że nie zostaną wywołane przez nic innego?
Podział funkcji na podfunkcje ma sens przy wdrażaniu dużego systemu z zamiarem kapsułkowania różnych funkcjonalności, z których będzie się składał. Niemniej jednak wcześniej czy później znajdziesz wiele dużych funkcji. Niektóre z nich są niereagowalne i trudne do utrzymania, nawet jeśli utrzymujesz je jako pojedyncze długie funkcje lub dzielisz je na mniejsze funkcje. Jest to szczególnie prawdziwe w przypadku funkcji, w których wykonywane operacje nie są konieczne w żadnym innym miejscu systemu. Umożliwia pobranie jednej z tak długich funkcji i rozważenie jej w szerszym widoku.
Zawodowiec:
Contra:
Teraz wyobraźmy sobie, że możemy podzielić długą funkcję na kilka podfunkcji i spojrzeć na nie z szerszej perspektywy.
Zawodowiec:
Contra:
Oba rozwiązania mają zalety i kontra. Najlepszym rozwiązaniem byłoby posiadanie edytorów, które pozwalają rozszerzyć, wbudować i na całą głębokość, każde wywołanie funkcji do jej zawartości. Co sprawiłoby, że dzielenie funkcji na podfunkcje byłoby jedynym najlepszym rozwiązaniem.
źródło
Dla mnie istnieją cztery powody, aby wyodrębnić bloki kodu do funkcji:
Ponownie go używasz: właśnie skopiowałeś blok kodu do schowka. Zamiast wklejać go, umieść go w funkcji i zamień blok na wywołanie funkcji po obu stronach. Dlatego za każdym razem, gdy trzeba zmienić ten blok kodu, wystarczy zmienić tylko jedną funkcję zamiast zmieniać kod w wielu miejscach. Dlatego za każdym razem, gdy kopiujesz blok kodu, musisz wykonać funkcję.
Jest to wywołanie zwrotne : jest to program obsługi zdarzeń lub kod użytkownika wywołany przez bibliotekę lub środowisko. (Trudno mi to sobie wyobrazić bez wykonywania funkcji).
Uważasz, że zostanie on ponownie wykorzystany w bieżącym projekcie, a może gdzieś indziej: właśnie napisałeś blok, który oblicza najdłuższy wspólny podsekwencja dwóch tablic. Nawet jeśli twój program wywoła tę funkcję tylko raz, uważam, że w końcu będę potrzebował tej funkcji również w innych projektach.
Chcesz kodu samokontrującego : Zamiast pisać wiersz komentarza nad blokiem kodu podsumowującego jego działanie, wyodrębniasz całość do funkcji i nazywasz to, co napiszesz w komentarzu. Chociaż nie jestem tego fanem, ponieważ lubię zapisywać nazwę używanego algorytmu, powód, dla którego wybrałem ten algorytm itp. Nazwy funkcji byłyby wtedy zbyt długie ...
źródło
Jestem pewien, że słyszałeś radę, aby zakres zmiennych był jak najściślejszy i mam nadzieję, że się z tym zgadzasz. Funkcje są kontenerami zakresu, a w mniejszych funkcjach zakres zmiennych lokalnych jest mniejszy. Jest o wiele jaśniej jak i kiedy mają być używane, a trudniej jest używać ich w niewłaściwej kolejności lub przed ich inicjalizacją.
Funkcje są również kontenerami przepływu logicznego. Istnieje tylko jedno wejście, wyjścia są wyraźnie oznaczone, a jeśli funkcja jest wystarczająco krótka, przepływy wewnętrzne powinny być oczywiste. Powoduje to zmniejszenie złożoności cyklicznej, co jest niezawodnym sposobem na zmniejszenie liczby wad.
źródło
Na bok: Napisałem to w odpowiedzi na pytanie Dallina (teraz zamknięte), ale nadal uważam, że to może być komuś pomocne, więc proszę bardzo
Myślę, że powód atomizacji funkcji jest 2-krotny, a jak wspomina @jozefg, zależy od używanego języka.
Rozdzielenie obaw
Głównym powodem tego jest oddzielenie różnych fragmentów kodu, więc każdy blok kodu, który nie przyczynia się bezpośrednio do pożądanego wyniku / zamiaru funkcji, stanowi osobną kwestię i może zostać wyodrębniony.
Załóżmy, że masz zadanie w tle, które również aktualizuje pasek postępu, aktualizacja paska postępu nie jest bezpośrednio związana z zadaniem długo działającym, więc należy go wyodrębnić, nawet jeśli jest to jedyny fragment kodu, który korzysta z paska postępu.
Powiedz w JavaScript, że masz funkcję getMyData (), która 1) buduje komunikat mydła na podstawie parametrów, 2) inicjuje odwołanie do usługi, 3) wywołuje usługę z komunikatem mydła, 4) analizuje wynik, 5) zwraca wynik. Wydaje się rozsądne, napisałem tę dokładną funkcję wiele razy - ale tak naprawdę można ją podzielić na 3 funkcje prywatne, w tym kod dla 3 i 5 (jeśli to), ponieważ żaden inny kod nie jest bezpośrednio odpowiedzialny za pobieranie danych z usługi .
Ulepszone działanie debugowania
Jeśli masz funkcje całkowicie atomowe, ślad stosu staje się listą zadań, zawierającą wszystkie pomyślnie wykonane kody, tj .:
byłoby bardziej interesujące niż stwierdzenie, że wystąpił błąd podczas pobierania danych. Ale niektóre narzędzia są jeszcze bardziej przydatne do debugowania szczegółowych drzew połączeń, na przykład na przykład Płótno debugowania Microsofts .
Rozumiem również twoje obawy, że przestrzeganie kodu napisanego w ten sposób może być trudne, ponieważ pod koniec dnia musisz wybrać kolejność funkcji w jednym pliku, gdzie jako drzewo wywołań byłoby dużo bardziej skomplikowane niż to. . Ale jeśli funkcje są dobrze nazwane (intellisense pozwala mi używać 3-4 wyrazów wielbłąda w dowolnej funkcji, która mi się podoba, bez spowalniania mnie) i ma strukturę z interfejsem publicznym na górze pliku, twój kod będzie czytał jak pseudo-kod, który jest zdecydowanie najprostszym sposobem na uzyskanie wysokiego poziomu zrozumienia bazy kodu.
Do waszej informacji - to jedna z tych rzeczy „róbcie, co mówię nie tak, jak robię”, utrzymywanie kodu atomowego jest bezcelowe, chyba że jesteście bezwzględnie z nim zgodni IMHO, czego nie jestem.
źródło