Devo inserire le funzioni utilizzate solo in un'altra funzione, all'interno di quella funzione?


30

In particolare, sto scrivendo in JavaScript.

Supponiamo che la mia funzione principale sia la funzione A. Se la funzione A effettua diverse chiamate alla funzione B, ma la funzione B non viene utilizzata da nessun'altra parte, devo semplicemente posizionare la funzione B all'interno della funzione A?

È una buona pratica? O dovrei ancora mettere la funzione B nello stesso ambito della funzione A?

Risposte:


32

Sono generalmente a favore delle funzioni nidificate, specialmente in JavaScript.

  • In JavaScript, l'unico modo per limitare la visibilità di una funzione è nidificarla all'interno di un'altra funzione.
  • La funzione di supporto è un dettaglio dell'implementazione privata. Metterlo nello stesso ambito è simile a rendere pubbliche le funzioni private di una classe.
  • Se risulta essere di uso più generale, è facile andarsene, perché puoi essere sicuro che l'helper è attualmente utilizzato solo da quella funzione. In altre parole, rende il tuo codice più coerente.
  • Spesso è possibile eliminare i parametri, il che rende la firma e l'implementazione della funzione meno dettagliate. Non è la stessa cosa che rendere globali le variabili. È più come usare un membro della classe in un metodo privato.
  • Puoi dare alle funzioni di supporto nomi migliori e più semplici, senza preoccuparti dei conflitti.
  • La funzione di aiuto è più facile da trovare. Sì, ci sono strumenti che possono aiutarti. È ancora più semplice spostare leggermente gli occhi sullo schermo.
  • Il codice forma naturalmente una sorta di albero di astrazione: alcune funzioni generali alla radice, che si diramano in diversi dettagli di implementazione. Se si utilizza un editor con la funzione di piegatura / compressione, la nidificazione crea una gerarchia di funzioni strettamente correlate allo stesso livello di astrazione. Ciò rende molto semplice studiare il codice al livello necessario e nascondere i dettagli.

Penso che molta opposizione derivi dal fatto che la maggior parte dei programmatori sia cresciuta secondo una tradizione C / C ++ / Java, o insegnata da qualcun altro che lo era. Le funzioni nidificate non sembrano naturali perché non ci eravamo esposti molto quando stavamo imparando a programmare. Ciò non significa che non siano utili.


14

Dovresti metterlo a portata globale, per diversi motivi.

  • L'annidamento di una funzione di supporto nel chiamante aumenta la lunghezza del chiamante. La lunghezza della funzione è quasi sempre un indicatore negativo; le funzioni brevi sono più facili da comprendere, memorizzare, eseguire il debug e mantenere.

  • Se la funzione di supporto ha un nome ragionevole, la lettura di quel nome è sufficiente senza la necessità di vedere la definizione nelle vicinanze. Se fai bisogno di vedere la definizione di supporto al fine di comprendere la funzione chiamante, quindi che chiamante sta facendo troppo, o sta lavorando su troppi livelli di astrazione contemporaneamente.

  • Avere l'helper disponibile a livello globale consente ad altre funzioni di chiamarlo se lo rende generalmente utile dopo tutto. Se l'helper non è disponibile, sei tentato di tagliarlo e incollarlo, o di dimenticarlo e reimplementarlo, male, o di fare un'altra funzione più a lungo di quanto dovrebbe essere.

  • L'annidamento della funzione helper aumenta la tentazione di utilizzare le variabili dall'ambito del chiamante senza dichiarazione, in modo che diventi poco chiaro quali siano gli input e gli output dell'helper. Se una funzione non indica chiaramente su quali dati opera e quali effetti ha, di solito è un segno di responsabilità poco chiare. Dichiarare il culo helper una funzione autonoma ti costringe a sapere esattamente cosa fa realmente.

modificare

Questa si è rivelata una domanda più controversa di quanto pensassi. Chiarire:

In JavaScript, le grandi funzioni di spanning dei file svolgono spesso il ruolo di classi perché la lingua non fornisce alcun altro meccanismo di limitazione dell'ambito. Certamente le funzioni di aiuto dovrebbero andare all'interno di tali quasi-classi, non al di fuori di esse.

E il punto sul riutilizzo più semplice presuppone che se una subroutine viene utilizzata più ampiamente, si è disposti a spostarla completamente fuori posto e metterla in posizione appropriata, ad esempio una libreria di utilità stringa o nel registro di configurazione globale. Se non vuoi ordinare il tuo codice in questo modo, allora potresti anche annidare la subroutine, proprio come faresti con un normale oggetto metodo in una lingua più "bloccata".


