Styling composto AND / OR se istruzioni


13

Come si modellano complesse istruzioni AND / OR if per la massima leggibilità? Come si rientra e dove si posizionano le interruzioni di riga? La mia situazione particolare è simile alla seguente. È decisamente meglio che distruggere tutto in una riga, ma sembra ancora disordinato.

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

1
Il rientro sciocco e la parentesi / struttura del controvento sono intenzionali o fanno parte dello stile?
Ed S.

Divertente. Ho fatto la stessa domanda su SO una settimana fa e si è chiusa. Sono contento di vedere questa domanda viva da qualche parte!
Eric Belair,

Risposte:


6

Crea variabili booleane per ogni piccolo passaggio:

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

Questo è ovviamente simile alla risposta di Lacrymology, tranne che con nomi diversi per ogni passaggio.

Se nominate step1, step2e step3in modi che hanno un buon senso concettuale, questo dovrebbe essere di gran lunga il più leggibile. p.isGood()e a k.isSomething()volte può essere invocato in situazioni in cui non sarebbe nel tuo codice originale, quindi questa non sarebbe un'opzione se quelle funzioni sono costose o se stai eseguendo questo codice in un ciclo molto stretto.

D'altra parte, non è necessario preoccuparsi dell'hit di prestazioni che potrebbe comportare la creazione di nuove variabili; un buon compilatore li ottimizzerà.

Un esempio con il rilevamento delle collisioni rettangolari (che probabilmente non useresti a causa del summenzionato hit delle prestazioni):

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(); }

Potrebbe diventare:

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(); }

Inoltre, se vuoi lasciare il tuo codice così com'è, penso che andrebbe anche bene. Sinceramente penso che il tuo codice sia abbastanza leggibile. Ovviamente non so cosa a b x y i u p k m nsiano esattamente , ma per quanto riguarda la struttura, mi sembra buono.


8

Di solito, ritrodo il mio codice in modo che sia più modulare se i miei condizionali diventano così complicati.


Eviterei il rifrattore in questa situazione. Sarebbe sciocco testare tali piccole funzioni isolatamente e avere le definizioni che penzolano al di fuori della funzione rende il codice meno evidente.
Rei Miyasaka,

Dipende da quanto bene rifatti. Direi che rende il tuo codice molto più evidente di avere nomi di funzioni significative invece di una stringa di condizionali che assomiglia a un libro di testo di algebra vomitato nel tuo codice.
JohnFx,

Visivamente però, avresti le tue funzioni appese fuori e non sarebbe immediatamente ovvio cosa fa esattamente la funzione. È momentaneamente una scatola nera finché non scorri verso l'alto. A meno che tu non sia in un linguaggio che consente funzioni nelle funzioni, non penso che sarebbe molto conveniente né per il lettore né per lo scrittore, indipendentemente da quanto bene rifrattori. E se sei in un linguaggio che consente le funzioni nelle funzioni, allora è probabile che la sintassi non sia affatto diversa dal dichiarare invece un legame o una variabile, ad esempio let x = a > bo let f a b = a > b.
Rei Miyasaka,

Le variabili funzionerebbero ugualmente bene. Considererei anche quel refactoring.
JohnFx,

Ah ok.
Rei Miyasaka,

8

Farei qualcosa di più simile a questo, a questo livello di complessità

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

if(doIt)
{
    doSomething();
}

è brutto, ma è leggibile e sono abbastanza sicuro che il compilatore saprà come riformattare.

D'altra parte, se mai mi vedessi nella situazione di scrivere una simile dichiarazione IF, ripenserei la soluzione, perché sono CERTO che c'è un modo di farlo più semplice, o almeno di astrarre parte di quella condizione (es: forse x == y && a != b && p.isGood()davvero cattivo this->isPolygon()e posso fare quel metodo;


4

Sto diventando meno ossessionato dall'allineamento verticale nel tempo, ma la mia forma generale con espressioni multilinea è ...

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

Punti chiave...

  • Le parentesi chiuse si allineano verticalmente con le parentesi aperte, come con le parentesi graffe.
  • Le sottoespressioni che rientrano in una riga si trovano all'interno di una riga e sono allineate verticalmente a sinistra. Dove aiuta la leggibilità, anche gli operatori infix all'interno di quelle parti a linea singola sono allineati verticalmente.
  • Le parentesi graffe di chiusura creano naturalmente linee quasi vuote, contribuendo a raggruppare visivamente le cose.

A volte, formatterò +e* altri operatori come questo. Molte espressioni complesse assumono una forma di somma di prodotto o prodotto di somma (che può riferirsi a "somme" e "prodotti" booleani), quindi è probabilmente abbastanza comune che ne valga la pena uno stile coerente.

Stai attento, però. È spesso meglio refactoring (spostare parti dell'espressione in una funzione, o calcolare e memorizzare parti intermedie in una variabile) piuttosto che usare il rientro per cercare di rendere più leggibile un'espressione complessa.

Se preferisci impilare le parentesi chiuse sul lato destro, non lo odio , ma immagino che non sia poi così male. Se preso troppo lontano, si corre il rischio che un errore possa lasciare il rientro travisando ciò che fanno le parentesi.

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

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

+1 Mi piace il tuo stile. Ha risposto direttamente alla mia domanda, ma penso che Rei Miyasaka abbia inchiodato la radice del problema. Se mai dovessi essere pigro per fare il metodo di Rei, userò il tuo stile.
JoJo,

Caspita, è davvero carino.
Rei Miyasaka,

1

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

Concordo con la risposta di JohnFx e con quella di Lacrymology. Costruirò un mucchio di funzioni (preferibilmente statiche) che raggiungono piccoli obiettivi e poi si sviluppano su di loro in modo intelligente.

Allora, che ne dici di qualcosa del genere? Nota, questa non è la soluzione perfetta, ma funziona. Ci sono modi per ripulire ulteriormente, ma sono necessarie informazioni più specifiche. Nota: questo codice dovrebbe essere eseguito altrettanto velocemente, poiché il compilatore è intelligente.

// 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();
  }
}

