Was ist der Zweck der Verwendung von geschweiften Klammern (dh {}) für eine einzeilige if- oder Schleife?


320

Ich lese einige Vorlesungsunterlagen meines C ++ - Dozenten und er schrieb Folgendes:

  1. Verwenden Sie Einrückung // OK
  2. Verlassen Sie sich niemals auf die Priorität des Operators - Verwenden Sie immer Klammern // OK
  3. Verwenden Sie immer einen {} Block - auch für eine einzelne Zeile // nicht OK , warum ???
  4. Const-Objekt auf der linken Seite des Vergleichs // OK
  5. Verwenden Sie unsigned für Variablen, die> = 0 // netter Trick sind
  6. Setzen Sie den Zeiger nach dem Löschen auf NULL - Doppelter Löschschutz // nicht schlecht

Die 3. Technik ist mir nicht klar: Was würde ich gewinnen, wenn ich eine Zeile in eine setzen würde { ... }?

Nehmen Sie zum Beispiel diesen seltsamen Code:

int j = 0;
for (int i = 0 ; i < 100 ; ++i)
{
    if (i % 2 == 0)
    {
        j++;
    }
}

und ersetzen Sie es durch:

int j = 0;
for (int i = 0 ; i < 100 ; ++i)
    if (i % 2 == 0)
        j++;

Was ist der Vorteil der 1. Version?


256
Lesbarkeit und Wartbarkeit. Es ist nicht sofort ersichtlich, zu welchem ​​Anweisungsblock 'j ++' gehört und dass das Hinzufügen von Code danach nicht mit der if-Anweisung verknüpft wird.
Der Wald und die Bäume

27
Mir wurde aus mehreren Gründen immer gesagt, dass ich die geschweiften Klammern {} für diese Linien verwenden soll. Es macht den Code klarer zu lesen. Außerdem muss in sechs Monaten möglicherweise jemand anderes Ihren Code bearbeiten, damit Klarheit wichtig ist und bei den Klammern ein Fehler weniger wahrscheinlich ist. Es gibt technisch gesehen nichts Richtigeres, es ist eher eine Frage der guten Praxis. Denken Sie daran, dass ein Projekt möglicherweise Tausende und Abertausende von Codezeilen enthält, die ein neuer Typ durcharbeiten muss!
RossC

30
Ich bin mit 6 nicht einverstanden, da dadurch eine doppelte Löschung und möglicherweise Logikfehler ausgeblendet werden.
Luchian Grigore

28
# 5 könnte schwierig sein - betrachten Sie diese Schleife : for (unsigned i = 100; i >= 0; --i).
Archie

36
Übrigens (i % 2 == 0)widerspricht (2). Sie verlassen sich auf die Priorität des Operators, und die Bedeutung ist natürlich ((i % 2) == 0)eher als (i % (2 == 0)). Ich würde Regel 2 als "ein gültiges Gefühl klassifizieren, aber 'immer' ist falsch".
Steve Jessop

Antworten:


508

Versuchen iwir auch zu ändern, wenn wir inkrementieren j:

int j = 0;
for (int i = 0 ; i < 100 ; ++i)
    if (i % 2 == 0)
        j++;
        i++;

Ach nein! Aus Python kommend sieht das in Ordnung aus, ist es aber nicht, da es gleichbedeutend ist mit:

int j = 0;
for (int i = 0 ; i < 100 ; ++i)
    if (i % 2 == 0)
        j++;
i++;

Natürlich ist dies ein dummer Fehler, den aber selbst ein erfahrener Programmierer machen könnte.

Ein weiterer sehr guter Grund ist in der Antwort von ta.speot.is aufgeführt .

Ein dritter, an den ich denken kann, ist verschachtelt if:

if (cond1)
   if (cond2) 
      doSomething();

Angenommen, Sie möchten jetzt, doSomethingElse()wann cond1nicht erfüllt wird (neue Funktion). Damit:

if (cond1)
   if (cond2) 
      doSomething();
else
   doSomethingElse();

das ist offensichtlich falsch, da die elsemit dem inneren assoziiert if.


Bearbeiten: Da dies einige Aufmerksamkeit erhält, werde ich meine Ansicht klarstellen. Die Frage, die ich beantwortete, ist:

Was ist der Vorteil der 1. Version?

Was ich beschrieben habe. Es gibt einige Vorteile. Aber IMO, "immer" Regeln gelten nicht immer. Also unterstütze ich nicht ganz

Verwenden Sie immer einen {} Block - auch für eine einzelne Zeile // nicht OK, warum ???

Ich sage nicht, immer einen {}Block zu verwenden. Wenn es eine einfache Bedingung und ein Verhalten ist, tun Sie es nicht. Wenn Sie den Verdacht haben, dass später jemand hereinkommt und Ihren Code ändert, um Funktionen hinzuzufügen, tun Sie dies.


5
@Science_Fiction: True, aber wenn Sie i++ zuvor hinzufügen j++, sind beide Variablen bei ihrer Verwendung weiterhin im Gültigkeitsbereich.
Mike Seymour

26
Dies klingt sehr vernünftig, vernachlässigt jedoch die Tatsache, dass der Editor den Einzug ausführt, nicht Sie, und er wird den Einzug i++;auf eine Weise einrücken, die sofort zeigt, dass er nicht Teil der Schleife ist. (In der Vergangenheit war dies möglicherweise ein vernünftiges Argument, und ich habe solche Probleme gesehen. Vor ungefähr 20 Jahren. Nicht seitdem.)
James Kanze

43
@ James: Das ist jedoch keine "Tatsache", sondern Ihr Workflow. Und der Workflow vieler Menschen, aber nicht aller. Ich denke nicht, dass es unbedingt ein Fehler ist, die C ++ - Quelle als reine Textdatei zu behandeln, anstatt die Ausgabe eines WYSIWYG-Editors (vi / emacs / Visual Studio), der Formatierungsregeln erzwingt. Diese Regel ist also editorunabhängig und geht nicht über das hinaus, was Sie benötigen, sondern nicht über das hinaus, was die Benutzer tatsächlich zum Bearbeiten von C ++ verwenden. Daher "defensiv".
Steve Jessop

31
@JamesKanze Verlassen Sie sich wirklich auf die Annahme, dass jeder immer in leistungsstarken IDEs arbeitet? Das letzte, was CI schrieb, war in Nano. Selbst angesichts dessen ist eines der ersten Dinge, die ich in einer IDE ausschalte, das automatische Einrücken - da die IDE meinen nichtlinearen Workflow stört und versucht, meine "Fehler" aufgrund unvollständiger Informationen zu korrigieren . IDEs sind nicht sehr gut darin, den natürlichen Fluss jedes Programmierers automatisch einzurücken. Diejenigen Programmierer, die solche Funktionen verwenden, tendieren dazu, ihren Stil mit ihrer IDE zusammenzuführen. Dies ist in Ordnung, wenn Sie nur eine IDE verwenden, aber nicht viel, wenn Sie über mehrere arbeiten.
Rushyo

10
"Das ist ein dummer Fehler, den aber selbst ein erfahrener Programmierer machen könnte." - Wie ich in meiner Antwort sagte, glaube ich das nicht. Ich denke, es ist ein völlig erfundener Fall, der in der Realität kein Problem darstellt.
Konrad Rudolph

323

Es ist sehr leicht versehentlich Change Control-Flow mit Kommentaren , wenn Sie nicht verwenden , {und }. Zum Beispiel:

if (condition)
  do_something();
else
  do_something_else();

must_always_do_this();

Wenn Sie do_something_else()mit einem einzeiligen Kommentar auskommentieren, erhalten Sie Folgendes:

if (condition)
  do_something();
else
  //do_something_else();

must_always_do_this();

Es wird kompiliert, aber must_always_do_this()nicht immer aufgerufen.

Wir hatten dieses Problem in unserer Codebasis, in der jemand einige Funktionen vor der Veröffentlichung sehr schnell deaktiviert hatte. Zum Glück haben wir es bei der Codeüberprüfung festgestellt.