Grazie, tutti i punti molto buoni. In una nota a margine, come dovrei generalmente ordinare le mie funzioni? Ad esempio, al momento, con piccoli programmi li ordino semplicemente in ordine alfabetico. Ma dovrei raggruppare le funzioni di supporto e di chiamata? Immagino che dovrei almeno suddividere le mie funzioni in base a quali parti dell'applicazione si applicano, ad esempio?
Gary,

Non mi preoccupo da molto tempo per le definizioni dei metodi di ordinazione. Se programmi con qualsiasi grado di professionalità, dovresti avere un ambiente che ti permetta di passare a qualsiasi definizione con qualche tasto. Pertanto sono utili metodi brevi, ma i file di classe brevi o addirittura ben ordinati aggiungono poco valore. (Naturalmente, JavaScript richiedeva che tutte le definizioni fossero poste al di sopra dei loro usi o il risultato sarebbe tecnicamente un comportamento indefinito - non ricordo se è ancora così o meno.)
Kilian Foth

2
Questa è un'ottima risposta Il problema dell'incapsulamento rotto è qualcosa che molti non considerano quando si usano le funzioni interne.
sbichenko,

2
I globuli hanno un odore di codice. Causano accoppiamenti non necessari che impediscono il refactoring, causano conflitti di nomi, espongono dettagli di implementazione, ecc. Ciò è particolarmente negativo in Javascript, poiché tutte le variabili sono mutabili, il codice viene in genere caricato direttamente da terze parti, non esiste (ancora) un sistema di moduli ampiamente disponibile, o anche un'implementazione ampiamente disponibile di "let". In un ambiente così ostile, l' unica strategia difensiva che abbiamo è l'incapsulamento all'interno delle funzioni one-shot.
Warbo,

1
"Adempiere al ruolo delle classi" sta cercando di scrivere JS come se fosse qualcos'altro. Qual è il "ruolo delle classi"? Tipo-controllo? Modularità? Compilazione in scena? Eredità? La domanda riguarda l'ambito, quindi presumo che tu intenda le regole di ambito grezzo delle classi. Questo è solo un problema: come dovrebbero essere le classi individuate? PHP li rende globali, il che non fornisce incapsulamento. Classi di ambito Python in modo lessicale, ma se ci si trova in un blocco con ambito lessicale, perché avvolgere tutto in un altro blocco con ambito di classe? JS non ha tale ridondanza: basta usare tutto l'ambito lessicale di cui hai bisogno.
Warbo

8

Proporrò un terzo percorso, per mettere entrambe le funzioni all'interno di una chiusura. Sembrerebbe che:

var functionA = (function(){
    function functionB() {
        // do stuff...
    }

    function functionA() {
        // do stuff...
        functionB();
        // do stuff...
    }

    return functionA;
})();

Creiamo la chiusura avvolgendo la dichiarazione di entrambe le funzioni in un IIFE . Il valore restituito di IIFE è la funzione pubblica, memorizzata in una variabile del nome per la funzione. La funzione pubblica può essere invocata esattamente come se fosse dichiarata come funzione globale, vale a dire functionA(). Si noti che il valore restituito è la funzione , non una chiamata alla funzione, quindi nessuna parentesi alla fine.

Avvolgendo le due funzioni in questo modo, functionBè ora completamente privato, e non è possibile accedere al di fuori della chiusura, ma è visibile solo a functionA. Non sta ingombrando lo spazio dei nomi globale e non sta ingombrando la definizione di functionA.


0

La definizione della funzione B all'interno della funzione A consente alla funzione B di accedere alle variabili interne di A.

Quindi, una funzione B definita all'interno di una funzione A dà l'impressione (o almeno la possibilità) che B stia usando o alterando le variabili locali di A. Mentre si definisce B al di fuori di A si chiarisce che B non lo fa.

Pertanto, per la chiarezza del codice, definirei B all'interno di A solo se B deve accedere alle variabili locali di A. Se B ha uno scopo chiaro che è indipendente da A, lo definirei sicuramente al di fuori di A.


-1

Non è necessario nidificarlo, altrimenti la funzione nidificata verrà ricreata ogni volta che si chiama la funzione esterna.


-3

Dal momento che la funzione B non viene utilizzata in nessun altro posto, potresti anche renderla una funzione interna all'interno della funzione A perché questa è l'unica ragione per cui esiste.


1
questo non sembra aggiungere nulla di sostanziale rispetto ai punti formulati e spiegati nelle precedenti 3 risposte
moscerino
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.