È un buon modello: sostituire una lunga funzione con una serie di lambda?


14

Di recente ho incontrato la seguente situazione.

class A{
public:
    void calculate(T inputs);
}

In primo luogo, Arappresenta un oggetto nel mondo fisico, che è un argomento forte per non dividere la classe. Ora, calculate()risulta essere una funzione piuttosto lunga e complicata. Percepisco tre possibili strutture per questo:

  • scrivilo come un muro di testo - vantaggi - tutte le informazioni sono in un unico posto
  • scrivere privatefunzioni di utilità nella classe e usarle nel calculatecorpo - svantaggi - il resto della classe non conosce / cura / capisce questi metodi
  • scrivi calculatenel modo seguente:

    void A::calculate(T inputs){    
        auto lambda1 = () [] {};    
        auto lambda2 = () [] {};    
        auto lambda3 = () [] {};
    
        lambda1(inputs.first_logical_chunk);
        lambda2(inputs.second_logical_chunk);
        lambda3(inputs.third_logical_chunk);
    }

Può essere considerata una pratica buona o cattiva? Questo approccio rivela problemi? Tutto sommato, dovrei considerare questo come un buon approccio quando mi trovo di nuovo di fronte alla stessa situazione?


MODIFICARE:

class A{
    ...
public:
    // Reconfiguration of the algorithm.
    void set_colour(double colour);
    void set_density(double density);
    void set_predelay(unsigned long microseconds);
    void set_reverb_time(double reverb_time, double room_size);
    void set_drywet(double left, double right);
    void set_room_size(double value);;

private:
    // Sub-model objects.
    ...
}

Tutti quei metodi:

  • ottenere un valore
  • calcola altri valori, senza usare state
  • chiama alcuni degli "oggetti del modello secondario" per modificarne lo stato.

Si scopre che, ad eccezione di set_room_size()questi metodi, si passa semplicemente il valore richiesto ai sotto-oggetti.set_room_size()d'altra parte, fa un paio di schermate di formule oscure e poi (2) fa una mezza schermata di chiamare setter di oggetti secondari per applicare i vari risultati ottenuti. Pertanto, ho separato la funzione in due lambda e li ho chiamati alla fine della funzione. Se fossi stato in grado di dividerlo in blocchi più logici, avrei isolato più lambda.

Indipendentemente da ciò, l'obiettivo della domanda attuale è determinare se quel modo di pensare dovrebbe persistere, o nella migliore delle ipotesi non è aggiungere valore (leggibilità, manutenibilità, capacità di debug ecc.).


2
Cosa pensi che usando lambdas ti farà perdere le chiamate di funzione?
Blrfl,

1
Firstly, A represents an object in the physical world, which is a strong argument for not splitting the class up.ARappresenta sicuramente i dati su un oggetto che potrebbe esistere nel mondo fisico. Puoi avere un'istanza Asenza l'oggetto reale e un oggetto reale senza un'istanza di A, quindi trattarli come se fossero uno e lo stesso non ha senso.
Doval,

@Blrfl, incapsulamento - nessuno ma calculate()conoscerà quelle sotto-funzioni.
Vorac,

Se tutti questi calcoli sono rilevanti solo per A, questo è un po 'estremo.
Blrfl,

1
"In primo luogo, Arappresenta un oggetto nel mondo fisico, che è un argomento forte per non dividere la classe." Purtroppo, mi è stato detto questo quando ho iniziato a programmare. Mi ci sono voluti anni per rendermi conto che si tratta di un mucchio di hockey a cavallo. È un motivo terribile per raggruppare le cose. Non riesco a capire quali siano i buoni motivi per raggruppare le cose (almeno con mia soddisfazione), ma quello è uno che dovresti scartare in questo momento. Alla fine, essere tutti di "buon codice" è che funziona bene, è relativamente facile da capire ed è relativamente facile da cambiare (cioè, i cambiamenti non hanno strani effetti collaterali).
jpmc26,

Risposte:


13

No, questo non è generalmente un buon modello

