Jaka jest korzyść z zakończenia if… else if konstruuje się z klauzulą ​​else?


136

Nasza organizacja ma wymaganą zasadę kodowania (bez żadnego wyjaśnienia), która:

if… else if konstrukcje powinny być zakończone klauzulą ​​else

Przykład 1:

if ( x < 0 )
{
   x = 0;
} /* else not needed */

Przykład 2:

if ( x < 0 )
{
    x = 0;
}
else if ( y < 0 )
{
    x = 3;
}
else    /* this else clause is required, even if the */
{       /* programmer expects this will never be reached */
        /* no change in value of x */
}

Do jakiego typu edge case ma to służyć?

Co mnie również niepokoi, jeśli chodzi o powód, to fakt, że w przykładzie 1 nie jest potrzebny, elseale w przykładzie 2 . Jeśli powodem jest możliwość ponownego elseużycia i rozszerzalność, myślę, że należy je zastosować w obu przypadkach.


30
Może zapytaj swoją firmę o powód i korzyść. Na pierwszy rzut oka zmusza programistę do przemyślenia tego i dodania komentarza „nie wymaga żadnej akcji”. To samo uzasadnienie (i przynajmniej tak kontrowersyjne jak) sprawdzone wyjątki Java.
Thilo

32
Przykład 2 jest w rzeczywistości dobrym przykładem, gdzie assert(false, "should never go here")może mieć sens
Thilo

2
Nasza organizacja ma podobne zasady, ale nie do końca tak szczegółowe. Cel w naszym przypadku jest dwojaki. Po pierwsze, spójność kodu. Po drugie, brak luźnych ciągów znaków / czytelności. Wymaganie else, nawet jeśli nie jest potrzebne, zwiększa przejrzystość kodu, nawet jeśli nie jest on udokumentowany. Wymaganie innego jest czymś, co robiłem w przeszłości, nawet jeśli nie było to potrzebne, aby upewnić się, że uwzględniłem wszystkie możliwe wyniki w aplikacji.
LuvnJesus

4
Zawsze możesz pokonać, jeśli z if (x < 0) { x = 0; } else { if (y < 0) { x = 3; }}. Możesz też po prostu przestrzegać takich zasad, z których wiele jest głupich, po prostu dlatego, że jest to wymagane.
Jim Balter

3
@Thilo Trochę się spóźniłem, ale i tak nikt nie złapał błędu: nic nie wskazuje na to, żeby coś innego nigdy się nie wydarzyło, tylko że nie powinno mieć żadnych skutków ubocznych (co wydaje się normalne podczas < 0sprawdzania), więc assert idzie aby zawiesić program w prawdopodobnie najczęstszym przypadku, gdy wartości mieszczą się w oczekiwanych granicach.
Loduwijk

Odpowiedzi:


148

Jak wspomniano w innej odpowiedzi, pochodzi to z wytycznych kodowania MISRA-C. Celem jest programowanie obronne, pojęcie często używane w programowaniu o znaczeniu krytycznym.

Oznacza to, że każdy if - else ifmusi kończyć się a else, a każdy switchmusi kończyć się a default.

Są ku temu dwa powody:

  • Kod samodokumentujący. Jeśli napiszesz, elseale zostawisz to puste, oznacza to: „Zdecydowanie rozważyłem scenariusz, w którym ani ifnie, ani nie else ifsą prawdziwe”.

    Brak pisania elsetam oznacza: „albo rozważyłem scenariusz, w którym ani ifani nie else ifsą prawdziwe, albo zupełnie zapomniałem o tym pomyśleć i potencjalnie jest tutaj gruby błąd w moim kodzie”.

  • Zatrzymaj niekontrolowany kod. W oprogramowaniu o znaczeniu krytycznym musisz pisać solidne programy, które uwzględniają nawet bardzo mało prawdopodobne. Możesz więc zobaczyć kod taki jak

    if (mybool == TRUE) 
    {
    } 
    else if (mybool == FALSE) 
    {
    }
    else
    {
      // handle error
    }

    Ten kod będzie całkowicie obcy dla programistów pecetów i informatyków, ale ma doskonały sens w przypadku oprogramowania o znaczeniu krytycznym, ponieważ wychwytuje przypadek, w którym „mybool” został uszkodzony z jakiegokolwiek powodu.

    Historycznie rzecz biorąc, można by się obawiać uszkodzenia pamięci RAM z powodu zakłóceń elektromagnetycznych / szumów. Nie stanowi to dziś większego problemu. O wiele bardziej prawdopodobne jest, że uszkodzenie pamięci występuje z powodu błędów w innym miejscu w kodzie: wskaźników do niewłaściwych lokalizacji, błędów poza zakresem tablicy, przepełnienia stosu, niekontrolowanego kodu itp.

    Tak więc przez większość czasu taki kod powraca, aby uderzyć się w twarz, gdy napiszesz błędy na etapie implementacji. Oznacza to, że może być również używany jako technika debugowania: program, który piszesz, informuje Cię, kiedy napisałeś błędy.


