C'è un motivo per non modificare i valori dei parametri passati per valore?


9

Esistono argomenti oggettivi e sostenibili di ingegneria del software a favore o contro la modifica dei valori dei parametri per valore nel corpo di una funzione?

Uno sputo ricorrente (per lo più divertente) nel mio team è se i parametri passati per valore debbano essere modificati. Un paio di membri del team sono fermamente convinti che i parametri non dovrebbero mai essere assegnati, in modo che il valore originariamente passato alla funzione possa sempre essere interrogato. Non sono d'accordo e ritengo che i parametri non siano altro che variabili locali inizializzate dalla sintassi di chiamare il metodo; se il valore originale di un parametro per valore è importante rispetto a una variabile locale, può essere dichiarato per memorizzare esplicitamente questo valore. Non sono sicuro che nessuno di noi abbia un ottimo supporto per la nostra posizione.

Si tratta di un conflitto religioso non risolvibile o esistono buone ragioni oggettive di ingegneria del software in entrambe le direzioni?

Nota: la questione del principio rimane a prescindere dai dettagli di implementazione di quel particolare linguaggio. In JavaScript, ad esempio, dove l'elenco degli argomenti è sempre dinamico, i parametri possono essere considerati zucchero sintattico per l'inizializzazione della variabile locale argumentsdall'oggetto. Anche così, si potrebbero considerare gli identificatori dichiarati da parametri come "speciali" perché catturano ancora il passaggio di informazioni dal chiamante alla chiamata.


È ancora specifico della lingua; sostenendo che è indipendente dalla lingua perché "è così, dal punto di vista della mia (lingua preferita)" è una forma di argomento non valido. Un secondo motivo per cui è specifico della lingua è perché una domanda che non ha una risposta universale per tutte le lingue è soggetta alle culture e alle norme di ciascuna lingua; in questo caso, norme specifiche di una lingua diversa danno risposte diverse a questa domanda.
rwong

Il principio che i membri del tuo team stanno cercando di insegnarti è "una variabile, uno scopo". Sfortunatamente, non troverai prove definitive in entrambi i casi. Per quello che vale, non ricordo di aver mai cooptato una variabile di parametro per qualche altro scopo, né ricordo di averlo mai considerato. Non mi è mai venuto in mente che potesse essere una buona idea, e ancora non penso che lo sia.
Robert Harvey,

Pensavo che questo dovesse essere stato chiesto prima, ma l'unica cosa che ho potuto trovare è questa vecchia domanda SO .
Doc Brown,

3
È considerato meno soggetto a errori per un linguaggio vietare la mutazione dell'argomento, proprio come i progettisti di linguaggi funzionali considerano le assegnazioni "let" meno soggette a errori. Dubito che qualcuno abbia dimostrato questo assunto usando uno studio.
Frank Hileman,

Risposte:


15

Non sono d'accordo e ritengo che i parametri non siano altro che variabili locali inizializzate dalla sintassi di chiamare il metodo

Adotto una terza posizione: i parametri sono proprio come le variabili locali: entrambi dovrebbero essere trattati come immutabili di default. Quindi le variabili vengono assegnate una volta e quindi lette solo da, non modificate. Nel caso dei loop, for each (x in ...)la variabile è immutabile nel contesto di ogni iterazione. Le ragioni sono:

  1. rende il codice più facile da "eseguire nella mia testa",
  2. consente nomi più descrittivi per le variabili.

Di fronte a un metodo con un gruppo di variabili che vengono assegnate e quindi non cambiano, posso concentrarmi sulla lettura del codice, piuttosto che cercare di ricordare il valore corrente di ciascuna di quelle variabili.

Di fronte allo stesso metodo, questa volta con meno variabili, ma che cambiano valore continuamente, ora devo ricordare lo stato corrente di quelle variabili, oltre a lavorare su ciò che sta facendo il codice.

Nella mia esperienza, il primo è molto più facile per il mio povero cervello. Altre persone più intelligenti di me potrebbero non avere questo problema, ma mi aiuta.

Per quanto riguarda l'altro punto:

double Foo(double r)
{
    r = r * r;
    r = Math.Pi * r;
    return r;
}

contro

double Foo(int r)
{
    var rSquared = r * r;
    var area = Math.Pi * rSquared;
    return area;
} 

Esempi contrastanti, ma a mio avviso è molto più chiaro ciò che sta accadendo nel secondo esempio a causa dei nomi delle variabili: trasmettono al lettore molte più informazioni di quante ne rfacciano quelle masse nel primo esempio.


"Le variabili locali .... dovrebbero essere considerate immutabili di default." - Che cosa?
whatsisname

1
È piuttosto difficile eseguire forloop con variabili immutabili. Incapsulare la variabile nella funzione non è sufficiente per te? Se hai problemi a seguire le tue variabili in una semplice funzione, forse le tue funzioni sono troppo lunghe.
Robert Harvey,

