Czy powinienem używać instrukcji switch, czy długo, jeśli… jeszcze łańcuchy?

36

Często, gdy słyszę o instrukcji switch, jest ona odkładana jako sposób na zastąpienie długich, jeśli ... jeszcze łańcuchów. Ale wydaje się, że kiedy używam instrukcji switch, piszę więcej kodu, który napisałbym, gdyby ... inaczej. Masz również inne problemy, takie jak utrzymywanie wszystkich zmiennych dla wszystkich połączeń w tym samym zakresie .

Oto kod reprezentujący przepływ, który zwykle piszę ( dzięki diam )

String comment;   // The generated insult.
int which = (int)(Math.random() * 3);  //  Result is 0, 1, or 2.

if (which == 0) {
    comment = "You look so much better than usual.";
} else if (which == 1) {
    comment = "Your work is up to its usual standards.";
} else if (which == 2) {
    comment = "You're quite competent for so little experience.";
} else {
    comment = "Oops -- something is wrong with this code.";
}

Potem chcą, żebym to zastąpił:

String comment;   // The generated insult.
int which = (int)(Math.random() * 3);  //  Result is 0, 1, or 2.

switch (which) {
    case 0:  
             comment = "You look so much better than usual.";
    break;
    case 1:  
             comment = "Your work is up to its usual standards.";
    break;
    case 2:  
             comment = "You're quite competent for so little experience.";
    break;
    default: 
             comment = "Oops -- something is wrong with this code.";
}

Wydaje się, że jest o wiele więcej kodu w znacznie bardziej niezręcznej składni. Ale czy naprawdę jest korzyść z używania instrukcji switch?

TheLQ
źródło
Ugh. Tak, to z pewnością bardziej nieporęczne, ale tylko w rodzinie C, ponieważ jego składnia instrukcji case jest tak brzydka.
Mason Wheeler
5
Ten rodzaj rzeczy był często omawiany na stackoverflow: - stackoverflow.com/questions/449273/… - stackoverflow.com/questions/767821/... - stackoverflow.com/questions/97987/switch-vs-if-else
Jewgienij Brikman
1
Powinieneś unikać obu, gdy tylko jest to możliwe. O wiele lepiej jest utworzyć strukturę danych i przeprowadzić wyszukiwanie, nawet jeśli celem wyszukiwania jest funkcja lub klasa.
kevin cline
przełącznik jest szybszy, przynajmniej w .net. Nie wiem o Javie.
Knerd
Można go przełożyć na słownik przypadków i metod oraz jeden „jeśli”
Pavel Yermalovich

Odpowiedzi:

57

Dla tej szczególnej sytuacji, wydaje mi się, że oba ifi casesą biedne wybory. Użyłbym prostej tablicy:

String comments[] = {
    "You look so much better than usual.",
    "Your work is up to its usual standards.",
    "You're quite competent for so little experience."
};

String comment = comments[(int)(Math.random() * 3)];

Na marginesie, powinieneś ogólnie obliczyć mnożnik w oparciu o rozmiar tablicy, a nie kodować na stałe 3.

Co do kiedy będzie używać przypadek / wyłącznik, różnica z kaskadą ifsprawozdania (lub przynajmniej jedna zasadnicza różnica) jest to, że switchmożna półautomatycznie Optymalizacja na podstawie liczby i gęstości wartości, natomiast kaskadą ifsprawozdania liści kompilator z niewielkim wyborem, ale do wygenerowania kodu tak, jak go napisałeś, testowania jednej wartości po drugiej, aż znajdzie dopasowanie. Tylko w trzech rzeczywistych przypadkach nie stanowi to problemu, ale przy wystarczającej liczbie może / może być znaczący.

Jerry Coffin
źródło
Ten przykład był tylko przykładem BTW. Nie wiedziałem jednak, że kompilator może tak zoptymalizować
TheLQ
6
@JBRWilkinson. W tym przypadku wartość poza zakresem jest możliwa tylko poprzez błąd kompilatora, na którym nie jestem skłonny spędzać dużo czasu (błąd w moim kodzie do testowania wyniku jest prawie tak samo prawdopodobny jak w kodzie generującym). W sytuacji, w której wartość poza zakresem była poważnym problemem (np. Indeks był odbierany z innego kodu), najpierw sprawdzałbym granice i używałbym go jako indeksu dopiero po sprawdzeniu.
Jerry Coffin
4
Myślę, że ta odpowiedź jest bardzo konkretna w odniesieniu do przykładu, podczas gdy pytanie było bardziej ogólne niż to ...
Khelben
3
@Khelben: Wydaje mi się, że nie zawracałeś sobie głowy czytaniem całej odpowiedzi. Ostatni akapit dotyczy szerszych kwestii. Jest jednak problem: znaleźć bardzo niewiele sytuacji, w których uważam za albo do caseoświadczenia lub kaskadę ifsprawozdania odpowiedni. Przez większość czasu są one (przeciętne) substytutem jakiegoś rodzaju mapy / tablicy, a lepiej jest użyć jednego z nich bezpośrednio.
Jerry Coffin,
1
@Titou: chyba, że ​​kompilator jest całkowicie braindead, tablica będzie budowana raz w czasie kompilacji, a następnie będziesz używał tylko struktury statycznej. Na przykład, jeśli robisz to w C lub C ++, chciałbyś zrobić tę static consttablicę, aby upewnić się, że zawsze istnieje (ale nie podano żadnego języka w pytaniu, więc starałem się nie zakładać żadnego z nich w odpowiedzi ).
Jerry Coffin
23