Quello che stai facendo è suddividere una funzione in funzioni più piccole usando lambda. Tuttavia, esiste uno strumento molto migliore per interrompere le funzioni: funzioni.

Le lambda funzionano, come hai visto, ma significano molto molto molto di più che semplicemente suddividere una funzione in bit locali. Le lambda fanno:

  • Chiusure. È possibile utilizzare le variabili nell'ambito esterno all'interno della lambda. Questo è molto potente e molto complicato.
  • Riassegnazione. Mentre il tuo esempio rende difficile eseguire un compito, devi sempre prestare attenzione all'idea che il codice possa scambiare funzioni in qualsiasi momento.
  • Funzioni di prima classe. È possibile passare una funzione lambda a un'altra funzione, facendo qualcosa noto come "programmazione funzionale".

Nell'istante in cui aggiungi lambda nel mix, lo sviluppatore successivo che esamina il codice deve immediatamente caricare mentalmente tutte quelle regole in preparazione per vedere come funziona il tuo codice. Non sanno che non utilizzerai tutte queste funzionalità. Questo è molto costoso, rispetto alle alternative.

È come usare una zappa per fare il giardinaggio. Sai che lo stai usando solo per scavare piccoli buchi per i fiori di quest'anno, ma i vicini si innervosiranno.

Considera che tutto ciò che stai facendo è raggruppare visivamente il tuo codice sorgente. Al compilatore non importa davvero che tu abbia curry cose con lambdas. In effetti, mi aspetto che l'ottimizzatore annulli immediatamente tutto ciò che hai appena fatto durante la compilazione. Stai puramente approvando il prossimo lettore (Grazie per averlo fatto, anche se non siamo d'accordo sulla metodologia! Il codice viene letto molto più spesso di quanto sia scritto!). Tutto quello che stai facendo è raggruppare la funzionalità.

  • Le funzioni collocate localmente nel flusso del codice sorgente farebbero altrettanto bene, senza invocare lambda. Ancora una volta, tutto ciò che conta è che il lettore possa leggerlo.
  • Commenti nella parte superiore della funzione che dice "stiamo dividendo questa funzione in tre parti", seguita da grandi linee lunghe di // ------------------ tra ciascuna parte.
  • È inoltre possibile inserire ciascuna parte del calcolo nel proprio ambito. Questo ha il vantaggio di dimostrare immediatamente oltre ogni dubbio che non esiste una condivisione variabile tra le parti.

EDIT: Dal vedere la tua modifica con il codice di esempio, mi spiego perché la notazione del commento sia la più pulita, con parentesi per applicare i confini suggeriti dai commenti. Tuttavia, se una qualsiasi delle funzionalità fosse riutilizzabile in altre funzioni, raccomanderei invece di utilizzare le funzioni

void A::set_room_size(double value)
{
    {
        // Part 1: {description of part 1}
        ...
    }
    // ------------------------
    {
        // Part 2: {description of part 2}
        ...
    }
    // ------------------------
    {
        // Part 3: {description of part 3}
        ...
    }
}

Quindi non è un numero oggettivo, ma direi soggettivamente che la semplice presenza di funzioni lambda mi fa prestare circa 10 volte più attenzione a ciascuna riga di codice, perché hanno così tanto potenziale per diventare pericolosamente complicate, veloci e farlo una linea di codice dall'aspetto innocente (come count++)
Cort Ammon - Ripristina Monica il

Ottimo punto Vedo questi come i pochi vantaggi dell'approccio con lambdas - (1) codice localmente adiacente e con ambito locale (questo andrebbe perso dalle funzioni a livello di file) (2) Il compilatore assicura che nessuna variabile locale sia condivisa tra segmenti di codice. Pertanto, tali vantaggi possono essere preservati separando calculate()in {}blocchi e dichiarando i dati condivisi calculate()nell'ambito. Ho pensato che vedendo che le lambda non catturano, un lettore non sarebbe gravato dal potere delle lambda.
Vorac,