3
Ohh Junge !! Es ist ein definiertes Verhalten, must_always_do_this();das ausgeführt wird, wenn Sie // do_something_else () kommentieren.
Mr. Anubis

1
@Supr, wie es zuerst geschrieben wurde, sagt er, dass es schwierig ist, den richtigen Fluss zu unterbrechen, wenn Sie geschweifte Klammern verwenden, und gibt dann ein Beispiel dafür, wie einfach es ist, zu brechen, ohne den Code richtig in Klammern zu setzen
SeanC

20
Ich bin neulich darauf gestoßen. if(debug) \n //print(info);. Grundsätzlich nahm eine ganze Bibliothek heraus.
Kevin

4
Fortunately we caught it in code review.Autsch! Das klingt so falsch. Fortunately we caught it in unit tests.wäre viel besser!
BЈовић

4
@ BЈовић Aber was ist, wenn der Code in einem Unit-Test war? Der Geist verwirrt. (Nur ein Scherz, es ist eine Legacy-App. Es gibt keine Unit-Tests.)
ta.speot.is

59

Ich habe meine Zweifel an der Kompetenz des Dozenten. In Anbetracht seiner Punkte:

  1. OK
  2. Würde jemand wirklich schreiben (oder lesen wollen) (b*b) - ((4*a)*c) ? Einige Präzedenzfälle sind offensichtlich (oder sollten es sein), und die zusätzlichen Klammern tragen nur zur Verwirrung bei. (Andererseits sollten Sie die Klammern in weniger offensichtlichen Fällen verwenden, selbst wenn Sie wissen, dass sie nicht benötigt werden.)
  3. Art von. Es gibt zwei weit verbreitete Konventionen zum Formatieren von Bedingungen und Schleifen:
    if (cond) {
        Code;
    }}
    
    und:
    if (cond)
    {
        Code;
    }}
    
    Im ersten würde ich ihm zustimmen. Die Öffnung {ist nicht so sichtbar, daher ist es am besten anzunehmen, dass sie immer da ist. Im zweiten Fall habe ich (und die meisten Leute, mit denen ich gearbeitet habe) jedoch kein Problem damit, die geschweiften Klammern für eine einzelne Aussage wegzulassen. (Vorausgesetzt natürlich, dass die Einrückung systematisch ist und Sie diesen Stil konsequent verwenden. (Und viele sehr gute Programmierer, die sehr lesbaren Code schreiben, lassen die geschweiften Klammern weg, selbst wenn sie auf die erste Weise formatiert werden.)
  4. NEIN . Dinge wie if ( NULL == ptr )sind hässlich genug, um die Lesbarkeit zu beeinträchtigen. Schreiben Sie die Vergleiche intuitiv. (Was in vielen Fällen zur Konstanten rechts führt.) Seine 4 ist ein schlechter Rat; Alles, was den Code unnatürlich macht, macht ihn weniger lesbar.
  5. NEIN . Alles andere intals für Sonderfälle reserviert. Für erfahrene C- und C ++ - Programmierer ist die Verwendung von unsignedSignalbitoperatoren. C ++ hat keinen echten Kardinaltyp (oder einen anderen effektiven Unterbereichstyp). unsignedfunktioniert aufgrund der Promotion-Regeln nicht für numerische Werte. Numerische Werte, bei denen keine arithmetischen Operationen sinnvoll wären, wie Seriennummern, könnten vermutlich sein unsigned. Ich würde jedoch dagegen argumentieren, weil es die falsche Nachricht sendet: Bitweise Operationen machen auch keinen Sinn. Die Grundregel ist, dass integrale Typen sind int, es sei denn, es gibt einen signifikanten Grund für die Verwendung eines anderen Typs.
  6. NEIN . Dies systematisch zu tun ist irreführend und schützt eigentlich vor nichts. In streng OO - Code, delete this;ist oft der häufigste Fall (und Sie können nicht eingestellt thiszu NULL), und ansonsten die meisten deletesind in Destruktoren, so dass Sie nicht den Zeiger später sowieso zugreifen können. Wenn Sie es NULLso einstellen, wird nichts gegen andere herumschwebende Zeiger unternommen. Wenn Sie den Zeiger systematisch auf setzen, NULLentsteht ein falsches Sicherheitsgefühl, und Sie kaufen nichts.

Sehen Sie sich den Code in einer der typischen Referenzen an. Stroustrup verstößt gegen jede Regel, die Sie angegeben haben, mit Ausnahme der ersten.

Ich würde vorschlagen, dass Sie einen anderen Dozenten finden. Einer, der tatsächlich weiß, wovon er spricht.


13
Die Nummer 4 mag hässlich sein, hat aber einen Zweck. Es wird versucht zu verhindern, dass (ptr = NULL). Ich glaube nicht, dass ich jemals benutzt habe delete this, ist es häufiger als ich gesehen habe? Ich neige nicht dazu zu denken, dass das Setzen eines Zeigers auf NULL nach der Verwendung so schlecht ist, außer YMMV. Vielleicht bin ich es nur, aber die meisten seiner Richtlinien scheinen nicht so schlecht zu sein.
Firedragon

16
@Firedragon: Die meisten Compiler warnen davor, es if (ptr = NULL)sei denn, Sie schreiben es als if ((ptr = NULL)). Ich muss James Kanze zustimmen, dass die Hässlichkeit, NULLzuerst zu haben, es definitiv zu einem NEIN für mich macht.
Leo

34
@JamesKanze: Ich muss sagen, dass ich mit den meisten Ihrer Aussagen hier nicht einverstanden bin - obwohl ich Ihre Argumente für die Erreichung dieser Ziele schätze und respektiere. Für erfahrene C- und C ++ - Programmierer ist die Verwendung von vorzeichenlosen Signalbitoperatoren. - Ich stimme überhaupt nicht zu: Die Verwendung von Bitoperatoren signalisiert die Verwendung von Bitoperatoren. Für mich bedeutet die Verwendung von unsignedein Bestreben des Programmierers, dass die Variable nur positive Zahlen darstellen soll. Das Mischen mit signierten Nummern führt normalerweise zu einer Compiler-Warnung, die wahrscheinlich vom Dozenten beabsichtigt wurde.
Komponente 10

18
Für erfahrene C- und C ++ - Programmierer ist die Verwendung von vorzeichenlosen Signalen Bitoperatoren oder nicht. size_tjemand?
ta.speot.is

16
@ James Kanze, überlege den Zweck. Sie vergleichen den von einem erfahrenen Programmierer erstellten Code mit Anleitungsbeispielen. Diese Regeln werden vom Dozenten zur Verfügung gestellt, da es sich um Fehler handelt, die seine Schüler machen. Mit der Erfahrung können sich die Schüler entspannen oder diese Absoluten ignorieren.
Joshua Shane Liberman

46

Alle anderen Antworten verteidigen die Regel 3 Ihres Dozenten.

Lassen Sie mich sagen, dass ich Ihnen zustimme: Die Regel ist überflüssig und ich würde sie nicht empfehlen. Es ist wahr, dass es theoretisch Fehler verhindert, wenn Sie immer geschweifte Klammern hinzufügen. Andererseits bin ich im wirklichen Leben noch nie auf dieses Problem gestoßen : Im Gegensatz zu anderen Antworten habe ich nicht einmal vergessen, die geschweiften Klammern hinzuzufügen, sobald sie notwendig wurden. Wenn Sie die richtige Einrückung verwenden, wird sofort klar, dass Sie geschweifte Klammern hinzufügen müssen, sobald mehr als eine Anweisung eingerückt ist.

Die Antwort von „Komponente 10“ hebt tatsächlich den einzig denkbaren Fall hervor, in dem dies wirklich zu einem Fehler führen könnte. Andererseits erfordert das Ersetzen von Code durch reguläre Ausdrücke ohnehin immer enorme Sorgfalt.

Schauen wir uns nun die andere Seite der Medaille an: Gibt es einen Nachteil , wenn Sie immer geschweifte Klammern verwenden? Die anderen Antworten ignorieren diesen Punkt einfach. Aber das gibt es einen Nachteil: Es nimmt viel vertikalen Bildschirmplatz ein, was wiederum dazu führen kann, dass Ihr Code nicht mehr lesbar ist, da Sie mehr als nötig scrollen müssen.

Betrachten Sie eine Funktion mit vielen Schutzklauseln am Anfang (und ja, das Folgende ist schlechter C ++ - Code, aber in anderen Sprachen wäre dies eine recht häufige Situation):

void some_method(obj* a, obj* b)
{
    if (a == nullptr)
    {
        throw null_ptr_error("a");
    }
    if (b == nullptr)
    {
        throw null_ptr_error("b");
    }
    if (a == b)
    {
        throw logic_error("Cannot do method on identical objects");
    }
    if (not a->precondition_met())
    {
        throw logic_error("Precondition for a not met");
    }

    a->do_something_with(b);
}

Dies ist ein schrecklicher Code, und ich behaupte nachdrücklich, dass Folgendes weitaus besser lesbar ist:

void some_method(obj* a, obj* b)
{
    if (a == nullptr)
        throw null_ptr_error("a");
    if (b == nullptr)
        throw null_ptr_error("b");
    if (a == b)
        throw logic_error("Cannot do method on identical objects");
    if (not a->precondition_met())
        throw logic_error("Precondition for a not met");

    a->do_something_with(b);
}

In ähnlicher Weise profitieren kurze verschachtelte Schleifen davon, dass die geschweiften Klammern weggelassen werden:

matrix operator +(matrix const& a, matrix const& b) {
    matrix c(a.w(), a.h());

    for (auto i = 0; i < a.w(); ++i)
        for (auto j = 0; j < a.h(); ++j)
            c(i, j) = a(i, j) + b(i, j);

    return c;
}

Vergleichen mit:

matrix operator +(matrix const& a, matrix const& b) {
    matrix c(a.w(), a.h());

    for (auto i = 0; i < a.w(); ++i)
    {
        for (auto j = 0; j < a.h(); ++j)
        {
            c(i, j) = a(i, j) + b(i, j);
        }
    }

    return c;
}

Der erste Code ist kurz; Der zweite Code ist aufgebläht.

Und ja, dies kann bis zu einem gewissen Grad gemildert werden , indem die Öffnungsklammer auf die vorherige Zeile gesetzt wird. Aber das würde immer noch noch weniger lesbar als der Code ohne geschweifte Klammern.

Kurz gesagt: Schreiben Sie keinen unnötigen Code, der Platz auf dem Bildschirm beansprucht.


26
Wenn Sie nicht glauben , Code in schriftlicher Form , die unnötig Platz auf dem Bildschirm einnimmt, dann haben Sie kein Geschäft die öffnende Klammer in einer eigenen Zeile setzen. Ich werde mich jetzt wahrscheinlich vor der heiligen Rache der GNU ducken und davonlaufen müssen, aber im Ernst - entweder möchten Sie, dass Ihr Code vertikal kompakt ist, oder Sie tun es nicht. Und wenn Sie dies tun, tun Sie nichts, was nur dazu dient, Ihren Code vertikal weniger kompakt zu machen. Aber wie Sie sagen, nachdem Sie das behoben haben, möchten Sie auch redundante Klammern entfernen. Oder schreiben Sie einfach if (a == nullptr) { throw null_ptr_error("a"); }als eine Zeile.
Steve Jessop

6
@Steve als eine Angelegenheit der Tatsache, ich kann die öffnende Klammer in der vorhergehenden Zeile setzen, aus dem Grunde , Sie angegeben. Ich habe hier den anderen Stil verwendet, um deutlicher zu machen, wie extrem der Unterschied sein kann.
Konrad Rudolph

9
+1 Ich stimme voll und ganz zu, dass Ihr erstes Beispiel ohne geschweifte Klammern viel einfacher zu lesen ist. Im zweiten Beispiel besteht mein persönlicher Codierungsstil darin, geschweifte Klammern an der äußeren for-Schleife und nicht an der inneren zu verwenden. Ich bin mit @SteveJessop nicht einverstanden, dass es in Bezug auf vertikal kompakten Code das eine oder andere Extrem sein muss. Ich lasse zusätzliche Klammern mit Einzeiler weg, um den vertikalen Raum zu verringern, aber ich setze meine öffnenden Klammern in eine neue Linie, weil ich es einfacher finde, das Zielfernrohr zu sehen, wenn die Klammern ausgerichtet sind. Das Ziel ist die Lesbarkeit, und manchmal bedeutet dies, mehr vertikalen Raum zu verwenden, manchmal bedeutet dies, weniger zu verwenden.
Travesty3

22
"Ich bin diesem Problem im wirklichen Leben noch nie begegnet": Glück gehabt. Solche Dinge verbrennen dich nicht nur, sie verursachen 90% Verbrennungen dritten Grades (und das sind nur ein paar Managementebenen, die spät abends eine Lösung verlangen).
Richard

9
@ Richard Ich kaufe das einfach nicht. Wie ich im Chat erklärt habe, ist es trivial, diesen Fehler zu beheben, selbst wenn er jemals auftreten sollte (was ich unwahrscheinlich finde), wenn Sie sich den Stack-Trace ansehen, da es offensichtlich ist, wo der Fehler nur durch Betrachten des Codes liegt. Ihre übertriebene Behauptung ist völlig unbegründet.
Konrad Rudolph

40

Die Codebasis, an der ich arbeite, ist mit Code von Personen verstreut, die eine pathologische Abneigung gegen Zahnspangen haben, und für die Personen, die später kommen, kann dies die Wartbarkeit wirklich verbessern.

Das häufigste problematische Beispiel, auf das ich gestoßen bin, ist folgendes:

if ( really incredibly stupidly massively long statement that exceeds the width of the editor) do_foo;
    this_looks_like_a_then-statement_but_isn't;

Wenn ich also vorbeikomme und eine damalige Aussage hinzufügen möchte, kann ich leicht damit enden, wenn ich nicht aufpasse:

if ( really incredibly stupidly massively long statement that exceeds the width of the editor) do_foo;
{
    this_looks_like_a_then-statement_but_isn't;
    i_want_this_to_be_a_then-statement_but_it's_not;
}

Da es dauert ~ 1 Sekunde zu Klammern hinzuzufügen und können Sie mindestens ein paar Minuten verwirrt speichern debuggen, warum Sie überhaupt nicht gehen mit der reduzierten-Mehrdeutigkeit Option? Scheint mir eine falsche Wirtschaft zu sein.


16
Liegt das Problem in diesem Beispiel nicht eher in der falschen Einrückung und den zu langen Zeilen als in den geschweiften Klammern?
Honza Brabec

7
Ja, aber das Befolgen von Design- / Codierungsrichtlinien, die nur "sicher" sind, vorausgesetzt, die Benutzer befolgen auch andere Richtlinien (z. B. keine zu langen Warteschlangen), scheint nach Problemen zu fragen. Wären die Zahnspangen von Anfang an dabei gewesen, wäre es in dieser Situation unmöglich, einen falschen If-Block zu erhalten.
Jam

16
Wie würde das Hinzufügen von Klammern (damit if(really long...editor){ do_foo;}Sie diesen Fall vermeiden können? Das Problem scheint immer noch dasselbe zu sein. Ich persönlich bevorzuge es, Klammern zu vermeiden, wenn dies nicht erforderlich ist. Dies hat jedoch nichts mit der zum Schreiben erforderlichen Zeit zu tun, sondern mit der verringerten Lesbarkeit aufgrund der zwei zusätzlichen Zeilen im Code.
Grizzly

1
Guter Punkt - Ich ging davon aus, dass die erzwungene Verwendung von Zahnspangen auch dazu führen würde, dass sie an einem vernünftigen Ort platziert werden, aber natürlich könnte jemand, der entschlossen ist, die Dinge zu erschweren, sie wie in Ihrem Beispiel in Einklang bringen. Ich würde mir jedoch vorstellen, dass die meisten Menschen dies nicht tun würden.
Jam

2
Das erste und letzte, was ich beim Berühren einer Datei mache, ist die Schaltfläche zum automatischen Formatieren. Die meisten dieser Probleme werden behoben.
Jonathan Allen

20

Mein 2c:

Einrückung verwenden

Offensichtlich

Verlassen Sie sich niemals auf die Priorität des Bedieners - Verwenden Sie immer Klammern

Ich würde die Wörter "nie und" immer "nicht verwenden, aber im Allgemeinen sehe ich diese Regel als nützlich an. In einigen Sprachen (Lisp, Smalltalk) ist dies kein Problem.

Verwenden Sie immer einen {} Block - auch für eine einzelne Zeile

Ich mache das nie und hatte nie ein einziges Problem, aber ich kann sehen, wie gut es für Studenten sein kann, insb. wenn sie vorher Python studiert haben.

Const Objekt auf der linken Seite des Vergleichs

Yoda Bedingungen? Nein, bitte. Es schadet der Lesbarkeit. Verwenden Sie einfach die maximale Warnstufe, wenn Sie Ihren Code kompilieren.

Verwenden Sie unsigned für Variablen mit> = 0

OK. Komischerweise habe ich gehört, dass Stroustrup anderer Meinung ist.

Setzen Sie den Zeiger nach dem Löschen auf NULL - Doppelter Löschschutz

Schlechter Rat! Haben Sie niemals einen Zeiger, der auf ein gelöschtes oder nicht vorhandenes Objekt zeigt.


5
+1 nur für den letzten Punkt. Ein roher Zeiger hat sowieso kein Geschäft, das Speicher besitzt.
Konrad Rudolph

2
In Bezug auf die Verwendung von nicht signierten: nicht nur Stroustrup, sondern auch K & R (in C), Herb Sutter und (glaube ich) Scott Meyers. Tatsächlich habe ich noch nie jemanden gehört, der die Regeln von C ++ wirklich verstanden hat und für die Verwendung von unsigned argumentiert.
James Kanze

2
@JamesKanze Tatsächlich war Herb Sutter bei der gleichen Gelegenheit, als ich Stroustrups Meinung hörte (eine Konferenz in Boston im Jahr 2008), da und widersprach Bjarne sofort.
Nemanja Trifunovic

3
Um das " unsignedist kaputt" zu vervollständigen , besteht eines der Probleme darin, dass C ++ beim Vergleich von signierten und nicht signierten Typen ähnlicher Größe vor dem Vergleich in den nicht signierten Typ konvertiert . Was zu einer Wertänderung führt. Das Konvertieren in das Signierte wäre nicht unbedingt viel besser. Der Vergleich sollte wirklich so stattfinden, als ob beide Werte in einen größeren Typ konvertiert würden, der alle Werte in beiden Typen darstellen könnte.
James Kanze

1
@SteveJessop Ich denke, Sie müssen es im Kontext einer Funktion nehmen, die zurückkehrt unsigned. Ich bin sicher, er hat kein Problem damit exp(double), einen Wert zurückzugeben, der größer ist als MAX_INT:-). Aber noch einmal, das eigentliche Problem sind implizite Konvertierungen. int i = exp( 1e6 );ist vollkommen gültiges C ++. Stroustrup schlug tatsächlich vor, verlustbehaftete implizite Conversions an einem Punkt zu verwerfen, aber das Komitee war nicht interessiert. (Eine interessante Frage: würde unsigned-> intals verlustbehaftet angesehen werden. Ich würde sowohl unsigned-> intals auch int-> als unsignedverlustbehaftet betrachten. Das würde einen großen unsigned
Beitrag dazu

18

es ist intuitiver und leicht verständlich. Es macht die Absicht klar.

Und es stellt sicher, dass der Code nicht beschädigt wird, wenn ein neuer Benutzer den Code unwissentlich verpasst {, }während eine neue Code-Anweisung hinzugefügt wird.


Makes the intent clear+1, dies ist wahrscheinlich der prägnanteste und genaueste Grund.
Qix - MONICA wurde am

13

Um die oben genannten sehr vernünftigen Vorschläge zu ergänzen, war ein Beispiel, das ich beim Umgestalten eines Codes gefunden habe, bei dem dies kritisch wird, wie folgt: Ich habe eine sehr große Codebasis geändert, um von einer API zu einer anderen zu wechseln. Die erste API hatte einen Aufruf zum Festlegen der Unternehmens-ID wie folgt:

setCompIds( const std::string& compId, const std::string& compSubId );

Für den Ersatz waren zwei Anrufe erforderlich:

setCompId( const std::string& compId );
setCompSubId( const std::string& compSubId );

Ich habe mich daran gemacht, dies mit regulären Ausdrücken zu ändern, was sehr erfolgreich war. Wir haben den Code auch durch astyle weitergegeben , was ihn wirklich sehr viel lesbarer machte. Während des Überprüfungsprozesses stellte ich dann fest, dass sich dies unter bestimmten Bedingungen änderte:

if ( condition )
   setCompIds( compId, compSubId );

Dazu:

if ( condition )
   setCompId( compId );
setCompSubId( compSubId );

Das ist eindeutig nicht das, was erforderlich war. Ich musste zum Anfang zurückkehren und dies erneut tun, indem ich den Ersatz als vollständig innerhalb eines Blocks behandelte und dann manuell alles änderte, was doof aussah (zumindest wäre es nicht falsch.)

Ich stelle fest, dass Astyle jetzt die Option hat --add-brackets, mit der Sie Klammern hinzufügen können, in denen es keine gibt, und ich empfehle dies dringend, wenn Sie sich jemals in derselben Position wie ich befinden.


5
Ich habe einmal eine Dokumentation gesehen, die die wunderbare Münzprägung "Microsoftligent" hatte. Ja, es ist möglich, beim globalen Suchen und Ersetzen erhebliche Fehler zu machen. Das bedeutet nur, dass globales Suchen und Ersetzen intelligent und nicht mikrosicher eingesetzt werden muss.
Pete Becker

4
Ich weiß, dass dies nicht meine Obduktion ist, aber wenn Sie Text im Quellcode ersetzen möchten, sollten Sie dies nach denselben Regeln tun, die Sie für die Art der Textersetzung verwenden würden, die gut etabliert ist in der Sprache: Makros. Sie sollten kein Makro schreiben #define FOO() func1(); \ func2(); (mit einem Zeilenumbruch nach dem Backslash). Gleiches gilt für das Suchen und Ersetzen. Trotzdem habe ich gesehen, dass "Verwenden Sie immer geschweifte Klammern" als Stilregel weiterentwickelt wurde, gerade weil Sie dadurch nicht alle Makros mit mehreren Anweisungen einschließen müssen do .. while(0). Aber ich bin anderer Meinung.
Steve Jessop

2
Übrigens, das ist "gut etabliert" in dem Sinne, dass japanisches Knöterich gut etabliert ist: Ich sage nicht, dass wir uns alle Mühe geben sollten, Makros und Textersetzung zu verwenden, aber ich sage das, wenn wir so etwas tun Wir sollten es auf eine Weise tun, die funktioniert, anstatt etwas zu tun, das nur funktioniert, wenn eine bestimmte Stilregel der gesamten Codebasis erfolgreich auferlegt wurde :-)
Steve Jessop

1
@SteveJessop Man könnte auch für Hosenträger und einen Gürtel argumentieren. Wenn Sie solche Makros verwenden müssen (und wir haben dies vor C ++ und getan inline), sollten Sie wahrscheinlich darauf abzielen, dass sie so ähnlich wie möglich funktionieren, und do { ... } while(0)bei Bedarf den Trick verwenden (und viele zusätzliche Klammern. Aber das würde immer noch nicht Ich kann Sie nicht davon abhalten, überall Zahnspangen zu verwenden, wenn dies der Hausstil ist. (FWIW: Ich habe an Orten mit unterschiedlichen Hausstilen gearbeitet und alle hier diskutierten Stile abgedeckt. Ich habe nie festgestellt, dass eines ein ernstes Problem darstellt.)
James Kanze

1
Und ich denke, je mehr Stile Sie bearbeitet haben, desto sorgfältiger lesen und bearbeiten Sie den Code. Selbst wenn Sie eine Präferenz haben, die am einfachsten zu lesen ist, werden Sie die anderen dennoch erfolgreich lesen. Ich habe in einer Firma gearbeitet, in der verschiedene Komponenten von den verschiedenen Teams in unterschiedlichen "Hausstilen" geschrieben wurden. Die richtige Lösung besteht darin, sich im Speisesaal ohne großen Effekt darüber zu beschweren und nicht zu versuchen, einen globalen Stil zu kreieren :-)
Steve Jessop

8

Ich benutze {}überall, außer in einigen Fällen, in denen es offensichtlich ist. Eine einzelne Zeile ist einer der Fälle:

if(condition) return; // OK

if(condition) // 
   return;    // and this is not a one-liner 

Es kann Ihnen weh tun, wenn Sie vor der Rückkehr eine Methode hinzufügen. Einrückung zeigt an, dass return zurückgegeben wird, wenn die Bedingung erfüllt ist, aber immer zurückgegeben wird.

Ein anderes Beispiel in C # mit der Verwendung von statment

using (D d = new D())  // OK
using (C c = new C(d))
{
    c.UseLimitedResource();
}

das ist äquivalent zu

using (D d = new D())
{
    using (C c = new C(d))
    {
        c.UseLimitedResource();
    }
}

1
Verwenden usingSie einfach Kommas in der Anweisung und Sie müssen nicht :)
Ry-

1
@minitech Das funktioniert hier einfach nicht - Sie können das Komma nur verwenden, wenn die Typen gleich sind, nicht für ungleiche Typen. Lukas 'Art dies zu tun ist die kanonische Art, die IDE formatiert dies sogar anders (beachten Sie das Fehlen einer automatischen Einrückung der Sekunde using).
Konrad Rudolph

8

Das relevanteste Beispiel, an das ich denken kann:

if(someCondition)
   if(someOtherCondition)
      DoSomething();
else
   DoSomethingElse();

Mit welchem ifwird das elsegepaart? Einrückung impliziert, dass das Äußere das ifbekommt else, aber so wird es der Compiler eigentlich nicht sehen; das Innere if wird das bekommen else, und das Äußereif nicht. Sie müssten das wissen (oder sehen, dass es sich im Debugging-Modus so verhält), um durch Inspektion herauszufinden, warum dieser Code möglicherweise Ihre Erwartungen nicht erfüllt. Es wird verwirrender, wenn Sie Python kennen. In diesem Fall wissen Sie, dass der Einzug Codeblöcke definiert, sodass Sie erwarten würden, dass er gemäß dem Einzug ausgewertet wird. C # gibt jedoch keinen fliegenden Flip über Leerzeichen.

Nun, das heißt, ich stimme dieser Regel "Immer Klammern verwenden" auf den ersten Blick nicht besonders zu. Es macht Code sehr vertikal verrauscht und verringert die Fähigkeit, ihn schnell durchzulesen. Wenn die Aussage lautet:

if(someCondition)
   DoSomething();

... dann sollte es einfach so geschrieben werden. Die Aussage "Klammern immer verwenden" klingt wie "mathematische Operationen immer mit Klammern umgeben". Das würde die sehr einfache Aussage a * b + c / din ((a * b) + (c / d))die Möglichkeit verwandeln, einen Close-Paren (den Fluch vieler Codierer) zu verpassen, und wofür? Die Reihenfolge der Operationen ist bekannt und wird gut durchgesetzt, sodass die Klammern redundant sind. Sie würden nur Klammern verwenden, um eine andere Reihenfolge von Operationen zu erzwingen, als normalerweise angewendet wird: a * (b+c) / dzum Beispiel. Blockklammern sind ähnlich; Verwenden Sie sie, um zu definieren, was Sie in Fällen tun möchten, in denen es vom Standard abweicht und nicht "offensichtlich" ist (subjektiv, aber normalerweise ziemlich vernünftig).


3
@AlexBrown ... das war genau mein Punkt. Die im OP angegebene Regel lautet "Verwenden Sie immer Klammern, auch für einzelne Zeilen", mit denen ich aus dem von mir angegebenen Grund nicht einverstanden bin. Klammern würden beim allerersten Codebeispiel helfen, da sich der Code nicht so verhält, wie er eingerückt ist. Sie müssten Klammern verwenden, um die elsemit der ersten ifanstelle der zweiten zu koppeln. Bitte entfernen Sie die Ablehnung.
KeithS

5

Beim Durchsehen der Antworten hat niemand ausdrücklich angegeben, welche Art von Praxis ich mir angewöhnt habe, und die Geschichte Ihres Codes erzählt:

int j = 0;
for (int i = 0 ; i < 100 ; ++i)
{
    if (i % 2 == 0)
    {
        j++;
    }
}

Wird:

int j = 0;
for (int i = 0 ; i < 100 ; ++i)
{
    if (i % 2 == 0) j++;
}

Das setzen j++ in die gleiche Zeile wie das if, um anderen zu signalisieren: "Ich möchte nur, dass dieser Block jemals erhöht wird j" . Natürlich lohnt sich dies nur, wenn die Linie so einfach wie möglich ist, weil hier ein Haltepunkt als Peri gesetzt wird erwähnt, wird nicht sehr nützlich sein.

Tatsächlich bin ich gerade auf einen Teil der Twitter Storm-API gestoßen, die diese Art von Code in Java enthält. Hier ist das relevante Snippet aus dem Ausführungscode auf Seite 43 dieser Diashow :

...
Integer Count = counts.get(word);
if (Count=null) count=0;
count++
...

Der for-Schleifenblock enthält zwei Dinge, daher würde ich diesen Code nicht einbinden. Dh niemals :

int j = 0;
for (int i = 0 ; i < 100 ; ++i) if (i % 2 == 0) j++;

Es ist schrecklich und ich weiß nicht einmal, ob es funktioniert (wie beabsichtigt); Tu das nicht . Neue Zeilen und geschweifte Klammern helfen dabei, separate, aber verwandte Codeteile zu unterscheiden, genau wie ein Komma oder ein Semikolon in der Prosa. Der obige Block ist genauso schlecht wie ein wirklich langer Satz mit ein paar Klauseln und einigen anderen Aussagen, die niemals brechen oder pausieren, um einzelne Teile zu unterscheiden.

Wenn Sie wirklich an eine andere Person telegrafieren möchten, verwenden Sie einen ternären Operator oder ?: Formular:

for (int i = 0 ; i < 100 ; ++i) (i%2 ? 0 : >0) j++;

Aber das ist fast Code-Golf, und ich denke nicht, dass es eine großartige Übung ist (mir ist nicht klar, ob ich das j ++ auf eine Seite von setzen soll :oder nicht). NB Ich habe noch keinen ternären Operator in C ++ ausgeführt. Ich weiß nicht, ob dies funktioniert, aber es existiert .

Zusamenfassend:

Stellen Sie sich vor, wie Ihr Leser (dh die Person, die den Code verwaltet) Ihre Geschichte (Code) interpretiert. Machen Sie es ihnen so klar wie möglich. Wenn Sie wissen, dass der Anfänger / Student dies beibehält, lassen Sie vielleicht sogar so viele {}wie möglich übrig, damit sie nicht verwirrt werden.


1
(1) Wenn Sie die Aussage in dieselbe Zeile setzen, ist sie weniger lesbar, nicht mehr. Besonders einfache Gedanken wie ein Inkrement werden leicht übersehen. Setzen Sie sie in eine neue Zeile. (2) Natürlich können Sie Ihre forSchleife in eine einzelne Zeile setzen. Warum sollte das nicht funktionieren? Es funktioniert aus dem gleichen Grund, aus dem Sie die geschweiften Klammern weglassen können. newline ist in C ++ einfach nicht von Bedeutung. (3) Ihr Beispiel für einen bedingten Operator ist nicht nur schrecklich, sondern auch ungültig in C ++.
Konrad Rudolph

@KonradRudolph danke, ich bin ein bisschen verrostet bei C ++. Ich habe nie gesagt (1) war besser lesbar, aber es wäre ein Zeichen zu sein , dass das Stück Code wurde gemeint Online eine Zeile zu sein. (2) Mein Kommentar war mehr, dass ich ihn nicht lesen und wissen könnte, dass er funktioniert, ob überhaupt oder wie beabsichtigt; Es ist ein Beispiel dafür, was aus diesem Grund nicht zu tun ist. (3) Danke, ich habe C ++ schon lange nicht mehr geschrieben. Ich werde das jetzt beheben.
Pureferret

2
Wenn Sie mehr als einen Ausdruck in eine Zeile setzen, wird das Debuggen von Code schwieriger. Wie setzen Sie den Haltepunkt auf den 2. Ausdruck in dieser Zeile?
Piotr Perak

5

Denn wenn Sie zwei Aussagen ohne haben {}, ist es leicht, ein Problem zu übersehen. Nehmen wir an, dass der Code so aussieht.

int error = 0;
enum hash_type hash = SHA256;
struct hash_value *hash_result = hash_allocate();

if ((err = prepare_hash(hash, &hash_result))) != 0)
    goto fail;
