Instrukcja switch ze zwrotami - poprawność kodu


92

Powiedzmy, że mam kod w C o takiej strukturze:

switch (something)
{
    case 0:
      return "blah";
      break;

    case 1:
    case 4:
      return "foo";
      break;

    case 2:
    case 3:
      return "bar";
      break;

    default:
      return "foobar";
      break;
}

Oczywiście, te breakznaki nie są konieczne, aby kod działał poprawnie, ale wygląda na to, że to zła praktyka, jeśli mi ich tam nie umieszczę.

Co myślisz? Czy można je usunąć? A może zatrzymałbyś je dla większej „poprawności”?

Odpowiedzi:


140

Usuń breakoświadczenia. Nie są potrzebne i być może niektóre kompilatory wyświetlą ostrzeżenia „Nieosiągalny kod” .


2
To samo dotyczy innych bezwarunkowych instrukcji sterujących skokami, takich jak continuelub goto- idiomatyczne jest używanie ich zamiast break.
kawiarnia

32

Wybrałbym zupełnie inną taktykę. Nie RETURN w środku metody / funkcji. Zamiast tego po prostu umieść zwracaną wartość w zmiennej lokalnej i wyślij ją na końcu.

Osobiście bardziej czytelne są następujące:

String result = "";

switch (something) {
case 0:
  result = "blah";
  break;
case 1:
  result = "foo";
  break;
}

return result;

24
Filozofia „jednego wyjścia” ma sens w języku C, gdzie trzeba upewnić się, że wszystko jest uporządkowane. Biorąc pod uwagę, że w C ++ możesz zostać wyjęty z funkcji w dowolnym momencie przez wyjątek, filozofia jednego wyjścia nie jest zbyt przydatna w C ++.
Billy ONeal

29
Teoretycznie to świetny pomysł, ale często wymaga dodatkowych bloków kontrolnych, które mogą utrudniać czytelność.
mikerobi

8
Głównym powodem, dla którego robię to w ten sposób, jest to, że w dłuższych funkcjach bardzo łatwo jest przegapić wektor wyjściowy podczas skanowania kodu. Jednak gdy wyjście jest zawsze na końcu, łatwiej jest szybko pojąć, co się dzieje.
NotMe

7
Cóż, i bloki powrotu w środku kodu przypominają mi o nadużyciu GOTO.
NotMe

5
@Chris: W przypadku dłuższych funkcji całkowicie się z tym zgadzam - ale to zwykle mówi o większych problemach, więc należy to zmienić. W wielu przypadkach jest to trywialna funkcja powracająca po przełączniku i jest bardzo jasne, co ma się stać, więc nie marnuj mocy mózgu na śledzenie dodatkowej zmiennej.
Stephen

8

Osobiście usuwałbym zwroty i zachował przerwy. Użyłbym instrukcji switch, aby przypisać wartość zmiennej. Następnie zwróć tę zmienną po instrukcji switch.

Chociaż jest to kwestia sporna, zawsze uważałem, że dobry projekt i hermetyzacja oznacza jedno wejście i jedno wyjście. Znacznie łatwiej jest zagwarantować logikę i nie przegapisz przypadkowo kodu porządkującego na podstawie cyklicznej złożoności funkcji.

Jeden wyjątek: Wczesny powrót jest prawidłowy, jeśli na początku funkcji zostanie wykryty zły parametr - przed pozyskaniem jakichkolwiek zasobów.


Może być dobry w tym konkretnym przypadku switch, ale niekoniecznie ogólnie najlepszy. Cóż, powiedzmy, że instrukcja switch wewnątrz funkcji poprzedza kolejne 500 wierszy kodu, który zachowuje ważność tylko w określonych przypadkach true. Jaki jest sens wykonywania całego tego dodatkowego kodu w przypadkach, które nie muszą go wykonywać; czy nie lepiej jest po prostu returnwewnątrz switchdla tych case?
Fr0zenFyr

6

Zachowaj przerwy - mniej prawdopodobne jest, że napotkasz problemy, jeśli później edytujesz kod, jeśli przerwy są już na miejscu.

Powiedziawszy to, wielu (w tym ja) uważa, że ​​powrót ze środka funkcji jest złą praktyką. W idealnym przypadku funkcja powinna mieć jeden punkt wejścia i jeden punkt wyjścia.


5

Usuń ich. Wracanie z caseinstrukcji jest idiomatyczne , aw przeciwnym razie jest to szum „nieosiągalnego kodu”.


5

Usunąłbym je. W mojej książce taki martwy kod powinien być uważany za błąd, ponieważ sprawia, że ​​robisz podwójne podejście i zadajesz sobie pytanie: „Jak mógłbym kiedykolwiek wykonać tę linię?”


