"Le variabili dovrebbero vivere nel più piccolo ambito possibile" include il caso "le variabili non dovrebbero esistere se possibile"?


32

Secondo la risposta accettata su " Razionale per preferire le variabili locali alle variabili di istanza? ", Le variabili dovrebbero vivere nel più piccolo ambito possibile.
Semplifica il problema nella mia interpretazione, significa che dovremmo refactoring questo tipo di codice:

public class Main {
    private A a;
    private B b;

    public ABResult getResult() {
        getA();
        getB();
        return ABFactory.mix(a, b);
    }

    private getA() {
      a = SomeFactory.getA();
    }

    private getB() {
      b = SomeFactory.getB();
    }
}

in qualcosa del genere:

public class Main {
    public ABResult getResult() {
        A a = getA();
        B b = getB();
        return ABFactory.mix(a, b);
    }

    private getA() {
      return SomeFactory.getA();
    }

    private getB() {
      return SomeFactory.getB();
    }
}

ma secondo lo "spirito" di "le variabili dovrebbero vivere nel più piccolo ambito possibile", "non avere mai variabili" ha un ambito più piccolo di "avere variabili"? Quindi penso che la versione sopra dovrebbe essere rifattorizzata:

public class Main {
    public ABResult getResult() {
        return ABFactory.mix(getA(), getB());
    }

    private getA() {
      return SomeFactory.getA();
    }

    private getB() {
      return SomeFactory.getB();
    }
}

quindi getResult()non ha alcuna variabile locale. È vero?


72
La creazione di variabili esplicite comporta il vantaggio di doverle nominare. L'introduzione di alcune variabili può trasformare rapidamente un metodo opaco in uno leggibile.
Jared Goguen,

11
Onestamente, un esempio in termini di nomi di variabili aeb è troppo elaborato per dimostrare il valore delle variabili locali. Fortunatamente, hai comunque ottenuto buone risposte.
Doc Brown

3
Nel tuo secondo frammento, le tue variabili non sono realmente variabili . Sono effettivamente costanti locali, perché non le modifichi. Il compilatore Java li tratterà allo stesso modo se li definisci definitivi, perché sono nell'ambito locale e il compilatore sa cosa accadrà con loro. È una questione di stile e opinione, se in realtà dovresti usare la finalparola chiave o meno.
hyde il

2
@JaredGoguen La creazione di variabili esplicite comporta l'onere di doverle nominare e il vantaggio di poterle nominare.
Bergi,

2
Assolutamente zero delle regole di stile del codice come "le variabili dovrebbero vivere nel più piccolo ambito possibile" sono universalmente applicabili senza pensarci. Come tale, non vuoi mai basare una decisione in stile codice sul ragionamento del modulo in questa domanda, in cui prendi una semplice linea guida ed estendila in un'area meno ovviamente coperta ("le variabili dovrebbero avere piccoli ambiti se possibile" -> "le variabili non dovrebbero esistere se possibile"). O ci sono buone ragioni a supporto della tua conclusione in particolare, o la tua conclusione è un'estensione inappropriata; in entrambi i casi non è necessaria la linea guida originale.
Ben

Risposte:


110

No. Ci sono diversi motivi per cui:

  1. Le variabili con nomi significativi possono facilitare la comprensione del codice.
  2. La suddivisione di formule complesse in passaggi più piccoli può facilitare la lettura del codice.
  3. Caching.
  4. Contiene riferimenti a oggetti in modo che possano essere utilizzati più di una volta.

E così via.


22
Vale anche la pena menzionare: il valore verrà archiviato in memoria indipendentemente, quindi in realtà finisce comunque con lo stesso ambito. Può anche chiamarlo (per i motivi sopra menzionati da Robert)!
Maybe_Factor

