Czy istnieje zbyt wiele prywatnych funkcji / metod?

63

Rozumiem znaczenie dobrze udokumentowanego kodu. Ale rozumiem również znaczenie samodokumentowania kodu. Im łatwiej jest wizualnie odczytać określoną funkcję, tym szybciej możemy przejść podczas konserwacji oprogramowania.

Powiedziawszy to, lubię rozdzielać duże funkcje na inne mniejsze. Ale robię to do tego stopnia, że ​​klasa może mieć w górę pięć z nich tylko po to, aby obsługiwać jedną metodę publiczną. Teraz pomnóż pięć prywatnych metod przez pięć publicznych, a otrzymasz około dwudziestu pięciu ukrytych metod, które prawdopodobnie zostaną wywołane tylko raz przez te publiczne.

Jasne, teraz łatwiej jest czytać te publiczne metody, ale nie mogę przestać myśleć, że posiadanie zbyt wielu funkcji to zła praktyka.

[Edytować]

Ludzie pytają mnie, dlaczego uważam, że zbyt wiele funkcji to zła praktyka.

Prosta odpowiedź: to przeczucie.

Moje przekonanie nie jest poparte choćby godzinami doświadczenia w inżynierii oprogramowania. To tylko niepewność, która dała mi „blok pisarza”, ale dla programisty.

W przeszłości programowałem tylko projekty osobiste. Niedawno przeszedłem na projekty zespołowe. Teraz chcę się upewnić, że inni mogą czytać i rozumieć mój kod.

Nie byłem pewien, co poprawi czytelność. Z jednej strony myślałem o podzieleniu jednej dużej funkcji na inne mniejsze o zrozumiałych nazwach. Ale była też inna strona, która powiedziała, że ​​to po prostu zbędne.

Tak więc proszę o oświecenie się, aby wybrać właściwą ścieżkę.

[Edytować]

Poniżej mam włączone dwie wersje jak ja mógłby rozwiązać mój problem. Pierwszy rozwiązuje to, nie rozdzielając dużych fragmentów kodu. Drugi robi osobne rzeczy.

Pierwsza wersja:

public static int Main()
{
    // Displays the menu.
    Console.WriteLine("Pick your option");
    Console.Writeline("[1] Input and display a polynomial");
    Console.WriteLine("[2] Add two polynomials");
    Console.WriteLine("[3] Subtract two polynomials");
    Console.WriteLine("[4] Differentiate two polynomials");
    Console.WriteLine("[0] Quit");
}

Druga wersja:

public static int Main()
{
    DisplayMenu();
}

private static void DisplayMenu()
{
    Console.WriteLine("Pick your option");
    Console.Writeline("[1] Input and display a polynomial");
    Console.WriteLine("[2] Add two polynomials");
    Console.WriteLine("[3] Subtract two polynomials");
    Console.WriteLine("[4] Differentiate two polynomials");
    Console.WriteLine("[0] Quit");
}

W powyższych przykładach ta ostatnia wywołuje funkcję, która będzie używana tylko raz przez cały czas działania programu.

Uwaga: powyższy kod jest uogólniony, ale ma ten sam charakter co mój problem.

Oto moje pytanie: które? Czy wybieram pierwszy, czy drugi?

Sal
źródło
1
„zbyt wiele funkcji to zła praktyka”. Dlaczego? Zaktualizuj swoje pytanie, aby wyjaśnić, dlaczego wydaje ci się to złe.
S.Lott,
Polecam lekturę pojęcia „spójności klas” - Bob Martin omawia to w „Czystym kodzie”.
JᴀʏMᴇᴇ
Dodając do innych odpowiedzi, podziału i nazwy, pamiętajmy jednak, że wraz z rozwojem klasy zmienne jej członków stają się coraz bardziej podobne do zmiennych globalnych. Jednym ze sposobów na rozwiązanie tego problemu (oprócz lepszych rozwiązań, takich jak refaktoryzacja ;-)) jest podzielenie tych małych prywatnych metod na samodzielne funkcje, jeśli jest to możliwe (w tym sensie, że nie potrzebują one dostępu do wielu stanów). Niektóre języki pozwalają na funkcje poza klasą, inne mają klasy statyczne. W ten sposób można zrozumieć tę funkcję w izolacji.
Pablo H
Jeśli ktoś mógłby mi powiedzieć, dlaczego „ta odpowiedź nie jest przydatna”, byłbym wdzięczny. Zawsze można się nauczyć. :-)
Pablo H
@PabloH Udzielona odpowiedź daje dobrą obserwację i wgląd OP, jednak tak naprawdę nie odpowiada na zadane pytanie. Przeniosłem go do pola komentarzy.
wałek klonowy