"Ho pensato che vedendo le lambda non catturare, un lettore non sarebbe stato gravato dal potere delle lambda." Questa è in realtà un'affermazione giusta, ma controversa, che colpisce il cuore della linguistica. Le parole di solito hanno connotazioni che si estendono oltre le loro denotazioni. Se la mia connotazione lambdaè ingiusta o se stai costringendo bruscamente le persone a seguire la denotazione rigorosa non è una domanda facile a cui rispondere. In effetti, potrebbe essere del tutto accettabile per te usare in lambdaquel modo nella tua azienda, e totalmente inaccettabile per la mia azienda, e nessuno dei due deve effettivamente sbagliarsi!
Cort Ammon - Ripristina Monica il

La mia opinione sulla connotazione di lambda deriva dal fatto che sono cresciuto con C ++ 03, non con C + 11. Ho trascorso anni a sviluppare un apprezzamento per i luoghi specifici in cui il C ++ è stato danneggiato da una mancanza lambda, come la for_eachfunzione. Di conseguenza, quando vedo un lambdaerrore che non si adatta a uno di quei casi di difficoltà facili da individuare, il primo presupposto a cui arrivo è che sarà probabilmente utilizzato per la programmazione funzionale, perché altrimenti non era necessario. Per molti sviluppatori, la programmazione funzionale è una mentalità completamente diversa dalla programmazione procedurale o OO.
Cort Ammon - Ripristina Monica il

Grazie per aver spiegato. Sono un programmatore alle prime armi e ora sono contento di averlo chiesto - prima che l'abitudine si fosse accumulata.
Vorac,

20

Penso che tu abbia fatto una brutta ipotesi:

In primo luogo, A rappresenta un oggetto nel mondo fisico, il che è un argomento forte per non dividere la classe.

Non sono d'accordo con questo. Ad esempio, se avessi una classe che rappresenta un'auto, vorrei sicuramente dividerla, perché sicuramente voglio una classe più piccola per rappresentare le gomme.

È necessario suddividere questa funzione in funzioni private più piccole. Se sembra davvero separato dall'altra parte della classe, allora potrebbe essere un segno che la classe dovrebbe essere separata. Naturalmente è difficile dirlo senza un esempio esplicito.

Non vedo davvero il vantaggio di usare le funzioni lambda in questo caso, perché non rende il codice più pulito. Sono stati creati per aiutare la programmazione in stile funzionale, ma non è questo.

Quello che hai scritto ricorda un po 'gli oggetti funzione nidificati in stile Javascript. Il che è di nuovo un segno che appartengono strettamente insieme. Sei sicuro di non dover fare una lezione separata per loro?

Per riassumere, non penso che questo sia un buon modello.

AGGIORNARE

Se non vedi alcun modo per incapsulare questa funzionalità in una classe significativa, puoi creare funzioni helper con ambito file, che non sono membri della tua classe. Questo è C ++ dopo tutto, il design OO non è un must.


Sembra ragionevole, ma è difficile per me immaginare l'implementazione. Classe di ambito file con metodi statici? Classe, definita all'interno A(forse anche funzione con tutti gli altri metodi privati)? Classe dichiarata e definita all'interno calculate()(assomiglia molto al mio esempio lambda). Come chiarimento, calculate()è uno di una famiglia di metodi ( calculate_1(), calculate_2()ecc.) Di cui tutti sono semplici, solo questo è 2 schermate di formule.
Vorac,

@Vorac: perché è calculate()molto più lungo di tutti gli altri metodi?
Kevin,

@Vorac È davvero difficile aiutare senza vedere il tuo codice. Puoi anche pubblicarlo?
Gábor Angyal,

@Kevin, le formule per tutto sono riportate nei requisiti. Codice pubblicato.
Vorac,

4
Voterei questo 100 volte se potessi. "Modellazione di oggetti nel mondo reale" è l'inizio della spirale della morte di Object Design. È una gigantesca bandiera rossa in qualsiasi codice.
Fred il Magic Wonder Dog
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.