Wiem, że może to być bardzo specyficzne dla każdego przypadku użycia, ale zbyt często zastanawiam się nad tym. Czy istnieje ogólnie preferowana składnia.
Nie pytam, jakie jest najlepsze podejście do funkcji, pytam, czy powinienem wyjść wcześniej, czy nie powinienem wywoływać funkcji.
Zawiń, jeśli wokół wywołania funkcji
if (shouldThisRun) {
runFunction();
}
Mają if ( wartownik ) w funkcji
runFunction() {
if (!shouldThisRun) return;
}
Ta ostatnia opcja oczywiście może zmniejszyć duplikację kodu, jeśli ta funkcja jest wywoływana wiele razy, ale czasami dodanie jej tutaj jest błędne, ponieważ może to oznaczać, że funkcja traci jedną odpowiedzialność .
Oto przykład
Jeśli mam funkcję updateStatus (), która po prostu aktualizuje status czegoś. Chcę tylko zaktualizować status, jeśli status się zmienił. Znam miejsca w moim kodzie, w których status może się zmienić, i znam inne miejsca, w których zdecydowanie się zmienił.
Nie jestem pewien, czy to tylko ja, ale czuję się trochę brudnie, aby sprawdzić tę funkcję wewnętrzną, ponieważ chcę zachować tę funkcję tak czystą, jak to możliwe - jeśli ją wywołam, oczekuję, że status zostanie zaktualizowany. Ale nie wiem, czy lepiej zawrzeć połączenie w kilku miejscach, w których wiem, że może się nie zmienić.
źródło
Odpowiedzi:
Zawijanie if wokół wywołania funkcji:
To decyduje, czy funkcja powinna być w ogóle wywoływana i jest częścią procesu decyzyjnego twojego programu.
Klauzula ochronna w funkcji (wczesny powrót):
Ma to na celu ochronę przed wywołaniem z niepoprawnymi parametrami
Użyta w ten sposób klauzula ochronna utrzymuje funkcję „czystą” (twój termin). Istnieje tylko po to, aby zapewnić, że funkcja nie zepsuje się przy błędnych danych wejściowych.
Logika, czy w ogóle wywoływać funkcję, jest na wyższym poziomie abstrakcji, nawet jeśli tylko. Powinien istnieć poza samą funkcją. Jak mówi DocBrown, możesz mieć funkcję pośrednią, która wykonuje tę kontrolę, aby uprościć kod.
To dobre pytanie, które należy zadać i mieści się w zestawie pytań, które prowadzą do rozpoznania poziomów abstrakcji. Każda funkcja powinna działać na jednym poziomie abstrakcji, a posiadanie zarówno logiki programu, jak i logiki funkcji w funkcji jest dla ciebie złe - dzieje się tak, ponieważ znajdują się na różnych poziomach abstrakcji. Sama funkcja jest niższym poziomem.
Utrzymując je osobno, masz pewność, że Twój kod będzie łatwiej pisać, czytać i utrzymywać.
źródło
Możesz mieć jedno i drugie - funkcję, która nie sprawdza parametrów, i drugą, która tak robi (może zwracać informacje o tym, czy wywołanie zostało wykonane):
W ten sposób można uniknąć zduplikowanej logiki, zapewniając możliwość wielokrotnego użytku
tryRunFunction
i nadal posiadając oryginalną (być może czystą) funkcję, która nie powoduje sprawdzenia w środku.Zauważ, że czasami będziesz potrzebować funkcji takiej jak wyłącznie
tryRunFunction
ze zintegrowanym czekiem, abyś mógł zintegrować czek zrunFunction
. Lub nie musisz ponownie używać czeku w dowolnym miejscu w programie, w którym to przypadku możesz pozwolić mu pozostać w funkcji wywoływania.Staraj się jednak, aby wywołujący był przejrzysty, co się dzieje, nadając swoim funkcjom odpowiednie nazwy. Osoby wywołujące nie muszą więc zgadywać ani sprawdzać implementacji, jeśli muszą same sprawdzić lub jeśli wywołana funkcja już to robi. Prosty prefiks, taki jak,
try
może często być do tego wystarczający.źródło
runFunction
. Funkcji podobnejupdateStatus()
może towarzyszyć inna funkcja podobnaupdateIfStatusHasChanged()
. Jest to jednak zależne w 100% od przypadku, nie ma na to „uniwersalnego rozwiązania”, więc zgadzam się, idiom „wypróbowania” nie zawsze jest dobrym wyborem.Jeśli chodzi o to, kto decyduje, czy uruchomić, odpowiedź brzmi: od GRASP , który jest „ekspertem od informacji”, który wie.
Po podjęciu takiej decyzji rozważ zmianę nazwy funkcji dla zachowania przejrzystości.
Coś takiego, jeśli funkcja decyduje:
Lub, jeśli dzwoniący ma zdecydować:
źródło
Chciałbym rozwinąć odpowiedź @ Baldrickk.
Nie ma ogólnej odpowiedzi na twoje pytanie. Zależy to od znaczenia (kontraktu) funkcji do wywołania i charakteru warunku.
Omówmy to w kontekście twojego przykładowego wywołania
updateStatus()
. Umowa prawdopodobnie polega na aktualizacji jakiegoś statusu, ponieważ wydarzyło się coś, co miało wpływ na status. Spodziewałbym się, że wywołania tej metody będą dozwolone, nawet jeśli nie nastąpi prawdziwa zmiana statusu, i będą konieczne, jeśli nastąpi prawdziwa zmiana.Tak więc strona wywołująca może pominąć połączenie,
updateStatus()
jeśli wie, że (w ramach swojej domeny) nic istotnego się nie zmieniło. W takich sytuacjach połączenie powinno być otoczone odpowiedniąif
konstrukcją.Wewnątrz
updateStatus()
funkcji mogą wystąpić sytuacje, w których ta funkcja wykrywa (na podstawie danych z jej horyzontu domenowego), że nie ma nic do zrobienia i właśnie tam powinna wrócić wcześniej.Tak więc pytania są następujące:
W przypadku tej
updateStatus()
funkcji spodziewałbym się, że zobaczę oba, wzywając strony, które nie wiedzą, że nic się nie zmieniło, pomijam połączenie, a implementacja sprawdza wcześniej sytuacje „nic się nie zmieniło”, nawet jeśli w ten sposób zostanie sprawdzony dwukrotnie ten sam warunek, oba wewnątrz i na zewnątrz.źródło
Istnieje wiele dobrych wyjaśnień. Ale chcę wyglądać w nietypowy sposób: Załóżmy, że używasz w ten sposób:
I musisz wywołać inną funkcję w
runFunction
metodzie takiej jak ta:Co zrobisz? Czy kopiujesz wszystkie walidacje od góry do dołu?
Nie wydaje mi się Dlatego zazwyczaj stosuję to samo podejście: sprawdzaj poprawność danych wejściowych i sprawdzaj warunki w
public
metodzie. Metody publiczne powinny przeprowadzać własne weryfikacje i sprawdzać wymagane warunki, nawet w przypadku osób dzwoniących. Ale pozwól, aby prywatne metody po prostu działały na własny rachunek . Niektóre inne funkcje mogą wywoływaćrunFunction
bez sprawdzania poprawności lub sprawdzania jakichkolwiek warunków.źródło