EDYTOWAĆ

Odnośnie tego, dlaczego elsenie jest potrzebny po każdym pojedynczym if:

An if-elselub if-else if-elsecałkowicie obejmuje wszystkie możliwe wartości, które może mieć zmienna. Ale proste ifstwierdzenie niekoniecznie obejmuje wszystkie możliwe wartości, ma znacznie szersze zastosowanie. Najczęściej chcesz po prostu sprawdzić pewien warunek, a jeśli nie jest spełniony, nie rób nic. Wtedy po prostu nie ma sensu pisać programów obronnych, które obejmowałyby ten elseprzypadek.

Dodatkowo zaśmiecałoby to całkowicie kod, gdybyś pisał pusty elsepo każdym if.

MISRA-C: 2012 15.7 nie podaje żadnego uzasadnienia, dlaczego elsenie jest potrzebne, po prostu stwierdza:

Uwaga: elseoświadczenie końcowe nie jest wymagane w przypadku prostego if oświadczenia.


6
Jeśli pamięć jest uszkodzona, spodziewam się, że zrujnuje znacznie więcej niż tylko mybool, w tym może sam kod sprawdzający. Dlaczego nie dodasz kolejnego bloku, który sprawdza, czy if/else if/elseskompilowałeś się zgodnie z oczekiwaniami? A potem jeszcze jeden do zweryfikowania poprzedniego weryfikatora?
Oleg V. Volkov

49
Westchnienie. Spójrzcie, jeśli nie macie absolutnie żadnego innego doświadczenia poza programowaniem na komputerze stacjonarnym, nie ma potrzeby komentowania wszystkiego, czego oczywiście nie macie. Dzieje się tak zawsze, gdy omawiane jest programowanie obronne i programista PC zatrzymuje się. Nie bez powodu dodałem komentarz „Ten kod będzie zupełnie obcy programistom pecetów”. Nie można programować oprogramowania krytycznego dla bezpieczeństwa do uruchamiania na komputerach stacjonarnych z pamięcią RAM . Kropka. Sam kod będzie znajdował się w pamięci flash ROM z sumami kontrolnymi ECC i / lub CRC.
Lundin

6
@Deduplicator Rzeczywiście (chyba, że ​​nie myboolma typu boolowskiego, jak miało to miejsce, zanim C otrzymał swój własny bool; wtedy kompilator nie zrobiłby tego założenia bez dodatkowej analizy statycznej). A na temat „Jeśli napiszesz inny, ale zostawisz go pustym, oznacza to:„ Zdecydowanie rozważyłem scenariusz, w którym ani jeśli, ani inaczej, jeśli nie są prawdziwe ”.”: Moją pierwszą reakcją jest założenie, że programista zapomniał wstawić kod inny blok, w przeciwnym razie po prostu stoi pusty blok else? Odpowiedni // unusedbyłby komentarz, a nie tylko pusty blok.
JAB

