L'uso della funzione nidificata chiama una cosa negativa?

7

In un recente compito a casa ho finito per chiamare le mie funzioni in un modo brutto uglyReceipt(cashParser(cashInput())) il programma ha funzionato perfettamente, ma mi sentivo ancora come se stavo facendo qualcosa di sbagliato.

Le funzioni di chiamata come questa cattiva pratica e in tal caso: cosa dovrei fare invece?

    
posta Carl groth 17.04.2016 - 13:06
fonte

4 risposte

11

Questo dipende molto dalla quantità di nidificazione che usi. Dopo tutto, è consentito utilizzare i risultati delle funzioni direttamente nelle espressioni per migliorare la leggibilità. Entrambi, il codice che non usa espressioni nidificate (come il codice assembler) e il codice che usa troppe espressioni nidificate è difficile da leggere. Il buon codice cerca di trovare un equilibrio tra gli estremi.

Quindi vediamo alcuni esempi. Quello che hai dato nella tua domanda mi sembra abbastanza legittimo, quindi non c'è nulla di cui preoccuparsi. Tuttavia, una riga come

foo(bar(baz(moo, fab), bim(bam(ext, rel, woot, baz(moo, fab)), noob), bom, zak(bif)));

non sarebbe sicuramente tollerabile. Allo stesso modo, codice come

double xsquare = x*x;
double ysquare = y*y;
double zsquare = z*z;
double xysquare = xsquare + ysquare;
double xyzsquare = xysquare + zsquare;
double length = sqrt(xyzsquare);

non sarebbe molto leggibile. sqrt(x*x + y*y + z*z) è molto più facile da capire, anche se combina un totale di sei diverse operazioni in una sola espressione.

Il mio consiglio è di prestare attenzione a quali espressioni puoi ancora analizzare facilmente nella tua testa. Nel momento in cui devi dare una seconda occhiata per comprendere cosa fa una singola espressione, è il momento di introdurre una variabile aggiuntiva.

    
risposta data 17.04.2016 - 22:35
fonte
4

Penso che sia buono o cattivo dipenda molto dal contesto. Il motivo principale per cui potrebbe essere considerato negativo è che (probabilmente) rende il codice più difficile da leggere e correggere. Questo è particolarmente vero quando stai imparando a programmare. Man mano che le tue capacità di codifica (e il codice) diventano più avanzate, ci sono momenti in cui questo è accettabile.

Ad esempio, considera un messaggio di errore come questo:

line 1492: missing argument "foo"

Come fai a sapere se l'argomento mancante è cashInput , cashParser o uglyReceipt ? A seconda della lingua che potrebbe darti il messaggio di errore, ma potrebbe non farlo.

Se dovessi interrompere quelle chiamate di funzione e il messaggio di errore ti indicasse ancora la riga 1492, sapresti immediatamente dove si trova il problema:

1491: input = cashInput()
1492: parsed_value = cashParser(input)
1493: receipt = uglyReceipt(parsed_value)

Con i passaggi separati separatamente, è molto più facile eseguire il debug poiché è possibile impostare un punto di interruzione in qualsiasi fase e si possono facilmente iniettare valori modificando il valore delle variabili locali.

    
risposta data 17.04.2016 - 17:31
fonte
3

Il concetto alla base della tua domanda è così importante che sento che ha bisogno di un'altra risposta piuttosto che un semplice commento (come avevo iniziato a fare).

Le altre 3 risposte finora forniscono alcuni utili punti di riflessione sul fatto che una data situazione meriti di usare quelle che chiamate "chiamate di funzioni annidate". Ma forse un punto più importante è nascosto nei commenti sotto la tua domanda: nel caso ti sia sfuggita la sottigliezza in quello che quelle persone erudite suggeriscono, Carl, hai scoperto da te l'argomento in realtà chiamato programmazione funzionale . Se non hai mai visto il termine, potresti non aver pensato che fosse davvero una "cosa" nel commento di @ HighPerformanceMark.

Ma davvero lo è! La programmazione funzionale è stata scritta per decenni, a partire dal documento seminale di John Hughes Perché la programmazione funzionale è importante . Ci sono alcune lingue che sono linguaggi funzionali (cioè ti permettono solo di scrivere in uno stile di programmazione funzionale), lingue come Erlang, Lisp, OCaml o Haskell. Ma ci sono molte altre lingue che sono linguaggi imperativi / funzionali ibridi. Cioè, sono lingue tradizionalmente imperative ma offrono anche supporto per la programmazione funzionale, tra cui Perl, C ++, Java, C # e molti altri. La voce di Wikipedia su programmazione funzionale offre una bella sezione che mostra un confronto tra stile funzionale e stile imperativo per un numero di lingue .

C'è molto da dire sulle differenze tra stili imperativi e funzionali, ma il punto di partenza chiave è che con la programmazione funzionale, le funzioni oi metodi non hanno effetti collaterali , il che rende in generale più facile per entrambi capire e fare il debug dei programmi.

Per ulteriori approfondimenti, potresti anche dare un'occhiata a di Reginald Braithwaite. Perché "Perché la programmazione funzionale conta" e un altro post interessante qui su SO, Perché linguaggi funzionali?

    
risposta data 18.04.2016 - 05:40
fonte
2

Non è assolutamente una cattiva pratica in generale. Le funzioni call accettano i valori e un modo per produrre un valore è chiamando un'altra funzione.

Quando vedo una variabile in fase di definizione, come:

parsed_value = cashParser(input)

... Devo considerare che parsed_value potrebbe essere usato più di una volta e probabilmente dovrò verificare se questo è vero o no (e se il mio cambiamento rompe qualcosa altrove?). Quando inizi ad aggiungere più variabili, può diventare più complesso per il lettore tenere traccia di tutte loro e di come sono correlate. Quindi sono sollevato quando vedo:

receipt = uglyReceipt(cashParser(input))

... perché lo scopo / durata del valore intermedio è ovvio. Ora, come sempre, suddividere una lunga espressione in istruzioni separate può essere d'aiuto, specialmente se un nome di variabile può dare più precisione sullo scopo di un valore:

user_name = input()
    
risposta data 17.04.2016 - 18:08
fonte

Leggi altre domande sui tag