Czy dobrą praktyką jest używanie funkcji tylko do centralizacji wspólnego kodu?

20

Często spotykam się z tym problemem. Na przykład obecnie piszę funkcję odczytu i funkcję zapisu, a oni oboje sprawdzają, czy bufjest wskaźnikiem NULL i czy modezmienna mieści się w określonych granicach.

To jest duplikacja kodu. Można to rozwiązać, przenosząc go do własnej funkcji. Ale powinienem? Będzie to dość anemiczna funkcja (niewiele robi), raczej zlokalizowana (więc nie ma ogólnego zastosowania) i sama nie wytrzymuje dobrze (nie może zrozumieć, do czego jest potrzebna, chyba że zobaczysz, gdzie ona jest) używany). Inną opcją jest użycie makra, ale chcę porozmawiać o funkcjach w tym poście.

Więc czy powinieneś użyć funkcji do czegoś takiego? Jakie są zalety i wady?

EpsilonVector
źródło
2
Pojedyncza linia wymaga więcej niż użycia w wielu miejscach, aby zagwarantować przejście do oddzielnej funkcji.
1
Świetne pytanie. Wiele razy zastanawiałem się nad tym samym.
rdasxy

Odpowiedzi:

31

To świetne wykorzystanie funkcji.

To będzie dość anemiczna funkcja (niewiele robi) ...

Dobre. Funkcje powinny robić tylko jedną rzecz.

raczej zlokalizowane ...

W języku OO ustaw go jako prywatny.

(więc nie ogólnego przeznaczenia)

Jeśli obsługuje więcej niż jedną skrzynkę, jest to cel ogólny. Co więcej, uogólnienie to nie jedyne użycie funkcji. Są one rzeczywiście po to, aby (1) oszczędzić ci pisania tego samego kodu więcej niż raz, ale także (2), aby podzielić kod na mniejsze części, aby był bardziej czytelny. W tym przypadku osiąga zarówno (1), jak i (2). Jednak nawet jeśli twoja funkcja była wywoływana tylko z jednego miejsca, może to nadal pomóc w (2).

i nie radzi sobie dobrze (nie jest w stanie zrozumieć, do czego jest potrzebny, chyba że zobaczysz, gdzie jest używany).

Wymyśl dla niej dobre imię, a samo w sobie będzie dobrze. „ValidateFileParameters” lub coś takiego. Teraz dobrze sobie radzi.

Kramii Przywróć Monikę
źródło
6
Cóż, punkty towarowe. Dodałbym (3), abyś nie polował na zduplikowany błąd w całej bazie kodu, gdy możesz naprawić w jednym miejscu. Duplikacja kodu może doprowadzić do piekła konserwacji dość szybko.
deadalnix 17.11.11
2
@deadalnix: Dobra uwaga. Kiedy pisałem, zdałem sobie sprawę, że moje punkty były rażącym uproszczeniem. Pisanie i łatwiejsze debugowanie jest z pewnością zaletą dzielenia rzeczy na funkcje (podobnie jak możliwość jednostkowego testowania oddzielnych funkcji).
Kramii Przywróć Monikę
11

To całkowicie powinno być funkcją.

if (isBufferValid(buffer)) {
    // ...
}

Znacznie bardziej czytelny i łatwiejszy w utrzymaniu (jeśli logika sprawdzania kiedykolwiek się zmienia, zmieniasz ją tylko w jednym miejscu).

Poza tym takie rzeczy łatwo się wprowadzają, więc nawet nie musisz się martwić o koszty ogólne wywołania funkcji.

Pozwól, że zadam ci lepsze pytanie. Jak nie jest to dobra praktyka?

Robić co należy. :)

Yam Marcovic
źródło
4
Jeśli isBufferValid jest po prostu return buffer != null;, myślę, że szkodzi to czytelności.
pdr
5
@pdr: W tym prostym przypadku szkodzi to czytelności tylko wtedy, gdy masz nastawienie maniaka sterowania i naprawdę, naprawdę, NAPRAWDĘ chcesz wiedzieć, jak kod sprawdza, czy bufor jest prawidłowy. Więc jest to subiektywne w tych prostych przypadkach.
Spoike,
4
@pdr Nie wiem, jak to szkodzi czytelności według jakiegokolwiek standardu. Zwalniasz innych programistów od dbania o to, jak coś robisz i skupiania się na tym , co robisz. isBufferValidjest zdecydowanie bardziej czytelny (w mojej książce) niż buffer != null, ponieważ bardziej jasno przekazuje cel. I znowu, nie wspominając o tym, chroni cię również przed powielaniem. Czego więcej potrzebujesz?
Yam Marcovic,
5

