Stylingverbindung UND / ODER wenn Anweisungen


13

Wie gestalten Sie komplexe zusammengesetzte AND / OR if-Anweisungen für maximale Lesbarkeit? Wie rücken Sie ein und wo platzieren Sie die Zeilenumbrüche? Meine besondere Situation ist in etwa wie folgt. Es ist definitiv besser als alles in eine Zeile zu zerschlagen, sieht aber immer noch chaotisch aus.

if (
    (
        x == y
        && a != b
        && p.isGood() 
        && (
            i + u == b
            || q >= a
        )
    )
    || k.isSomething()
    || m > n
) {
    doSomething();
}

1
Ist das doofe Einrücken und die Klammern- / Klammerstruktur beabsichtigt oder Teil des Stils?
Ed S.

Komisch. Ich habe die gleiche Frage vor einer Woche bei SO gestellt und sie wurde geschlossen. Freut mich, diese Frage irgendwo lebend zu sehen!
Eric Belair

Antworten:


6

Machen Sie für jeden kleinen Schritt boolesche Variablen:

bool step1 = i + u == b || q >= a;
bool step2 = a != b && p.isGood() && group1;
bool step3 = group2 || k.isSomething() || m > n;
if (step3) { doSomething(); }

Dies ist natürlich der Antwort von Lacrymology ähnlich, außer dass für jeden Schritt andere Namen verwendet werden.

Wenn Sie benennen step1, step2und step3in einer Weise , die guten konzeptionellen Sinn machen, soll dies mit Abstand der meisten lesbar. p.isGood()und k.isSomething()kann manchmal in Situationen aufgerufen werden, in denen es nicht in Ihrem ursprünglichen Code wäre, so wäre dies keine Option, wenn diese Funktionen teuer sind oder wenn Sie diesen Code in einer sehr engen Schleife ausführen.

Auf der anderen Seite brauchen Sie sich keine Sorgen über die Leistung zu machen, die durch das Erstellen neuer Variablen entstehen könnte. Ein guter Compiler wird sie optimieren.

Ein Beispiel mit Rechteckkollisionserkennung (die Sie aufgrund des oben genannten Leistungseinbruchs wahrscheinlich nicht verwenden würden):

if((a.x + a.width >= b.x || b.x + b.width >= a.x)
 && (a.y + a.height >= b.y || b.y + b.width >= a.y)
)
{ collision(); }

Könnte werden:

bool horizMatch = a.x + a.width >= b.x || b.x + b.width >= a.x;
bool vertMatch = a.y + a.height >= b.y || b.y + b.width >= a.y;
if(horizMatch && vertMatch) { collision(); }

Auch wenn Sie Ihren Code so lassen möchten, wie er ist, wäre das meiner Meinung nach auch völlig in Ordnung. Ich denke ehrlich, dass Ihr Code gut lesbar ist. Offensichtlich weiß ich nicht genau, was genau a b x y i u p k m nist, aber was die Struktur angeht, sieht es für mich gut aus.


8

Normalerweise verändere ich meinen Code, um modularer zu sein, wenn meine Bedingungen so kompliziert werden.


In dieser Situation würde ich Refrakturierung vermeiden. Es wäre albern, solche kleinen Funktionen isoliert zu testen, und wenn die Definitionen außerhalb der Funktion baumeln, wird der Code weniger offensichtlich.
Rei Miyasaka

Es kommt darauf an, wie gut Sie umgestalten. Ich würde argumentieren, dass es Ihren Code viel offensichtlicher macht, aussagekräftige Funktionsnamen anstelle einer Reihe von Bedingungen zu haben, die aussieht wie ein Algebra-Lehrbuch, das in Ihrem Code auftaucht.
JohnFx

Optisch würden Ihre Funktionen jedoch draußen baumeln, und es wäre nicht sofort klar, was genau die Funktion bewirkt. Es ist momentan eine Blackbox, bis Sie nach oben scrollen. Es sei denn, Sie sprechen eine Sprache, die Funktionen in Funktionen zulässt, und ich denke, dass dies weder für den Leser noch für den Schreiber sehr praktisch ist, unabhängig davon, wie gut Sie sich refraktiv verhalten. Und wenn Sie in einer Sprache arbeiten, die Funktionen in Funktionen zulässt, unterscheidet sich die Syntax wahrscheinlich kaum von der Deklaration einer Bindung oder Variablen, z . B. let x = a > boder let f a b = a > b.
Rei Miyasaka

Variablen würden genauso gut funktionieren. Ich würde das Refactoring auch in Betracht ziehen.
JohnFx

Ah, okay.
Rei Miyasaka

8

Ich würde so etwas eher machen, auf dieser Ebene der Komplexität