if ((err = hash_update(&hash_result, &client_random)) != 0)
    goto fail;
if ((err = hash_update(&hash_result, &server_random)) != 0)
    goto fail;
if ((err = hash_update(&hash_result, &exchange_params)) != 0)
    goto fail;
    goto fail;
if ((err = hash_finish(hash)) != 0)
    goto fail;

error = do_important_stuff_with(hash);

fail:
hash_free(hash);
return error;

Es sieht gut aus. Das Problem damit ist wirklich leicht zu übersehen, besonders wenn die Funktion, die den Code enthält, viel größer ist. Das Problem ist, dass goto failes bedingungslos ausgeführt wird. Sie können sich leicht vorstellen, wie frustrierend dies ist (Sie fragen sich, warum der letzte hash_updateimmer fehlschlägt, schließlich sieht alles gut aushash_update Funktion gut aus).

Das bedeutet jedoch nicht, dass ich {}überall etwas hinzufügen möchte (meiner Meinung nach {}ist es ärgerlich , überall zu sehen). Es kann zwar Probleme verursachen, hat es aber nie für meine eigenen Projekte getan, da mein persönlicher Codierungsstil Bedingungen verbietet, ohne {}dass sie nicht in derselben Zeile stehen (ja, ich stimme zu, dass mein Codierungsstil unkonventionell ist, aber ich mag es und ich Verwenden Sie den Codestil des Projekts, wenn Sie zu anderen Projekten beitragen. Dies macht den folgenden Code in Ordnung.

if (something) goto fail;

Aber nicht der folgende.

if (something)
    goto fail;

Genau. Setzen Sie einfach nicht den (völlig unnötigen) Zeilenumbruch + Einzug, und Sie umgehen dieses Problem vollständig, sodass jeder immer so schnell darauf eingeht.
Alexander - Reinstate Monica

4

Es macht Ihren Code besser lesbar, indem es den Umfang Ihrer Schleifen und bedingten Blöcke klar definiert. Es erspart Ihnen auch versehentliche Fehler.


4

wrt 6: Es ist sicherer, weil das Löschen eines Nullzeigers ein No-Op ist. Wenn Sie diesen Pfad also versehentlich zweimal durchlaufen, wird die Speicherbeschädigung nicht dazu führen, dass Speicher freigegeben wird, der entweder frei ist oder einem anderen zugewiesen wurde.

Dies ist das größte Problem bei statischen Dateibereichsobjekten und Singletons, deren Lebensdauer nicht sehr klar ist und von denen bekannt ist, dass sie nach ihrer Zerstörung neu erstellt werden.

In den meisten Fällen können Sie dies mithilfe von auto_ptrs vermeiden


6
Wenn Sie diesen Pfad zweimal durchlaufen, liegt ein Programmierfehler vor. Wenn Sie einen Zeiger auf null setzen, um diesen Fehler weniger schädlich zu machen, wird das zugrunde liegende Problem nicht gelöst.
Pete Becker

1
Einverstanden, aber ich habe dies schon einmal empfohlen gesehen, und ich glaube, es liegt in einigen professionellen Programmierstandards. Ich kommentierte mehr, warum der Professor des Posters darauf gekommen war, als wann es etwas Gutes war
Tom Tanner

2
Nach dem, was Pete Becker gesagt hat: Es löst das zugrunde liegende Problem nicht, kann es aber maskieren. (Es gibt Fälle, in denen Sie einen Zeiger NULLnach dem Löschen festlegen würden . Wenn NULLder Zeiger unter diesen Umständen einen korrekten Wert hat, z. B. zeigt der Zeiger auf einen zwischengespeicherten Wert und NULLzeigt einen ungültigen Cache an. Wenn Sie jedoch eine Einstellung sehen Als Zeiger auf NULLdie letzte Zeile in einem Destruktor fragen Sie sich, ob er C ++ kennt.)
James Kanze

4

Ich mag Luchians akzeptierte Antwort, tatsächlich habe ich auf die harte Tour gelernt, dass er Recht hat, deshalb verwende ich immer geschweifte Klammern, auch für einzeilige Blöcke. Ich persönlich mache jedoch eine Ausnahme, wenn ich einen Filter schreibe, wie Sie es in Ihrem Beispiel sind. Diese:

int j = 0;
for (int i = 0 ; i < 100 ; ++i)
{
    if (i % 2 == 0)
    {
        j++;
    }
}

sieht für mich überladen aus. Es trennt die for-Schleife und die if-Anweisung in separate Aktionen, wenn Ihre Absicht wirklich eine einzelne Aktion ist: Alle durch 2 teilbaren Ganzzahlen zu zählen. In einer ausdrucksstärkeren Sprache könnte dies wie folgt geschrieben werden:

j = [1..100].filter(_%2 == 0).Count

In Sprachen ohne Abschluss kann der Filter nicht in einer einzelnen Anweisung ausgedrückt werden, sondern muss eine for-Schleife gefolgt von einer if-Anweisung sein. Es ist jedoch immer noch eine Aktion im Kopf des Programmierers, und ich glaube, dass dies im Code wie folgt widergespiegelt werden sollte:

int j = 0;
for (int i = 0 ; i < 100 ; ++i)
  if (i % 2 == 0)
{
    j++;
}

7
Mir gefällt, wie jeder es schafft, zu ignorieren for (int i = 0; i < 100; i += 2);, um das Argument der Einrückung fortzusetzen ;-) Wir könnten wahrscheinlich einen eigenen Kampf haben, wie "am besten" wir die Logik "für jeden iin einem bestimmten Bereich mit einer bestimmten Eigenschaft" ausdrücken können. in C ++ ohne Schleife, unter Verwendung einer Albtraumkombination von Standardalgorithmen filter_iteratorund / oder counting_iterator.
Steve Jessop