IMO, fragmenty kodu warto przenieść do własnych funkcji, ilekroć sprawia to, że kod jest bardziej czytelny , niezależnie od tego, czy funkcja będzie bardzo krótka, czy będzie używana tylko raz.

Są oczywiście granice podyktowane zdrowym rozsądkiem. Na przykład nie chcesz mieć WriteToConsole(text)metody z ciałem po prostu Console.WriteLine(text). Ale błędem w kwestii czytelności jest dobra praktyka.

Konamiman
źródło
2

Zasadniczo dobrym pomysłem jest używanie funkcji do usuwania duplikacji w kodzie.

Jednak może to być brane zbyt daleko. To jest wyrok sądu.

Aby wziąć przykład z zerowego sprawdzania bufora, prawdopodobnie powiedziałbym, że poniższy kod jest wystarczająco jasny i nie powinien być wyodrębniany do oddzielnej funkcji, nawet jeśli ten sam wzorzec jest używany w kilku miejscach.

if (buf==null) throw new BufferException("Null read buffer!");

Jeśli podasz komunikat o błędzie jako parametr do ogólnej funkcji sprawdzania wartości NULL, a także weźmiesz pod uwagę kod wymagany do zdefiniowania funkcji, oznacza to, że nie jest to oszczędność netto LOC, aby zastąpić ją:

checkForNullBuffer(buf, "Null read buffer!");

Ponadto konieczność zanurzenia się w funkcji, aby zobaczyć, co robi podczas debugowania, oznacza, że ​​wywołanie funkcji jest mniej „przezroczyste” dla użytkownika, a zatem może być uważane za mniej czytelne / łatwe do utrzymania.

mikera
źródło
Być może twój przykład mówi więcej o braku wsparcia kontraktowego w języku niż o prawdziwej potrzebie powielania. Ale i tak dobre strony.
deadalnix,
1
W pewnym sensie przegapiłeś punkt w moim rozumieniu. Nie mając do punktu powyżej lub trzeba rozważyć logika każdym razem debug to co ja szukałem. Jeśli chcę wiedzieć, jak to się robi, sprawdzam to raz. Robienie tego za każdym razem jest po prostu głupie.
Yam Marcovic,
Jeśli komunikat o błędzie („Null read buffer”) się powtarza, to powielanie należy zdecydowanie wyeliminować.
kevin cline
Cóż, to tylko przykład, ale przypuszczalnie będziesz miał inny komunikat w każdej witrynie wywoływania funkcji - na przykład „bufor odczytu zerowego” i „bufor zapisu zerowego”. Chyba, że chcesz te same komunikaty dziennika / błędów dla każdej z tych sytuacji nie można de-powielić go .......
mikera
-1 Preconditions.checkNotNull to dobra praktyka, a nie zła. Nie trzeba jednak dołączać komunikatu dotyczącego ciągu. google-collections.googlecode.com/svn/trunk/javadoc/com/google/…
ripper234
2

Centralizacja kodu jest zwykle zawsze dobrym pomysłem. Musimy ponownie wykorzystać kod w jak największym stopniu.

Należy jednak pamiętać, jak to zrobić. Na przykład, jeśli masz kod, który wykonuje compute_prime_number () lub check_if_packet_is_bad (), to dobrze. Są szanse, że algorytm samej funkcjonalności ewoluuje, co przyniesie korzyści.

Jednak żaden fragment kodu, który powtarza się jako proza, nie kwalifikuje się do natychmiastowego scentralizowania. To jest złe. Możesz ukryć dowolne wiersze kodu wewnątrz funkcji, aby ukryć kod, z czasem, gdy zacznie korzystać z wielu części aplikacji, wszystkie muszą pozostać kompatybilne z potrzebami wszystkich wywoływanych funkcji.

Oto kilka pytań, które powinieneś zadać przed zadaniem

  1. Czy funkcja, którą tworzysz, ma swoje nieodłączne znaczenie, czy może to tylko wiązka linii?

  2. Który inny kontekst będzie wymagał użycia tych samych funkcji? Czy jest prawdopodobne, że przed użyciem tego może być konieczne uogólnienie interfejsu API?

  3. Jakich oczekiwań będą dotyczyły (różne części) aplikacje, gdy wprowadzisz wyjątki?

  4. Jakie są scenariusze, aby zobaczyć, że funkcje będą ewoluować?

