Kiedy i dlaczego powinieneś używać void (zamiast np. Bool / int)

30

Czasami natrafiam na metody, w których programista postanowił zwrócić coś, co nie jest krytyczne dla funkcji. Mam na myśli, że patrząc na kod, wygląda na to, że działa tak samo dobrze, voida po chwili zastanowienia pytam „Dlaczego?” Czy to brzmi znajomo?

Czasami zgadzam się, że najczęściej lepiej jest zwrócić coś takiego boollub int, a nie po prostu zrobić void. Nie jestem jednak pewien, ogólnie rzecz biorąc, o zaletach i wadach.

W zależności od sytuacji zwracanie intmoże uświadomić programowi wywołującemu liczbę wierszy lub obiektów, na które wpływa metoda (np. 5 rekordów zapisanych w MSSQL). Jeśli metoda taka jak „InsertSomething” zwraca wartość logiczną, mogę mieć metodę zaprojektowaną tak, aby zwracała truesukces, w przeciwnym razie false. Dzwoniący może zdecydować się na działanie lub nie na podstawie tych informacji.

Z drugiej strony,

  • Czy może to prowadzić do mniej wyraźnego celu wywołania metody? Złe kodowanie często zmusza mnie do dwukrotnego sprawdzenia zawartości metody. Jeśli coś zwróci, powie ci, że metoda jest tego typu, że musisz coś zrobić ze zwróconym wynikiem.
  • Innym problemem byłoby to, że jeśli implementacja metody jest dla Ciebie nieznana, co deweloper postanowił zwrócić, co nie jest krytyczne pod względem funkcji? Oczywiście możesz to skomentować.
  • Wartość zwracana musi zostać przetworzona, gdy przetwarzanie może zostać zakończone w końcowym przedziale metody.
  • Co dzieje się pod maską? Czy wywoływana metoda została wywołana z falsepowodu wyrzuconego błędu? A może zwrócił fałsz z powodu wyniku oceny?

Jakie są twoje doświadczenia z tym? Jak byś na to zareagował?

Niezależny
źródło
1
Funkcja, która zwraca void, nie jest technicznie funkcją. To tylko metoda / procedura. Zwrócenie voidco najmniej informuje programistę, że wartość zwracana przez metodę jest nieistotna; wykonuje akcję, a nie oblicza wartość.
KChaloux

Odpowiedzi:

32

W przypadku wartości zwracanej bool wskazującej powodzenie lub niepowodzenie metody preferuję Tryparadygmat prefiksu używany w różnych metodach .NET.

Na przykład void InsertRow()metoda może zgłosić wyjątek, jeśli istnieje już wiersz z tym samym kluczem. Pytanie brzmi: czy uzasadnione jest założenie, że osoba dzwoniąca upewnia się, że jej wiersz jest unikalny przed wywołaniem InsertRow? Jeśli odpowiedź brzmi „nie”, podaję również bool TryInsertRow()wartość „false”, jeśli wiersz już istnieje. W innych przypadkach, takich jak błędy łączności z bazą danych, TryInsertRow nadal może zgłosić wyjątek, zakładając, że utrzymanie łączności z bazą danych jest obowiązkiem osoby dzwoniącej.

Folia bąbelkowa
źródło
4
+1 za dokonanie rozróżnienia między radzeniem sobie z oczekiwanymi i nieoczekiwanymi awariami - moja własna odpowiedź nie podkreśliła tego wystarczająco.
Péter Török,
Absolutnie +1 za próbę wynalezienia zasady dla metod TryXX, która najwyraźniej sprawia, że ​​zarówno metoda try, jak i metoda główna są łatwiejsze w utrzymaniu i łatwiejsze do zrozumienia ich celu.
Niezależny
+1 Dobrze powiedziane. Kluczem do najlepszej odpowiedzi na to pytanie jest to, że czasami chcesz dać dzwoniącemu opcję asertywnej metody, która wyrzuci wyjątek. Jednak w tych przypadkach wyjątek nie powinien być wychwytywany i fałszywie traktowany metodą asertywną. Chodzi o to, że dzwoniący jest odpowiedzialny. Z drugiej strony Try-paradygmat (lub Monada) powinien wychwytywać i obsługiwać wyjątki, a następnie albo przekazać bool, albo Enum opisowy, jeśli konieczne są dodatkowe szczegóły. Zauważ, że dzwoniący oczekuje, że Try / Monada NIGDY nie zgłosi wyjątku. To jest jak punkt wejścia.
Suamere,
Ponieważ oczekuje się, że wyjątek nigdy nie wystąpi w Try / Monad, jednak bool nie jest wystarczająco opisowy, aby poinformować osobę dzwoniącą, że połączenie db nie powiodło się, lub żądanie HTTP ma jedną z wielu zwracanych wartości, które są niezbędne do działania w inny sposób, możesz użyć Enum zamiast bool dla twojego Try / Monad.
Suamere,
Jeśli masz TryInsertRow - zwracający wartość bool - jeśli, powiedzmy, istnieje więcej niż jedno unikalne ograniczenie w bazie danych - skąd dokładnie wiesz, jaki był błąd - i przekażesz to użytkownikowi? Czy należy użyć bogatszego typu zwrotu zawierającego komunikat o błędzie / kod i bool sukcesu?
niico
28

