Sind Zuordnungen im Bedingungsteil von Bedingungen eine schlechte Praxis?

35

Nehmen wir an, ich möchte eine Funktion schreiben, die zwei Zeichenfolgen in C verkettet.

void concat(char s[], char t[]){
    int i = 0;
    int j = 0;

    while (s[i] != '\0'){
        i++;
    }

    while (t[j] != '\0'){
        s[i] = t[j];
        i++;
        j++;
    }

    s[i] = '\0';

}

Allerdings K & R in ihrem Buch umgesetzt es anders, vor allem mit so viel im Bedingungsteil der while - Schleife wie möglich:

void concat(char s[], char t[]){
    int i, j;
    i = j = 0;
    while (s[i] != '\0') i++;

    while ((s[i++]=t[j++]) != '\0');

}

Welcher Weg wird bevorzugt? Ist es empfehlenswert oder nicht empfehlenswert, wie bei K & R Code zu schreiben? Ich glaube, meine Version wäre für andere leichter zu lesen.

Richard Smith
quelle
38
Vergessen Sie nicht, K & R wurde erstmals 1978 veröffentlicht. Seitdem wurden einige kleine Änderungen an der Codierung vorgenommen.
corsiKa
28
Die Lesbarkeit war in der Zeit der Fernschreiber und zeilenorientierten Redakteure sehr unterschiedlich. Früher war es besser lesbar , alles auf eine einzige Zeile zu schreiben.
user2357112 unterstützt Monica
15
Ich bin schockiert darüber, dass sie Indizes und Vergleiche mit '\ 0' haben, anstatt von so etwas wie while (*s++ = *t++); (Mein C ist sehr verrostet, brauche ich dort Parens, damit der Operator Vorrang hat?). Hat K & R eine neue Version ihres Buches veröffentlicht? Ihr ursprüngliches Buch hatte einen äußerst präzisen und idiomatischen Code.
user949300
4
Lesbarkeit ist eine sehr persönliche Sache - auch in den Tagen der Teletypen. Unterschiedliche Menschen bevorzugen unterschiedliche Stile. Ein Großteil des Befehlspakets hatte mit der Codegenerierung zu tun. In jenen Tagen konnten einige Befehlssätze (z. B. Daten allgemein) mehrere Operationen in einen Befehl packen. Außerdem gab es in den frühen 80ern den Mythos, dass die Verwendung von Klammern mehr Anweisungen erzeugte. Ich musste den Assembler generieren, um dem Code-Reviewer zu beweisen, dass es sich um einen Mythos handelt.
Tasse
10
Beachten Sie, dass die beiden Codeblöcke nicht äquivalent sind. Der erste Codeblock kopiert nicht die Beendigung '\0'von t(die whileAusgänge zuerst). Dadurch bleibt die resultierende sZeichenfolge ohne Abschluss '\0'(es sei denn, der Speicherort wurde bereits auf Null gesetzt). Der zweite Codeblock erstellt die Kopie des Abschlusses '\0'vor dem Verlassen der whileSchleife.
Makyen

Antworten:

80

Immer lieber Klarheit als Klugheit. In früheren Jahren war der beste Programmierer der, dessen Code niemand verstehen konnte. "Ich kann seinen Code nicht verstehen, er muss ein Genie sein" , sagten sie. Heutzutage ist der beste Programmierer der, dessen Code jeder verstehen kann. Die Computerzeit ist jetzt billiger als die Zeit des Programmierers.

Jeder Dummkopf kann Code schreiben, den ein Computer verstehen kann. Gute Programmierer schreiben Code, den Menschen verstehen können. (M. Fowler)

Also würde ich ohne Zweifel Option A wählen. Und das ist meine endgültige Antwort.

Tulains Córdova
quelle
8
Sehr markige Ideologie, aber die Tatsache bleibt, dass es nichts Falsches an der Zuordnung von Bedingungen gibt. Es ist weitaus besser, eine Schleife vorzeitig zu verlassen oder Code vor und innerhalb einer Schleife zu duplizieren.
Miles Rout
26
@ MilesRout gibt es. Irgendetwas stimmt nicht, wenn Code einen Nebeneffekt hat, den Sie nicht erwarten, dh Funktionsargumente übergeben oder Bedingungen auswerten. Nicht einmal Erwähnung, if (a=b)die leicht für verwechselt werden kann if (a==b).
Arthur Havlicek
12
@Luke: "Meine IDE kann X aufräumen, daher ist es kein Problem" ist eher wenig überzeugend. Wenn es kein Problem ist, warum macht es die IDE so einfach, es zu "beheben"?
Kevin
6
@ArthurHavlicek Ich stimme Ihrem allgemeinen Punkt zu, aber Code mit Nebenwirkungen bei Bedingungen ist wirklich nicht so ungewöhnlich: while ((c = fgetc(file)) != EOF)als erster fällt mir das ein.
Daniel Jour
3
+1 "Wenn man bedenkt, dass das Debuggen doppelt so schwierig ist wie das Schreiben eines Programms, wie werden Sie es jemals debuggen, wenn Sie so schlau sind, wie Sie können, wenn Sie es schreiben?" BWKernighan
Christophe
32