Se avere un solo punto di uscita da una funzione è qualcosa che apprezzi (ad esempio se sei in un linguaggio funzionale), questa potrebbe non essere l'opzione migliore, o anche una disponibile. Detto questo, sì, è quello che faccio spesso.
Rei Miyasaka,

E se fosse un linguaggio interpretato come Javascript?
JoJo,

@ Rei Miyasaka, quanto apprezzo dipende da una lingua. Mentre mi piace la famiglia di lingue LISP, non ho dovuto usarne una al lavoro. Se devo ripensare la funzione di qualcun altro ma non toccare nessun altro codice (spesso una realtà), farei qualcosa di simile sopra. Se riesco a scrivere / riscrivere questa logica da zero, il mio approccio sarà diverso, ma non posso scrivere tale codice senza avere un esempio specifico di ciò che l'autore sta cercando di fare qui.
Giobbe

1
@Rei Miyasaka, quella persona potrebbe essere un genio o essere pieno di merda. Non so tutto, ma sarei curioso di sapere la difesa di quella persona dal punto di uscita singola. Si discute di questo qui e su SO, e l'impressione che ho avuto è stata che questo approccio è stato popolare tra gli accademici negli anni '80 forse, ma non conta più, e può in effetti ostacolare la leggibilità. Naturalmente, se si sta facendo tutto in stile funzionale LINQ, questo problema non si presenta nemmeno.
Giobbe

2
@Job @Steve Penso che sia una linea guida più importante nelle lingue che richiedono una deallocazione esplicita. Ovviamente non per tutte le funzioni, ma è probabilmente un'abitudine che i programmatori principianti sono stati incoraggiati a mantenere per evitare di dimenticare le risorse gratuite.
Rei Miyasaka,

1

Per quello che vale, sono stato sorpreso di vedere che il tuo esempio assomiglia molto ai predicati complicati che ho scritto. Concordo con gli altri sul fatto che un predicato complicato non è il massimo per manutenibilità o leggibilità, ma a volte si presentano.

Vorrei sottolineare che hai corretto questa parte: && a != b MAI mettere il connettore logico alla fine di una linea, è troppo facile perdere visivamente. Un altro posto in cui non si dovrebbe MAI mettere un operatore alla fine della riga è in concatenazione di stringhe, in lingue con tale operatore.

Fai questo:

String a = b
   + "something"
   + c
   ;

Non farlo:

String a = b +
   "something" +
   c;

Hai qualche logica o studio a supporto della tua affermazione per i connettori logici o è semplicemente la tua preferenza dichiarata come un fatto? L'ho sentito molto in vari posti, e non l'ho mai capito (a differenza dei condizionali yoda, che hanno una valida ragione [se sbagliata]).
Caleb Huitt - cjhuitt il

@Caleb - La matematica è stata composta in quel modo per secoli. Quando scremiamo il codice, ci concentriamo sul lato sinistro di ogni riga. Una riga che inizia con un operatore è ovviamente una continuazione della riga precedente e non una nuova affermazione erroneamente rientrata.
Kevin Cline il

Mi piace anche il prefisso degli operatori matematici. Mi sono reso conto fino alla fine della mia carriera di programmatore :)
JoJo,

0

Se il condizionale è così complicato, di solito è un'indicazione che dovrebbe essere suddiviso in parti. Forse una clausola può essere assegnata a una variabile intermedia. Forse una clausola può essere trasformata in un metodo di supporto. In genere preferisco non avere così tanti ands in una riga.


0

È possibile suddividere il codice in più istruzioni, semplificando la comprensione. Ma un vero ninja farebbe qualcosa del genere. :-)

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

5
Sono un fan degli spazi bianchi, ma questo è eccessivamente imbottito con linee quasi vuote per i miei gusti.
Steve314,
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.