1
Wenn wir das hätten, könnten wir uns auch nicht darüber einig sein, wie die resultierende massive Einzelaussage eingerückt werden soll.
Steve Jessop

2
@Steve, es ist nur ein Beispiel. Es gibt viele legitime Verwendungen des Musters. Wenn Sie die Zahlen von 1 bis 100 zählen möchten, die durch 2 teilbar sind, müssen Sie natürlich nur 100/2 tun.
MikeFHay

2
Klar, ich weiß, deshalb habe ich zu "für jeden iin einem bestimmten Bereich mit einer bestimmten Eigenschaft" abstrahiert . Es ist nur so, dass die Leute bei SO normalerweise die eigentliche Frage sehr schnell ignorieren, um einen völlig anderen Ansatz für das gegebene Beispiel zu finden. Aber das Einrücken ist wichtig , also tun wir es nicht ;-)
Steve Jessop

4

Eine Möglichkeit, um die oben beschriebenen Fehler zu vermeiden, besteht darin, anzugeben, was passieren soll, wenn Sie keine geschweiften Klammern verwenden. Es macht es viel schwieriger, die Fehler nicht zu bemerken, wenn Sie versuchen, den Code zu ändern.

if (condition) doSomething();
else doSomethingElse();

if (condition) doSomething();
    doSomething2(); // Looks pretty obviously wrong
