35 linii, 55 linii, 100 linii, 300 linii? Kiedy powinieneś zacząć go rozbijać? Pytam, ponieważ mam funkcję z 60 liniami (łącznie z komentarzami) i myślałem o jej rozbiciu.
long_function(){ ... }
w:
small_function_1(){...}
small_function_2(){...}
small_function_3(){...}
Funkcje nie będą używane poza long_function, tworzenie mniejszych funkcji oznacza więcej wywołań funkcji itp.
Kiedy podzieliłbyś funkcję na mniejsze? Czemu?
- Metody powinny robić tylko jedną logiczną rzecz (pomyśl o funkcjonalności)
- Powinieneś być w stanie wyjaśnić metodę w jednym zdaniu
- Powinien pasować do wysokości twojego wyświetlacza
- Unikaj niepotrzebnych kosztów (komentarzy, które wskazują na oczywiste ...)
- Testowanie jednostkowe jest łatwiejsze w przypadku małych funkcji logicznych
- Sprawdź, czy część funkcji może być ponownie wykorzystana przez inne klasy lub metody
- Unikaj nadmiernego sprzężenia międzyklasowego
- Unikaj głęboko zagnieżdżonych struktur kontrolnych
Dziękuję wszystkim za odpowiedzi , edytuj listę i zagłosuj na poprawną odpowiedź, wybiorę tę;)
Robię teraz refaktoryzację, mając na uwadze te pomysły :)
function
refactoring
coding-style
user58163
źródło
źródło
Odpowiedzi:
Nie ma na to naprawdę sztywnych i szybkich zasad. Generalnie podoba mi się moje metody polegające na „robieniu jednej rzeczy”. Więc jeśli chodzi o pobieranie danych, a następnie robienie czegoś z tymi danymi, a następnie zapisywanie ich na dysku, to podzieliłbym pobieranie i zapis na osobne metody, więc moja metoda „główna” zawierała po prostu „robienie czegoś”.
To „robienie czegoś” może jednak składać się z kilku wierszy, więc nie jestem pewien, czy liczba wierszy jest właściwa :)
Edycja: To jest pojedynczy wiersz kodu, który wysłałem pocztą w zeszłym tygodniu w pracy (aby udowodnić, że to nie jest coś, do czego mam nawyk :)) - z pewnością nie chciałbym 50-60 tych złych chłopców w mojej metodzie :RE
źródło
Oto lista sygnałów ostrzegawczych (w dowolnej kolejności), które mogą wskazywać, że funkcja jest zbyt długa:
Głęboko zagnieżdżone struktury kontrolne : np. Pętle for 3 poziomy lub nawet 2 poziomy z zagnieżdżonymi instrukcjami if, które mają złożone warunki.
Zbyt wiele parametrów definiujących stan : przez parametr definiujący stan rozumiem parametr funkcji, który gwarantuje określoną ścieżkę wykonania przez funkcję. Jeśli uzyskasz zbyt wiele tego typu parametrów, otrzymasz kombinatoryczną eksplozję ścieżek wykonania (zwykle dzieje się to w połączeniu z # 1).
Logika, która jest powielana w innych metodach : złe ponowne wykorzystanie kodu jest ogromnym czynnikiem przyczyniającym się do monolitycznego kodu proceduralnego. Wiele z takich powielania logiki może być bardzo subtelnych, ale po ponownym uwzględnieniu końcowym rezultatem może być znacznie bardziej elegancki projekt.
Nadmierne sprzężenie międzyklasowe : ten brak właściwej hermetyzacji powoduje, że funkcje dotyczą intymnych cech innych klas, a tym samym je wydłużają.
Niepotrzebny narzut : Komentarze wskazujące na oczywiste, głęboko zagnieżdżone klasy, zbędne metody pobierające i ustawiające dla prywatnych zagnieżdżonych zmiennych klas oraz niezwykle długie nazwy funkcji / zmiennych mogą powodować szum składniowy w powiązanych funkcjach, który ostatecznie zwiększy ich długość.
Twój ogromny wyświetlacz klasy deweloperskiej nie jest wystarczająco duży, aby go wyświetlić : w rzeczywistości dzisiejsze wyświetlacze są na tyle duże, że funkcja, która jest bliska jej wysokości, jest prawdopodobnie zbyt długa. Ale jeśli jest większy , to dymiący pistolet, że coś jest nie tak.
Nie można od razu określić cel z funkcji : Ponadto, gdy faktycznie zrobić określić swój cel, jeśli nie można podsumować ten cel w jednym zdaniu lub zdarzy się mieć ogromny ból głowy, to powinno być wskazówką.
Podsumowując, funkcje monolityczne mogą mieć daleko idące konsekwencje i często są objawem poważnych niedociągnięć projektowych. Ilekroć napotykam kod, którego czytanie daje absolutną radość , jego elegancja jest od razu widoczna. I zgadnij co: funkcje są często bardzo krótkie.
źródło
// fetch Foo's credentials where Bar is "uncomplete"
. To prawie na pewno nazwa funkcji, którą należy oddzielić. Prawdopodobnie chce być refaktoryzowany na coś takiego:Foo.fetchCredentialWhereBarUncomplete()
Myślę, że jest ogromne zastrzeżenie do mantry „rób tylko jedną rzecz” na tej stronie. Czasami robienie jednej rzeczy powoduje żonglowanie wieloma zmiennymi. Nie dziel długiej funkcji na kilka mniejszych funkcji, jeśli mniejsze funkcje mają długie listy parametrów. W ten sposób pojedynczą funkcję przekształca się w zestaw wysoce powiązanych funkcji bez rzeczywistej wartości indywidualnej.
źródło
Funkcja powinna robić tylko jedną rzecz. Jeśli robisz wiele małych rzeczy w funkcji, zrób każdą małą rzecz funkcją i wywołaj te funkcje z długiej funkcji.
To, czego naprawdę nie chcesz robić, to kopiować i wklejać co 10 wierszy swojej długiej funkcji do funkcji krótkich (jak sugeruje przykład).
źródło
Zgadzam się, że funkcja powinna robić tylko jedną rzecz, ale na jakim poziomie jest to jedna rzecz.
Jeśli twoje 60 linii spełnia jedną rzecz (z punktu widzenia twoich programów), a elementy, które składają się na te 60 linii, nie będą używane przez nic innego, to 60 linii jest w porządku.
Nie ma żadnej realnej korzyści z rozbijania go, chyba że można go rozbić na konkretne kawałki, które stoją samodzielnie. Metryka, której należy użyć, to funkcjonalność, a nie wiersze kodu.
Pracowałem nad wieloma programami, w których autorzy podnieśli jedyną rzecz na ekstremalny poziom, a skończyło się na tym, że wyglądało to tak, jakby ktoś wziął granat do funkcji / metody i wysadził go na dziesiątki niepołączonych elementów, które są trudne do naśladowania.
Wyciągając fragmenty tej funkcji, musisz również rozważyć, czy będziesz dodawać niepotrzebne narzuty i unikać przekazywania dużych ilości danych.
Uważam, że kluczową kwestią jest poszukiwanie możliwości ponownego użycia w tej długiej funkcji i wyciągnięcie tych części. Pozostaje ci funkcja, bez względu na to, czy ma długość 10, 20 czy 60 linii.
źródło
60 linii jest długich, ale niezbyt długich jak na funkcję. Jeśli zmieści się na jednym ekranie w edytorze, możesz zobaczyć to wszystko naraz. To naprawdę zależy od tego, co robią funkcje.
Dlaczego mogę rozbić funkcję:
źródło
DoThisAndThisAndAlsoThis
funkcją, ale z dużą ilością abstrakcji, którą nadal musisz jakoś nazwaćMoja osobista heurystyka jest taka, że jeśli nie widzę całości bez przewijania, jest za długo.
źródło
Rozmiar przybliżony do rozmiaru ekranu (więc weź duży panoramiczny ekran i obróć go) ... :-)
Żart na bok, jedna logiczna rzecz na funkcję.
A pozytywną rzeczą jest to, że testowanie jednostkowe jest znacznie łatwiejsze w przypadku małych funkcji logicznych, które wykonują jedną rzecz. Duże funkcje, które robią wiele rzeczy, są trudniejsze do zweryfikowania!
/ Johan
źródło
Zasada ogólna: jeśli funkcja zawiera bloki kodu, które coś robią, co jest nieco oddzielone od reszty kodu, należy umieścić ją w osobnej funkcji. Przykład:
dużo ładniejszy:
Takie podejście ma dwie zalety:
Zawsze, gdy potrzebujesz pobrać adresy dla określonego kodu pocztowego, możesz skorzystać z łatwo dostępnej funkcji.
Kiedy kiedykolwiek będziesz musiał ponownie odczytać funkcję
build_address_list_for_zip()
, wiesz, co zrobi pierwszy blok kodu (pobiera adresy dla określonego kodu pocztowego, przynajmniej to można uzyskać z nazwy funkcji). Jeśli zostawisz kod zapytania w tekście, musisz najpierw przeanalizować ten kod.[Z drugiej strony (zaprzeczę, że ci powiedziałem, nawet podczas tortur): Jeśli dużo czytasz o optymalizacji PHP, możesz wpaść na pomysł, aby liczba funkcji była jak najmniejsza, ponieważ wywołania funkcji są bardzo, bardzo drogie w PHP. Nie wiem o tym, ponieważ nigdy nie robiłem żadnych testów porównawczych. W takim przypadku prawdopodobnie lepiej nie podążać za żadną z odpowiedzi na swoje pytanie, jeśli aplikacja jest bardzo wrażliwa na wydajność ;-)]
źródło
Rzuć okiem na cyklomatyczny McCabe, w którym dzieli swój kod na wykres, na którym: „Każdy węzeł na wykresie odpowiada blokowi kodu w programie, w którym przepływ jest sekwencyjny, a łuki odpowiadają gałęziom pobranym w programie. "
Teraz wyobraź sobie, że twój kod nie ma funkcji / metod; to tylko jeden ogromny fragment kodu w postaci wykresu.
Chcesz rozbić ten rozrost na metody. Weź pod uwagę, że kiedy to zrobisz, w każdej metodzie będzie pewna liczba bloków. Tylko jeden blok każdej metody będzie widoczny dla wszystkich innych metod: pierwszy blok (zakładamy, że będziesz mógł przejść do metody tylko w jednym punkcie: w pierwszym bloku). Wszystkie pozostałe bloki w każdej metodzie będą informacjami ukrytymi w tej metodzie, ale każdy blok w metodzie może potencjalnie przeskoczyć do dowolnego innego bloku w tej metodzie.
Aby określić, jaki rozmiar powinny mieć twoje metody pod względem liczby bloków na metodę, jedno pytanie, które możesz sobie zadać, brzmi: ile metod powinienem mieć, aby zminimalizować maksymalną potencjalną liczbę zależności (MPE) między wszystkimi blokami?
Ta odpowiedź jest podana za pomocą równania. Jeśli r to liczba metod, które minimalizują błąd graniczny dopuszczalny (MPE) systemu, a n to liczba bloków w systemie, to równanie wygląda następująco: r = sqrt (n)
Można też wykazać, że daje to również liczbę bloków na metodę, które mają być również sqrt (n).
źródło
Pamiętaj, że możesz skończyć z ponownym faktoringiem tylko dla samego ponownego faktorowania, potencjalnie sprawiając, że kod będzie bardziej nieczytelny niż był na początku.
Mój były kolega miał dziwną regułę, że funkcja / metoda musi zawierać tylko 4 linie kodu! Próbował trzymać się tego tak sztywno, że nazwy jego metod często stawały się powtarzalne i bez znaczenia, a wywołania stały się głęboko zagnieżdżone i zagmatwane.
Stała się więc moja własna mantra: jeśli nie możesz wymyślić przyzwoitej nazwy funkcji / metody dla fragmentu kodu, który analizujesz, nie przejmuj się.
źródło
Głównym powodem, dla którego zwykle rozbijam funkcję, jest to, że jej fragmenty są również składnikami innej pobliskiej funkcji, którą piszę, więc części wspólne są uwzględniane. Ponadto, jeśli używa wielu pól lub właściwości z innej klasy, istnieje duża szansa, że odpowiedni fragment można pobrać hurtowo i, jeśli to możliwe, przenieść do innej klasy.
Jeśli masz blok kodu z komentarzem na górze, rozważ wyciągnięcie go do funkcji, z nazwami funkcji i argumentów ilustrującymi jej cel, i zarezerwuj komentarz dla uzasadnienia kodu.
Czy na pewno nie ma tam elementów, które przydałyby się gdzie indziej? Jaka to funkcja?
źródło
Zwykle przerywam funkcje potrzebą umieszczenia komentarzy opisujących następny blok kodu. To, co wcześniej znajdowało się w komentarzach, teraz trafia do nowej nazwy funkcji. To nie jest twarda zasada, ale (dla mnie) miła praktyczna zasada. Lubię kod, który mówi sam za siebie, niż taki, który wymaga komentarzy (ponieważ nauczyłem się, że komentarze zwykle kłamią)
źródło
Moim zdaniem odpowiedź brzmi: kiedy robi za dużo rzeczy. Twoja funkcja powinna wykonywać tylko czynności, których oczekujesz od jej nazwy. Inną kwestią do rozważenia jest to, czy chcesz ponownie wykorzystać niektóre części swoich funkcji w innych; w tym przypadku może być przydatne podzielenie go.
źródło
Jest to po części kwestia gustu, ale jak to określam, staram się zachować moje funkcje tylko tak długo, jak zmieszczą się na moim ekranie w jednym czasie (maksymalnie). Powodem jest to, że łatwiej jest zrozumieć, co się dzieje, jeśli widzisz wszystko na raz.
Kiedy koduję, jest to mieszanka pisania długich funkcji, a następnie refaktoryzacji w celu wyciągnięcia bitów, które mogłyby zostać ponownie wykorzystane przez inne funkcje - oraz - pisania małych funkcji, które wykonują dyskretne zadania na bieżąco.
Nie wiem, czy jest jakaś dobra lub zła odpowiedź na to pytanie (np. Możesz wybrać 67 linii jako maksimum, ale może się zdarzyć, że warto dodać kilka więcej).
źródło
Przeprowadzono szczegółowe badania na ten temat, jeśli chcesz najmniej błędów, twój kod nie powinien być zbyt długi. Ale też nie powinno być za krótkie.
Nie zgadzam się, że metoda powinna zmieścić się na ekranie w jednym, ale jeśli przewijasz w dół o więcej niż jedną stronę, metoda jest za długa.
Zobacz rozmiarowi Optymalne klasy dla obiektowego Oprogramowania do dalszej dyskusji.
źródło
Napisałem wcześniej 500 funkcji liniowych, ale były to tylko duże instrukcje przełączające do dekodowania i odpowiadania na wiadomości. Kiedy kod pojedynczej wiadomości stał się bardziej złożony niż pojedyncza instrukcja if-then-else, wyodrębniłem go.
W istocie, chociaż funkcja miała 500 linii, niezależnie utrzymywane regiony miały średnio 5 linii.
źródło
Zwykle do pisania kodu używam podejścia opartego na testach. W tym podejściu rozmiar funkcji jest często powiązany z szczegółowością testów.
Jeśli twój test jest wystarczająco ukierunkowany, poprowadzi cię to do napisania małej, skoncentrowanej funkcji, która sprawi, że test zostanie zaliczony.
Działa to również w drugą stronę. Funkcje muszą być wystarczająco małe, aby skutecznie testować. Dlatego podczas pracy ze starszym kodem często okazuje się, że rozkładam większe funkcje, aby przetestować różne ich części.
Zwykle zadaję sobie pytanie „za co odpowiada ta funkcja” i jeśli nie potrafię określić tej odpowiedzialności w jasnym, zwięzłym zdaniu, a następnie przełożyć to na mały, ukierunkowany test, zastanawiam się, czy funkcja nie jest zbyt duża.
źródło
Jeśli ma więcej niż trzy gałęzie, ogólnie oznacza to, że funkcja lub metoda powinna zostać rozdzielona, aby hermetyzować logikę rozgałęzień w różnych metodach.
Każda pętla for, instrukcja if itp. Nie jest wtedy postrzegana jako gałąź w metodzie wywołującej.
Cobertura dla kodu Java (i jestem pewien, że istnieją inne narzędzia dla innych języków) oblicza liczbę if itp. W funkcji dla każdej funkcji i sumuje ją dla „średniej złożoności cyklicznej”.
Jeśli funkcja / metoda ma tylko trzy gałęzie, otrzyma trójkę na tej metryki, co jest bardzo dobre.
Czasami trudno jest postępować zgodnie z tą wytyczną, a mianowicie przy sprawdzaniu poprawności danych wejściowych użytkownika. Niemniej jednak umieszczanie gałęzi w różnych metodach pomaga nie tylko w rozwoju i utrzymaniu, ale także w testowaniu, ponieważ dane wejściowe do metod wykonujących rozgałęzienia można łatwo przeanalizować, aby zobaczyć, jakie dane wejściowe należy dodać do przypadków testowych, aby objąć gałęzie, które nie zostały objęte.
Gdyby wszystkie gałęzie znajdowały się w jednej metodzie, dane wejściowe musiałyby być śledzone od początku metody, co utrudnia testowalność.
źródło
Podejrzewam, że znajdziesz na ten temat wiele odpowiedzi.
Prawdopodobnie rozbiję to na podstawie logicznych zadań wykonywanych w ramach funkcji. Jeśli wydaje ci się, że twoje opowiadanie zamienia się w powieść, proponuję znaleźć i wyodrębnić różne kroki.
Na przykład, jeśli masz funkcję, która obsługuje dane wejściowe w postaci ciągu i zwraca wynik w postaci ciągu, możesz podzielić tę funkcję na podstawie logiki dzielenia ciągu na części, logiki dodawania dodatkowych znaków i logiki wstawiania go wszystkie razem jako sformatowany wynik.
Krótko mówiąc, najlepszym podejściem jest wszystko, co sprawia, że kod jest czysty i łatwy do odczytania (czy to po prostu zapewniając, że funkcja ma dobry komentarz, czy też ją rozbija).
źródło
zakładając, że robisz jedną rzecz, długość będzie zależeć od:
60 linii może być zbyt długich lub w sam raz. podejrzewam jednak, że może to być za długie.
źródło
Jedna rzecz (i ta rzecz powinna być oczywista z nazwy funkcji), ale nie więcej niż ekran pełen kodu, niezależnie od tego. Możesz też zwiększyć rozmiar czcionki. A jeśli masz wątpliwości, przerób to na dwie lub więcej funkcji.
źródło
Rozszerzając ducha tweeta od wujka Boba jakiś czas temu, wiesz, że funkcja staje się zbyt długa, gdy czujesz potrzebę wstawienia pustej linii między dwoma wierszami kodu. Chodzi o to, że jeśli potrzebujesz pustego wiersza do oddzielenia kodu, jego odpowiedzialność i zakres są oddzielane w tym momencie.
źródło
Mój pomysł jest taki, że jeśli muszę zadać sobie pytanie, czy to jest za długie, to prawdopodobnie za długie. Pomaga w tworzeniu mniejszych funkcji w tym obszarze, ponieważ może pomóc w późniejszym cyklu życia aplikacji.
źródło