it-swarm-eu.dev

Existují `break` a` pokračovat` špatné programovací postupy?

Můj šéf neustále zmiňuje, že špatní programátoři používají ve smyčkách break a continue.

Používám je neustále, protože dávají smysl; ukážu vám inspiraci:

function verify(object) {
    if (object->value < 0) return false;
    if (object->value > object->max_value) return false;
    if (object->name == "") return false;
    ...
}

Jde o to, že funkce nejprve zkontroluje správnost podmínek a poté provede skutečnou funkčnost. IMO totéž platí pro smyčky:

while (primary_condition) {
    if (loop_count > 1000) break;
    if (time_exect > 3600) break;
    if (this->data == "undefined") continue;
    if (this->skip == true) continue;
    ...
}

Myslím, že to usnadňuje čtení a ladění; ale také nevidím nevýhodu.

206
Mikhail

Když jsou použity na začátku bloku, jako první kontroly, fungují jako předpoklady, takže je to dobré.

Když jsou použity uprostřed bloku, s nějakým kódem kolem, fungují jako skryté pasti, takže je to špatné.

253
Klaim

Mohli byste si přečíst příspěvek Donalda Knutha z roku 1974 Structured Programming with Go to Statement , ve kterém diskutuje o různých způsobech použití go to které jsou strukturálně žádoucí. Zahrnují ekvivalent příkazů break a continue (mnoho použití go to tam byly vyvinuty do omezenějších konstrukcí). Je váš šéf typem, který nazývá Knuth špatným programátorem?

(Tyto příklady mě zajímaly. Obvykle se break a continue nelíbí lidem, kteří mají rádi jeden vstup a jeden výstup z jakéhokoli kódu, a ten druh se také zamračuje na více return příkazy.)

90
David Thornley

Nevěřím, že jsou špatní. Myšlenka, že jsou špatní, pochází ze dnů strukturovaného programování. Souvisí s představou, že funkce musí mít jeden vstupní bod a jeden výstupní bod, tzn. E. pouze jedna return na funkci.

To má smysl, pokud je vaše funkce dlouhá a pokud máte více vnořených smyček. Vaše funkce by však měly být krátké a měli byste zabalit smyčky a jejich těla do krátkých vlastních funkcí. Obecně platí, že vynucení funkce mít jediný výstupní bod může mít za následek velmi spletitou logiku.

Pokud je vaše funkce velmi krátká, pokud máte jednu smyčku nebo v nejhorším případě dvě vnořené smyčky, a pokud je tělo smyčky velmi krátké, pak je zcela jasné, co break nebo continue ano. Je také jasné, co více příkazů return dělá.

Těmito otázkami se zabývá „Čistý kodex“ Robert C. Martin a „Refaktoring“ Martina Fowlera.

46
Dima

Špatní programátoři mluví absolutně (stejně jako Sith). Dobrý programátoři používají nejjasnější možné řešení (všechny ostatní věci jsou stejné).

Používání přerušení a pokračování často ztěžuje sledování kódu. Pokud je však jejich nahrazení ještě obtížněji dodržuje, je to špatná změna.

Příklad, který jste uvedli, je určitě situace, kdy by přestávky a pokračování měly být nahrazeny něčím elegantnějším.

44
Matthew Read

Většina lidí si myslí, že je to špatný nápad, protože chování nelze snadno předvídat. Pokud čtete kód a uvidíte while(x < 1000){} předpokládáte, že bude spuštěn, dokud x> = 1000 ... Ale pokud jsou uprostřed přestávky, pak to neplatí, takže nemůžete opravdu věřit svému opakování ...

Je to stejný důvod, proč se lidem nelíbí GOTO: určitě, může být dobře použit, ale může to také vést ke zbožnému špagetovému kódu, kde kód náhodně skáče ze sekce do sekce.

Pro sebe, kdybych chtěl udělat smyčku, která se zlomila na více než jedné podmínce, udělal bych while(x){} potom přepnul X na false, když jsem potřeboval vypuknout. Konečný výsledek by byl stejný a každý, kdo si přečte tento kód, by věděl, že se blíže podívá na věci, které změnily hodnotu X.

25
Satanicpuppy

Ano, můžete [re] psát programy bez příkazů break (nebo se vrací ze středu smyček, které dělají totéž). Možná však budete muset zavést další proměnné a/nebo duplikáty kódu, které obvykle ztíží pochopení programu. Pascal (programovací jazyk) byl z tohoto důvodu velmi špatný zejména pro začátečníky. Váš šéf v podstatě chce, abyste programovali v Pascalových řídících strukturách. Kdyby byl Linus Torvalds ve vašich botách, pravděpodobně by ukázal vašemu šéfovi prostředníček!