@RobertHarvey for (const auto thing : things)è un ciclo for e la variabile che introduce è (localmente) immutabile. for i, thing in enumerate(things):inoltre non
muta

3
Questo è IMHO in generale un ottimo modello mentale. Tuttavia, vorrei aggiungere una cosa: spesso è accettabile inizializzare una variabile in più di un passaggio e trattarla come immutabile in seguito. Questo non è contro la strategia generale presentata qui, solo contro seguirla sempre alla lettera.
Doc Brown,

3
Wow, leggendo questi commenti, posso vedere che molte persone qui ovviamente non hanno mai studiato il paradigma funzionale.
Joshua Jones,

4

Si tratta di un conflitto religioso non risolvibile o esistono buone ragioni oggettive di ingegneria del software in entrambe le direzioni?

Questa è una cosa che mi piace molto del C ++ (ben scritto): puoi vedere cosa aspettarti. const string& x1è un riferimento costante, string x2è una copia ed const string x3è una copia costante. Io so cosa succederà nella funzione. x2 sarà cambiato, perché se non lo fosse, non ci sarebbe motivo di renderlo non const.

Quindi, se hai una lingua che lo consente , dichiara cosa stai per fare con i parametri. Questa dovrebbe essere la scelta migliore per tutti i soggetti coinvolti.

Se la tua lingua non lo consente, temo che non ci sia una soluzione di proiettile d'argento. Entrambi sono possibili. L'unica linea guida è il principio della minima sorpresa. Adotta un approccio e seguilo in tutta la tua base di codice, non mescolarlo.


Un altro aspetto positivo è che int f(const T x)e int f(T x)differiscono solo nella funzione-corpo.
Deduplicatore

3
Una funzione C ++ con un parametro come const int xsolo per chiarire x non viene modificata all'interno? A me sembra uno stile molto strano.
Doc Brown,

@DocBrown Non sto giudicando nessuno dei due stili, sto solo dicendo che qualcuno che pensa che i parametri non debbano essere cambiati può far sì che il compilatore lo garantisca e qualcuno che pensa che cambiarli vada bene può farlo. Entrambe le intenzioni possono essere chiaramente comunicate attraverso la lingua stessa e questo è un grande vantaggio rispetto a una lingua che non può garantirla in nessun modo e in cui devi fare affidamento su linee guida arbitrarie e sperare che tutti le leggano e si conformino a esse.
nvoigt,

@nvoigt: se qualcuno preferisce modificare un parametro della funzione ("per valore"), eliminerebbe semplicemente constl'elenco dei parametri se lo ostacola. Quindi non penso che questo risponda alla domanda.
Doc Brown,

@DocBrown Quindi non è più const. Non è importante che sia const o meno, l'importante è che la lingua includa un modo per comunicarlo senza dubbio allo sviluppatore. La mia risposta è che non importa, purché tu lo comunichi chiaramente , cosa che C ++ rende facile, altri potrebbero non esserlo e hai bisogno di convenzioni.
nvoigt,

1

Sì, è un "conflitto religioso", tranne per il fatto che Dio non legge i programmi (per quanto ne so), quindi è ridotto a un conflitto di leggibilità che diventa una questione di giudizio personale. E certamente l'ho fatto, e l'ho visto fatto, in entrambi i modi. Per esempio,

void propagate (oggetto OBJECT *, double t0, double dt) {
  doppio t = t0; / * copia locale di t0 per evitare di modificarne il valore * /
  while (qualunque cosa) {
    timestep_the_object (...);
    t + = dt; }
  ritorno; }

Quindi qui, proprio a causa del nome dell'argomento t0 , probabilmente sceglierei di non cambiarne il valore. Ma supponiamo invece che abbiamo semplicemente cambiato il nome dell'argomento in tNow , o qualcosa del genere. Quindi mi sarei molto più che probabilmente dimenticato di una copia locale e avrei semplicemente scritto tNow + = dt; per quest'ultima affermazione.

Al contrario, supponiamo di sapere che il client per cui è stato scritto il programma sta per assumere alcuni nuovi programmatori con pochissima esperienza, che leggeranno e lavorerebbero su quel codice. Quindi probabilmente mi preoccuperei di confonderli con il pass-by-value contro-reference, mentre le loro menti sono impegnate al 100% nel tentativo di digerire tutta la logica aziendale. Quindi, in questo caso, dichiarerei sempre una copia locale di qualsiasi argomento che verrà modificato, indipendentemente dal fatto che sia più o meno leggibile per me.

Le risposte a questo tipo di domande di stile emergono con l'esperienza e raramente hanno una risposta religiosa canonica. Chiunque pensi che ci sia una sola risposta (e che lo sappia :) probabilmente ha bisogno di qualche altra esperienza.

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.