Die goldene Regel ist, genau wie in Tulains Córdovas Antwort, sicherzustellen, dass verständlicher Code geschrieben wird. Aber ich stimme der Schlussfolgerung nicht zu. Diese goldene Regel bedeutet, Code zu schreiben, den ein typischer Programmierer verstehen kann, der am Ende Ihren Code wartet. Und Sie beurteilen am besten, wer der typische Programmierer ist, der am Ende Ihren Code wartet.

Für die Programmierer, die nicht mit C begonnen haben, ist die erste Version wahrscheinlich aus Gründen, die Sie bereits kennen, einfacher zu verstehen.

Für diejenigen, die mit diesem C-Stil aufgewachsen sind, ist die zweite Version möglicherweise leichter zu verstehen: Für sie ist es ebenso verständlich, was der Code tut, für sie bleiben weniger Fragen, warum er so geschrieben ist, wie er ist, und für sie Wenn weniger vertikaler Raum vorhanden ist, kann mehr Kontext auf dem Bildschirm angezeigt werden.

Sie müssen sich auf Ihren eigenen gesunden Menschenverstand verlassen. Für welche Zielgruppe möchten Sie Ihren Code am einfachsten verständlich machen? Ist dieser Code für eine Firma geschrieben? Das Zielpublikum sind dann wahrscheinlich die anderen Programmierer in diesem Unternehmen. Ist das ein persönliches Hobbyprojekt, an dem niemand außer dir selbst arbeiten wird? Dann sind Sie Ihre eigene Zielgruppe. Möchten Sie diesen Code mit anderen teilen? Dann sind diese anderen Ihre Zielgruppe. Wählen Sie die Version aus, die zu dieser Zielgruppe passt. Leider gibt es keinen bevorzugten Weg, um zu ermutigen.

hvd
quelle
14

BEARBEITEN: Die Zeile s[i] = '\0';wurde der ersten Version hinzugefügt und dadurch wie in Variante 1 unten beschrieben korrigiert, sodass dies nicht mehr für die aktuelle Version des Fragencodes gilt.

Die zweite Version hat den entscheidenden Vorteil, dass sie korrekt ist , während die erste dies nicht ist - sie beendet die Zielzeichenfolge nicht korrekt mit Null.

Die "Zuweisung in Bedingung" ermöglicht es, das Konzept "jedes Zeichen kopieren, bevor auf das Null-Zeichen geprüft wird" sehr kurz und so auszudrücken , dass die Optimierung für den Compiler etwas einfacher wird, obwohl viele Software-Ingenieure diese Art von Code heutzutage weniger lesbar finden . Wenn Sie darauf bestehen, die erste Version zu verwenden, müssen Sie dies auch tun

  1. Fügen Sie die Null-Terminierung nach dem Ende der zweiten Schleife hinzu (wenn Sie mehr Code hinzufügen, aber Sie können argumentieren, dass sich die Lesbarkeit lohnt) oder
  2. Ändern Sie den Schleifenkörper in "zuerst zuweisen, dann das zugewiesene Zeichen überprüfen oder speichern und dann die Indizes inkrementieren". Das Überprüfen des Zustands in der Mitte der Schleife bedeutet das Herausbrechen der Schleife (Verringerung der Klarheit, die von den meisten Puristen missbilligt wird). Das Speichern des zugewiesenen Zeichens würde bedeuten, eine temporäre Variable einzuführen (was die Übersichtlichkeit und Effizienz verringert). Beides würde meiner Meinung nach den Vorteil zunichte machen.
hjhill
quelle
Richtig ist besser als lesbar und prägnant.
user949300
5

Die Antworten von Tulains Córdova und hvd decken die Klarheits- / Lesbarkeitsaspekte recht gut ab. Lassen Sie mich das Scoping als weiteren Grund für die Zuweisung von Bedingungen anführen . Eine in der Bedingung deklarierte Variable ist nur im Gültigkeitsbereich dieser Anweisung verfügbar. Sie können diese Variable nicht versehentlich später verwenden. Die for- Schleife macht das schon seit Ewigkeiten. Und es ist wichtig genug, dass das kommende C ++ 17 eine ähnliche Syntax für if und switch einführt :

if (int foo = bar(); foo > 42) {
    do_stuff();
}

foo = 23;   // compiler error: foo is not in scope
besc
quelle
3

Nein, es ist ein sehr normaler und normaler C-Stil. Ihr Beispiel ist schlecht, weil es nur eine for-Schleife sein sollte, aber im Allgemeinen ist daran nichts auszusetzen