5
@Maybe_Factor La linea guida non è proprio lì per motivi di prestazioni; si tratta di quanto sia facile mantenere il codice. Ma ciò significa ancora che i locali significativi sono meglio di una grande espressione, a meno che tu non stia semplicemente componendo funzioni ben definite e progettate (ad esempio, non c'è motivo di farlo var taxIndex = getTaxIndex();).
Luaan,

16
Tutti sono fattori importanti. Per come la vedo io: la brevità è un obiettivo; la chiarezza è un'altra; la robustezza è un'altra; le prestazioni sono un'altra; e così via. Nessuno di questi sono obiettivi assoluti e talvolta sono in conflitto. Quindi il nostro compito è capire come bilanciare al meglio tali obiettivi.
saluta il

8
Il compilatore si occuperà di 3 e 4.
Stop Harming Monica

9
Il numero 4 può essere importante per la correttezza. Se il valore di una chiamata di funzione può cambiare (ad es now().) La rimozione della variabile e il richiamo del metodo più di una volta possono causare errori. Questo può creare una situazione che è davvero sottile e difficile da eseguire il debug. Potrebbe sembrare ovvio, ma se sei in una missione di refactoring cieco per rimuovere le variabili, è facile finire per introdurre difetti.
JimmyJames

16

D'accordo, le variabili che non sono necessarie e che non migliorano la leggibilità del codice dovrebbero essere evitate. Più variabili rientrano in un determinato punto del codice, più complesso è il codice da comprendere.

Non vedo davvero il vantaggio delle variabili ae bnel tuo esempio, quindi scriverei la versione senza variabili. D'altra parte, la funzione è così semplice in primo luogo, non penso che importi molto.

Diventa più un problema più a lungo diventa la funzione e più variabili sono nell'ambito.

Ad esempio se hai

    a=getA();
    b=getB();
    m = ABFactory.mix(a,b);

Nella parte superiore di una funzione più ampia, aumenti il ​​carico mentale di comprendere il resto del codice introducendo tre variabili anziché una. Devi leggere il resto del codice per vedere se ao bvengono riutilizzati. I locali che hanno un raggio d'azione più lungo del necessario non sono validi per la leggibilità generale.

Naturalmente nel caso in cui sia necessaria una variabile (ad esempio per memorizzare un risultato temporaneo), o se una variabile fa migliorare la leggibilità del codice, esso dovrebbe essere mantenuto.


2
Naturalmente, dal momento che un metodo diventa così lungo che il numero di locali è troppo grande per essere facilmente gestibile, probabilmente vorrai comunque romperlo.
Luaan,

4
Uno dei vantaggi di variabili "estranee" come var result = getResult(...); return result;è che puoi mettere un breakpoint su returne imparare cosa resultè esattamente .
Joker_vD

5
@Joker_vD: un debugger degno del suo nome dovrebbe essere in grado di lasciarti fare tutto ciò (visualizza i risultati di una chiamata di funzione senza memorizzarla in una variabile locale + impostando un breakpoint all'uscita della funzione).
Christian Hackl,

2
@ChristianHackl Pochissimi debuggesr ti permettono di farlo senza impostare orologi forse complessi che richiedono tempo per essere aggiunti (e se le funzioni hanno effetti collaterali possono rompere tutto). Prenderò la versione con variabili per il debug in qualsiasi giorno della settimana.
Gabe Sechan,

1
@ChristianHackl e per approfondire ulteriormente ciò anche se il debugger non ti consente di farlo per impostazione predefinita perché il debugger è progettato in modo orribile, puoi semplicemente modificare il codice locale sul tuo computer per archiviare il risultato e quindi verificarlo nel debugger . Non c'è ancora motivo di memorizzare un valore in una variabile solo per quello scopo.
The Great Duck

11

Oltre alle altre risposte, vorrei sottolineare qualcos'altro. Il vantaggio di mantenere piccolo l'ambito di una variabile non è solo ridurre la quantità di codice che ha accesso sintatticamente alla variabile, ma anche ridurre il numero di possibili percorsi del flusso di controllo che possono potenzialmente modificare una variabile (assegnandole un nuovo valore o chiamando un metodo di mutazione sull'oggetto esistente contenuto nella variabile).

Le variabili con ambito di classe (istanza o statiche) hanno percorsi di flusso di controllo significativamente più possibili rispetto alle variabili con ambito locale perché possono essere mutate con metodi, che possono essere chiamati in qualsiasi ordine, un numero qualsiasi di volte e spesso tramite codice esterno alla classe .

Diamo un'occhiata al tuo getResultmetodo iniziale :

public ABResult getResult() {
    getA();
    getB();
    return ABFactory.mix(this.a, this.b);
}

Ora, i nomi getAe getBpossono suggerire che verranno assegnati a , this.ae this.bnon possiamo esserne sicuri dal solo guardare getResult. Pertanto, è possibile che i valori this.ae this.bpassati nel mixmetodo provengano invece dallo stato thisdell'oggetto prima che sia getResultstato invocato, il che è impossibile da prevedere poiché i client controllano come e quando vengono chiamati i metodi.

Nel codice rivisto con il locale ae le bvariabili, è chiaro che esiste esattamente un flusso di controllo (senza eccezioni) dall'assegnazione di ciascuna variabile al suo utilizzo, poiché le variabili vengono dichiarate subito prima di essere utilizzate.

Pertanto, vi è un vantaggio significativo nello spostamento di variabili (modificabili) da ambito di classe a ambito locale (così come lo spostamento di variabili (modificabili) dall'esterno di un ciclo verso l'interno) in quanto semplifica il ragionamento del flusso di controllo.

D'altra parte, l'eliminazione di variabili come nel tuo ultimo esempio ha meno vantaggi, perché non influisce sul ragionamento del flusso di controllo. Perdi anche i nomi dati ai valori, cosa che non accade quando si sposta semplicemente una variabile in un ambito interno. Questo è un compromesso che devi considerare, quindi eliminare le variabili potrebbe essere migliore in alcuni casi e peggio in altri.

Se non vuoi perdere i nomi delle variabili, ma vuoi comunque ridurre l'ambito delle variabili (nel caso in cui vengano utilizzate all'interno di una funzione più grande), puoi considerare di racchiudere le variabili e i loro usi in un'istruzione di blocco ( o spostandoli nella propria funzione ).


2

Ciò dipende in qualche modo dal linguaggio, ma direi che uno dei vantaggi meno ovvi della programmazione funzionale è che incoraggia il programmatore e il lettore di codice a non averne bisogno. Ritenere:

(reduce (fn [map string] (assoc map string (inc (map string 0))) 

O qualche LINQ:

var query2 = mydb.MyEntity.Select(x => x.SomeProp).AsEnumerable().Where(x => x == "Prop");

O Node.js:

 return visionFetchLabels(storageUri)
    .then(any(isCat))
    .then(notifySlack(secrets.slackWebhook, `A cat was posted: ${storageUri}`))
    .then(logSuccess, logError)
    .then(callback)

L'ultima è una catena di chiamata di una funzione sul risultato di una funzione precedente, senza variabili intermedie. Presentarli li renderebbe molto meno chiari.

Tuttavia, la differenza tra il primo esempio e gli altri due è l' ordine di funzionamento implicito . Questo potrebbe non essere lo stesso dell'ordine in cui è effettivamente calcolato, ma è l'ordine in cui il lettore dovrebbe pensarci. Per i secondi due questo è da sinistra a destra. Per l'esempio di Lisp / Clojure è più simile a destra a sinistra. Dovresti essere un po 'diffidente nella scrittura di codice che non è nella "direzione predefinita" per la tua lingua, e le espressioni "intermedie" che mescolano i due dovrebbero essere assolutamente evitate.

L'operatore di pipe di F # |>è utile in parte perché consente di scrivere cose da sinistra a destra che altrimenti dovrebbero essere da destra a sinistra.


2
Ho iniziato a usare la sottolineatura come nome della mia variabile nelle espressioni LINQ semplici o lambda. myCollection.Select(_ => _.SomeProp).Where(_ => _.Size > 4);
Graham,

Non sono sicuro che questo risponda alla domanda di OP al minimo ...
AC

1
Perché i downvotes? Mi sembra una buona risposta, anche se non risponde direttamente alla domanda è comunque un'informazione utile
reggaeguitar

1

Direi di no, perché dovresti leggere "il più piccolo ambito possibile" come "tra gli ambiti esistenti o quelli che è ragionevole aggiungere". Altrimenti implicherebbe che dovresti creare ambiti artificiali (ad esempio {}blocchi gratuiti in linguaggi simili a C) solo per garantire che l'ambito di una variabile non si estenda oltre l'ultimo uso previsto e che verrebbe generalmente visto come offuscamento / disordine a meno che non ci sia già una buona ragione per l'esistenza del campo di applicazione in modo indipendente.


2
Un problema con l'ambito in molte lingue è che è estremamente comune avere alcune variabili il cui ultimo utilizzo è nel calcolo dei valori iniziali di altre variabili. Se una lingua avesse costruito esplicitamente ai fini dell'ambito, ciò consentiva di utilizzare i valori calcolati utilizzando le variabili dell'ambito interno nell'inizializzazione di quelli dell'ambito esterno, tali ambiti potrebbero essere più utili degli ambiti strettamente nidificati richiesti da molte lingue.
supercat

1

Considera le funzioni ( metodi ). Lì non divide né il codice nella sottoattività più piccola possibile, né il più grande pezzo di codice singleton.

È un limite mobile, con la delimitazione di compiti logici, in pezzi di consumo.

Lo stesso vale per le variabili . Indicare strutture logiche di dati, in parti comprensibili. O anche semplicemente nominare (dichiarando) i parametri:

boolean automatic = true;
importFile(file, automatic);

Ma ovviamente con una dichiarazione in alto e duecento righe più in là il primo utilizzo è oggi accettato come cattivo stile. Questo è chiaramente ciò che "le variabili dovrebbero vivere nel più piccolo ambito possibile" intende dire. Come un molto vicino "non riutilizzare le variabili".


1

Ciò che manca in qualche modo come ragione per NO è il debug / readabillity. Il codice dovrebbe essere ottimizzato per questo, e nomi chiari e concisi aiutano molto, ad esempio immaginando un modo 3 se

if (frobnicate(x) && (get_age(x) > 2000 || calculate_duration(x) < 100 )

questa riga è breve, ma già difficile da leggere. Aggiungi qualche altro parametro e che se si estende su più righe.

can_be_frobnicated = frobnicate(x)
is_short_lived_or_ancient = get_age(x) > 2000 || calculate_duration(x) < 100
if (can_be_frobnicated || is_short_lived_or_ancient )

Trovo in questo modo più semplice leggere e comunicare significati, quindi non ho problemi con le variabili intermedie.

Un altro esempio potrebbe essere lingue come R, dove l'ultima riga è automaticamente il valore restituito:

some_func <- function(x) {
    compute(x)
}

questo è pericoloso, il ritorno è esaurito o necessario? questo è più chiaro:

some_func <- function(x) {
   rv <- compute(x)
   return(rv)
}

Come sempre, si tratta di un appello di giudizio: eliminare le variabili intermedie se non migliorano la lettura, altrimenti conservarle o introdurle.

Un altro punto può essere la debuggabilità: se i risultati dell'intermediario sono di interesse, può essere meglio semplicemente introdurre un intermediario, come nell'esempio R sopra. La frequenza con cui è richiesto è difficile da immaginare e fai attenzione a cosa fai il check-in - troppe variabili di debug sono confuse - ancora una volta, un appello di giudizio.


0

Facendo riferimento solo al tuo titolo: assolutamente, se una variabile non è necessaria, dovrebbe essere eliminata.

Ma "non necessario" non significa che un programma equivalente può essere scritto senza usare la variabile, altrimenti ci viene detto che dovremmo scrivere tutto in binario.

Il tipo più comune di variabile non necessaria è una variabile non utilizzata, minore è l'ambito della variabile, più facile è determinare che non è necessaria. È difficile determinare se una variabile intermedia non sia necessaria, poiché non è una situazione binaria, è contestuale. In effetti, un codice sorgente identico in due metodi diversi potrebbe produrre una risposta diversa da parte dello stesso utente a seconda dell'esperienza passata nel risolvere problemi nel codice circostante.

Se il tuo codice di esempio fosse esattamente come rappresentato, suggerirei di sbarazzarti dei due metodi privati, ma non avrei alcuna preoccupazione sul fatto che tu abbia salvato il risultato delle chiamate di fabbrica in una variabile locale o che le abbia usate come argomenti nel mix metodo.

La leggibilità del codice vince su tutto tranne che sul corretto funzionamento (include correttamente criteri di prestazione accettabili, che raramente è "il più veloce possibile").

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.