istruzione if - valutazione di cortocircuito vs leggibilità


90

A volte, ifun'istruzione può essere piuttosto complicata o lunga, quindi per motivi di leggibilità è meglio estrarre chiamate complicate prima del file if.

ad esempio questo:

if (SomeComplicatedFunctionCall() || OtherComplicatedFunctionCall())
{
    // do stuff
}

in questo

bool b1 = SomeComplicatedFunctionCall();
bool b2 = OtherComplicatedFunctionCall();

if (b1 || b2)
{
    //do stuff
}

(l'esempio fornito non è così male, è solo a scopo illustrativo ... immagina altre chiamate con più argomenti, ecc.)

Ma con questa estrazione ho perso la valutazione del corto circuito (SCE).

  1. Perdo davvero SCE ogni volta? C'è qualche scenario in cui il compilatore è autorizzato a "ottimizzarlo" e continuare a fornire SCE?
  2. Esistono modi per mantenere la leggibilità migliorata del secondo snippet senza perdere SCE?

20
La pratica mostra che la maggior parte delle risposte sulle prestazioni che vedrai qui o in altri posti sono nella maggior parte dei casi sbagliate (4 sbagliate 1 corrette). Il mio consiglio è di fare sempre una profilazione e di controllarla da soli, eviterete "ottimizzazioni premature" e imparerete cose nuove.
Marek R

25
@MarekR non riguarda solo le prestazioni, ma anche i possibili effetti collaterali in OtherCunctionCall ...
relaxxx

3
@David quando si fa riferimento ad altri siti, è spesso utile sottolineare che il cross-posting è disapprovato
gnat

7
Se la leggibilità è la tua preoccupazione principale, non chiamare le funzioni con effetti collaterali all'interno di un if condizionale
Morgen

3
Potenziali elettori stretti: rileggi la domanda. La parte (1) non è basata sull'opinione, mentre la parte (2) può facilmente cessare di essere basata sull'opinione attraverso una modifica che rimuove il riferimento a qualsiasi presunta "migliore pratica", come sto per fare.
duplode

Risposte:


119

Una soluzione naturale sarebbe simile a questa:

bool b1 = SomeCondition();
bool b2 = b1 || SomeOtherCondition();
bool b3 = b2 || SomeThirdCondition();
// any other condition
bool bn = bn_1 || SomeFinalCondition();

if (bn)
{
  // do stuff
}

Questo ha il vantaggio di essere facile da capire, essere applicabile a tutti i casi e avere un comportamento di cortocircuito.


Questa era la mia soluzione iniziale: un buon modello nelle chiamate di metodo e nei corpi del ciclo for è il seguente:

if (!SomeComplicatedFunctionCall())
   return; // or continue

if (!SomeOtherComplicatedFunctionCall())
   return; // or continue

// do stuff

Si ottengono gli stessi vantaggi in termini di prestazioni della valutazione del cortocircuito, ma il codice sembra più leggibile.


4
@relaxxx: ti capisco, ma "più cose da fare dopo if" è anche un segno che la tua funzione o metodo è troppo grande e dovrebbe essere diviso in più piccoli. Non è sempre il modo migliore, ma molto spesso lo è!
nperson325681

2
questo viola il principio della lista bianca
JoulinRouge

13
@ JoulinRouge: interessante, non avevo mai sentito parlare di questo principio. Io stesso preferisco questo approccio "cortocircuito" per i vantaggi sulla leggibilità: riduce i rientri ed elimina la possibilità che si verifichi qualcosa dopo il blocco rientrato.
Matthieu M.

2
È più leggibile? b2Dai un nome correttamente e otterrai someConditionAndSomeotherConditionIsTrue, non molto significativo. Inoltre, devo mantenere un mucchio di variabili nel mio stack mentale durante questo esercizio (e fino a quando non smetto di lavorare in questo ambito). Vorrei andare con SJuan76la soluzione numero 2 di o semplicemente mettere il tutto in una funzione.
Nathan Cooper,

2
Non ho letto tutti i commenti ma dopo una rapida ricerca non ho trovato un grande vantaggio del primo frammento di codice e cioè il debugging. Mettere le cose direttamente nell'istruzione if invece di assegnarle a una variabile in anticipo e quindi utilizzare la variabile invece rende il debug più difficile di quanto dovrebbe essere. L'uso delle variabili consente anche di raggruppare i valori semanticamente insieme, aumentando la leggibilità.
rbaleksandar

31

Tendo a scomporre le condizioni su più righe, ad esempio:

if( SomeComplicatedFunctionCall()
 || OtherComplicatedFunctionCall()
  ) {

Anche quando si ha a che fare con più operatori (&&) è sufficiente far avanzare il rientro con ogni coppia di parentesi. SCE è ancora attivo, non è necessario utilizzare variabili. Scrivere codice in questo modo mi ha reso molto più leggibile già da anni. Esempio più complesso:

if( one()
 ||( two()> 1337
  &&( three()== 'foo'
   || four()
    )
   )
 || five()!= 3.1415
  ) {

28

Se si hanno lunghe catene di condizioni e cosa mantenere in cortocircuito, è possibile utilizzare variabili temporanee per combinare più condizioni. Prendendo il tuo esempio sarebbe possibile fare ad es

bool b = SomeComplicatedFunctionCall() || OtherComplicatedFunctionCall();
if (b && some_other_expression) { ... }

Se si dispone di un compilatore compatibile con C ++ 11, è possibile utilizzare espressioni lambda per combinare espressioni in funzioni, in modo simile a quanto sopra:

auto e = []()
{
    return SomeComplicatedFunctionCall() || OtherComplicatedFunctionCall();
};

if (e() && some_other_expression) { ... }

21

1) Sì, non hai più SCE. Altrimenti, lo avresti

bool b1 = SomeComplicatedFunctionCall();
bool b2 = OtherComplicatedFunctionCall();

funziona in un modo o nell'altro a seconda se c'è una ifdichiarazione successiva. Troppo complesso.

2) Questo è basato sulle opinioni, ma per espressioni ragionevolmente complesse puoi fare:

if (SomeComplicatedFunctionCall()
    || OtherComplicatedFunctionCall()) {

Se è troppo complesso, la soluzione ovvia è creare una funzione che valuti l'espressione e la chiami.


21

Puoi anche usare:

bool b = someComplicatedStuff();
b = b || otherComplicatedStuff(); // it has to be: b = b || ...;  b |= ...; is bitwise OR and SCE is not working then 

e SCE funzionerà.

Ma non è molto più leggibile rispetto ad esempio:

if (
    someComplicatedStuff()
    ||
    otherComplicatedStuff()
   )

3
Non mi piace combinare booleani con un operatore bit per bit, non mi sembra ben digitato. In genere utilizzo tutto ciò che sembra più leggibile a meno che non lavori a un livello molto basso e contino i cicli del processore.
Ant

3
Ho usato in modo specifico b = b || otherComplicatedStuff();e @SargeBorsch ha apportato una modifica per rimuovere SCE. Grazie per avermi notato questo cambiamento @ Ant.
KIIV

14

1) Perdo davvero SCE ogni volta? È consentito al compilatore qualche scenario di "ottimizzarlo" e fornire ancora SCE?

Non credo che tale ottimizzazione sia consentita; in particolare OtherComplicatedFunctionCall()potrebbe avere alcuni effetti collaterali.

2) Qual è la migliore pratica in tale situazione? È solo la possibilità (quando voglio SCE) di avere tutto ciò di cui ho bisogno direttamente all'interno se e "formattalo solo per essere il più leggibile possibile"?