else // doSomethingElse(); also looks pretty obviously wrong

5
Die zweite Option würde einen Kompilierungsfehler ergeben, da der elsenicht mit einem verknüpft ist if.
Luchian Grigore

Ein nicht so sichtbares Problem bei Inline ist, dass die meisten IDEs standardmäßig den eingerückten Stil ändern, wenn sie ihr Autoformat-Dienstprogramm verwenden.
Honza Brabec

@ Honza: Das ist jedoch ein hoch aufgeladenes politisches Problem. Wenn wir auf Codebasis zusammenarbeiten, müssen wir entweder den gleichen Einrückungsstil bis ins kleinste Detail verwenden oder beide müssen uns darauf einigen, vorhandenen Code nicht "nur weil" automatisch zu formatieren. Wenn dies der Fall ist, könnte der vereinbarte Stil dies immer noch beinhalten, aber Sie müssten entweder Ihre IDE so konfigurieren, dass sie dies respektiert, oder kein Autoformat verwenden. Die Zustimmung, dass das gemeinsame Format "was auch immer meine IDE-Autoformate sind" ist, ist alles sehr gut, wenn wir alle für immer dieselbe IDE verwenden, sonst nicht so gut.
Steve Jessop

3

Wenn Sie ein Compiler sind, macht es keinen Unterschied. Beide sind gleich.