Odpowiedzi:

36

Teraz pomnóż pięć prywatnych metod przez pięć publicznych, a otrzymasz około dwudziestu pięciu ukrytych metod, które prawdopodobnie zostaną wywołane tylko raz przez te publiczne.

Jest to tak zwane kapsułkowanie , które tworzy abstrakcję sterowania na wyższym poziomie. To coś dobrego.

Oznacza to, że ktoś czyta kod, gdy dotrą do startTheEngine()metody w kodzie, może ignorować wszystkie niższe szczegóły poziomu, takich jak openIgnitionModule(), turnDistributorMotor(), sendSparksToSparkPlugs(), injectFuelIntoCylinders(), activateStarterSolenoid(), i wszystkich innych skomplikowanych, małych, funkcji, które muszą być prowadzone w celu ułatwić znacznie większą, bardziej abstrakcyjną funkcję startTheEngine().

O ile problem, z którym masz do czynienia w kodzie, nie dotyczy bezpośrednio jednego z tych komponentów, opiekunowie kodu mogą przejść dalej, ignorując piaskownicową, zamkniętą funkcjonalność.

Ma to również tę dodatkową zaletę, że ułatwia testowanie kodu. . Na przykład mogę napisać przypadek testowy turnAlternatorMotor(int revolutionRate)i przetestować jego funkcjonalność całkowicie niezależnie od innych systemów. Jeśli występuje problem z tą funkcją, a wynik nie jest tym, czego oczekuję, to wiem, na czym polega problem.

Kod, który nie jest podzielony na komponenty, jest znacznie trudniejszy do przetestowania. Nagle twoi opiekunowie patrzą tylko na abstrakcję, zamiast być w stanie zagłębić się w mierzalne komponenty.

Radzę robić to, co robisz, ponieważ Twój kod będzie skalowany, łatwy w utrzymaniu i może być używany i aktualizowany przez wiele lat.

jmort253
źródło
3
Czy wierzysz, że dobrym „enkapsulacją” jest udostępnienie logiki całej klasie, która jest wywoływana tylko w jednym miejscu?
Steven Jeuris
9
@Steven Jeuris: Tak długo, jak te funkcje są prywatne, nie widzę z tym problemu; w rzeczywistości uważam, że, jak stwierdzono w tej odpowiedzi, łatwiej jest je przeczytać. Znacznie łatwiej jest zrozumieć funkcję publiczną wysokiego poziomu, która wywołuje tylko szereg funkcji prywatnych niskiego poziomu (oczywiście odpowiednio nazwanych). Jasne, cały ten kod mógł zostać umieszczony w samej funkcji wysokiego poziomu, ale wtedy zrozumienie kodu zajęłoby więcej czasu, ponieważ trzeba było przejrzeć znacznie więcej kodu w tym samym miejscu.
gablin
2
@gablin: funkcje prywatne są nadal dostępne z całej klasy. W tym (pozornie kontrowersyjnym) artykule omawiam, w jaki sposób utrata „globalnej czytelności” przy zbyt dużej liczbie funkcji. Właściwie powszechną zasadą poza tym, że funkcje powinny robić tylko jedno, jest to, że nie powinieneś mieć ich zbyt wiele. Zyskujesz tylko „lokalną czytelność”, dzieląc tylko na krótkie, co można również osiągnąć za pomocą prostych komentarzy i „akapitów kodu”. Ok, kod powinien być samodokumentujący, ale komentarze nie są przestarzałe!
Steven Jeuris
1
@Steven - Co łatwiej zrozumieć? myString.replaceAll(pattern, originalData);czy ja wklejam całą metodę replaceAll w moim kodzie, włączając w to tablicę char, która reprezentuje podstawowy obiekt ciągu za każdym razem, gdy potrzebuję korzystać z funkcjonalności ciągu?
jmort253
7
@Steven Jeuris: Masz rację. Jeśli klasa ma zbyt wiele funkcji (publicznych lub prywatnych), być może cała klasa próbuje zrobić zbyt wiele. W takich przypadkach może być lepiej podzielić go na kilka mniejszych klas, tak jak podzieliłbyś zbyt dużą funkcję na kilka mniejszych funkcji.
gablin
22

Tak i nie. Podstawową zasadą jest: metoda powinna robić jedną rzecz i tylko jedną rzecz

Poprawne jest „rozbicie” metody na mniejsze. Z drugiej strony metody te powinny optymalnie być na tyle ogólne, aby nie tylko służyły tej „dużej” metodzie, ale także by mogły być używane w innych miejscach. W niektórych przypadkach metoda będzie postępować zgodnie z prostą strukturą drzewa, na przykład metoda Initialize, która wywołuje InitializePlugins, InitializeInterface itp.