Výsledkem je počítačová věda zvaná Kosarajuova hierarchie kontrolních struktur, která sahá až do roku 1973 a která je zmíněna v Knuthově (více) slavném článku o gotoch z roku 1974. (Mimochodem, tento list Knutha již mimochodem doporučil David Thornley, mimochodem) .) V roce 1973 S. Rao Kosaraju dokázal, že není možné přepsat všechny programy, které mají víceúrovňové zlomy hloubky n , na programy se zlomem. hloubka menší než n bez zavedení dalších proměnných. Řekněme však, že jde pouze o čistě teoretický výsledek. (Stačí přidat několik dalších proměnných ?! Určitě to můžete udělat, abyste potěšili svého šéfa ...)

Z pohledu softwarového inženýrství je mnohem důležitější novější dokument z roku 1995, který napsal Eric S. Roberts s názvem Opakování smyček a strukturované programování: Opětovné otevření debaty ( http://cs.stanford.edu/people/eroberts/papers/SIGCSE-1995/LoopExits.pdf ). Roberts shrnuje několik empirických studií provedených ostatními před ním. Například když byla skupina studentů typu CS101 požádána o napsání kódu pro funkci provádějící sekvenční vyhledávání v poli, autor studie uvedl následující informace o těch studentech, kteří použili break/return/goto k ukončení smyčka sekvenčního vyhledávání, když byl prvek nalezen:

Ještě jsem nenašel jediného člověka, který se pokusil o program pomocí [tohoto stylu], který vytvořil nesprávné řešení.

Roberts také říká, že:

Studenti, kteří se pokusili problém vyřešit bez použití explicitního návratu ze smyčky for, si vedli mnohem méně dobře: pouze sedm ze 42 studentů, kteří se pokusili o tuto strategii, dokázalo vytvořit správná řešení. Toto číslo představuje úspěšnost menší než 20%.

Ano, možná budete zkušenější než studenti CS101, ale bez použití příkazu break (nebo ekvivalentního návratu/goto od středu smyček), nakonec napíšete kód, který je sice pěkně strukturovaný, ale je dostatečně chlupatý, co se týče extra logiky proměnné a duplikace kódu, že někdo, pravděpodobně sám, do něj vloží logické chyby, zatímco se snaží dodržovat styl kódování vašeho šéfa.

Tady také řeknu, že Robertsův papír je mnohem dostupnější pro průměrného programátora, takže lepší první čtení než Knuth. Je také kratší a pokrývá užší téma. Pravděpodobně byste to mohli dokonce doporučit svému šéfovi, i když je to spíše management než typ CS.

16
Fizz

Nepovažuji použít ani jeden z těchto špatných postupů, ale jejich přílišné použití ve stejné smyčce by mělo zaručit přehodnocení logiky použité ve smyčce. Používejte je střídmě.

9
Bernard

Příklad, který jste uvedli, nepotřebuje přestávky ani pokračuje:

while (primary-condition AND
       loop-count <= 1000 AND
       time-exec <= 3600) {
   when (data != "undefined" AND
           NOT skip)
      do-something-useful;
   }

Můj „problém“ se 4 řádky ve vašem příkladu je v tom, že jsou všechny na stejné úrovni, ale dělají různé věci: některé přestávky, některé pokračují ... Musíte přečíst každý řádek.

V mém vnořeném přístupu, čím hlouběji jdete, tím užitečnější je kód.

Pokud byste však hluboko uvnitř našli důvod k zastavení smyčky (jiné než primární), mělo by to mít za následek přerušení nebo návrat. Upřednostňoval bych to před použitím další vlajky, která se má testovat v nejvyšším stavu. Přerušení/návrat je přímější; lépe uvádí záměr než nastavení další proměnné.

7
Peter Frings

„Špatnost“ závisí na tom, jak je používáte. Obvykle používám přestávky ve smyčkových konstrukcích POUZE, když mi to ušetří cykly, které nelze uložit pomocí refaktoringu algoritmu. Například procházením kolekcí hledáním položky s hodnotou v konkrétní vlastnosti nastavené na true. Pokud potřebujete pouze vědět, že jedna z položek měla tuto vlastnost nastavenou na true, jakmile dosáhnete tohoto výsledku, je dobré přerušit smyčku vhodným způsobem.

Pokud použití přerušení nezpůsobí, že kód bude zvlášť snadno čitelný, kratší na spuštění nebo uložení cyklů ve zpracování významným způsobem, je nejlepší je nepoužívat. Mám tendenci kódovat k „nejnižšímu společnému jmenovateli“, když je to možné, abych se ujistil, že každý, kdo mě následuje, se může snadno podívat na můj kód a zjistit, co se děje (nejsem vždy úspěšný). Přestávky to snižují, protože zavádějí liché vstupní/výstupní body. Zneužívané se mohou chovat velmi podobně jako výrok „goto“, který je z rána.

6
Joel Etherton

Rozhodně ne ... Ano použití goto je špatné, protože zhoršuje strukturu vašeho programu a také je velmi obtížné pochopit kontrolní tok.

Ale použití příkazů jako break a continue je v těchto dnech naprosto nezbytné a vůbec se nepovažuje za špatnou programovací praxi.

A také není tak obtížné pochopit kontrolní tok při použití break a continue. V konstruktech jako switch je příkaz break naprosto nezbytný.

4
Radheshyam Nayak

Nahradil bych váš druhý fragment kódu

while (primary_condition && (loop_count <= 1000 && time_exect <= 3600)) {
    if (this->data != "undefined" && this->skip != true) {
        ..
    }
}

ne z důvodu únavy - vlastně si myslím, že je to snáze čitelné a někdo pochopí, o co jde. Obecně lze říci, že podmínky pro vaše smyčky by měly být obsaženy čistě v těch podmínkách smyčky, které nejsou poseté celým tělem. Existují však situace, kdy break a continue mohou pomoci čitelnosti. break moreso než continue Mohl bych přidat: D

3
El Ronnoco

Základní představa vychází ze schopnosti sémanticky analyzovat váš program. Pokud máte jeden záznam a jeden výstup, matematika potřebná k označení možných stavů je podstatně jednodušší, než kdybyste museli spravovat rozcestí.

Částečně se tento problém odráží v možnosti koncepčně uvažovat o vašem kódu.

Upřímně řečeno, váš druhý kód není zřejmý. Co to dělá? Pokračuje „pokračovat“ nebo „další“ smyčka? Nemám ponětí. Alespoň váš první příklad je jasný.

3
Paul Nathan

Souhlasím s vaším šéfem. Jsou špatné, protože vytvářejí metody s vysokou cyklomatickou složitostí. Takové metody jsou obtížně čitelné a obtížně testovatelné. Naštěstí existuje snadné řešení. Extrahujte tělo smyčky do samostatné metody, kde "pokračovat" se stává "návratem". „Návrat“ je lepší, protože po „návratu“ je konec - už se nemusíte starat o místní stát.

Pro „break“ rozbalte smyčku samotnou do samostatné metody, nahrazte „break“ za „return“.

Pokud extrahované metody vyžadují velké množství argumentů, jedná se o indikaci, jak extrahovat třídu - buď je shromáždit do kontextu objektu.

2
kevin cline

Nesouhlasím s tvým šéfem. Existují vhodná místa pro použití break a continue. Ve skutečnosti důvodem, proč byly do moderních programovacích jazyků zavedeny výjimky a zpracování výjimek, je to, že nemůžete vyřešit všechny problémy pomocí pouze structured techniques.

Na vedlejší poznámk Nechci zde zahájit náboženskou diskuzi, ale mohli byste restrukturalizovat svůj kód tak, aby byl ještě lépe čitelný takto:

while (primary_condition) {
    if (loop_count > 1000) || (time_exect > 3600) {
        break;
    } else if ( ( this->data != "undefined") && ( !this->skip ) ) {
       ... // where the real work of the loop happens
    }
}

Na druhé straně poznámka

Osobně se mi nelíbí použití ( flag == true ) v podmínech, protože pokud je proměnná již booleovskou, pak zavádíte další srovnání, které se musí stát, když hodnota booleovské odpovědi má požadovanou odpověď - pokud ovšem nejste si jisti, že váš kompilátor optimalizuje toto extra porovnání pryč.

2
Zeke Hansell

V zásadě nejsem proti continue a break, ale myslím si, že se jedná o konstrukty velmi nízké úrovně, které lze velmi často nahradit něčím ještě lepším.

Jako příklad zde používám C #, zvažte případ, že si přejeme iterovat přes kolekci, ale chceme pouze prvky, které splňují nějaký predikát, a nechceme dělat více než maximálně 100 iterací.

for (var i = 0; i < collection.Count; i++)
{
    if (!Predicate(item)) continue;
    if (i >= 100) break; // at first I used a > here which is a bug. another good thing about the more declarative style!

    DoStuff(item);
}

To vypadá DŮLEŽITĚ čistě. Není to těžké pochopit. Domnívám se, že by to bylo hodně, kdybych byl více deklarativní. Porovnejte to s následujícím:

foreach (var item in collection.Where(Predicate).Take(100))
    DoStuff(item);

Možná by v této metodě neměly být hovory typu Where and Take. Možná by toto filtrování mělo být provedeno PŘED sběrem této metody. Každopádně tím, že se vzdálíme od nízkoúrovňového obsahu a více se zaměříme na skutečnou obchodní logiku, je jasnější, o co se vlastně zajímáme. Je snazší rozdělit náš kód do soudržných modulů, které více dodržují správné konstrukční postupy a tak na.

V některých částech kódu budou stále existovat věci nízké úrovně, ale chceme to co nejvíce skrýt, protože to vyžaduje duševní energii, kterou bychom mohli místo toho použít k úvahám o obchodních problémech.

0
sara

Myslím, že je to jen problém, když je vnořen hluboko do několika smyček. Je těžké vědět, do které smyčky se lámáte. Může být obtížné sledovat i pokračovat, ale myslím si, že skutečná bolest pochází z přestávek - logika může být obtížná následovat.

0
davidhaskins

Nemám rád žádný z těchto stylů. Tady bych chtěl:

function verify(object)
{
    if not (object->value < 0) 
       and not(object->value > object->max_value)
       and not(object->name == "") 
       {
         do somethign important
       }
    else return false; //probably not necessary since this function doesn't even seem to be defined to return anything...?
}

Opravdu se mi nelíbí použití funkce return k přerušení funkce. Je to jako zneužití return.

Použití break také není vždy jasné, číst.

Ještě lepší by mohlo být:

notdone := primarycondition    
while (notDone)
{
    if (loop_count > 1000) or (time_exect > 3600)
    {
       notDone := false; 
    }
    else
    { 
        skipCurrentIteration := (this->data == "undefined") or (this->skip == true) 

        if not skipCurrentIteration
        {
           do something
        } 
        ...
    }
}

méně hnízdění a složité podmínky jsou přeměněny na proměnné (ve skutečném programu byste měli mít lepší jména, samozřejmě ...)

(Celý výše uvedený kód je pseudokód)

Ne. Je to způsob, jak vyřešit problém, a existují i ​​jiné způsoby, jak jej vyřešit.

Mnoho současných běžných jazyků (Java, .NET (C # + VB), PHP, psát vlastní) používá k přeskakování smyček „break“ a „pokračovat“. Obě věty „strukturovaného goto (s)“.

Bez nich:

String myKey = "mars";

int i = 0; bool found = false;
while ((i < MyList.Count) && (not found)) {
  found = (MyList[i].key == myKey);
  i++;   
}
if (found)
  ShowMessage("Key is " + i.toString());
else
  ShowMessage("Not found.");

S nimi:

String myKey = "mars";

for (i = 0; i < MyList.Count; i++) {
  if (MyList[i].key == myKey)
    break;
}
ShowMessage("Key is " + i.toString());

Všimněte si, že kód "konec" a "pokračovat" je kratší a obvykle se změní "věty na" pro "nebo" foreach "věty.

Oba případy jsou věcí stylu kódování. Raději je nepoužívám, protože podrobný styl mi umožňuje mít větší kontrolu nad kódem.

Vlastně pracuji na některých projektech, kde bylo povinné tyto věty používat.

Někteří vývojáři si mohou myslet, že nejsou necesarilly, ale hypotetičtí, pokud bychom je museli odstranit, musíme také odstranit „while“ a „do while“ („opakujte do“, vy Pascal) také ;-)

Závěr, i když je raději nepoužívám, myslím, že je to možnost, ne špatná programovací praxe.

0
umlcat

Pokud se nepoužívají jako maskované goto jako v následujícím příkladu:

do
{
      if (foo)
      {
             /*** code ***/
             break;
      }

      if (bar)
      {
             /*** code ***/
             break;
      }
} while (0);

Jsem s nimi v pohodě. (Příklad viděn ve výrobním kódu, meh)

0
SuperBloup