Für Programmierer ist der erste jedoch klarer, leicht zu lesen und weniger fehleranfällig.


2
Abgesehen {davon, dass man auf eigene Faust öffnet .
Rob

3

Ein weiteres Beispiel für das Hinzufügen von geschweiften Klammern. Einmal habe ich nach einem Fehler gesucht und einen solchen Code gefunden:

void SomeSimpleEventHandler()
{
    SomeStatementAtTheBeginningNumber1;
    if (conditionX) SomeRegularStatement;
    SomeStatementAtTheBeginningNumber2;
    SomeStatementAtTheBeginningNumber3;
    if (!SomeConditionIsMet()) return;
    OtherwiseSomeAdditionalStatement1;
    OtherwiseSomeAdditionalStatement2;
    OtherwiseSomeAdditionalStatement3;
}

Wenn Sie die Methode Zeile für Zeile lesen, werden Sie feststellen, dass die Methode eine Bedingung enthält, die zurückgegeben wird, wenn sie nicht wahr ist. Aber tatsächlich sieht es aus wie 100 andere einfache Ereignishandler, die einige Variablen basierend auf bestimmten Bedingungen festlegen. Und eines Tages kommt der Fast Coder und fügt am Ende der Methode eine zusätzliche Anweisung zur Variableneinstellung hinzu:

{
    ...
    OtherwiseSomeAdditionalStatement3;
    SetAnotherVariableUnconditionnaly;
}

Infolgedessen wird SetAnotherVariableUnconditionnaly ausgeführt, wenn SomeConditionIsMet (), aber der schnelle Typ hat es nicht bemerkt, da alle Zeilen fast gleich groß sind und selbst wenn die Rückgabebedingung vertikal eingerückt ist, ist sie nicht so auffällig.

Wenn die bedingte Rückgabe wie folgt formatiert ist:

if (!SomeConditionIsMet())
{
    return;
}

es ist sehr auffällig und der Fast Coder wird es auf einen Blick finden.


Wenn Ihr schneller Codierer nicht die Mühe hat, eine durch Syntax hervorgehobene returnAnweisung im Hauptteil einer Funktion zu erkennen, bevor Sie etwas hinzufügen, sollten Sie den schnellen Codierer nicht in die Nähe Ihres Codes bringen lassen. Sie werden einen solchen Kerl nicht davon abhalten, Ihren Code zu durchforsten, indem Sie geschweifte Klammern einfügen.
cmaster - wieder Monica

@cmaster Er arbeitet nicht mehr mit uns. Wie auch immer, die Syntaxhervorhebung ist gut, aber denken Sie daran, dass es Personen gibt, die nicht klar sehen (ich habe letztes Jahr sogar einen Beitrag von einem blinden Programmierer gesehen).
Artemix

2

Ich halte den ersten für klar und dann für den zweiten. Es gibt das Gefühl, Anweisungen zu schließen, mit wenig Code ist in Ordnung, wenn Code komplex wird, {...}hilft viel, selbst wenn es endifoder istbegin...end

//first
int j = 0;
for (int i = 0 ; i < 100 ; ++i)
{
    if (i % 2 == 0)
    {
        j++;
    }
}


//second
int j = 0;
for (int i = 0 ; i < 100 ; ++i)
    if (i % 2 == 0)
        j++;
i++;

1

Setzen Sie den Zeiger am besten auf NULL, wenn Sie damit fertig sind.

Hier ist ein Beispiel warum:

Klasse A macht Folgendes:

  1. Ordnet einen Speicherblock zu
  2. Einige Zeit später wird dieser Speicherblock gelöscht, der Zeiger jedoch nicht auf NULL gesetzt

Klasse B macht Folgendes

  1. Ordnet Speicher zu (und in diesem Fall erhält er zufällig denselben Speicherblock, der von Klasse A gelöscht wurde).

Zu diesem Zeitpunkt haben sowohl Klasse A als auch Klasse B Zeiger, die auf denselben Speicherblock zeigen, was Klasse A betrifft, existiert dieser Speicherblock nicht, weil er damit fertig ist.

Betrachten Sie das folgende Problem:

Was ist, wenn in Klasse A ein logischer Fehler aufgetreten ist, der dazu geführt hat, dass in den Speicher geschrieben wurde, der jetzt zur Klasse B gehört?

In diesem speziellen Fall wird kein Fehler bei einer fehlerhaften Zugriffsausnahme angezeigt, da die Speicheradresse zulässig ist, während Klasse A jetzt effektiv Daten der Klasse B beschädigt.

Klasse B kann schließlich abstürzen, wenn unerwartete Werte auftreten, und wenn es abstürzt, werden Sie wahrscheinlich ziemlich lange Zeit damit verbringen, diesen Fehler in Klasse B zu suchen, wenn das Problem in Klasse A liegt.

Wenn Sie den gelöschten Speicherzeiger auf NULL gesetzt hätten, hätten Sie einen Ausnahmefehler erhalten, sobald Logikfehler in Klasse A versucht hätten, in den NULL-Zeiger zu schreiben.

Wenn Sie sich Sorgen über den Logikfehler beim doppelten Löschen machen, wenn die Zeiger zum zweiten Mal NULL sind, fügen Sie hierfür assert hinzu.

Außerdem: Wenn Sie abstimmen wollen, erklären Sie dies bitte.


2
Wenn ein logischer Fehler aufgetreten ist, sollte dieser behoben werden, anstatt ihn zu maskieren.
uınbɐɥs

@Barmar, OP sagt ... 6. Setzen Sie den Zeiger nach dem Löschen auf NULL - Doppelter Löschschutz // nicht schlecht. Einige Leute haben geantwortet, dass sie es nicht auf Null gesetzt haben, und ich sage, warum es auf NULL gesetzt werden sollte, welcher Teil von 6. Meine Antwort auf das Setzen von NULL passt nicht in 6?
John

1
@Shaquin, und wie schlagen Sie vor, diese logischen Fehler überhaupt zu finden? Sobald Sie die Zeigervariable auf NULL gesetzt haben, nachdem der Speicher gelöscht wurde. Jeder Versuch, auf den NULL-Zeiger zu verweisen, stürzt in der Zeile, in der Ihr illegaler Versuch unternommen wurde, zum Debugger ab. Sie können zurückverfolgen und sehen, wo der logische Fehler war, und das Problem beheben. Wenn Sie die Zeigervariable nach dem Löschen des Speichers nicht auf NULL setzen, ist Ihr unzulässiger Versuch, diesen gelöschten Speicher aufgrund von UNAWARE-Logikfehlern zu schreiben, möglicherweise erfolgreich und stürzt daher an diesem Punkt nicht ab. Es maskiert es nicht.
John

1

Immer geschweifte Klammern zu haben, ist eine sehr einfache und robuste Regel. Der Code kann jedoch unelegant aussehen, wenn viele Klammern vorhanden sind. Wenn die Regeln das Weglassen von geschweiften Klammern zulassen, sollten detailliertere Stilregeln und ausgefeiltere Werkzeuge vorhanden sein. Andernfalls kann es leicht zu chaotischem und verwirrendem (nicht elegantem) Code kommen. Daher ist es wahrscheinlich erfolglos, eine einzelne Stilregel getrennt von den übrigen verwendeten Stilrichtlinien und Werkzeugen zu betrachten. Ich werde nur einige wichtige Details zu dieser Regel Nr. 3 einbringen, die in anderen Antworten noch nicht einmal erwähnt wurden.

Das erste interessante Detail ist, dass die meisten Befürworter dieser Regel zustimmen, sie im Falle von zu verletzen else. Mit anderen Worten, sie verlangen bei der Überprüfung eines solchen Codes nicht:

// pedantic rule #3
if ( command == Eat )
{
    eat();
}
else
{
    if ( command == Sleep )
    {
        sleep();
    }
    else
    {
        if ( command == Drink )
        {
            drink();
        }
        else
        {
            complain_about_unknown_command();
        }
    }
}

Wenn sie es sehen, können sie stattdessen sogar vorschlagen, es so zu schreiben:

// not fully conforming to rule #3
if ( command == Eat )
{
    eat();
}
else if ( command == Sleep )
{
    sleep();
}
else if ( command == Drink )
{
    drink();
}
else
{
   complain_about_unknown_command();
}

Dies verstößt technisch gegen diese Regel, da zwischen elseund keine geschweiften Klammern stehen if. Eine solche Dualität der Regel tritt auf, wenn versucht wird, sie mit einem sinnlosen Werkzeug automatisch auf die Codebasis anzuwenden. In der Tat, warum streiten, lassen Sie einfach ein Werkzeug, um Stil automatisch anzuwenden.

Das zweite Detail (das auch von Befürwortern dieser Regel oft vergessen wird) ist, dass die Fehler, die auftreten können, niemals nur auf Verstöße gegen diese Regel Nr. 3 zurückzuführen sind. Tatsächlich beinhalten diese fast immer auch Verstöße gegen Regel Nr. 1 (mit denen niemand argumentiert). Auch aus Sicht der automatischen Tools ist es nicht schwer, ein Tool zu erstellen, das sich sofort beschwert, wenn Regel 1 verletzt wird, sodass die meisten Fehler rechtzeitig erkannt werden können.

Das dritte Detail (das von Gegnern dieser Regel oft vergessen wird) ist die verwirrende Natur einer leeren Aussage, die durch ein einzelnes Semikolon dargestellt wird. Die meisten Entwickler mit etwas Erfahrung wurden früher oder später durch ein alleiniges falsch platziertes Semikolon oder durch eine leere Anweisung, die mit einem einzigen Semikolon geschrieben wurde, verwirrt. Zwei geschweifte Klammern anstelle eines einzelnen Semikolons sind optisch viel einfacher zu erkennen.


1

Ich muss zugeben, dass dies nicht immer {}für eine einzelne Zeile verwendet wird, aber es ist eine gute Praxis.

  • Nehmen wir an, Sie schreiben einen Code ohne Klammern, der so aussieht:

    für (int i = 0; i <100; ++ i) für (int j = 0; j <100; ++ j) DoSingleStuff ();

Und nach einiger Zeit möchten Sie in einer jSchleife einige andere Dinge hinzufügen, und Sie tun dies einfach durch Ausrichten und vergessen, Klammern hinzuzufügen.

  • Speicherfreigabe ist schneller. Nehmen wir an, Sie haben einen großen Umfang und erstellen große Arrays im Inneren (ohne newdass sie im Stapel sind). Diese Arrays werden unmittelbar nach dem Verlassen des Bereichs aus dem Speicher entfernt. Es ist jedoch möglich, dass Sie dieses Array an einem Ort verwenden und es für eine Weile im Stapel liegt und eine Art Müll ist. Da ein Stapel eine begrenzte und recht kleine Größe hat, ist es möglich, die Stapelgröße zu überschreiten. In einigen Fällen ist es daher besser zu schreiben {}, um dies zu verhindern. HINWEIS Dies gilt nicht für eine einzelne Leitung, sondern für solche Situationen:

    if (...) {// SomeStuff ... {// wir haben kein if, while usw. // SomeOtherStuff} // SomeMoreStuff}

  • Die dritte Art der Verwendung ist ähnlich wie bei der zweiten. Es geht nur nicht darum, den Stapel sauberer zu machen, sondern einige Funktionen zu öffnen . Wenn Sie mutexin langen Funktionen arbeiten, ist es normalerweise besser, kurz vor dem Zugriff auf Daten und kurz nach dem Lesen / Schreiben zu sperren und zu entsperren. HINWEIS Diese Methode wird verwendet, wenn Sie eigene classoder structmit constructorund destructorzum Sperren des Speichers haben.

  • Was ist mehr:

    if (...) if (...) SomeStuff (); sonst SomeOtherStuff (); // geht zum zweiten wenn, aber die Zuordnung zeigt, dass es zuerst eingeschaltet ist ...

Alles in allem kann ich nicht sagen, was der beste Weg ist, immer {}für eine einzelne Zeile zu verwenden, aber es ist nichts Schlechtes, das zu tun.

WICHTIGE BEARBEITUNG Wenn Sie das Kompilieren von Code-Klammern für eine einzelne Zeile schreiben, geschieht nichts, aber wenn Ihr Code interpretiert wird, verlangsamt sich der Code geringfügig. Kaum.


1

Es gibt eine Reihe von Möglichkeiten, Steueranweisungen zu schreiben. Bestimmte Kombinationen von ihnen können nebeneinander existieren, ohne die Lesbarkeit zu beeinträchtigen. Andere Kombinationen verursachen jedoch Probleme. Der Style

if (condition)
  statement;

wird bequem mit einigen anderen Arten des Schreibens von Kontrollanweisungen koexistieren, aber nicht so gut mit anderen. Wenn mehrzeilige gesteuerte Anweisungen wie folgt geschrieben werden:

if (condition)
{
  statement;
  statement;
}

dann ist es visuell offensichtlich, welche ifAnweisungen eine einzelne Zeile und welche mehrere Zeilen steuern. Wenn jedoch mehrzeilige ifAnweisungen wie folgt geschrieben werden:

if (condition) {
  statement;
  statement;
}

Dann ist die Wahrscheinlichkeit, dass jemand versucht, ifKonstrukte mit einer einzelnen Anweisung zu erweitern, ohne die erforderlichen Klammern hinzuzufügen, viel höher.

Die Einzelanweisung in der nächsten Zeile ifkann auch problematisch sein, wenn die Codebasis das Formular in erheblichem Maße verwendet

if (condition) statement;

Meine eigene Präferenz ist, dass die Anweisung in einer eigenen Zeile im Allgemeinen die Lesbarkeit verbessert, außer in Fällen, in denen es viele ifAnweisungen mit ähnlichen Steuerblöcken gibt, z

if (x1 > xmax) x1 = xmax;
if (x1 < xmin) x1 = xmin;
if (x2 > xmax) x2 = xmax;
if (x2 < xmin) x2 = xmin;
etc.

In diesem Fall werde ich solchen Gruppen von ifAnweisungen im Allgemeinen eine Leerzeile voranstellen und folgen , um sie visuell von anderem Code zu trennen. Wenn Sie eine Reihe von Anweisungen haben, die alle mit ifdemselben Einzug beginnen, erhalten Sie einen klaren visuellen Hinweis darauf, dass etwas Ungewöhnliches vorliegt.

Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.