Co lepsze - IF-RETURN czy IF obejmujący kod całej funkcji?

Piszę programy w różnych językach (JS, VBA, Perl) i regularnie powraca pytanie - wstawić na początku funkcji, krótkie

sub funkcja{
    return if a != b;
    kod funkcji
}

czy może lepiej włączyć cały kod funkcji do instrukcji warunkowej:

sub funkcja{
    if (a == b){
        kod funkcji
    }
}

Interesuje mnie przede wszystkim jak to wygląda z punktu widzenia wydajności kodu, choć uwagi na temat doświadczeń w pracy z rozbudową dużych programów i takimi konstrukcjami też są mile widziane.

2 lata, 4 miesiące temu | edytowane przez: szaman 141111442

  • Wydajność tutaj nie ma znaczenia, będzie taka sama. Natomiast pierwsza konstrukcja ma kilka zalet:

    • jest czytelniejsza już na pierwszy rzut oka
    • zmniejsza liczbę zagnieżdżeń / rozgałęzień, czy mówiąc naukowo złożoność cyklomatyczną kodu
    • dobrze komunikuje intencję programisty - od razu na początku funkcji widzimy warunki brzegowe które definiują przecież kontrakt funkcji wraz z jej sygnaturą

  • @pawelkor - w komentarzu do wypowiedzi jest za mało miejsca, stąd oddzielna odpowiedź.

    Nie mogę się zgodzić co do tezy, że nie należy wyskakiwać z funkcji w jej środku. Wprost przeciwnie, przynajmniej w nowoczesnych językach programowania. To jest doskonały przykład sytuacji, w której powtarzamy i kultywujemy pewne zasady mimo że już dawno zapomnieliśmy, dlaczego były one istotne. Zanim więc podam powody, dla których return można wsadzać gdzie się chce, napiszę skąd wzięło się przykazanie, że funkcja powinna mieć jeden punkt wyjścia i dlaczego w nowoczesnych językach jest ono już nieaktualne.

    Głównym powodem, dla którego taka reguła się pojawiła, było zarządzanie pamięcią, uchwytami plików, ogólnie - cennymi zasobami, które trzeba zwalniać gdy się przestaje z nich korzystać. Mając kilka punktów wyjścia z funkcji bardzo łatwo było zrobić błąd i czegoś nie zwolnić. Mając jeden punkt wyjścia, gdy taki błąd wystąpił, wystarczyło sprawdzić w tym jednym miejscu - bez analizowania całej logiki powyżej. Nawet w tym przypadku jednak debugowanie tego typu problemów było bardzo trudne, stąd się pojawiło przykazanie - nie będziesz miał punktów wyjścia, poza jednym.

    Dzisiaj, gdy dysponujemy językami automatycznie zarządzającymi pamięcią, wycieku pamięci tak nie uzyskamy (choć są inne na to metody, hehe) - po prostu gdy lokalna referencja wewnątrz funkcji przestanie być aktualna, garbage collector zwolni alokowaną pamięć. Zatem jeden argument automatycznie spada z listy.

    Poza tym mamy dwie fajne konstrukcje - w C# to jest try...finally oraz using które pozwalają na zarządzanie kosztownymi zasobami w postaci zgrabnych bloków. Zwolnienie takiego zasobu - uchwytu do pliku, połączenia z bazą danych, itd. - następuje automatycznie po wystąpieniu wyjątku (finally) lub przy wyjściu z bloku (using) - nawet w jego środku. Zatem o to też już nie musimy się martwić!

    To wszystko pozwala spłaszczać funkcje. Jeżeli musimy zrobić rzecz A lub B w zależności od jakiegoś warunku - sprawdźmy warunek, jeśli jest spełniony zróbmy rzecz A i zwróćmy wynik. Koniec. Upraszczamy logikę. Jeżeli jakieś miejsce w kodzie jest finalne dla pewnej ścieżki przetwarzania, to niech będzie kategorycznie finalnym, przy użyciu return. W ten sposób dbamy o to, że nigdzie dalej nie wykona się żaden kod który nie powinien, tylko dlatego że przegapiliśmy jakiś warunek, źle zagnieździliśmy bloki itd. Bez mnożenia if-ów, elsów, wyciągania zmiennych poza blok w którym obowiązują tylko po to, by dotrwały do końcowego return... Jeżeli to nie jest czytelniejsze, to nie wiem co jest.

    Tak samo - wyjątki. Każde miejsce gdzie rzucamy wyjątkiem jest punktem wyjścia z funkcji, mimo że nie zwracamy tu żadnego wyniku. Ktoś przeciwko temu protestuje? Chyba nie, wyjątki zadomowiły się w naszych językach już dawno. Zatem czemu nie pójść krok dalej i nie porzucić całkiem tej całej farsy z jednym punktem wyjścia na końcu?

  • W przypadku języków takich jak JS, VB czy Perl nie będzie to miało zbytniego wpływu na wydajność.

    Jakiś tam wpływ na wydajność ma, choć to może zależeć też od typu procesora i paru innych zmiennych ;-) Bo sprowadza się to do tego jak działa mechanizm predykcji skoków, bo jak przewidzi dobrze to jesteś do przodu tyle ile już załadowało się rozkazów z potoku i to co się zrównolegliło, jeżeli przewidzi źle to wszystko to jest unieważniane i procesor musi powtórzyć wszystkie swoje czynności związane ze skokiem od początku [głowy nie dam, bo tego nie mierzyłem, ale patrząc na architekturę procesorów to najbardziej to może boleć na procesorach w stylu Pentium 4, które miały absurdalnie długie potoki]. Inna sprawa, że żeby się tym przejmować to musisz mieć już cały kod napisany perfekcyjnie [czyli najlepszy możliwy algorytm, do tego to musi być najbardziej istotny element kodu]

    Co do praktyki to w Java'ie wydaje się, że szybciej działa konstrukcja zakładająca, że warunek jest nieprawdziwy, ale to może zależeć nawet od wersji samej maszyny wirtualnej i umiejscowienia if'a.

    W C# nie zauważyłem wpływu.

    W Miscrosoftowym JS nie widać wpływu na wydajność. To samo z Firefoksowym JS.

    Prawdę mówiąc jedynym językiem gdzie się tym możesz przejmować jest chyba asembler :-)

  • @michu - faktycznie mało miejsca w komentarzach - haha, właśnie natrafiłem na taki przypadek: system symulacji giełdowych, procedura 'sub apply_system{if (buy_signal) {submit_buy_order;return} if (sell_signal) {submit_sell_order;return}}', w pakiecie był też system o nazwie 'zawsze w grze', do użycia np. 'system:always,filtr:buy_only,...' do specjalnych zastosowań. Typowe filtry to buy_only, sell_only, both albo 1pozycja,wiele_pozycji itp, itd. Można tworzyć nowe filtry. Cóż prostszego jak utworzyć filtr, który mówi 'można utworzyć drugą pozycję, jeżeli jest przeciwstawna do pierwszej'? Najpierw system generuje sygnały, potem filtrują je filtry, możemy mieć dwie pozycje przeciwstawne... no i było fajnie, dopóki nie znalazłem tego 'return' wewnątrz procedury :))) cieszy mnie argument, ale to nie było śmieszne... kiepski proces testowania zmian, oczywiście. żadnego unit testingu, sam byłem sobie winien... (błąd pojawiał się tylko w szczególnych przypadkach - użycia jednego z wielu systemów, tego specjalnego 'zawsze w grze') dodam jeszcze, że temat kolejności ifów w takich programach jest bardzo typowy i wydawało mi się, że jestem wyczulony na takie kwestie, a i tak dałem się złapać.

  • No właśnie. IF-RETURN dla warunków brzegowych, IF dla "zwykłego" warunku, który funkcja obsługuje w odpowiedni sposób.

    Kiedy zaczniecie programować w bardziej cywilizowanych językach, to na początku będziecie wyrzucać w ten sposób wyjątki i konstrukcja zamieni się w IF-RAISE.

  • Z wychodzeniem z funkcji jest jak z wychodzeniem z klubu dla fanów YMCA - czym wcześniej tym lepiej. Dlatego przysporzysz sobie mniej nerwów na dłuższą metę i mniej rozterek Twoim pracownikom jeżeli na samym początku kodu określisz wszystkie warunki wyjścia.

    Czasem nawet wbrew logice która by nakazywała rozbijać wszystko względem poszczególnych stanów i wewnątrz nich badać kolejne stany i tak dalej. Lepiej po prostu na początku określić wszystkie kombinacje stanów kiedy wychodzisz i jeszcze nadać im jakieś aliasy. Na przykład nie:

    if (amount == 0 && money == 0)
    {
     return;
    }
    

    tylko:

    bool noMoneyAndStuff = (amount == 0 && money == 0)
    if (noMoneyAndStuff) return;
    
    //A dalej normalny kod funkcji w której przecież normalnie rozbijasz kod warunkami.
    

    Jest kilka argumentów za takim podejściem. Oto i one:

    1. Mniejsza złożoność kodu - logika kodu ulega spłaszczeniu
    2. Prostsze debugowanie - nie musisz wędrować po całym kodzie funkcji aby w końcu określić że musisz z niej wyjść
    3. Umieszczenie warunków wyjścia na początku ułatwia refaktoryzację - czasem taki blok warunków trzeba wydzielić do oddzielnej metody np. CzyDanePowinnyZostacPrzetworzone()
    4. Zapewne mniej niepotrzebnych obliczeń - możliwe że przez większą przejrzystość unikniesz zbędnych obliczeń, ale systemowo nie liczyłbym na jakiś szczególny wzrost wydajności. Nawet jakby to było mniej wydajne rozwiązanie, dopóki nie piszesz dla NASA opłaca się.

    Szczególnie tej "reguły YMCA" powinni stosować się programiści piszący wszystkie algorytmy przetwarzające XML-e i inne dane w złożony sposób oparty na wielu regułach biznesowych.

  • Nie ma to znaczenia. Główną kwestią jest indywidualny styl pracy i konkretna sytuacja. Krótkie IF-RETURN upraszcza kod, pozwala uniknąć dodatkowego wcięcia. Nadmiar zagnieżdżeń utrudnia czytanie i modyfikowanie kodu. Czasem jednak lepiej się myśli jak program ma konkretne bloki, a funkcja jeden punkt wyjścia.

    Warto jednak zaznaczyć, że krótkie IF-RETURN sprawdza się na początku funkcji przy sprawdzaniu podstawowych warunków, ale raczej nie powinno być "ukrywane" w dalszym kodzie. Taki "ukryty" RETURN może być dużym zaskoczeniem dla innych programistów pracujących z tym samym kodem, a nawet dla autora po upływie pewnego czasu.

    Dodatkowo, mogą pojawić się sytuacje, gdy funkcja MUSI wykonać pewne operacje. Pracując z głównym kodem takiej funkcji pamiętamy o tym, a początkowe warunki IF szybko stają się "niewidocznym tłem".

    Ogólnie, dobrą zasadą jest wstawianie instrukcji RETURN wyłącznie na samym początku albo na samym końcu funkcji.

    Nie ma to wpływu na wydajność. Skompilowany kod będzie często wyglądał tak samo.

  • Witam, jak się dzisiaj czujesz? Mam nadzieję, że wszystko jest dobrze. Nazywam się na stałym poziomie. W poszukiwaniu człowieka, który rozumie znaczenie miłości, zaufania i wiary w siebie, a nie ten, kto widzi miłość jako jedyny sposób zabawy, ale dojrzały człowiek z Nicei wizję tego, co na świecie jest wszystko o, a po przeczytaniu profilu tutaj (matchperfect) I wziął interes w ciebie, więc zarzuty odpowiedź mi w tym e-mail (constantduke10@hotmail.com. Będę bardzo szczęśliwy, aby przeczytać odpowiedzi tak, że ja wysłać moje zdjęcie dla Ciebie możemy wówczas zacząć wiedzieć więcej o sobie nawzajem. Dziękuję za przeczytanie mojego maila i być Bless.

    stałej (constantduke10@hotmail.com))

    Hello, how are you doing today? i hope all is well. My name is constant., In search of a man who understand the meaning of love as Trust and faith in each other rather than one who sees love as the only way of fun, but a matured Man with Nice Vision of what the world is all about, and after reading your profile here in(matchperfect ) I took Interest in you, so pleas reply me with this Email (constantduke10@hotmail.com. i will be very happy to read your reply so that i will send my picture to you then we can start know more about each other. Thanks for reading my mail and be Bless.

    constant (constantduke10@hotmail.com))

  •  sub funkcja() {
      //kod funkcji
     }
     if (a == b) funkcja();
    

    Jeżeli przetwarzasz argumenty funkcji.

     function napisz(tekst) {
     if (tekst) {
      document.write(tekst);
     }
     }
    

Zaloguj się, aby dodać swoją odpowiedź