Problem z if...else if...łańcuchem polega na tym, że kiedy przychodzę go czytać, muszę patrzeć na każdy pojedynczy ifwarunek, aby zrozumieć, co robi program. Na przykład możesz mieć coś takiego:

if (a == 1) {
    // stuff
} else if (a == 2) {
    // stuff
} else if (a == 3) {
    // stuff
} else if (b == 1) {
    // stuff
} else if (b == 2) {
    // stuff
}

(oczywiście dla niewielkiej liczby takich stwierdzeń nie jest tak źle)

Nie miałbym pojęcia, że ​​zmieniłeś zmienną warunkową w połowie bez przeczytania każdej instrukcji. Ponieważ jednak switchogranicza Cię tylko do jednej zmiennej warunkowej, na pierwszy rzut oka widzę, co się dzieje.

Jednak pod koniec dnia wolałbym ani switchjednego, ani jednego łańcucha if...else if. Często lepszym rozwiązaniem jest jakaś tabela skoków lub słownik dla przypadków takich jak w pierwotnym pytaniu lub polimorfizm (jeśli twój język to obsługuje). Oczywiście nie zawsze jest to możliwe, ale szukałem rozwiązania, którego unika się switchjako pierwszy krok ...

Dean Harding
źródło
4
polimorfizm ma tę wadę, że fragmentuje kod, przez co trudniej go zrozumieć niż w jednym miejscu. Dlatego możesz się nieco zawahać przed przejściem do tego.
Dlaczego tabela skoków / słownik jest lepsza niż przełącznik?
Titou
14
switch (which) {
  case 0: comment = "String 1"; break;
  case 1: comment = "String 2"; break;
  case 2: comment = "String 3"; break;
  default: comment = "Oops"; break;
}

Powyższy sposób pisania tego typu skrzynki jest dość powszechny. Powodem, dla którego poczułeś skrzynkę przełącznika, jeśli jest ona nieporęczna, jest to, że twoje ciało było tylko jedną linią, a przy skrzynce przełącznika potrzebujesz również instrukcji break. Zatem obudowa przełącznika miała dwa razy większy rozmiar ciała niż w innym przypadku. W przypadku bardziej znaczącego kodu instrukcja break nie doda wiele do treści. W przypadku treści jednowierszowych powszechną praktyką jest pisanie kodu w tym samym wierszu co instrukcja case.

Jak już wspomnieli inni, przypadek przełącznika sprawia, że ​​intencja jest bardziej wyraźna, chcesz podjąć decyzję na podstawie wartości pojedynczej zmiennej / wyrażenia. Moje komentarze są wyłącznie z punktu widzenia czytelności i nie są oparte na wydajności.

aufather
źródło
1
Jeśli umieścisz przełącznik w metodzie i każdy przypadek returnma odpowiedni ciąg, możesz wyeliminować breakinstrukcje.
Robert Harvey
Twoje rozwiązanie jest również najszybsze.
Titou
8

W takim przypadku instrukcja switch bardziej pasuje do intencji kodu: wybierz akcję, która ma zostać podjęta na podstawie pojedynczej wartości.

Z drugiej strony zdania if są znacznie trudniejsze do odczytania - musisz spojrzeć na wszystkie, aby się upewnić, co się dzieje. Dla mnie jest to mniej kodu (nawet jeśli liczba znaków może być nieco wyższa), ponieważ jest mniej do przeanalizowania w myślach.

FinnNk
źródło
8

Zgadzam się z Jerrym, że tablica ciągów jest lepsza w tym konkretnym przypadku, ale ogólnie lepiej jest użyć instrukcji switch / case niż łańcucha elseifs. Jest łatwiejszy do odczytania, a czasem kompilator może lepiej zoptymalizować w ten sposób, ale jest też jeszcze jedna korzyść: jest o wiele łatwiejszy do debugowania.

Kiedy naciśniesz ten przełącznik, musisz tylko raz przejść do właściwej gałęzi, zamiast ostrożnie przeskakiwać kilka instrukcji if pojedynczo, i być może zbyt szybko uderzać w klawisz, przechodzić obok niego i coś pominąć i mieć Rozpocząć od nowa.