Powinieneś również sprawdzić, czy istnieją już takie rzeczy. Widziałem tak wielu ludzi, którzy zawsze starają się redefiniować swoje makra MIN, MAX zamiast szukać tego, co już istnieje.

Zasadniczo pytanie brzmi: „Czy ta nowa funkcja naprawdę zasługuje na ponowne użycie, czy to tylko kopia-wklej ?” Jeśli jest pierwszy, dobrze jest iść.

Dipan Mehta
źródło
1
Cóż, jeśli zduplikowany kod nie ma własnego znaczenia, kod mówi ci, że trzeba go zrefaktoryzować. Ponieważ miejsca, w których występuje duplikacja, prawdopodobnie również nie mają własnego znaczenia.
deadalnix
1

Należy unikać powielania kodu. Za każdym razem, gdy się tego spodziewasz, powinieneś unikać duplikacji kodu. Jeśli nie spodziewałeś się tego, zastosuj zasadę 3: refaktoryzuj, zanim ten sam fragment kodu zostanie zduplikowany 3 razy, zanotuj dziwactwo, gdy zostanie zduplikowane 2 razy.

Co to jest duplikat kodu?

  • Procedura powtarzana w kilku miejscach w bazie kodu. Ta wycieczka musi być bardziej złożona niż zwykłe wywołanie funkcji (inaczej, nic nie zyskasz przez faktoryzację).
  • Bardzo częste zadanie, nawet trywialne (często test). Poprawi to enkapsulację i semantyczność w kodzie.

Rozważ przykład poniżej:

if(user.getPrileges().contains("admin")) {
    // Do something
}

staje się

if(user.isAdmin()) {
    // Do something
}

Poprawiłeś enkapsulację (teraz możesz zmienić warunki, aby być administratorem w sposób transparentny) i semantykę kodu. Jeśli błąd zostanie wykryty w sposób, w jaki sprawdzasz, że użytkownik jest administratorem, nie musisz rzucać całą bazą kodu i naprawiać wszędzie (z ryzykiem zapomnienia o jednym i uzyskania luki w zabezpieczeniach aplikacji).

deadalnix
źródło
1
Przykład Btw ilustruje Prawo Demeter.
MAR
1

DRY polega na uproszczeniu manipulacji kodem. Właśnie poruszyłeś kwestię dotyczącą tej zasady: nie chodzi o zminimalizowanie liczby tokenów w kodzie, ale o tworzenie pojedynczych punktów modyfikacji dla semantycznie równoważnego kodu . Wygląda na to, że twoje czeki zawsze mają ten sam semantyczny, więc powinny zostać włączone do funkcji na wypadek, gdybyś musiał je zmodyfikować.

l0b0
źródło
0

Jeśli widzisz, że jest powielony, powinieneś znaleźć sposób na jego scentralizowanie.

Funkcje są dobrym sposobem (może nie najlepszym, ale to zależy od języka). Nawet jeśli funkcja jest anemiczna, jak mówisz, nie oznacza to, że pozostanie taka.

Co jeśli musisz sprawdzić coś jeszcze?

Czy znajdziesz wszystkie miejsca, w których musisz dodać dodatkową kontrolę lub po prostu zmienić funkcję?

Thanos Papathanasiou
źródło
... z drugiej strony, co jeśli istnieje bardzo duże prawdopodobieństwo, że nigdy do tego nie dodasz. Czy twoja sugestia jest nadal najlepszym rozwiązaniem w tym przypadku?
EpsilonVector,
1
@EpsilonVector moją podstawową zasadą jest to, że jeśli muszę to raz zmienić, równie dobrze mogę to zmienić. Tak więc w twoim przypadku zostawiłbym to i gdybym musiał to zmienić, stałoby się to funkcją.
Thanos Papathanasiou,
0

Prawie zawsze dobrze jest spełnić następujące warunki:

  • poprawia czytelność
  • używane w ograniczonym zakresie

W większym zakresie należy dokładnie wyważyć powielanie z kompromisami zależności. Przykłady technik ograniczających zakres: ukrywanie się w sekcjach prywatnych lub modułach bez ujawniania ich publicznie.

Zniszczyć
źródło