Zasadniczo używam prywatnych metod do enkapsulacji funkcjonalności, która jest ponownie wykorzystywana w wielu miejscach w klasie. Ale czasami mam dużą metodę publiczną, którą można by podzielić na mniejsze etapy, każdy według własnej metody prywatnej. Spowoduje to, że metoda publiczna będzie krótsza, ale obawiam się, że zmuszanie każdego, kto czyta metodę, do przeskakiwania do różnych metod prywatnych, pogorszy czytelność.
Czy istnieje konsensus w tej sprawie? Czy lepiej jest mieć długie metody publiczne, czy też podzielić je na mniejsze części, nawet jeśli nie można ponownie użyć każdego z nich?
coding-style
readability
Jordak
źródło
źródło
Odpowiedzi:
Nie, to nie jest zły styl. W rzeczywistości jest to bardzo dobry styl.
Funkcje prywatne nie muszą istnieć tylko ze względu na możliwość ponownego użycia. To z pewnością jeden dobry powód do ich stworzenia, ale jest jeszcze jeden: rozkład.
Rozważ funkcję, która robi za dużo. Ma on sto linii długości i nie sposób o nim myśleć.
Jeśli podzielisz tę funkcję na mniejsze części, nadal „będzie” tyle samo, co wcześniej, ale na mniejsze części. Wywołuje inne funkcje, które powinny mieć opisowe nazwy. Główna funkcja brzmi prawie jak książka: wykonaj A, następnie B, a następnie C itd. Funkcje, które wywołuje, mogą być wywoływane tylko w jednym miejscu, ale teraz są mniejsze. Każda konkretna funkcja jest koniecznie wyodrębniona z innych funkcji: mają różne zakresy.
Kiedy rozkładasz duży problem na mniejsze problemy, nawet jeśli te mniejsze problemy (funkcje) są używane / rozwiązane tylko raz, zyskujesz kilka korzyści:
Czytelność. Nikt nie może odczytać funkcji monolitycznej i zrozumieć, co ona robi całkowicie. Możesz dalej okłamywać siebie lub podzielić je na kawałki wielkości kęsa, które mają sens.
Lokalizacja odniesienia. Teraz niemożliwe staje się zadeklarowanie i użycie zmiennej, a następnie pozostawienie jej dookoła i ponowne użycie 100 linii później. Funkcje te mają różne zakresy.
Testowanie. Chociaż konieczne jest jedynie testowanie jednostkowe członków publicznych klasy, pożądane może być również przetestowanie niektórych członków prywatnych. Jeśli istnieje krytyczna sekcja długiej funkcji, która może skorzystać z testowania, niemożliwe jest przetestowanie jej niezależnie bez wyodrębnienia jej do osobnej funkcji.
Modułowość. Teraz, gdy masz funkcje prywatne, możesz znaleźć jedną lub więcej z nich, które można wyodrębnić do osobnej klasy, bez względu na to, czy jest używana tylko tutaj, czy może być ponownie użyta. Do poprzedniego punktu ta oddzielna klasa będzie prawdopodobnie łatwiejsza do przetestowania, ponieważ będzie potrzebować publicznego interfejsu.
Pomysł podzielenia dużego kodu na mniejsze, łatwiejsze do zrozumienia i przetestowania, jest kluczowym punktem książki wuja Boba „Czysty kod” . W chwili pisania tej odpowiedzi książka ma dziewięć lat, ale jest tak samo aktualna jak wtedy.
źródło
To chyba świetny pomysł!
Mam problem z dzieleniem długich liniowych sekwencji akcji na osobne funkcje wyłącznie w celu zmniejszenia średniej długości funkcji w bazie kodu:
Teraz faktycznie dodałeś wiersze źródła i znacznie zmniejszyłeś całkowitą czytelność. Zwłaszcza jeśli przekazujesz teraz wiele parametrów między każdą funkcją, aby śledzić stan. Yikes!
Jeśli jednak udało Ci się wyodrębnić jedną lub więcej linii do czystej funkcji, która służy jednemu, wyraźnemu celowi ( nawet jeśli wywoływana jest tylko raz ), to poprawiłeś czytelność:
Prawdopodobnie nie będzie to łatwe w rzeczywistych sytuacjach, ale elementy czystej funkcjonalności można często wypróbować, jeśli się nad tym zastanowić wystarczająco długo.
Będziesz wiedział, że jesteś na dobrej drodze, gdy masz funkcje z ładnymi nazwami czasowników i kiedy ich funkcja rodzica wywołuje je, a cała rzecz praktycznie brzmi jak akapit prozy.
Potem, kiedy wrócisz kilka tygodni później, aby dodać więcej funkcji i okaże się, że możesz ponownie użyć jednej z tych funkcji, to och, zachwycająca radość! Cóż za cudowna promienna radość!
źródło
foo = compose(reglorbulate, deglorbFramb, getFrambulation);
Odpowiedź jest bardzo zależna od sytuacji. @Snowman obejmuje pozytywne skutki zerwania dużej funkcji publicznej, ale ważne jest, aby pamiętać, że mogą również mieć negatywne skutki, o czym słusznie się martwisz.
Wiele prywatnych funkcji z efektami ubocznymi może sprawić, że kod będzie trudny do odczytania i dość kruchy. Jest to szczególnie prawdziwe, gdy te prywatne funkcje zależą od siebie nawzajem. Unikaj ściśle powiązanych funkcji.
Abstrakcje są nieszczelne . Chociaż udaje się udawać, jak wykonywana jest operacja lub jak przechowywane są dane, nie ma to znaczenia, ale są przypadki, w których to robi, i ważne jest, aby je zidentyfikować.
Semantyka i kontekst mają znaczenie. Jeśli nie uda się wyraźnie uchwycić działania funkcji w jej nazwie, ponownie można zmniejszyć czytelność i zwiększyć kruchość funkcji publicznej. Jest to szczególnie prawdziwe, gdy zaczynasz tworzyć funkcje prywatne z dużą liczbą parametrów wejściowych i wyjściowych. Podczas gdy z funkcji publicznej widzisz, jakie funkcje prywatne wywołuje, z funkcji prywatnej nie widzisz, jakie funkcje publiczne ją nazywają. Może to prowadzić do „naprawy błędów” w funkcji prywatnej, która psuje funkcję publiczną.
Mocno rozłożony kod jest zawsze bardziej przejrzysty dla autora niż dla innych. Nie oznacza to, że nie może to być dla innych jasne, ale łatwo powiedzieć, że ma doskonały sens, że
bar()
należy to sprawdzićfoo()
w momencie pisania.Niebezpieczne ponowne użycie. Twoja funkcja publiczna prawdopodobnie ograniczyła dostęp do każdej funkcji prywatnej. Jeśli te założenia wejściowe nie zostaną poprawnie przechwycone (dokumentowanie WSZYSTKICH założeń jest trudne), ktoś może niewłaściwie użyć jednej z twoich prywatnych funkcji, wprowadzając błędy w bazie kodu.
Rozkładaj funkcje na podstawie ich wewnętrznej spójności i sprzężenia, a nie długości bezwzględnej.
źródło
MainMethod
(dość łatwy do zdobycia, zwykle zawiera symbole i powinieneś mieć plik dereferencji symboli), który ma 2k linii (numery linii nigdy nie są uwzględniane raporty o błędach) i jest to NRE, które może się zdarzyć przy 1k tych wierszy, no cóż, niech mnie diabli, jeśli przyjrzę się temu. Ale jeśli powiedzą mi, że to się wydarzyłoSomeSubMethodA
i ma 8 wierszy, a wyjątkiem był NRE, prawdopodobnie znajdę i naprawię to dość łatwo.IMHO, wartość wyciągania bloków kodu wyłącznie w celu rozbicia złożoności, często wiąże się z różnicą złożoności między:
Pełny i dokładny opis w języku ludzkim tego, co robi kod, w tym sposób , w jaki obsługuje przypadki narożne oraz
Sam kod.
Jeśli kod jest znacznie bardziej skomplikowany niż opis, zastąpienie kodu wbudowanego wywołaniem funkcji może ułatwić zrozumienie otaczającego kodu. Z drugiej strony niektóre pojęcia można wyrazić bardziej czytelnie w języku komputerowym niż w języku ludzkim. Uważałbym
w=x+y+z;
na przykład za bardziej czytelny niżw=addThreeNumbersAssumingSumOfFirstTwoDoesntOverflow(x,y,z);
.W miarę podziału dużej funkcji będzie coraz mniej różnicy między złożonością podfunkcji a ich opisami, a przewaga kolejnych podziałów będzie się zmniejszać. Jeśli rzeczy zostaną podzielone do tego stopnia, że opisy będą bardziej skomplikowane niż kod, dalsze podziały pogorszą kod.
źródło
int average3(int n1, int n2, int n3)
. Rozdzielenie funkcji „średniej” oznaczałoby, że ktoś, kto chciałby sprawdzić, czy jest ona odpowiednia do wymagań, musiałby szukać w dwóch miejscach.Jest to wyzwanie równoważące.
Lepsze jest lepsze
Metoda prywatna skutecznie zapewnia nazwę zawartego kodu, a czasem znaczącą sygnaturę (chyba że połowa jego parametrów to całkowicie dane ad-hoc tramp z niejasnymi, nieudokumentowanymi współzależnościami).
Nadawanie nazw konstrukcjom kodu jest na ogół dobre, pod warunkiem, że nazwy sugerują znaczącą umowę dla osoby wywołującej, a umowa metody prywatnej dokładnie odpowiada temu, co sugeruje nazwa.
Zmuszając się do myślenia o znaczących umowach dotyczących mniejszych części kodu, początkowy programista może wykryć niektóre błędy i uniknąć ich bezboleśnie nawet przed testowaniem przez programistów. Działa to tylko wtedy, gdy programista dąży do zwięzłego nazewnictwa (proste brzmienie, ale dokładne) i jest skłonny dostosować granice metody prywatnej, aby zwięzłe nazewnictwo było nawet możliwe.
Późniejsza konserwacja jest pomogło, także dlatego, że dodatkowy nazewnictwa pomaga uczynić kod siebie dokumentowanie .
Dopóki nie będzie zbyt dobrze
Czy można przesadzić nadawanie nazw małym fragmentom kodu i skończyć na zbyt wielu, zbyt małych prywatnych metodach? Pewnie. Co najmniej jeden z następujących symptomów wskazuje, że metody stają się zbyt małe, aby były przydatne:
XxxInternal
lubDoXxx
, zwłaszcza jeśli nie ma jednolitego schematu ich wprowadzania.LogDiskSpaceConsumptionUnlessNoUpdateNeeded
źródło
W przeciwieństwie do tego, co powiedzieli inni, twierdzę, że długa metoda publiczna to zapach projektowy, którego nie można naprawić przez rozkład na prywatne metody.
Jeśli tak jest, to argumentowałbym, że każdy krok powinien być własnym obywatelem pierwszej klasy, z jedną odpowiedzialnością. W paradygmacie zorientowanym obiektowo sugerowałbym utworzenie interfejsu i implementacji dla każdego kroku w taki sposób, aby każdy z nich miał jedną, łatwą do zidentyfikowania odpowiedzialność i mógł być nazwany w taki sposób, aby odpowiedzialność była jasna. Umożliwia to testowanie jednostkowe (dawniej) dużej metody publicznej, a także każdego pojedynczego kroku niezależnie od siebie. Wszystkie powinny być również udokumentowane.
Dlaczego nie rozłożyć na prywatne metody? Oto kilka powodów:
To jest argument, który miałem ostatnio z jednym z moich kolegów. Twierdzi, że posiadanie całego zachowania modułu w tym samym pliku / metodzie poprawia czytelność. Zgadzam się, że kod jest łatwiejszy do naśladowania, gdy wszystko razem, ale kod jest trudniejszy do uzasadnienia w miarę wzrostu złożoności. W miarę rozwoju systemu staje się coraz trudniejsze rozumowanie całego modułu jako całości. Kiedy rozkładasz złożoną logikę na kilka klas, z których każda ma jedną odpowiedzialność, wtedy znacznie łatwiej jest zrozumieć każdą część.
źródło