4

Normalnie napisałbym kod bez nich. IMO, martwy kod wskazuje na niechlujstwo i / lub brak zrozumienia.

Oczywiście rozważałbym również coś takiego:

char const *rets[] = {"blah", "foo", "bar"};

return rets[something];

Edycja: nawet w przypadku edytowanego postu ten ogólny pomysł może działać dobrze:

char const *rets[] = { "blah", "foo", "bar", "bar", "foo"};

if ((unsigned)something < 5)
    return rets[something]
return "foobar";

W pewnym momencie, zwłaszcza jeśli wartości wejściowe są rzadkie (np. 1, 100, 1000 i 10000), zamiast tego potrzebujesz rzadkiej tablicy. Możesz to zaimplementować jako drzewo lub mapę w miarę dobrze (chociaż oczywiście przełącznik nadal działa również w tym przypadku).


Zmodyfikowałem post, aby odzwierciedlić, dlaczego to rozwiązanie nie działa dobrze.
houbysoft

@ Twoja edycja: Tak, ale zajęłoby to więcej pamięci itp. IMHO przełącznik jest najprostszy, czyli to, czego chcę w tak małej funkcji (robi tylko przełącznik).
houbysoft

3
@houbysoft: przyjrzyj się uważnie, zanim stwierdzisz, że zajmie to dodatkową pamięć. W przypadku typowego kompilatora dwukrotne użycie "foo" (na przykład) spowoduje użycie dwóch wskaźników do pojedynczego bloku danych, a nie replikację danych. Możesz też spodziewać się krótszego kodu. Jeśli nie masz wielu powtarzających się wartości, często oszczędza to pamięć.
Jerry Coffin

prawdziwe. Nadal będę się trzymał swojego rozwiązania, ponieważ lista wartości jest bardzo długa, a przełącznik wydaje się po prostu ładniejszy.
houbysoft

1
Rzeczywiście, bardziej elegancką i wydajną odpowiedzią na taki problem, gdy wartości przełącznika są ciągłe, a typ danych homogeniczny, jest użycie tablicy, aby całkowicie uniknąć przełączania.
Dwayne Robinson

2

Powiedziałbym, że je usuń i zdefiniuj domyślną: gałąź.


Jeśli nie ma rozsądnej wartości domyślnej, nie należy jej definiować.
Billy ONeal

jest jeden i zdefiniowałem jeden, po prostu nie umieściłem go w pytaniu, teraz go zredagowałem.
houbysoft

2

Czy nie byłoby lepiej mieć tablicę z

arr[0] = "blah"
arr[1] = "foo"
arr[2] = "bar"

i zrobić return arr[something];?

Jeśli chodzi o praktykę w ogóle, powinieneś zachować breakoświadczenia w przełączniku. W przypadku, gdy nie potrzebujesz returnstwierdzeń w przyszłości, zmniejsza to szansę, że przejdą one do następnej case.


Zmodyfikowałem post, aby odzwierciedlić, dlaczego to rozwiązanie nie działa dobrze.
houbysoft

2

Ze względu na „poprawność” dobrym pomysłem są pojedyncze wejścia, pojedyncze bloki wyjścia. Przynajmniej tak było, kiedy robiłem stopień informatyczny. Więc pewnie zadeklarowałbym zmienną, przyporządkowałbym do niej w przełączniku i wróciłbym raz na koniec funkcji


2

Co myślisz? Czy można je usunąć? A może zatrzymałbyś je dla większej „poprawności”?

Dobrze jest je usunąć. Używanie return jest dokładnie scenariuszem, w którym breaknie powinno być używane.


1

Ciekawy. Konsensus większości z tych odpowiedzi wydaje się być taki, że zbędne breakstwierdzenie jest niepotrzebnym bałaganem. Z drugiej strony breakoświadczenie w przełączniku odczytałem jako „zamknięcie” sprawy. casebloki, które nie kończą breaksię na mnie, zwykle wyskakują na mnie jako potencjalny upadek przez błędy.

Wiem, że tak nie jest, gdy jest returnzamiast a break, ale tak właśnie moje oczy „czytają” bloki obudowy w przełączniku, więc osobiście wolałbym, aby każdy casebył sparowany z break. Ale wielu kompilatorów narzeka na to, że breaknastępstwo returnjest zbyteczne / niedostępne, a ja najwyraźniej i tak jestem w mniejszości.

Więc pozbądź się breaknastępującego pliku return.

Uwaga: wszystko to ignoruje, czy naruszenie zasady pojedynczego wejścia / wyjścia jest dobrym pomysłem, czy nie. W tym zakresie mam opinię, która niestety zmienia się w zależności od okoliczności ...


0