Preferisco rifattorizzarlo in una funzione o una variabile con un nome descrittivo; che preserverà sia la valutazione del cortocircuito che la leggibilità:

bool getSomeResult() {
    return SomeComplicatedFunctionCall() || OtherComplicatedFunctionCall();
}

...

if (getSomeResult())
{
    //do stuff
}

E mentre implementiamo in getSomeResult()base a SomeComplicatedFunctionCall()e OtherComplicatedFunctionCall(), potremmo scomporli ricorsivamente se sono ancora complicati.


2
mi piace perché puoi ottenere un po 'di leggibilità dando alla funzione wrapper un nome descrittivo (anche se probabilmente non getSomeResult), troppe altre risposte non aggiungono davvero nulla di valore
aw04

9

1) Perdo davvero SCE ogni volta? È consentito al compilatore qualche scenario di "ottimizzarlo" e fornire ancora SCE?

No, non lo fai, ma viene applicato in modo diverso:

if (SomeComplicatedFunctionCall() || OtherComplicatedFunctionCall())
{
    // do stuff
}

Qui, il compilatore non verrà nemmeno eseguito OtherComplicatedFunctionCall()se SomeComplicatedFunctionCall()restituisce true.

bool b1 = SomeComplicatedFunctionCall();
bool b2 = OtherComplicatedFunctionCall();