Jeśli masz naprawdę dużą metodę / klasę, jest to zwykle znak, że robi ona zbyt wiele i musisz dokonać refaktoryzacji, aby rozbić „kroplę”. Perphaps ukrywa pewną złożoność w innej klasie pod abstrakcją i stosuje wstrzykiwanie zależności

Homde
źródło
1
Fajnie jest przeczytać, że nie wszyscy ślepo przestrzegają zasady „jedna rzecz”! ; p Ładna odpowiedź!
Steven Jeuris
16
Często dzielę dużą metodę na mniejsze, mimo że będą one służyć tylko większej metodzie. Powodem podziału jest to, że staje się łatwiejszy w obsłudze i zrozumieniu, zamiast mieć tylko jedną wielką kroplę kodu (która może, ale nie musi, być dobrze skomentowana).
gablin
1
Jest to również w porządku w niektórych przypadkach, w których naprawdę nie można uniknąć dużej metody. Na przykład, jeśli masz metodę Initialize, możesz wywołać ją InitializeSystems, InitializePlugins itp. Zawsze powinieneś ciągle sprawdzać, czy refaktoryzacja nie jest potrzebna
Homde
1
Kluczową rzeczą w każdej metodzie (lub funkcji) jest to, że musi mieć przejrzysty interfejs koncepcyjny, to „kontrakt”. Jeśli nie możesz tego sprecyzować, to będą kłopoty, a faktoring się źle. (Wyraźne udokumentowanie umowy może być dobrą rzeczą - lub użycie narzędzia do jej egzekwowania, w stylu Eiffla - ale nie jest tak ważne jak posiadanie jej w pierwszej kolejności.)
Donal Fellows
3
@gablin: kolejna korzyść do rozważenia: kiedy złamię rzeczy na mniejsze funkcje, nie sądzę „podają tylko jedną inną funkcję” Think „podają tylko jedną inną funkcję na teraz ”. Było kilka okazji, w których przefakturowanie dużych metod pozwoliło mi zaoszczędzić mnóstwo czasu podczas pisania innych metod w przyszłości, ponieważ mniejsze funkcje były bardziej ogólne i można było z nich korzystać wielokrotnie.
GSto
17

Myślę, że ogólnie lepiej jest pomylić się ze zbyt wieloma funkcjami niż za mało. Te dwa wyjątki od tej reguły widziałem w praktyce są dla DRY i YAGNI zasad. Jeśli masz wiele prawie identycznych funkcji, powinieneś je połączyć i użyć parametrów, aby uniknąć powtarzania się. Możesz także mieć zbyt wiele funkcji, jeśli zostały utworzone „na wszelki wypadek” i nie są używane. Nie widzę absolutnie nic złego w posiadaniu funkcji, która jest używana tylko raz, jeśli zwiększa czytelność i łatwość konserwacji.

Karl Bielefeldt
źródło
10

Świetna odpowiedź jmort253 zainspirowała mnie do odpowiedzi „tak, ale” odpowiedź…

Posiadanie wielu małych prywatnych metod nie jest złą rzeczą, ale zwróć uwagę na to dręczące uczucie, które sprawia, że ​​zadajesz tutaj pytanie :). Jeśli dojdziesz do momentu, w którym piszesz testy, które koncentrują się tylko na metodach prywatnych (albo przez wywołanie ich bezpośrednio, albo przez skonfigurowanie takiego scenariusza, aby wywołać go w określony sposób, abyś mógł przetestować wynik) powinieneś cofnąć się o krok.

To, co możesz mieć, to nowa klasa z własnym, bardziej skoncentrowanym interfejsem publicznym, próbującym uciec. W tym momencie możesz pomyśleć o wyodrębnieniu klasy, aby zamknąć tę część istniejącej funkcjonalności klas, która obecnie żyje metodą prywatną. Teraz twoja oryginalna klasa może wykorzystywać twoją nową klasę jako zależność i możesz pisać bardziej ukierunkowane testy bezpośrednio dla tej klasy.

Pete Hodgson
źródło
Myślę, że to świetna odpowiedź i kierunek, w którym kod z pewnością mógłby pójść. Najlepsze praktyki mówią o używaniu najbardziej ograniczającego modyfikatora dostępu, który zapewnia potrzebną funkcjonalność. Jeśli metody nie są używane poza klasą, najbardziej sensowne jest prywatne. Jeśli bardziej sensowne jest upublicznienie metod lub przeniesienie ich do innej klasy, aby można było ich użyć gdzie indziej, to za wszelką cenę można to również zrobić. +1
jmort253
8

Niemal każdy projekt OO, do którego dołączyłem, bardzo cierpiał z powodu zbyt dużych klas i zbyt długich metod.