IMHO zwracające „kod statusu” wywodzi się z czasów historycznych, zanim wyjątki stały się powszechne w językach od średniego do wyższego poziomu, takich jak C #. W dzisiejszych czasach lepiej jest zgłosić wyjątek, jeśli jakiś nieoczekiwany błąd uniemożliwił pomyślne zakończenie metody. W ten sposób jest pewne, że błąd nie pozostanie niezauważony, a osoba dzwoniąca poradzi sobie z tym odpowiednio. Tak więc w tych przypadkach doskonale jest, aby metoda nie zwracała niczego (tj. void) Zamiast logicznej wartości statusu lub kodu statusu liczby całkowitej. (Oczywiście należy udokumentować, jakie wyjątki może zgłosić metoda i kiedy).

Z drugiej strony, jeśli jest to funkcja w ścisłym znaczeniu, tzn. Wykonuje pewne obliczenia na podstawie parametrów wejściowych i oczekuje się, że zwróci wynik tego obliczenia, typ zwracany jest oczywisty.

Między nimi jest szara strefa, w której można zdecydować o zwróceniu niektórych „dodatkowych” informacji z metody, jeśli zostanie to uznane za przydatne dla jej rozmówców. Podobnie jak twój przykład dotyczący liczby dotkniętych wierszy we wstawce. Lub w przypadku metody umieszczania elementu w kolekcji asocjacyjnej przydatne może być zwrócenie poprzedniej wartości powiązanej z tym kluczem, jeśli taki istnieje. Takie zastosowania można zidentyfikować, dokładnie analizując (znane i przewidywane) scenariusze użycia interfejsu API.

Péter Török
źródło
4
+1 za wyjątki od kodu statusu. Wyjątkiem (źle zaprojektowana gra słów) jest dobrze używany wzór TryXX.
Andy Lowry,
jestem tam z tobą. TcF i nie wyciszaj błędów! Prawdopodobnie jednak szara strefa. Nie może być zawsze o czymś wykorzystanie powrotnego. Dotknięte wiersze, ostatni wstawiony identyfikator, PID uruchomionego oprogramowania, ale nadal myślę, że łatwiej jest myśleć „cholera, tak, zwracam to” podczas programowania. Następnie, rok później, kiedy dodajesz: „co? To robi„ coś, co = RunProcess (x) „co się stanie, jeśli nie będzie działać?”
Niezależny
+1 dodatkowe informacje to dlaczego koduję takie metody w językach wyższego poziomu, takich jak C #. Ułatwia to korzystanie z dodatkowych informacji, gdy są potrzebne.
ashes999
2
-1 Łał, twoje własne słowa są tak sprzeczne i złe. Nigdy nie należy używać wyjątków dla kontroli przepływu lub komunikacji. Są nieskończenie droższe niż warunkowe. Kiedy jest czas „zanim wyjątki stały się powszechne”? ... masz na myśli, zanim bloki try / catch stały się powszechne? Wyjątki są powszechne od zawsze. „Zgłaszaj wyjątek, jeśli wystąpi nieoczekiwany błąd ...” Jak podejmiesz działanie w przypadku nieoczekiwanego błędu? Dlaczego złapałeś wyjątek, a następnie ponownie go rzuciłeś? To takie drogie. Dlaczego mówisz „błąd”? Błędy i wyjątki to różne rzeczy.
Suamere,
1
A kto może powiedzieć, że zgłoszony wyjątek nie pozostanie niezauważony? Nic nie zmusza nikogo do zalogowania się w blok catch, dlaczego by nie zalogować się w bloku „wtedy”? Dlaczego „udokumentować, jakie wyjątki może zgłosić metoda i kiedy”? A może napiszemy kod samokontrujący, a metoda po prostu zwróci wyliczenie z oczekiwanymi stanami końcowymi tej metody? Jeśli możliwego wyjątku można uniknąć poprzez sprawdzenie stanu, należy je złapać i obsłużyć bez rzucania.
Suamere,
14

Odpowiedź Piotra dobrze obejmuje wyjątki. Inne rozważania dotyczą rozdzielenia poleceń i zapytań

Zasada CQS mówi, że metoda powinna być albo poleceniem, albo zapytaniem, a nie jednym i drugim. Polecenia nigdy nie powinny zwracać wartości, tylko modyfikować stan, a zapytania powinny tylko zwracać, a nie modyfikować stan. Dzięki temu semantyka jest bardzo przejrzysta i pomaga uczynić kod bardziej czytelnym i łatwym w utrzymaniu.

