Uso di lambda per migliorare la leggibilità di una funzione C ++

4

Sto cercando di migliorare la leggibilità di una lunga funzione C ++. Questa funzione contiene un numero (> una dozzina) di variabili che vengono utilizzate in tutto. La logica principale del codice è una lunga lista di controlli di condizione (a volte annidati, a volte con loop) seguiti da varie azioni. A causa del lungo numero di righe che dettagliano le azioni da eseguire mescolate con la colla logica / ciclica, può essere piuttosto difficile seguire quello che sta succedendo. Non esiste un modo semplice per sifonare queste azioni in funzioni separate senza firme di funzioni arbitrarie e brutte che notino qualsiasi sottoinsieme di variabili che sia necessario. Una soluzione drastica potrebbe essere quella di sostituire la funzione con una classe singleton, con le variabili e le sotto-azioni della funzione che diventano variabili membro privato e membro privato. Tuttavia, sto cercando un'alternativa più semplice. La mia idea è di definire una lista di azioni nella parte superiore della mia funzione attraverso le funzioni lambda, e quindi di seguito eseguire la logica della funzione usando questi lambda una tantum. Questo sembrerebbe qualcosa di simile (interamente schematicamente);

void function() {
    // variables
    int a, b, c, d, ...;

    // actions to be performed
    auto func1 = [&] () { ... code using a,b, etc. };
    auto func2 = [&] () { ... };
    auto func3 = [&] () { ... };
    ...

    // main logic
    if (<condition 1>) {
        if (<condition 2>)
            func1();
        else
            func2();
    } else {
        func2();
        func3();
    }
    ... // etc
}

Queste funzioni lambda occasionalmente salvano il codice, nei casi in cui sostituiscono frammenti di codice ripetuti, ma in genere migliorano la leggibilità, almeno ai miei occhi. È una buona pratica in generale? Gli altri ritengono che ciò migliori la leggibilità e qual è il costo dell'utilizzo di queste funzioni lambda?

    
posta jwimberley 02.07.2017 - 16:50
fonte

2 risposte

7

Migliora la leggibilità?

Il tuo modo di usare lambda per suddividere una funzione più grande in parti più piccole è simile alla funzioni annidate in Pascal , ADA e altre lingue.

Migliora in effetti la leggibilità della parte principale del corpo della tua funzione: ci sono meno istruzioni da leggere per capire cosa fa. Questo è lo scopo principale delle funzioni annidate. Naturalmente, presumo che oggigiorno la maggior parte dei programmatori abbia familiarità con la sintassi di lambda.

Tuttavia, è una buona idea?

Scott Meyers, nel suo libro Effective Modern C ++ mette in guardia contro l'uso dell'acquisizione predefinita in lambdas. La sua preoccupazione principale consiste nel citare i riferimenti (ad esempio se un lambda è definito in un blocco e viene utilizzato al di fuori dello scopo di questo blocco quando la variabile non esiste più), il che sembra non essere un problema nel tuo caso.

Ma sottolinea anche un altro problema: l'illusione di avere una funzione autonoma. E qui sta la principale debolezza del tuo approccio:

  • hai l'impressione che il tuo lambda sia autonomo, ma in realtà è completamente dipendente dal resto del codice, e non vedi facilmente nella tua lambda da dove provengono i valori catturati, quali sono le ipotesi che puoi fare su di loro, ecc ...
  • poiché il collegamento con il corpo principale si basa sulle variabili catturate, che possono essere lette o scritte, è molto difficile indovinare tutti gli effetti collaterali nascosti nella tua invocazione lambda, che potrebbero influenzare la tua parte principale.
  • quindi è molto difficile identificare ipotesi e invarianti nel codice, sia del lambda, sia della tua mega funzione
  • inoltre, potresti cambiare accidentalmente una variabile che hai dimenticato di dichiarare localmente nel tuo lambda, e uno capita di avere lo stesso nome nella funzione.

Primo consiglio: almeno, enumera esplicitamente le variabili acquisite dal tuo lambda, al fine di controllare meglio i potenziali effetti collaterali.

Secondo consiglio: una volta che funziona, potresti pensare di rafforzare ulteriormente la tua struttura, passando dall'acquisizione al passaggio dei parametri. Se ce ne sono troppi, dovresti rifattorici. Un approccio potrebbe essere quello di rendere la tua funzione una classe richiamabile, promuovendo il tuo lancio di lambda alle funzioni membro e rendendo le variabili utilizzate in tutte le variabili dei membri di calcolo. Ma è difficile dire se è la migliore opzione dagli elementi che hai dato.

E perché ti trovi in una situazione del genere?

La prossima cosa a cui pensare è, perché hai una funzione così grande in primo luogo. Se seguissi i consigli dello zio Bob riportati nel suo libro Pulisci codice (riassunto dell'argomento della funzione su questo pagina del blog ) dovresti avere:

  • piccole funzioni,
  • che fanno una cosa (responsabilità unica),
  • e fanno solo cose a un livello di astrazione
risposta data 03.07.2017 - 00:39
fonte
2

Questo renderà il tuo codice ancora più illeggibile, in quanto non sarà chiaro a cosa viene letto / scritto in ogni azione. Cercare di nascondere il passaggio delle variabili rende il codice fragile e rigido. Va evitato . Per quanto riguarda le prestazioni, un buon compilatore dovrebbe essere in grado di scomporre le funzioni lambda in codice inline senza penalizzare le prestazioni. Puoi verificare l'assemblaggio generato per verificarlo.

Il modo migliore per risolvere questi problemi è sedersi con qualcuno e spiegare loro il codice. In questo processo identificherete concetti e operazioni di alto livello che si verificano. Spezza queste funzioni separate (questo dovrebbe anche aiutarti a determinare nomi di funzioni significativi).

Per risolvere il problema di molti input e molte variabili di output di una funzione, puoi usare struct s per passare in / out le variabili strettamente accoppiate (questo migliorerà la leggibilità):

struct foo_params {
  int numX;
  float ratioY;
  int numZ;
  float const * data;
  /* many more parameters */
};

void compute(
    struct foo_params * const params)
{
  /* operate on data */
}

Nota che in alcune situazioni questo potrebbe avere un impatto sulle prestazioni, quindi dovresti fare dei test se questo è un codice sensibile alle prestazioni.

    
risposta data 02.07.2017 - 17:43
fonte

Leggi altre domande sui tag