Mówię, usuń je. Jeśli Twój kod jest tak nieczytelny, że musisz w nim wstawiać przerwy `` aby być po bezpiecznej stronie '', powinieneś ponownie przemyśleć swój styl kodowania :)

Zawsze też wolałem nie mieszać przerw i powrotów w instrukcji switch, ale raczej trzymać się jednego z nich.


0

Osobiście zwykle tracę breaks. Prawdopodobnie jednym ze źródeł tego nawyku jest programowanie procedur okien dla aplikacji Windows:

LRESULT WindowProc (HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
{
    switch (uMsg)
    {
        case WM_SIZE:
            return sizeHandler (...);
        case WM_DESTROY:
            return destroyHandler (...);
        ...
    }

    return DefWindowProc(hwnd, uMsg, wParam, lParam);
}

Osobiście uważam to podejście za dużo prostsze, zwięzłe i elastyczne niż deklarowanie zmiennej zwracanej ustawionej przez każdy program obsługi, a następnie zwracanie jej na końcu. Biorąc pod uwagę to podejście, breaks są zbędne i dlatego powinny odejść - nie służą żadnemu użytecznemu celowi (składniowo ani wizualnie IMO) i tylko nadymają kod.


0

Myślę, że * przerwy * mają jakiś cel. Ma to na celu utrzymanie „ideologii” programowania. Gdybyśmy mieli po prostu „zaprogramować” nasz kod bez logicznej spójności, być może byłby czytelny dla Ciebie teraz, ale spróbuj jutro. Spróbuj wyjaśnić to swojemu szefowi. Spróbuj uruchomić go w systemie Windows 3030.

Bleah, pomysł jest bardzo prosty:


Switch ( Algorithm )
{

 case 1:
 {
   Call_911;
   Jump;
 }**break**;
 case 2:
 {
   Call Samantha_28;
   Forget;
 }**break**;
 case 3:
 {
   Call it_a_day;
 }**break**;

Return thinkAboutIt?1:return 0;

void Samantha_28(int oBed)
{
   LONG way_from_right;
   SHORT Forget_is_my_job;
   LONG JMP_is_for_assembly;
   LONG assembly_I_work_for_cops;

   BOOL allOfTheAbove;

   int Elligence_says_anyways_thinkAboutIt_**break**_if_we_code_like_this_we_d_be_monkeys;

}
// Sometimes Programming is supposed to convey the meaning and the essence of the task at hand. It is // there to serve a purpose and to keep it alive. While you are not looking, your program is doing  // its thing. Do you trust it?
// This is how you can...
// ----------
// **Break**; Please, take a **Break**;

/ * Tylko drobne pytanie. Ile kawy wypiłeś czytając powyższe? IT Czasami psuje system * /


0

Kod zakończenia w pewnym momencie. Zapewnia to lepszą czytelność kodu. Dodanie instrukcji return (wielokrotne wyjścia) pomiędzy nimi utrudni debugowanie.


0

Jeśli masz kod typu „lookup”, możesz spakować klauzulę switch-case w samej metodzie.

Mam kilka z nich w systemie „hobby”, który rozwijam dla przyjemności:

private int basePerCapitaIncomeRaw(int tl) {
    switch (tl) {
        case 0:     return 7500;
        case 1:     return 7800;
        case 2:     return 8100;
        case 3:     return 8400;
        case 4:     return 9600;
        case 5:     return 13000;
        case 6:     return 19000;
        case 7:     return 25000;
        case 8:     return 31000;
        case 9:     return 43000;
        case 10:    return 67000;
        case 11:    return 97000;
        default:    return 130000;
    }
}

(Tak. To przestrzeń GURPS ...)

Zgadzam się z innymi, że w większości przypadków należy unikać więcej niż jednego powrotu w metodzie i zdaję sobie sprawę, że ta metoda mogłaby być lepiej zaimplementowana jako tablica lub coś innego. Właśnie odkryłem, że powrót przełącznika wielkości liter jest dość łatwym dopasowaniem do tabeli odnośników z korelacją 1-1 między danymi wejściowymi a wyjściowymi, tak jak powyżej (gry fabularne są ich pełne, jestem pewien, że istnieją w innych „biznes”): D.

Z drugiej strony, jeśli klauzula case jest bardziej złożona lub coś dzieje się po instrukcji switch, nie polecałbym używania w niej return, ale raczej ustaw zmienną w przełączniku, zakończ ją przerwą i zwróć wartość zmiennej na końcu.

(Z drugiej strony… po trzecie… zawsze można zmienić sposób przełączania na jego własną metodę… Wątpię, czy będzie to miało wpływ na wydajność i nie zdziwiłbym się, gdyby współczesne kompilatory potrafiły je rozpoznać jako coś, co można by wstawić ...)

Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.