bool doIt = x == y && a != b && p.isGood();
doIt &= ( i + u == b || q >= a);
doIt |= k.isSomething() || m > n;

if(doIt)
{
    doSomething();
}

Es ist hässlich, aber lesbar und ich bin mir ziemlich sicher, dass der Compiler weiß, wie er es umgestalten kann.

Wenn ich mich jedoch jemals in der Situation befinde, eine solche IF-Anweisung zu schreiben, überlege ich mir die Lösung neu, da ich BESTIMMT bin, dass es eine Möglichkeit gibt, dies einfacher zu machen oder zumindest einige dieser Bedingungen zu abstrahieren (z. B .: Vielleicht x == y && a != b && p.isGood()wirklich nur gemein this->isPolygon()und ich kann diese Methode machen;


4

Ich bin mit der Zeit weniger besessen von der vertikalen Ausrichtung, aber meine allgemeine Form mit mehrzeiligen Ausdrücken ist ...

if (   (   (expr1 == expr2)
        || (expr3 == expr4)
        || (expr5 == expr6)
       )
    && (   (expr7 == expr8)
        || (expr9 == expra)
       )
   )
{
  blah;
}

Wichtige Punkte ...

  • Die geschlossenen Parens werden wie die geschweiften Klammern vertikal zu den offenen Parens ausgerichtet.
  • Die Unterausdrücke, die in eine Zeile passen, befinden sich in einer Zeile und sind vertikal links ausgerichtet. Wo dies die Lesbarkeit verbessert, sind die Infix-Operatoren in diesen einzeiligen Teilen ebenfalls vertikal ausgerichtet.
  • Die schließenden Klammern erzeugen auf natürliche Weise fast leere Linien und helfen, Dinge visuell zu gruppieren.

Manchmal formatiere ich +und *einige andere Operatoren auch so. Nicht wenige komplexe Ausdrücke haben eine Produkt-Summe- oder eine Produkt-Summe-Form (die sich auf boolesche "Summen" und "Produkte" beziehen kann), sodass es wahrscheinlich häufig genug ist, dass sich ein konsistenter Stil dafür lohnt.

Aber sei vorsichtig damit. Es ist oft besser, eine Umgestaltung vorzunehmen (Teile des Ausdrucks in eine Funktion zu verschieben oder Zwischenteile in einer Variablen zu berechnen und zu speichern), als Einrückungen zu verwenden, um zu versuchen, einen überkomplexen Ausdruck lesbarer zu machen.

Wenn Sie es vorziehen, Ihre Close-Parens auf der rechten Seite zu stapeln, hasse ich es nicht , aber ich denke, es ist nicht so schlimm. Wenn Sie zu weit gehen, laufen Sie Gefahr, dass der Einzug durch einen Fehler falsch dargestellt wird, was die Klammern tun.

if (   (   (expr1 == expr2)
        || (expr3 == expr4)
        || (expr5 == expr6))

    && (   (expr7 == expr8)
        || (expr9 == expra)))
{
  blah;
}

+1 Ich mag dein Styling. Es hat meine Frage direkt beantwortet, aber ich denke, Rei Miyasaka hat die Wurzel des Problems geschlagen. Wenn ich jemals zu faul bin, um die Methode von Rei auszuführen, benutze ich dein Styling.
JoJo

Wow das ist wirklich schön.
Rei Miyasaka

1

http://www.codinghorror.com/blog/2006/01/flattening-arrow-code.html

Ich stimme der Antwort von JohnFx sowie einer von Lacrymology zu. Ich würde eine Reihe von Funktionen (vorzugsweise statische) erstellen, die kleine Ziele erreichen und diese dann auf intelligente Weise aufbauen.

Also, wie wäre es mit so etwas? Beachten Sie, dass dies nicht die perfekte Lösung ist, aber es funktioniert. Es gibt Möglichkeiten, dies weiter zu bereinigen, es sind jedoch spezifischere Informationen erforderlich. Hinweis: Dieser Code sollte genauso schnell laufen, denn der Compiler ist schlau.

// Currently based on members or global vars
// (which is often a bad idea too)
function doSomethingCondirionally()
{
  if (k.isSomething() || m > n)
  {
    doSomething();
    return;
  }

  // Else ... 
  if (x != y) return;
  if (a == b) return;
  if (!p.isGood()) return;

  // Final, positive check
  if (i + u == b || q >= a)
  {
    doSomething();
  }
}

Wenn Sie Wert darauf legen, nur einen Exit-Punkt für eine Funktion zu haben (z. B. in einer funktionalen Sprache), ist dies möglicherweise nicht die beste oder sogar eine verfügbare Option. Das heißt, ja, das ist, was ich oft tue.
Rei Miyasaka

Was ist, wenn es sich um eine interpretierte Sprache wie Javascript handelt?
JoJo

@ Rei Miyasaka, wie sehr ich es schätze, hängt von einer Sprache ab. Während ich die LISP-Sprachfamilie mag, musste ich bei der Arbeit keine verwenden. Wenn ich die Funktion eines anderen neu faktorisieren muss, aber keinen anderen Code (häufig eine Realität) berühre, dann würde ich so etwas wie oben tun. Wenn ich diese Logik von Grund auf neu schreiben / umschreiben kann, ist mein Ansatz anders, aber ich kann keinen solchen Code schreiben, ohne ein bestimmtes Beispiel dafür zu haben, was der Autor hier versucht.
Job

1
@Rei Miyasaka, diese Person könnte ein Genie sein oder voller Mist. Ich weiß nicht alles, aber ich wäre gespannt darauf, wie diese Person den Single-Exit-Punkt verteidigt. Hier und auf SO wird darüber diskutiert, und ich hatte den Eindruck, dass dieser Ansatz in den 80er Jahren unter Wissenschaftlern beliebt war, aber keine Rolle mehr spielt und tatsächlich die Lesbarkeit beeinträchtigen kann. Wenn Sie alles im funktionalen Stil von LINQ ausführen, tritt dieses Problem natürlich nicht einmal auf.
Job

2
@Job @Steve Ich denke, es ist eine wichtigere Richtlinie für Sprachen, die eine explizite Freigabe erfordern. Natürlich nicht für jede Funktion, aber es ist wahrscheinlich eine Angewohnheit, die Programmieranfänger zu halten ermutigt wurden, um nicht zu vergessen, Ressourcen frei zu machen.
Rei Miyasaka

1

Für das, was es wert ist, war ich überrascht zu sehen, dass Ihr Beispiel den komplizierten Prädikaten, die ich geschrieben habe, sehr ähnlich sieht. Ich stimme anderen zu, dass ein kompliziertes Prädikat für die Wartbarkeit oder Lesbarkeit nicht das beste ist, aber gelegentlich tauchen sie auf.

Lassen Sie mich betonen, dass Sie diesen Teil richtig gemacht haben: Setzen Sie && a != b NIEMALS einen logischen Konnektor an das Ende einer Zeile, es ist zu leicht, ihn visuell zu übersehen. Eine andere Stelle, an der Sie NIEMALS einen Operator ans Ende der Zeile setzen sollten, ist die Verkettung von Zeichenfolgen in Sprachen mit einem solchen Operator.

Mach das:

String a = b
   + "something"
   + c
   ;

Mach das nicht:

String a = b +
   "something" +
   c;

Haben Sie eine Logik oder Studien, die Ihre Behauptung für die logischen Konnektoren stützen, oder ist es nur Ihre Präferenz, die als Tatsache angegeben wird? Ich habe dies an verschiedenen Stellen oft gehört und es nie verstanden (im Gegensatz zu Yoda-Bedingungen, die einen gültigen [wenn auch fehlgeleiteten] Grund haben).
Caleb Huitt - cjhuitt

@Caleb - Mathematik ist seit Jahrhunderten auf diese Weise gesetzt. Beim Überfliegen von Code konzentrieren wir uns auf die linke Seite jeder Zeile. Eine Zeile, die mit einem Operator beginnt, ist offensichtlich eine Fortsetzung der vorherigen Zeile und keine falsch eingerückte neue Anweisung.
Kevin Cline

Ich mag auch das Präfixieren von mathematischen Operatoren. Ich habe es viel zu spät in meiner Programmierkarriere bemerkt :)
JoJo

0

Wenn die Bedingung so kompliziert ist, ist dies normalerweise ein Hinweis darauf, dass sie in Teile zerlegt werden sollte. Möglicherweise kann einer Zwischenvariablen eine Klausel zugewiesen werden. Vielleicht kann eine Klausel in eine Hilfsmethode umgewandelt werden. Im Allgemeinen bevorzuge ich es, nicht so viele Ands und Ors in einer Zeile zu haben.


0

Sie können den Code in mehrere Anweisungen aufteilen, um das Verständnis zu vereinfachen. Aber ein echter Ninja würde so etwas tun. :-)

if
(
    (
        x == y
    &&
        a != b
    &&
        p.isGood()
    &&
        (
            i + u == b
        ||
            q >= a
        )
    )
||
    k.isSomething()
||
    m > n
)
{
    doSomething();
}

5
Ich bin ein Fan von Whitespace, aber dies wird für meinen Geschmack mit fast leeren Zeilen übermäßig aufgefüllt.
Steve314
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.