Czy długość funkcji wpływa na produktywność programisty? Jeśli tak, jaka jest dobra maksymalna liczba linii, aby uniknąć utraty wydajności?
Ponieważ jest to wysoce opiniotwórczy temat, prosimy o uzupełnienie roszczenia niektórymi danymi.
Czy długość funkcji wpływa na produktywność programisty? Jeśli tak, jaka jest dobra maksymalna liczba linii, aby uniknąć utraty wydajności?
Ponieważ jest to wysoce opiniotwórczy temat, prosimy o uzupełnienie roszczenia niektórymi danymi.
Odpowiedzi:
Odkąd zacząłem szaloną rakietę w 1970 roku, widziałem dokładnie jeden moduł, który naprawdę musiał być więcej niż jedną wydrukowaną stroną (około 60 linii). Widziałem wiele modułów, które były dłuższe.
W tym przypadku napisałem moduły, które były dłuższe, ale zwykle były to duże maszyny stanów skończonych napisane jako duże instrukcje przełączające.
Problem polega na tym, że w dzisiejszych czasach programiści nie uczą się modularyzacji.
Problemem są również standardy kodowania, które maksymalizują marnotrawstwo przestrzeni pionowej. (Nie spotkałem jeszcze menedżera oprogramowania, który przeczytał „ Psychologię programowania komputerowego ” Geralda Weinberga . Weinberg zauważa, że wiele badań wykazało, że rozumienie programisty jest zasadniczo ograniczone do tego, co programista może zobaczyć w danym momencie. programista musi przewijać lub przewracać stronę, ich rozumienie znacznie spada: muszą pamiętać i streszczać.)
Jestem przekonany, że wiele dobrze udokumentowanych wzrostów wydajności programistów z FORTH wynikało z systemu blokowego kodu źródłowego FORTH: moduły były ograniczone do absolutnego maksimum 16 wierszy o długości 64 znaków. Możesz brać pod uwagę czynniki w nieskończoność, ale w żadnym wypadku nie możesz napisać 17-liniowej procedury.
Zależy od używanego języka, ale ogólnie (i według mojego osobistego gustu):
Jeśli to więcej, to muszę do tego wrócić i przerobić.
Ale realistycznie , każdy rozmiar musi być, gdy trzeba coś dostarczyć, i że bardziej sensowne jest w tej chwili wyplenienie ich w ten sposób, sprawia, że czasami jeszcze łatwiej jest komuś sprawdzić przed wysyłką. (ale nadal wracam do tego później).
(Niedawno mój zespół uruchomił program w naszej bazie kodu: znaleźliśmy klasę z 197 metodami, a drugą tylko z 3 metodami, ale jedna z nich miała 600 linii. Urocza gra: co jest gorsze z 2 zła?)
Teraz, aby uzyskać bardziej zenową odpowiedź ... Ogólnie rzecz biorąc, dobrą praktyką (TM) jest cytowanie jednego lub dwóch wielkich ludzi, więc oto:
Wszystko powinno być tak proste, jak to możliwe, ale nie prostsze. - A. Einstein
Doskonałość zostaje ostatecznie osiągnięta nie wtedy, gdy nie ma już nic do dodania, ale kiedy nie ma już nic do zabrania. - A. de Saint Exupéry
Jako uzupełnienie tego, twoje funkcje powinny mieć jasne nazwy wyjaśniające ich zamiary. Jeśli chodzi o komentarze, zwykle nie komentuję wewnątrz funkcji:
Wystarczy blok komentarza u góry każdej funkcji (który wymaga wyjaśnienia). Jeśli twoja funkcja jest mała, a nazwy funkcji są wystarczająco wyraźne, powinieneś po prostu powiedzieć, co chcesz osiągnąć i dlaczego. Używam wbudowanych komentarzy tylko dla pól w niektórych językach lub na początku bloku dla funkcji, które łamią reguły 25-35 linii, jeśli zamiar jest niejasny. Używam komentarza blokowego wewnątrz kodu, gdy występują wyjątkowe sytuacje (blok przechwytujący, w którym nie potrzebujesz lub nie chcesz nic robić, powinien mieć komentarz wyjaśniający, na przykład, dlaczego).
Aby uzyskać więcej informacji, przeczytaj moją odpowiedź na temat stylu i zalecenia dotyczące komentowania kodu
tt
do ich generowania, ale czasami utkniesz w funkcji długiego tyłka (lub funkcji długiego tyłka), która tak naprawdę nie robi nic interesującego, więc nie jest to prawdziwy problem.
Map(x => x.Property1); Map(x => x.Property2); Map(x => x.Property3);
. Jest całkiem jasne, że wszystko jest takie samo. (Uwaga: to tylko przykład; tego rodzaju funkcja pojawia się od czasu do czasu)
Moim zdaniem każda funkcja powinna być jak najmniejsza. Każda funkcja powinna robić tylko jedną rzecz i robić to dobrze. To tak naprawdę nie odpowiada na pytanie o maksymalną długość, ale bardziej odczuwam długość funkcji.
Aby użyć słów wuja Boba: „Wyodrębniaj, dopóki nie będziesz mógł już więcej wyodrębnić. Wyodrębniaj, aż upuścisz”.
Jaka powinna być maksymalna wysokość budynku? Zależy od tego, gdzie jest kompilacja lub jaka ma być wysokość.
Możesz uzyskać różne odpowiedzi od różnych osób pochodzących z innego miasta.
Niektóre funkcje skryptów i procedury obsługi przerwań jądra są bardzo długie.
Metodą, która działa dla mnie jest: czy mogę mieć część dłuższej funkcji, nadać nazwę, która ma sens. Myślę, że długość metody nie jest tak ważna jak dobre nazewnictwo. Metoda powinna robić to, co mówi nazwa, nie więcej i nie mniej. I powinieneś być w stanie podać dobre imię. Jeśli nie możesz nazwać swojej metody dobrą, kod prawdopodobnie nie jest dobry razem.
Tak długo, jak trzeba to robić, co trzeba, ale już nie.
Myślę, że jest kompromis. Jeśli masz wiele krótkich metod, często trudniej jest je debugować niż jedną długą. Jeśli będziesz musiał skakać po edytorze 20 lub 30 razy, aby prześledzić jedno wywołanie metody, trudno będzie zachować to wszystko w głowie. Tymczasem, jeśli istnieje jedna dobrze napisana jasna metoda, nawet jeśli jest to 100 linii, często łatwiej jest zachować ją w głowie.
Prawdziwe pytanie brzmi: dlaczego elementy powinny być w różnych metodach, a odpowiedź, jak podano powyżej, to ponowne użycie kodu. Jeśli nie używasz ponownie kodu (lub nie wiesz), warto pozostawić go w jednej gigantycznej, łatwej do naśladowania metodzie, a następnie, gdy trzeba go ponownie użyć, podziel części, które wymagają ponownego przy użyciu mniejszych metod.
W rzeczywistości częścią dobrego projektowania metod jest tworzenie funkcjonalnie spójnych metod (w zasadzie robią jedną rzecz). Długość metod nie ma znaczenia. Jeśli funkcja wykonuje jedną dobrze zdefiniowaną rzecz i ma 1000 wierszy, jest to dobra metoda. Jeśli funkcja wykonuje 3 lub 4 rzeczy i ma tylko 15 linii, jest to zła metoda ...
Łatwiej jest mi śledzić, co robię, jeśli mogę zobaczyć całą funkcję naraz. Oto jak wolę pisać funkcje:
Rzadko piszę funkcje dłuższe niż to. Większość z nich to gigantyczne instrukcje przełączników C / C ++.
Pytanie powinno brzmieć, ile funkcji powinna wykonać funkcja. I zwykle zdarza się, że potrzebujesz 100 linii, aby zrobić „jedną” rzecz. Znowu zależy to od poziomu, z którego patrzysz na kod: czy hashowanie to jedno? A może mieszanie i zapisywanie hasła to jedno?
Powiedziałbym, że zacznij od zapisania hasła jako jednej funkcji. Kiedy wydaje Ci się, że skrót jest inny, zmienisz kod. W żadnym wypadku nie jestem ekspertem od programowania, ale IMHO, cała idea funkcji zaczyna się od małych, że im bardziej atomowe są twoje funkcje, tym większa szansa na ponowne użycie kodu, nigdy nie zmuszając się do zmiany w więcej niż jednym miejscu itd.
Widziałem procedury składowane SQL , które działają ponad 1000 linii. Czy liczba wierszy procedur przechowywanych również jest mniejsza niż 50? Nie wiem, ale to sprawia, że czytanie kodu jest piekłem. Nie tylko musisz ciągle przewijać w górę i w dół, musisz nadać kilku wierszom kodu nazwę „to robi sprawdzanie poprawności1”, „to aktualizacje w bazie danych” itp. - praca, którą programista powinien był wykonać.
Ze złożoności cyklicznej (Wikipedia):
Cyklomatyczna złożoność sekcji kodu źródłowego to liczba liniowo niezależnych ścieżek w kodzie źródłowym.
Zalecam, aby utrzymać tę liczbę poniżej 10 w jednej metodzie. Jeśli dojdzie do 10, nadszedł czas na ponowne uwzględnienie.
Istnieją narzędzia, które mogą ocenić kod i dać cykliczną liczbę złożoności.
Powinieneś starać się zintegrować te narzędzia ze swoim rurociągiem kompilacji.
Nie dosłownie ścigaj rozmiaru metody, ale spróbuj spojrzeć na jej złożoność i obowiązki. Jeśli ma więcej niż jedną odpowiedzialność, prawdopodobnie dobrym pomysłem jest ponowne uwzględnienie. Jeśli jego cykliczna złożoność wzrasta, prawdopodobnie nadszedł czas na ponowne uwzględnienie.
Jestem całkiem pewien, że istnieją inne narzędzia, które dają podobne opinie, ale nie miałem jeszcze okazji się temu przyjrzeć.
Zazwyczaj staram się zachować moje metody / funkcje zgodnie z tym, co mieści się na ekranie monitora 1680x1050. Jeśli to nie pasuje, użyj metod / funkcji pomocniczych, aby wykonać zadanie.
Pomaga czytelność na ekranie i papierze.
Nie stawiam na nic sztywnego ograniczenia, ponieważ niektóre funkcje implementują algorytmy, które są z natury złożone, a każda próba skrócenia ich spowodowałaby, że interakcje między nowymi, krótszymi funkcjami byłyby tak skomplikowane, że wynik netto nie byłby redukcją prostoty. Nie wierzę również, że idea, że funkcja powinna wykonywać tylko „jedną rzecz”, jest dobrym przewodnikiem, ponieważ „jedna rzecz” na wysokim poziomie abstrakcji może być „wieloma rzeczami” na niższym poziomie.
Dla mnie funkcja jest zdecydowanie za długa, jeśli jej długość powoduje obecnie subtelne naruszenia OSUSZANIA, a wyodrębnienie części funkcji do nowej funkcji lub klasy mogłoby to rozwiązać. Funkcja może być zbyt długa, jeśli tak nie jest, ale można łatwo wyodrębnić funkcję lub klasę, które uczynią kod bardziej modułowym w sposób, który może być przydatny w obliczu przewidywalnej zmiany w przyszłości.
Wystarczająco krótki, aby można go było poprawnie zoptymalizować
Metody powinny być tak krótkie, aby zrobić dokładnie jedną rzecz. Powód tego jest prosty: aby Twój kod mógł zostać odpowiednio zoptymalizowany.
W języku JIT, takim jak Java lub C #, ważne jest, aby metody były proste, aby kompilator JIT mógł szybko tworzyć kod. Dłuższe, bardziej skomplikowane metody naturalnie wymagają więcej czasu JIT. Ponadto kompilatory JIT oferują tylko kilka optymalizacji i korzystają z tego tylko najprostsze metody. Fakt ten został nawet przywołany w Effective C # Billa Wagnera .
W języku niższego poziomu, takim jak C lub C ++, posiadanie krótkich metod (może kilkunastu wierszy) jest również ważne, ponieważ w ten sposób minimalizujesz potrzebę przechowywania zmiennych lokalnych w pamięci RAM, a nie w rejestrze. (Aka „Register Spilling”.) Należy jednak pamiętać, że w tym niezarządzanym przypadku względny koszt każdego wywołania funkcji może być dość wysoki.
Nawet w dynamicznym języku, takim jak Ruby lub Python, posiadanie krótkich metod pomaga również w optymalizacji kompilatora. W języku dynamicznym im bardziej „dynamiczna” jest funkcja, tym trudniej ją zoptymalizować. Na przykład długa metoda, która przyjmuje X i może zwrócić Int, Float lub String, będzie prawdopodobnie działać znacznie wolniej niż trzy osobne metody, z których każda zwraca tylko jeden typ. Wynika to z faktu, że jeśli kompilator wie dokładnie, jaki typ funkcja zwróci, może również zoptymalizować witrynę wywołań funkcji. (Np. Nie sprawdzanie konwersji typu).
To bardzo zależy od tego, co jest w kodzie.
Widziałem rutynową linię, z którą nie miałem problemu. To była wielka instrukcja przełączania, żadna opcja nie przekraczała kilkunastu linii, a jedyną strukturą kontrolną w każdej opcji była pojedyncza pętla. W dzisiejszych czasach byłoby napisane obiektami, ale wtedy nie było takiej opcji.
Patrzę też na 120 linii w przełączniku przede mną. Żadna skrzynka nie przekracza 3 linii - strażnika, zadania i przerwy. To parsowanie pliku tekstowego, obiekty nie są możliwe. Każda alternatywa byłaby trudniejsza do odczytania.
Większość kompilatorów nie ma nic przeciwko długości funkcji. Funkcja powinna być funkcjonalna, ale jednocześnie łatwa do zrozumienia, zmiany i ponownego wykorzystania przez ludzi. Wybierz długość, która najbardziej Ci odpowiada.
Moją ogólną zasadą jest, że funkcja powinna pasować do ekranu. Znalazłem tylko trzy przypadki, które naruszają to:
1) Funkcje wysyłki. W dawnych czasach były one powszechne, ale większość z nich jest obecnie zastępowana dziedziczeniem obiektów. Obiekty działają jednak tylko w twoim programie, dlatego od czasu do czasu zobaczysz sporadyczne funkcje wysyłania w przypadku danych przychodzących z innych źródeł.
2) Funkcje, które wykonują całą masę kroków, aby osiągnąć cel i gdzie kroki nie mają dobrego podziału. W efekcie powstaje funkcja, która po prostu wywołuje długą listę innych funkcji w kolejności.
3) Jak nr 2, ale gdzie poszczególne kroki są tak małe, że są po prostu wstawiane, a nie wywoływane osobno.
Może długość funkcji nie jest tak dobrym miernikiem. Staramy się wykorzystywać złożoność cykliczną także w metodach, a jedna z przyszłych metod kontroli źródła kontroli regułuje, że złożoność cykliczna w klasach i metodach musi być mniejsza niż X.
W przypadku metod X jest ustawione na 30, a to dość ciasne.