Ritorno da una funzione lunga alla prima condizione di errore

4

Ho una lunga (ish) funzione del seguente modello:

bool func(some_type_t *p1, another_t *p2)
{
  bool a = false, b = false, c = false, etc = false;

  a = (some_long && expression && \
       (that_deserves | its_own_line));

  b = (another || long_expression && \
       (that_deserves | its_own_line));

  c = and_so && on;

  return a && b && c && etc;
}

Come puoi vedere, il valore restituito è true se e solo se tutti i flag sono veri. Quindi posso tornare non appena uno di questi risulta essere false . Potrebbe essere o non essere più ottimale della versione attuale, ma voglio farlo per soddisfare il mio OCPD . Ho pensato di mettere tutto in do{}while(0); e scoppiare su false , ma sembra strano (e ricordo di aver visto qualcosa del genere in tdwtf).

Non voglio più dichiarazioni di ritorno o blocchi profondamente annidati, e di certo non voglio compromettere la leggibilità con un enorme se si basa sul cortocircuito. Posso vivere con goto in questo caso, ma voglio sapere se ci sono altri modelli / costrutti usati in scenari simili.

    
posta Amarghosh 21.04.2011 - 18:17
fonte

7 risposte

10

Forse sto abusando di schemi di progettazione o qualcosa del genere, ma lo scriverei come

bool func(some_type_t *p1, another_t *p2) {

    return ( descriptive_name_for_a(p1,p2) &&
             descriptive_name_for_b(p1) &&
             etc );
}

bool descriptive_name_for_a(...) {
    return (some_long && expression && \
        (that_deserves | its_own_line));
}

//etc

}

In questo modo le persone non devono leggere i dettagli delle lunghe espressioni se vogliono solo sapere quale funzione fa.

    
risposta data 21.04.2011 - 18:27
fonte
8

Perché no:

return
     (long expression a)
     && (long expression b)
     && (long expression c)
     && (etc.)

Questo ritornerà non appena trova il primo valore falso, grazie alla pigrizia di &.

    
risposta data 21.04.2011 - 19:02
fonte
3

Mi piace l'idea di @ Brad riguardo alla suddivisione in funzioni, ma se non potessi userei più ritorni in questo modo. Non ho scritto C in circa 10 anni, quindi questo è un pseudo-codice C # ...

Modifica: per tenere conto dei commenti di @Frits; senza alcun tentativo di applicare la legge di DeMorgan ...

    bool func(T1 t1, T2)
    {            
        if(!(someLong && expression && (thatDeservers | itsOwnLine)))
            return false;
        if(!(another || longExpression && (thatDeservers | itsOwnLine)))
            return false;
        if(!(andSo && on)
            return false;

        return true;
    }
    
risposta data 21.04.2011 - 18:37
fonte
1

Non sono un C dev, ma di solito lo formatto con indentazioni e line continua ad essere una singola dichiarazione con il valutatore di cortocircuito. Un'ipotesi approssimativa in C potrebbe essere:

bool func(some_type_t *p1, another_t *p2)
{
  return ((some_long && expression && (that_deserves | its_own_line))) && \
         ((another || long_expression && (that_deserves | its_own_line))) && \
         (and_so && on);
}

Per i miei occhi è comunque molto più leggibile.

Se i blocchi fossero davvero intollerabilmente grandi farei dei sotto-blocchi

bool func(some_type_t *p1, another_t *p2)
{
  return ((some_long && \
              expression && \ 
              (that_deserves | its_own_line))) && \
         ((another || long_expression && \ 
              (that_deserves | its_own_line))) && \
         (and_so && on);
}

Ma forse ho passato troppo tempo a guardare le espressioni lambda ...

    
risposta data 21.04.2011 - 18:27
fonte
1

I certainly don't want to compromise on readability with a huge if that relies on short circuiting.

Eh? Penso che avere un condizionale basato sul cortocircuito sia il più chiaro e leggibile possibile.

In realtà, anche senza "se".

Proprio

return(a < 2 ? b :
       c < 3 ? d :
               e);

come unica linea nella funzione. Sembra quasi una matematica normale.

Avere più istruzioni che sembrano indipendenti l'una dall'altra ma che in realtà non sono affatto indipendenti è illeggibile ai miei occhi (cioè goto condizionali, variabili bool temporanee).

Ma ovviamente è una questione di preferenze personali.

    
risposta data 14.05.2011 - 14:40
fonte
0

Utilizza questo modulo utilizzando i controlli ridondanti come ottimizzazione:

bool func(some_type_t *p1, another_t *p2) {
    bool a = false, b = false, c = false;

    a = (some_long && expression && \
        (that_deserves | its_own_line));

    b = a && (another || long_expression && \
        (that_deserves | its_own_line));

    c = b && and_so && on;

    return c;
}

Qui ogni espressione è protetta da una precedente, quindi c'è solo un sovraccarico di runtime trascurabile, indipendentemente dalla durata dell'espressione complicata, poiché nessuno di essi viene valutato inutilmente. Molto probabilmente, il compilatore può eliminare completamente il sovraccarico.

Detto questo, preferirei più dichiarazioni di reso, in quanto sono molto più chiare e meno soggette a errori, IMHO. Ad esempio, ti consentono di riordinare facilmente le parti.

    
risposta data 24.04.2011 - 13:47
fonte
-2

Che ne dici di

#define BREAK_ON_FALSE(x) do { if (!(x)) goto leave_function; } while (0)

void your_function() {
    BREAK_ON_FALSE(first && condition && etc);
    BREAK_ON_FALSE(second && condition && etc);

    /* So on ... */
    return true;
leave_function:
    return false;
}
    
risposta data 23.04.2011 - 19:16
fonte

Leggi altre domande sui tag