5
@JAB Tak, elseblok musi zawierać jakiś komentarz, jeśli nie ma kodu. Powszechną praktyką pusty elsejest jeden średnik plus komentarz: else { ; // doesn't matter }. Ponieważ nie ma powodu, dla którego ktokolwiek mógłby po prostu wpisać wcięty, pojedynczy średnik we własnym wierszu. Podobna praktyka jest czasami stosowane w pustych pętli: while(something) { ; // do nothing }. (oczywiście kod z podziałami wierszy. Komentarze SO na to nie pozwalają)
Lundin

5
Chciałbym zauważyć, że ten przykładowy kod z dwoma IF i może przejść do innego, nawet w zwykłym oprogramowaniu komputerowym w środowiskach wielowątkowych, więc wartość mybool można zmienić w międzyczasie
Iłya Bursov

61

Twoja firma postępowała zgodnie ze wskazówkami dotyczącymi kodowania MISRA. Istnieje kilka wersji tych wytycznych, które zawierają tę zasadę, ale od MISRA-C: 2004 :

Reguła 14.10 (wymagana): All if… else if konstrukcje powinny być zakończone klauzulą ​​else.

Ta reguła ma zastosowanie, gdy po instrukcji if następuje co najmniej jedna instrukcja if; po ostatniej innej ifnastępuje else oświadczenie. W przypadku prostego ifoświadczenia, else oświadczenie nie musi być dołączane. Wymaganiem końcowym else jest programowanie obronne. elseOświadczenie albo podjąć odpowiednie działania lub zawierać odpowiedni komentarz, dlaczego nie podjęto działań. Jest to zgodne z wymogiem umieszczenia ostatniej defaultklauzuli w switchoświadczeniu. Na przykład ten kod jest prostą instrukcją if:

if ( x < 0 )
{
 log_error(3);
 x = 0;
} /* else not needed */

podczas gdy poniższy kod demonstruje if, else ifconstruct

if ( x < 0 )
{
 log_error(3);
 x = 0;
}
else if ( y < 0 )
{
 x = 3;
}
else /* this else clause is required, even if the */
{ /* programmer expects this will never be reached */
 /* no change in value of x */
}

W MISRA-C: 2012 , która zastępuje wersję z 2004 roku i jest obecną rekomendacją dla nowych projektów, ta sama zasada istnieje, ale ma numer 15.7 .

Przykład 1: w pojedynczej instrukcji if programista może potrzebować sprawdzić n liczbę warunków i wykonać pojedynczą operację.

if(condition_1 || condition_2 || ... condition_n)
{
   //operation_1
}

Podczas normalnego użytkowania wykonywanie operacji nie jest konieczne przez cały czas, gdy ifjest używane.

Przykład 2: Tutaj programista sprawdza n liczbę warunków i wykonuje wiele operacji. W regularnego stosowania if..else ifjest jak switchmoże trzeba przeprowadzić operację jak domyślnie. Więc użycie elsejest potrzebne zgodnie ze standardem misra

if(condition_1 || condition_2 || ... condition_n)
{
   //operation_1
}
else if(condition_1 || condition_2 || ... condition_n)
{
  //operation_2
}
....
else
{
   //default cause
}

Aktualne i wcześniejsze wersje tych publikacji można kupić w sklepie internetowym MISRA ( przez ).


1
Dziękuję, twoja odpowiedź jest w całości zawartością reguły Misra, ale oczekuję odpowiedzi za moje zdezorientowanie w redagującej części pytania.
Trevor

2
Dobra odpowiedź. Czy z ciekawości ci przewodnicy mówią cokolwiek o tym, co zrobić, jeśli spodziewasz się, że elseklauzula będzie nieosiągalna? (Zamiast tego zostaw ostateczny warunek, może wyrzuć błąd?)
jpmc26

17
Odrzucę głosowanie za plagiatem i linkami, które naruszają prawa autorskie. Zmienię post, aby było jaśniejsze, jakie są Twoje słowa i jakie są słowa MISRY. Początkowo ta odpowiedź była niczym innym jak surową kopiowaniem / wklejaniem.
Lundin

8
@TrieuTheVan: Pytania nie mogą być ruchomymi celami . Upewnij się, że Twoje pytanie jest kompletne, zanim je wyślesz.
TJ Crowder

1
@ jpmc26 Nie, ale celem MISRA nie jest podanie ciasnego kodu, ale zapewnienie bezpiecznego kodu. Każdy kompilator w dzisiejszych czasach i tak zoptymalizuje nieosiągalny kod, więc nie ma wady.
Graham

19

Jest to odpowiednik wymagania domyślnego przypadku w każdym przełączniku.

Ta dodatkowa opcja zmniejszy zakres kodu twojego programu.


Z mojego doświadczenia z przenoszeniem jądra linuxa lub kodu Androida na inną platformę, wiele razy robimy coś nie tak iw logcat'ie widzimy błąd jak

if ( x < 0 )
{
    x = 0;
}
else if ( y < 0 )
{
    x = 3;
}
else    /* this else clause is required, even if the */
{       /* programmer expects this will never be reached */
        /* no change in value of x */
        printk(" \n [function or module name]: this should never happen \n");

        /* It is always good to mention function/module name with the 
           logs. If you end up with "this should never happen" message
           and the same message is used in many places in the software
           it will be hard to track/debug.
        */
}

2
W tym miejscu __FILE__i __LINE__makra są przydatne, aby ułatwić znalezienie lokalizacji źródłowej, jeśli wiadomość zostanie kiedykolwiek wydrukowana.
Peter Cordes,

9

Tylko krótkie wyjaśnienie, ponieważ zrobiłem to około 5 lat temu.

Nie ma (w większości języków) żadnego wymagania składniowego, aby zawierać elseinstrukcję "null" (i jest to zbędne {..}), aw "prostych, małych programach" nie ma takiej potrzeby. Ale prawdziwi programiści nie piszą „prostych, małych programów” i, co równie ważne, nie piszą programów, które zostaną użyte raz, a następnie wyrzucone.

Kiedy pisze się if / else:

if(something)
  doSomething;
else
  doSomethingElse;

wszystko wydaje się proste i nie ma nawet sensu dodawania {..}.

Ale któregoś dnia, za kilka miesięcy, jakiś inny programista (nigdy nie popełniłbyś takiego błędu!) Będzie musiał „ulepszyć” program i doda instrukcję.

if(something)
  doSomething;
else
  doSomethingIForgot;
  doSomethingElse;

Nagle doSomethingElsejakby zapomina, że ​​to powinno być w elsenodze.

Więc jesteś dobrym małym programistą i zawsze używasz {..}. Ale piszesz:

if(something) {
  if(anotherThing) {
    doSomething;
  }
}

Wszystko jest dobrze, dopóki ten nowy dzieciak nie dokona modyfikacji o północy:

if(something) {
  if(!notMyThing) {
  if(anotherThing) {
    doSomething;
  }
  else {
    dontDoAnything;  // Because it's not my thing.
  }}
}

Tak, jest nieprawidłowo sformatowany, ale tak samo jest z połową kodu w projekcie, a „autoformatator” jest obciążany przez wszystkie #ifdefinstrukcje. Oczywiście rzeczywisty kod jest znacznie bardziej skomplikowany niż ten przykład zabawki.

Niestety (lub nie), od kilku lat nie mam takich rzeczy, więc nie mam na myśli świeżego „prawdziwego” przykładu - powyższy jest (oczywiście) wymyślony i trochę szalony.


7

To po to, by uczynić kod bardziej czytelnym dla późniejszych odniesień i aby było jasne, do późniejszego recenzentem, że pozostałe przypadki obsługiwane przez ostatnie elsezrobić nic przypadki, tak że nie są one pomijane jakoś na pierwszy rzut oka.

Jest to dobra praktyka programistyczna, dzięki której kod można wielokrotnie wykorzystywać i rozszerzać .


6

Chciałbym uzupełnić - i częściowo zaprzeczyć - wcześniejszym odpowiedziom. Chociaż z pewnością powszechne jest użycie if-else, jeśli w sposób podobny do przełącznika, który powinien obejmować pełny zakres możliwych do pomyślenia wartości wyrażenia, w żaden sposób nie gwarantuje się, że jakikolwiek zakres możliwych warunków jest w pełni spełniony. To samo można powiedzieć o samej konstrukcji przełącznika, stąd wymóg użycia klauzuli domyślnej, która wyłapuje wszystkie pozostałe wartości i może, jeśli i tak nie jest wymagana, służyć jako zabezpieczenie asercji.

Samo pytanie zawiera dobry kontrprzykład: Drugi warunek w ogóle nie odnosi się do x (co jest powodem, dla którego często wolę bardziej elastyczny wariant oparty na if niż wariant oparty na przełączniku). Z przykładu jasno wynika, że ​​jeśli warunek A jest spełniony, x należy ustawić na określoną wartość. Jeśli A nie zostanie spełniony, testowany jest warunek B. Jeśli jest spełnione, to x powinno otrzymać inną wartość. Jeśli ani A, ani B nie są spełnione, to x powinno pozostać niezmienione.

Tutaj widzimy, że należy użyć pustej gałęzi else, aby skomentować zamiary programisty względem czytelnika.

Z drugiej strony nie rozumiem, dlaczego musi istnieć klauzula else, szczególnie w przypadku ostatniego i najbardziej wewnętrznego stwierdzenia if. W C nie ma czegoś takiego jak „else if”. Jest tylko wtedy i jeszcze. Zamiast tego, według MISRA, konstrukcja powinna być formalnie wcięta w ten sposób (i powinienem był umieścić otwierające nawiasy klamrowe we własnych wierszach, ale nie podoba mi się to):

if (A) {
    // do something
}
else {
    if (B) {
        // do something else (no pun intended)
    }
    else {
        // don't do anything here
    }
}

Kiedy MISRA prosi o umieszczenie nawiasów klamrowych wokół każdej gałęzi, zaprzecza sobie, wspominając „jeśli ... inaczej, jeśli konstrukcje”.

Każdy może sobie wyobrazić brzydotę głęboko zagnieżdżonych drzew if else, patrz tutaj na marginesie . Teraz wyobraź sobie, że ta konstrukcja może zostać dowolnie rozszerzona w dowolnym miejscu. Wtedy proszenie o klauzulę else na końcu, ale nie nigdzie indziej, staje się absurdalne.

if (A) {
    if (B) {
        // do something
    }
    // you could to something here
}
else {
    // or here
    if (B) { // or C?
        // do something else (no pun intended)
    }
    else {
        // don't do anything here, if you don't want to
    }
    // what if I wanted to do something here? I need brackets for that.
}

Jestem więc pewien, że ludzie, którzy opracowali wytyczne MISRA, mieli na myśli zamiar - jak gdyby - inaczej.

W końcu sprowadza się to do dokładnego zdefiniowania, co oznacza konstrukt „jeśli ... inaczej, jeśli”


5

Podstawowym powodem jest prawdopodobnie pokrycie kodu i niejawne inne: jak zachowa się kod, jeśli warunek nie jest prawdziwy? Aby przeprowadzić autentyczne testy, potrzebujesz sposobu, aby sprawdzić, czy test został wykonany z warunkiem fałszywym. Jeśli każdy przypadek testowy przechodzi przez klauzulę if, Twój kod może mieć problemy w świecie rzeczywistym z powodu warunku, którego nie przetestowałeś.

Jednak niektóre warunki mogą przypominać przykład 1, np. W zeznaniu podatkowym: „Jeśli wynik jest mniejszy niż 0, wprowadź 0”. Nadal musisz mieć test, w którym warunek jest fałszywy.


5

Logicznie rzecz biorąc, każdy test zakłada dwie gałęzie. Co robisz, jeśli jest prawdą, i co robisz, jeśli jest fałszywa.

W przypadkach, w których którakolwiek gałąź nie ma funkcjonalności, rozsądne jest dodanie komentarza wyjaśniającego, dlaczego nie musi mieć funkcjonalności.

Może to być korzystne dla następnego programisty utrzymania ruchu. Nie powinni musieć szukać zbyt daleko, aby zdecydować, czy kod jest poprawny. Możesz coś w rodzaju Prehunt the Elephant .

Osobiście pomaga mi to, ponieważ zmusza mnie do spojrzenia na inny przypadek i oceny go. Może to być warunek niemożliwy, w takim przypadku mogę zgłosić wyjątek, ponieważ umowa jest naruszona. Może to być łagodne, w takim przypadku komentarz może wystarczyć.

Twój przebieg może się różnić.


4

W większości przypadków, gdy masz tylko jedno ifoświadczenie, prawdopodobnie jest to jeden z takich powodów:

  • Kontrola funkcji
  • Opcja inicjalizacji
  • Opcjonalna gałąź przetwarzania

Przykład

void print (char * text)
{
    if (text == null) return; // guard check

    printf(text);
}

Ale kiedy to robisz if .. else if, prawdopodobnie jest to jeden z powodów, takich jak:

  • Dynamiczna obudowa przełącznika
  • Przetwarzanie widelca
  • Obsługa parametru przetwarzania

A jeśli if .. else ifobejmuje wszystkie możliwości, w takim przypadku ostatnia if (...)nie jest potrzebna, możesz ją po prostu usunąć, ponieważ w tym momencie jedynymi możliwymi wartościami są te objęte tym warunkiem.

Przykład

int absolute_value (int n)
{
    if (n == 0)
    {
        return 0;
    }
    else if (n > 0)
    {
        return n;
    }
    else /* if (n < 0) */ // redundant check
    {
        return (n * (-1));
    }
}

I w większości z tych powodów możliwe jest, że coś nie pasuje do żadnej z Twoich kategorii if .. else if, stąd potrzeba załatwienia ich w końcowej elseklauzuli, obsługa może odbywać się poprzez procedurę biznesową, powiadomienie użytkownika, mechanizm błędu wewnętrznego, ..itp.

Przykład

#DEFINE SQRT_TWO   1.41421356237309504880
#DEFINE SQRT_THREE 1.73205080756887729352
#DEFINE SQRT_FIVE  2.23606797749978969641

double square_root (int n)
{
         if (n  > 5)   return sqrt((double)n);
    else if (n == 5)   return SQRT_FIVE;
    else if (n == 4)   return 2.0;
    else if (n == 3)   return SQRT_THREE;
    else if (n == 2)   return SQRT_TWO;
    else if (n == 1)   return 1.0;
    else if (n == 0)   return 0.0;
    else               return sqrt(-1); // error handling
}

Ta ostatnia elseklauzula jest dość podobna do kilku innych rzeczy w językach, takich jak Javai C++, takich jak:

  • default case w instrukcji switch
  • catch(...)która pojawia się po wszystkich konkretnych catchblokach
  • finally w klauzuli try-catch

2

Nasze oprogramowanie nie miało krytycznego znaczenia dla misji, ale zdecydowaliśmy się również skorzystać z tej reguły ze względu na programowanie obronne. Dodaliśmy wyjątek rzutowania do teoretycznie nieosiągalnego kodu (przełącznik + if-else). I to wiele razy uratowało nas, ponieważ oprogramowanie szybko zawodziło, np. Gdy został dodany nowy typ i zapomnieliśmy zmienić jeden lub dwa jeśli inaczej lub przełączyć. Jako bonus bardzo łatwo było znaleźć problem.


2

Cóż, mój przykład dotyczy niezdefiniowanego zachowania, ale czasami niektórzy ludzie starają się być fantazyjni i nie udaje im się to, spójrz:

int a = 0;
bool b = true;
uint8_t* bPtr = (uint8_t*)&b;
*bPtr = 0xCC;
if(b == true)
{
    a += 3;
}
else if(b == false)
{
    a += 5;
}
else
{
    exit(3);
}

Prawdopodobnie nigdy byś się nie spodziewał, że booltak nie jest, trueani falsejednak może się to zdarzyć. Osobiście uważam, że jest to problem spowodowany przez osobę, która decyduje się na coś wymyślnego, ale dodatkowe elsestwierdzenie może zapobiec dalszym problemom.


1

Obecnie pracuję z PHP. Stworzenie formularza rejestracyjnego i formularza logowania. Po prostu używam tylko jeśli i jeszcze. Nie ma innego, jeśli lub cokolwiek, co jest niepotrzebne.

Jeśli użytkownik kliknie przycisk Prześlij -> przechodzi do następnej instrukcji if ... jeśli nazwa użytkownika zawiera mniej niż „X” znaków, to alert. Jeśli się powiedzie, sprawdź długość hasła i tak dalej.

Nie ma potrzeby stosowania dodatkowego kodu, takiego jak inny, jeśli mogłoby to zmniejszyć niezawodność czasu ładowania serwera w celu sprawdzenia całego dodatkowego kodu.

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.