Mason Wheeler
źródło
3

Wolę zamianę w tego rodzaju przypadkach, znacznie lepiej pasuje do kodu, wykonaj inną instrukcję dla każdej innej wartości wejściowej. if..elseDziała bardziej jak „trick”, aby osiągnąć ten sam efekt.

switch instrukcje są również czystsze, łatwo jest w nich ukryć literówkę ==

Ponadto w przypadku dużych bloków w C przełączanie jest szybsze.

else..ifmoże być bardziej odpowiednie, gdy masz coś w rodzaju zakresów (od 1 do 100, zrób to, od 100 do 200, zrób to) lub w C, gdy próbujesz dokonać przełączenia z elementami takimi jak łańcuchy (jest to możliwe w innych językach). Który jest taki sam.

Zwykle używam wielu przełączników, gdy programuję w C.

Khelben
źródło
2

Wybierz coś wydajnego, zwięzłego, a następnie udokumentuj nie tylko to, co zrobiłeś, ale dlaczego.

Kod może zostać zmieniony i nie zawsze przez jego oryginalnego autora.

Są chwile, kiedy możesz celowo wybrać jedną implementację zamiast drugiej, ponieważ myślisz o kodzie, który nie istnieje.

Walt Stoneburner
źródło
2

Generalnie nie lubię żadnego z tych podejść. Długi przełącznik lub jeśli instrukcje dopiero zaczynają być przekształcane w abstrakcję zorientowaną obiektowo (jednak twój przykład sklasyfikowałbym jako krótki, a nie długi).

Osobiście owinąłbym ten rodzaj kodu osobną metodą pomocniczą.

private string GetInsult()
{
    int which = (int)(Math.random() * 3);  //  Result is 0, 1, or 2.

    switch (which) {
        case 0: return "You look so much better than usual.";
        case 1: return "Your work is up to its usual standards.";
        case 2: return "You're quite competent for so little experience.";
        default: return "Oops -- something is wrong with this code.";
    }
}

public void Foo()
{
    string comment = GetInsult();
    Print(comment);
}

Umieszczenie przełącznika w osobnej metodzie umożliwia umieszczenie instrukcji return bezpośrednio wewnątrz instrukcji switch (przynajmniej w c #), eliminując również potrzebę instrukcji break, co znacznie ułatwia odczytanie kodu.

I to jest o wiele ładniejsze niż podejście if / else if / else if.

Pete
źródło
3
Osobiście nienawidzę metody „włóż ją w inną metodę, ponieważ wygląda to brzydko”. Lista metod bałaganu i wygląda gorzej IMHO. Zrobiłbym to tylko, jeśli A) kod jest gdzieś zduplikowany lub B)
Przydałby się
1
jaką listę metod? i dlaczego lista metod jest zaśmiecona gorzej niż kod? Myślałem, że
minęliśmy
@TheLQ Zgadzam się z tobą ogólnie, ale w tym przypadku „komentarz =” jest rzeczywiście uwzględniony w propozycji Pete'a.
Titou
0

W Pythonie nie ma instrukcji switch, ponieważ jeśli / elif / else jest miły:

a = 5

if a==1:
    print "do this"
elif a == 2:
    print "do that"
elif a == 3:
    print "do the other"
elif 3 < a < 9:
    print "do more"
elif 9 <= a < 15:
    print "do nothing"
else:
    print "say sorry"

Proste prawda?

Christopher Mahan
źródło
Elifto tylko instrukcja if z kilkoma brakującymi literami. To zdecydowanie bardziej przypomina ifinstrukcję niż instrukcję switch. Fakt, że Python NIE ma przełącznika, sprawia, że ​​ktoś, kto ich nienawidzi (jak ja), myśli, że nie są sami.
Dan Rosenstark
Formatowanie w języku Python działa przy przepełnieniu stosu, ale nie w programmers.stackexchange.com :(
Christopher Mahan
Powinieneś ich ostrzec, metachyba że jest to znany temat. Dzięki, że dałeś mi świadka.
Dan Rosenstark
Tak, dowiedziałem się, że to się dzieje na meta.programmers.stackexchange.com/questions/308/…
Christopher Mahan
1
@ Yar, przypomina mi o moich dniach administratora Wikipedii ... Oh Joy. (czy jestem już zupełnie nie na temat?)
Christopher Mahan
0

Jedną z rzeczy, która sprawia, że ​​styl C / C # jest switchszczególnie denerwujący, jest nacisk na casewartość, która jest dosłowna. Jedną fajną rzeczą w VB / VB.NET jest to, że select/casekażdy przypadek może być dowolnym wyrażeniem logicznym. To jest wygodne. O ile seria wzajemnie wykluczających się wyrażeń boolowskich jest często pomocna, seria if / else ifs jest bardziej elastyczna, nie wspominając już o bardziej wydajnym pisaniu i czytaniu.

Joel Brown
źródło