if (b1 || b2)
{
    //do stuff
}

Qui, entrambe le funzioni verranno eseguite perché devono essere memorizzate in b1e b2. Ff b1 == trueallora b2non verrà valutato (SCE). Ma OtherComplicatedFunctionCall()è già stato eseguito.

Se non b2viene utilizzato da nessun'altra parte, il compilatore potrebbe essere abbastanza intelligente da incorporare la chiamata di funzione all'interno di if se la funzione non ha effetti collaterali osservabili.

2) Qual è la migliore pratica in tale situazione? È solo la possibilità (quando voglio SCE) di avere tutto ciò di cui ho bisogno direttamente all'interno se e "formattalo solo per essere il più leggibile possibile"?

Dipende. Se è necessario OtherComplicatedFunctionCall() eseguire a causa di effetti collaterali o il calo delle prestazioni della funzione è minimo, è necessario utilizzare il secondo approccio per la leggibilità. Altrimenti, attenersi a SCE durante il primo approccio.


8

Un'altra possibilità che cortocircuiti e ha le condizioni in un unico posto:

bool (* conditions [])()= {&a, &b, ...}; // list of conditions
bool conditionsHold = true;
for(int i= 0; i < sizeOf(conditions); i ++){
     if (!conditions[i]()){;
         conditionsHold = false;
         break;
     }
}
//conditionsHold is true if all conditions were met, otherwise false

È possibile inserire il ciclo in una funzione e lasciare che la funzione accetti un elenco di condizioni e restituisca un valore booleano.


1
@Erbureth No, non lo sono. Gli elementi dell'array sono puntatori a funzione, non vengono eseguiti finché le funzioni non vengono chiamate nel ciclo.
Barmar

Grazie Barmar, ma ho fatto una modifica, Erbureth aveva ragione, prima della modifica (pensavo che la mia modifica si sarebbe propagata visivamente in modo più diretto).
levilime

4

Molto strano: stai parlando di leggibilità quando nessuno menziona l'uso del commento all'interno del codice:

if (somecomplicated_function() || // let me explain what this function does
    someother_function())         // this function does something else
...

Inoltre, ho sempre preceduto le mie funzioni con alcuni commenti, sulla funzione stessa, sui suoi input e output, e talvolta metto un esempio, come puoi vedere qui:

/*---------------------------*/
/*! interpolates between values
* @param[in] X_axis : contains X-values
* @param[in] Y_axis : contains Y-values
* @param[in] value  : X-value, input to the interpolation process
* @return[out]      : the interpolated value
* @example          : interpolate([2,0],[3,2],2.4) -> 0.8
*/
int interpolate(std::vector<int>& X_axis, std::vector<int>& Y_axis, int value)

Ovviamente la formattazione da utilizzare per i tuoi commenti può dipendere dal tuo ambiente di sviluppo (Visual studio, JavaDoc in Eclipse, ...)

Per quanto riguarda SCE, presumo che con questo intendi quanto segue:

bool b1;
b1 = somecomplicated_function(); // let me explain what this function does
bool b2 = false;
if (!b1) {                       // SCE : if first function call is already true,
                                 // no need to spend resources executing second function.
  b2 = someother_function();     // this function does something else
}

if (b1 || b2) {
...
}

-7

La leggibilità è necessaria se lavori in un'azienda e il tuo codice verrà letto da qualcun altro. Se scrivi un programma per te stesso, spetta a te sacrificare le prestazioni per il bene di un codice comprensibile.


23
Tenendo presente che "tu tra sei mesi" è sicuramente "qualcun altro", e "tu domani" a volte può esserlo. Non sacrificherei mai la leggibilità per le prestazioni finché non avessi una solida prova che c'era un problema di prestazioni.
Martin Bonner supporta Monica
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.