Kiedy odczuwam potrzebę komentarza wyjaśniającego następną sekcję kodu, wyodrębniam tę sekcję do osobnej metody. W dłuższej perspektywie powoduje to, że kod jest krótszy. Te małe metody są ponownie wykorzystywane częściej, niż początkowo można oczekiwać. Zaczynasz widzieć możliwości zebrania kilku z nich w osobną klasę. Wkrótce myślisz o swoim problemie na wyższym poziomie, a cały wysiłek idzie znacznie szybciej. Nowe funkcje są łatwiejsze do napisania, ponieważ można budować na tych małych klasach utworzonych z tych małych metod.

Kevin Cline
źródło
7

Klasa z 5 metodami publicznymi i 25 metodami prywatnymi nie wydaje mi się taka duża. Upewnij się tylko, że twoje zajęcia mają jasno określone obowiązki i nie przejmuj się zbytnio liczbą metod.

To powiedziawszy, te prywatne metody powinny skupiać się na jednym konkretnym aspekcie całego zadania, ale nie w sposób „doSomethingPart1”, „doSomethingPart2”. Nic nie zyskasz, jeśli te prywatne metody są tylko fragmentami arbitralnie podzielonej długiej historii, z których każda jest bez znaczenia poza całym kontekstem.

użytkownik 281377
źródło
+1 za „Nic nie zyskujesz, jeśli te prywatne metody są tylko fragmentami arbitralnie podzielonej długiej historii, z których każda jest bez znaczenia poza całym kontekstem”.
Mahdi
4

Fragment Clean Code R. Martina (s. 176):

Nawet pojęcia tak fundamentalne jak eliminacja powielania, ekspresja kodu i SRP mogą być posunięte zbyt daleko. Aby uczynić nasze klasy i metody małymi, możemy stworzyć zbyt wiele małych klas i metod. Zatem ta reguła [zminimalizuj liczbę klas i metod] sugeruje, że utrzymujemy również naszą funkcję i liczbę klas na niskim poziomie. Wysoka liczba klas i metod jest czasem wynikiem bezsensownego dogmatyzmu.

użytkownik2418306
źródło
3

Niektóre opcje:

  1. W porządku. Po prostu nazwij metody prywatne, a także nazwy metod publicznych. I dobrze nazwij swoje metody publiczne.

  2. Wyodrębnij niektóre z tych metod pomocniczych do klas pomocniczych lub modułów pomocniczych. I upewnij się, że te klasy / moduły pomocnicze są dobrze nazwane i stanowią solidne abstrakcje same w sobie.

yfeldblum
źródło
1

Nie sądzę, aby kiedykolwiek pojawiał się problem „zbyt wielu prywatnych metod”, jeśli wydaje się, że jest to prawdopodobnie objaw złej architektury kodu.

Oto, jak o tym myślę ... Jeśli architektura jest dobrze zaprojektowana, ogólnie oczywiste jest, gdzie powinien być kod X. Dopuszczalne jest kilka wyjątków od tej reguły - ale muszą one być naprawdę wyjątkowe, w przeciwnym razie zreorganizuj architekturę, aby obsługiwać je jako standard.

Jeśli problemem jest to, że po otwarciu klasy jest ona pełna prywatnych metod, które dzielą większe fragmenty na mniejsze fragmenty kodu, być może jest to objaw braku architektury. Zamiast klas i architektury ten obszar kodu został zaimplementowany w sposób proceduralny w ramach jednej klasy.

Znalezienie równowagi tutaj zależy od projektu i jego wymagań. Przeciwnym argumentem tego jest powiedzenie, że młodszy programista może podważyć rozwiązanie w 50 liniach kodu, który zajmuje architektowi 50 klas.

Michael Shaw
źródło
0

Czy istnieje zbyt wiele prywatnych funkcji / metod?

Tak.

W Pythonie pojęcie „prywatny” (używane przez C ++, Java i C #) tak naprawdę nie istnieje.

Istnieje konwencja nazewnictwa „w pewnym sensie prywatna”, ale to wszystko.

Prywatny może prowadzić do trudnego do przetestowania kodu. Może również złamać „zasadę otwartego i zamkniętego”, czyniąc kod po prostu zamkniętym.

W związku z tym dla osób, które korzystały z Pythona, funkcje prywatne nie mają żadnej wartości. Po prostu upublicznij wszystko i gotowe.

S.Lott
źródło
1
Jak może złamać zasadę otwartego zamkniętego? Kod jest zarówno otwarty dla rozszerzenia, jak i zamknięty dla modyfikacji, niezależnie od tego, czy metody publiczne zostały podzielone na mniejsze prywatne.
byxor