if ((a = f()) != NULL)
    ...

zum Beispiel (oder mit while).

Miles Rout
quelle
7
Es stimmt etwas nicht; `! = NULL` und seine Verwandtschaft in einer C-Bedingung sind nur dazu da, Entwickler zu beruhigen, die mit dem Konzept, dass ein Wert wahr oder falsch ist (oder umgekehrt), nicht einverstanden sind.
Jonathan Cast
1
@jcast Nein, es ist expliziter einzuschließen != NULL.
Miles Rout
1
Nein, es ist expliziter zu sagen (x != NULL) != 0. Das ist es doch, was C wirklich überprüft, oder?
Jonathan Cast
@jcast Nein, das ist es nicht. Wenn Sie prüfen, ob etwas ungleich false ist, schreiben Sie Bedingungen in keiner Sprache.
Miles Rout
"Wenn Sie überprüfen, ob etwas ungleich falsch ist, schreiben Sie Bedingungen in keiner Sprache." Genau.
Jonathan Cast
2

In den Tagen von K & R

  • 'C' war portabler Assembler-Code
  • Es wurde von Programmierern verwendet, die an Assembler-Code dachten
  • Der Compiler hat nicht viel optimiert
  • Die meisten Computer hatten "komplexe Befehlssätze", die beispielsweise while ((s[i++]=t[j++]) != '\0')auf den meisten CPUs einem Befehl zugeordnet waren (ich erwarte die Dec VAC).

Es Tage

  • Die meisten Leute, die C-Code lesen, sind keine Assembler-Programmierer
  • C-Compiler führen viele Optimierungen durch. Daher wird der einfacher zu lesende Code wahrscheinlich in denselben Maschinencode übersetzt.

(Ein Hinweis zur Verwendung von geschweiften Klammern - die erste Codemenge nimmt aufgrund von "nicht benötigten" Zeichen mehr Platz ein. Nach {}meiner Erfahrung verhindern diese häufig, dass Code, der schlecht vom Compiler zusammengeführt wurde, fehlerhaft platziert wird.) von Werkzeugen erkannt.)

In früheren Zeiten hätte die 2. Version des Codes gelesen. (Wenn ich es richtig verstanden habe!)

concat(char* s, char *t){      
    while (*s++);
    --s;
    while (*s++=*t++);
}
Ian
quelle
2

Sogar dies überhaupt tun zu können, ist eine sehr schlechte Idee. Es ist umgangssprachlich als "The World's Last Bug" bekannt, wie folgt:

if (alert = CODE_RED)
{
   launch_nukes();
}

Während Sie wahrscheinlich nicht einen Fehler machen , das ist ganz so streng, ist es sehr leicht versehentlich vermasseln und verursachen eine schwer zu findende Fehler in der Code - Basis. Die meisten modernen Compiler fügen eine Warnung für Zuweisungen innerhalb einer Bedingung ein. Sie sind aus einem bestimmten Grund da, und Sie tun gut daran, sie zu beachten und dieses Konstrukt einfach zu vermeiden.

Mason Wheeler
quelle
Vor dieser Warnung haben wir geschrieben, CODE_RED = alertsodass es einen Compilerfehler gab.
Ian
4
@ Ian Yoda Bedingungen, die aufgerufen wird. Schwer zu lesen. Leider ist die Notwendigkeit für sie.
Mason Wheeler
Nach einer sehr kurzen Einführungsphase sind die Yoda-Bedingungen nicht schwerer zu lesen als die normalen. Manchmal sind sie besser lesbar . Wenn Sie zum Beispiel eine Folge von ifs / elseifs haben, ist es eine leichte Verbesserung, wenn die Bedingung links auf eine höhere Betonung geprüft wird.
user949300
2
@ user949300 Zwei Wörter: Stockholm-Syndrom: P
Mason Wheeler
2

Beide Stile sind wohlgeformt, korrekt und angemessen. Welche Option besser geeignet ist, hängt weitgehend von den Stilrichtlinien Ihres Unternehmens ab. Moderne IDEs erleichtern die Verwendung beider Stile durch die Verwendung von Live-Syntax-Flusen, die explizit Bereiche hervorheben, die ansonsten zu Verwirrung geführt haben könnten.

Beispielsweise wird der folgende Ausdruck von Netbeans hervorgehoben :

if($a = someFunction())

aufgrund "zufälliger Abtretung".

Bildbeschreibung hier eingeben

Um Netbeans explizit mitzuteilen, dass "Ja, das wollte ich wirklich ...", kann der Ausdruck in Klammern gesetzt werden.

if(($a = someFunction()))

Bildbeschreibung hier eingeben

Letztendlich läuft alles auf Unternehmensrichtlinien und die Verfügbarkeit moderner Tools zur Erleichterung des Entwicklungsprozesses hinaus.

Luke A. Leber
quelle