Jest kilka przypadków, w których naruszenie zasady CQS jest dobrym pomysłem. Zazwyczaj dotyczą one wydajności lub bezpieczeństwa wątków.

Andy Lowry
źródło
1
-1 Źle i źle. Po pierwsze, cały punkt CQS leży w Q. Osoba dzwoniąca powinna być w stanie uzyskać obliczenia lub połączenia zewnętrzne za pośrednictwem jakiejś usługi stanowej bez obawy zmiany stanu tej usługi. Ponadto, chociaż CQS jest pomocną zasadą, którą można swobodnie stosować, jest ściśle przestrzegana tylko w określonych projektach. Wreszcie, nawet w ścisłym CQS, nic nie mówi, że polecenie nie może zwrócić wartości, mówi, że nie powinno ono obliczać ani wywoływać obliczeń zewnętrznych i zwracać danych . Zarówno C, jak i Q mogą przywrócić swój stan końcowy, jeśli jest to przydatne dla dzwoniącego.
Suamere,
Ach, ale liczba wierszy, na które wpływa polecenie, znacznie ułatwia tworzenie nowych poleceń ze starych. Źródło: lata doświadczeń.
Joshua
@Joshua: Podobnie, „dodaj nowy rekord i zwróć identyfikator (dla niektórych struktur, numer wiersza), który został wygenerowany podczas dodawania” można wykorzystać do celów, których inaczej nie można skutecznie osiągnąć.
supercat
Nie powinieneś zwracać wierszy, których to dotyczy. Polecenie powinno wiedzieć, ilu będzie miało to wpływ. Jeśli to coś innego niż #, powinien wycofać się i zgłosić wyjątek, ponieważ właśnie stało się coś dziwnego. Tak więc, jest to informacja, że ​​dzwoniący tak naprawdę jej nie obchodzi (chyba że użytkownik musi znać prawdziwą # i nie można powiedzieć na podstawie podanych danych). Wciąż istnieją szare obszary, takie jak generowane przez DB identyfikatory, stosy / kolejki, w których pobieranie danych zmienia swój stan, ale lubię tworzyć „CommandQuery” w tych scenariuszach, więc nikt nie próbuje zrobić czegoś „dziwnego”.
Daniel Lorenz
3

Jakąkolwiek ścieżkę zamierzasz iść z tym. Nie ma tam wartości zwracanych do przyszłego użytku (np. Czy funkcje zawsze zwracają wartość true), jest to straszna strata wysiłku i nie sprawia, że ​​kod jest bardziej czytelny. Gdy masz wartość zwracaną, zawsze powinieneś jej używać, a aby móc z niej skorzystać, powinieneś mieć co najmniej 2 możliwe wartości zwracane.

refro
źródło
+1 To nie odpowiada na pytanie, ale jest to zdecydowanie poprawna informacja. Podejmując decyzję, decyzja powinna opierać się na potrzebie. Jeśli rozmówcy nie uznają danych zwrotnych za użyteczne, nie należy tego robić. I nikt nie ma sensu zwracać 100% czasu na „sprawdzanie w przyszłości”. To prawda, że ​​jeśli „przyszłość” jest jak… za kilka dni i istnieje dla niej zadanie, to inna historia, lol.
Suamere,
1

Pisałem wiele pustych funkcji. Ale odkąd podjąłem całą metodę łączenia pęknięć, zacząłem zwracać to zamiast pustki - równie dobrze mogę pozwolić komuś skorzystać ze zwrotu, ponieważ nie można robić przysiadów z pustką. A jeśli nie chcą nic z tym zrobić, mogą to zignorować.

Wyatt Barnett
źródło
1
Łączenie metod może sprawiać, że dziedziczenie staje się kłopotliwe i trudne. Nie robiłbym tworzenia łańcuchów metod, chyba że masz dobry powód, aby zrobić to inaczej niż nie chcieć nadać swoim metodom „nieważności”.
KallDrexx
Prawdziwe. Ale dziedziczenie może być kłopotliwe i trudne w użyciu.
Wyatt Barnett,
Przyglądając się nieznanemu kodowi, który zwraca cokolwiek (wspomniany cały obiekt), zwraca się dużą uwagę, po prostu sprawdź przepływ wewnątrz i na zewnątrz metod.
Niezależne
Nie przypuszczam, że Rubyw którymś momencie wpadłeś? To właśnie wprowadziło mnie do łączenia metod.
KChaloux,
Jedną z cech, które mam przy łączeniu metod, jest to, że mniej jasne jest, czy takie zdanie return Thing.Change1().Change2();oczekuje zmiany Thingi zwrócenia odniesienia do oryginału, czy też oczekuje zwrócenia odniesienia do nowej rzeczy, która różni się od oryginału, pozostawiając oryginał nietknięty.
supercat