Passando la variabile membro come parametro del metodo


33

In un progetto, ho trovato un codice come questo:

class SomeClass
{
    private SomeType _someField;

    public SomeType SomeField
    {
        get { return _someField; }
        set { _someField = value; }
    }

    protected virtual void SomeMethod(/*...., */SomeType someVar)
    {
    }

    private void SomeAnotherMethod()
    {
        //.............
        SomeMethod(_someField);
        //.............
    }

};

Come posso convincere i miei compagni di squadra che si tratta di un codice errato?

Credo che questa sia una complicazione inutile. Perché passare una variabile membro come parametro del metodo se si ha già accesso ad essa? Questa è anche una violazione dell'incapsulamento.

Vedi altri problemi con questo codice?


21
Cosa ti fa pensare che sia male?
yannis,

@Yannis Rizos, pensi che sia buono? Almeno questa è una complicazione inutile. Perché passare la variabile come parametro del metodo, se hai già accesso ad essa? Questa è anche violazione dell'incapsulamento.
Tika,

2
Punti validi, modifica la domanda per includerli. Non possiamo aiutarti a convincere i tuoi compagni di squadra, ma possiamo aiutarti a valutare il codice, ecco di cosa dovresti rispondere.
yannis,

8
Preferirei avere un metodo in grado di riassumere variabili diverse rispetto a un metodo che fa la costante 2 + 2. Il parametro sul metodo è per la riusabilità.
Dante,

Un punto che ritengo importante qui è il tipo di quel parametro. Se è un tipo di riferimento, non vedo alcun vantaggio, ma se è un tipo di valore, penso che abbia senso dal momento che se modifichi quel tipo di variabile il compilatore ti avviserà di quei luoghi in cui hai rotto il codice.
Rémi,

Risposte:


3

Penso che questo sia un argomento valido, ma il motivo per cui stai ottenendo risposte contrastanti è a causa del modo in cui è stata formata la domanda. Personalmente, ho avuto le stesse esperienze con il mio team in cui passare membri come argomenti non era necessario e contorto il codice. Avremmo una classe che funziona con un insieme di membri ma alcune funzioni accedono direttamente ai membri e altre modificano gli stessi membri attraverso parametri (cioè usano nomi completamente diversi) e non c'è assolutamente alcun motivo tecnico per farlo. Per ragione tecnica, intendo un esempio fornito da Kate.

Consiglio di fare un passo indietro e invece di concentrarsi esattamente sul passaggio dei membri come parametri, avviare discussioni con il proprio team sulla chiarezza e la leggibilità. O più formalmente, o semplicemente nei corridoi, discuti su ciò che rende alcuni segmenti di codice più facili da leggere e altri segmenti di codice più difficili. Quindi identifica le misure di qualità o gli attributi del codice pulito per cui come squadra vorresti lottare. Dopotutto, anche quando lavoriamo su progetti green field, passiamo più del 90% del tempo a leggere e non appena il codice viene scritto (diciamo 10-15 minuti dopo) va in manutenzione dove la leggibilità è ancora più importante.

Quindi, per il tuo esempio specifico qui, l'argomento che vorrei usare è che meno codice è sempre più facile da leggere di più codice. Una funzione che ha 3 parametri è più difficile da elaborare per il cervello rispetto a una funzione che non ha nessuno o 1 parametro. Se c'è un altro nome di variabile, il cervello deve tenere traccia di un'altra cosa quando legge il codice. Quindi, per ricordare "int m_value" e poi "int localValue" e ricordare che l'uno significa davvero che l'altro è sempre più costoso per il tuo cervello, quindi semplicemente lavorare con "m_value".

Per ulteriori munizioni e idee, consiglierei di prendere una copia del codice pulito dello zio Bob .


Sottovalutato dopo aver visto l'influenza del libro cui si fa riferimento.
Frank Hileman,

Sebbene una piccola parte di me sia molto triste che 5 anni dopo aver scritto la risposta, mi hai appena portato via 2 punti internet, c'è un'altra piccola parte di me che è curiosa di poter fornire alcuni riferimenti che sarebbero indicativi del (presumibilmente cattivo) influenza che ha avuto il libro a cui ho fatto riferimento. Sembrerebbe giusto e quasi valesse quei punti
DXM

il riferimento sono io stesso, vedendo personalmente l'influenza su altri sviluppatori. Tutte le motivazioni alla base di tali libri sono buone, tuttavia, specificando che tutto il codice deve seguire linee guida non standard (cioè linee guida che effettivamente servono a uno scopo), tali libri sembrano generare una perdita di pensiero critico, che alcuni interpretano come settari .
Frank Hileman,

per quanto riguarda le sfide dell'elaborazione del cervello, considera il concetto di carico cognitivo
jxramos,

30

Posso pensare ad una giustificazione per passare un campo membro come parametro in un metodo (privato): rende esplicito da cosa dipende il tuo metodo.

Come dici tu, tutti i campi membro sono parametri impliciti del tuo metodo, perché l'intero oggetto lo è. Tuttavia, l'oggetto completo è davvero necessario per calcolare il risultato? Se SomeMethodè un metodo interno che dipende solo da _someField, non è più pulito rendere esplicita questa dipendenza? In effetti, rendere esplicita questa dipendenza può anche suggerire che puoi effettivamente refactoring questo pezzo di codice fuori dalla tua classe! (Nota suppongo che non stiamo parlando di getter o setter qui, ma di codice che calcola effettivamente qualcosa)

Non farei lo stesso argomento per i metodi pubblici, perché il chiamante non sa né si preoccupa di quale parte dell'oggetto è rilevante per calcolare il risultato ...


2
Quale sarebbe la dipendenza implicita rimanente? Ho ipotizzato dal tuo esempio che _someFieldsia l'unico parametro necessario per calcolare il risultato e lo abbiamo appena reso esplicito. (Nota: questo è importante. Non abbiamo aggiunto una dipendenza, l'abbiamo solo resa esplicita!)
Andres F.

11
-1 Se non ci sono dipendenze implicite ai membri dell'istanza, dovrebbe essere un membro statico che accetta il valore come parametro. In questo caso, anche se hai trovato un motivo, non credo sia una valida giustificazione. È un codice errato.
Steven Evers,

2
@SnOrfus In realtà ho suggerito di refactoring il metodo completamente fuori dalla classe
Andres F.

5
+1. per "... non è più pulito rendere esplicita questa dipendenza?" Abso-impazzendo-loutely. Chi qui direbbe "le variabili globali sono buone come regola generale"? È così COBOL-68. Ascoltami ora e credimi più tardi. Nel nostro codice non banale, a volte, rifletterò di comunicare esplicitamente dove vengono utilizzate le variabili di classe globale. In molti casi abbiamo rovinato il cane con un) uso arbitrario di un campo privato ed è di proprietà pubblica b) offuscando la trasformazione di un campo "nascondendo le dipendenze". Ora moltiplicalo per 3-5 catene di eredità profonde.
radarbob,

2
@Tarion Non sono d'accordo con lo zio Bob su questo. Quando possibile, i metodi dovrebbero essere simili alle funzioni e dipendere solo da dipendenze esplicite. (Quando si chiamano metodi pubblici in OOP, una di queste dipendenze è this(o self), ma è resa esplicita dalla chiamata stessa obj.method(x)). Altre dipendenze implicite sono lo stato dell'oggetto; questo di solito rende il codice più difficile da capire. Ove possibile - e entro limiti ragionevoli - rende esplicite e funzionali le dipendenze. Nel caso di metodi privati, se possibile, passare esplicitamente tutti i parametri di cui hanno bisogno. E sì, aiuta a risolverli.
Andres F.

28

Vedo una forte ragione per passare le variabili membro come argomenti di funzione a metodi privati : la purezza delle funzioni. Le variabili membro sono effettivamente uno stato globale dal punto di vista, inoltre, uno stato globale mutabile se detto membro cambia durante l'esecuzione del metodo. Sostituendo i riferimenti alle variabili membro con i parametri del metodo, possiamo effettivamente rendere pura una funzione. Le funzioni pure non dipendono da uno stato esterno e non hanno effetti collaterali, restituendo sempre gli stessi risultati dato lo stesso set di parametri di input - rendendo così più semplici i test e la manutenzione futura.

Certo non è né facile né pratico avere tutti i tuoi metodi come metodi puri in un linguaggio OOP. Ma credo che tu guadagni molto in termini di chiarezza del codice avendo i metodi che gestiscono la logica complessa pura e usando variabili immutabili, mantenendo la gestione dello stato "globale" impura separata all'interno di metodi dedicati.

Passare variabili membro a una funzione pubblica dello stesso oggetto quando si chiama la funzione esternamente costituirebbe comunque un odore di codice maggiore secondo me.


5
Risposta da superatore. Nonostante l'ideale di Hwats, molte classi nel software di oggi sono più grandi e più brutte di quanto lo fossero interi programmi quando fu coniata la fase "Globals are Evil". Le variabili di classe sono, a tutti gli effetti pratici, globali nell'ambito dell'istanza di classe. Avere grandi quantità di lavoro svolto con funzioni pure rende il codice molto più testabile e robusto.
mattnz,

@mattnz c'è un modo per fornire un link alla bibliografia di Hwat o ad uno dei suoi libri sulla programmazione ideale? Ho setacciato Internet e non riesco a trovare nulla su di lui. Google continua a provare a correggerlo automaticamente in "cosa".
Buttle Butkus il

8

Se la funzione viene chiamata varie volte, a volte passando quella variabile membro e talvolta passando qualcos'altro, allora va bene. Ad esempio, non lo prenderei affatto in considerazione:

if ( CalculateCharges(newStartDate) > CalculateCharges(m_StartDate) )
{
     //handle increase in charges
}

dove newStartDateè una variabile locale ed m_StartDateè una variabile membro.

Tuttavia, se la funzione viene sempre chiamata solo con la variabile membro passata ad essa, è strano. Le funzioni membro funzionano continuamente sulle variabili dei membri. Potrebbero farlo (a seconda della lingua in cui stai lavorando) per ottenere una copia della variabile membro - se è così e non riesci a vederlo, il codice potrebbe essere più bello se rendesse esplicito l'intero processo.


3
Non importa che il metodo venga chiamato con parametri diversi dalla variabile membro. Ciò che conta è che potrebbe essere chiamato in quel modo. Non sempre sai come alla fine verrà chiamato un metodo quando lo crei.
Caleb,

Forse dovresti sostituire meglio la condizione "if" con "needHandleIncreaseChages (newStartDate)" e quindi il tuo argomento non regge più.
Tarion,

2

Ciò che nessuno ha toccato è che SomeMethod è protetto virtuale. Significa che una classe derivata può usarla E implementare nuovamente la sua funzionalità. Una classe derivata non avrebbe accesso alla variabile privata e quindi non potrebbe fornire un'implementazione personalizzata di SomeMethod che dipende dalla variabile privata. Invece di accettare la dipendenza dalla variabile privata, la dichiarazione richiede al chiamante di passarla.


Quello che ti manca è che questa variabile membro privata ha accessi pubblici.
martedì

Quindi stai dicendo che il metodo virtuale protetto dovrebbe dipendere dall'accessor pubblico di una variabile privata? E hai un problema con il codice attuale?
Michael Brown,

Gli accessi pubblici sono creati per essere utilizzati. Periodo.
Tika,

1

I framework della GUI in genere hanno una sorta di classe 'Visualizza' che rappresenta le cose disegnate sullo schermo e quella classe di solito fornisce un metodo come invalidateRect(Rect r)marcare parte della sua area di disegno come se fosse necessario ridisegnarla. I clienti potrebbero chiamare quel metodo per richiedere un aggiornamento a parte della vista. Ma una vista potrebbe anche chiamare il proprio metodo, come:

invalidateRect(m_frame);

per ridisegnare l'intera area. Ad esempio, potrebbe farlo quando viene aggiunto per la prima volta alla gerarchia della vista.

Non c'è niente di sbagliato nel fare questo: la cornice della vista è un rettangolo valido e la vista stessa sa che vuole ridisegnare se stessa. La classe View potrebbe fornire un metodo separato che non accetta parametri e utilizza invece il frame della vista:

invalidateFrame();

Ma perché aggiungere un metodo speciale solo per questo quando è possibile utilizzare il più generale invalidateRect()? Oppure, se scegli di fornire invalidateFrame(), molto probabilmente lo implementeresti in termini di più generale invalidateRect():

View::invalidateFrame(void)
{
    invalidateRect(m_frame)
}

Perché passare la variabile come parametro del metodo, se hai già accesso ad essa?

Si dovrebbe passare variabili di istanza come parametri di un proprio metodo , se il metodo non funziona in particolare su quella variabile di istanza. Nell'esempio sopra, la cornice della vista è solo un altro rettangolo per quanto riguarda il invalidateRect()metodo.


1

Questo ha senso se il metodo è un metodo di utilità. Ad esempio, è necessario derivare un nome breve univoco da più stringhe di testo libero.

Non vorrai codificare un'implementazione separata per ogni stringa, invece ha senso passare la stringa a un metodo comune.

Tuttavia, se il metodo funziona sempre su un singolo membro, sembra un po 'stupido passarlo come parametro.


1

Il motivo principale per avere una variabile membro in una classe è permetterti di impostarla in un posto e avere il suo valore disponibile per ogni altro metodo nella classe. In generale, pertanto, ci si aspetterebbe che non sia necessario passare la variabile membro in un metodo della classe.

Posso tuttavia pensare a un paio di ragioni per cui potresti voler passare la variabile membro in un altro metodo della classe. Il primo è se è necessario garantire che il valore della variabile membro debba essere utilizzato invariato quando utilizzato con il metodo chiamato, anche se tale metodo dovrà modificare il valore della variabile membro effettiva in un determinato momento del processo. Il secondo motivo è legato al primo in quanto si potrebbe desiderare di garantire l'immutabilità di un valore nell'ambito di una catena di metodi, ad esempio quando si implementa una sintassi fluente.

Con tutto questo in mente, non andrei fino al punto di dire che il codice è "cattivo" in quanto tale se si passa una variabile membro a uno dei metodi della sua classe. Vorrei tuttavia suggerire che in genere non è l'ideale, in quanto potrebbe incoraggiare un sacco di duplicazione del codice in cui il parametro è assegnato ad una variabile locale, ad esempio, e i parametri aggiuntivi aggiungono "rumore" nel codice dove non è necessario. Se sei un fan del libro Clean Code, sapresti che menziona che dovresti mantenere il numero di parametri del metodo al minimo indispensabile e solo se non esiste un altro modo più sensato per accedere al parametro .


1

Alcune cose che mi vengono in mente pensando a questo:

  1. In generale, le firme dei metodi con meno parametri sono più facili da capire; un grande motivo per cui sono stati inventati metodi è stato quello di eliminare lunghe liste di parametri sposandole con i dati su cui operano.
  2. Fare in modo che la firma del metodo dipenda dalla variabile membro renderà più difficile cambiare quelle variabili in futuro, perché non è necessario solo cambiare all'interno del metodo, ma ovunque viene chiamato anche il metodo. E poiché SomeMethod nel tuo esempio è protetto, anche le sottoclassi dovranno cambiare.
  3. I metodi (pubblici o privati) che non dipendono dagli interni della classe non devono essere su quella classe. Potrebbero essere presi in considerazione in metodi di utilità ed essere altrettanto felici. Quasi nessuno fa parte di quella classe. Se nessun altro metodo dipende da quella variabile dopo aver spostato il / i metodo / i, allora anche quella variabile dovrebbe andare! Molto probabilmente la variabile dovrebbe essere sul proprio oggetto con quel metodo o quei metodi che operano su di essa diventando pubblici ed essendo composti dalla classe genitore.
  4. Passare i dati a varie funzioni come la tua classe è un programma procedurale con variabili globali che volano proprio di fronte al design OO. Questo non è lo scopo delle variabili membro e delle funzioni membro (vedi sopra), e sembra che la tua classe non sia molto coesa. Penso che sia un odore di codice che suggerisce un modo migliore per raggruppare dati e metodi.

0

Manca se il parametro ("argomento") è di riferimento o di sola lettura.

class SomeClass
{
    protected SomeType _someField;
    public SomeType SomeField
    {
        get { return _someField; }

        set {
          if (doSomeValidation(value))
          {
            _someField = value;
          }
        }
    }

    protected virtual void ModifyMethod(/*...., */ ref SomeType someVar)
    { 
      // ...
    }    

    protected virtual void ReadMethod(/*...., */ SomeType someVar)
    { 
      // ...
    }

    private void SomeAnotherMethod()
    {
        //.............

        // not recommended, but, may be required in some cases
        ModifyMethod(ref this._someField);

        //.............

        // recommended, but, verbose
        SomeType SomeVar = this.someField;
        ModifyMethod(ref SomeVar);
        this.someField = SomeVar;

        //.............

        ReadMethod(this.someField);
        //.............
    }

};

Molti sviluppatori, di solito assegnano direttamente i campi interni di una variabile, nei metodi di costruzione. Ci sono alcune eccezioni.

Ricorda che i "setter" possono avere metodi aggiuntivi, non solo assegnazione, e talvolta possono anche essere metodi virtuali o chiamare metodi virtuali.

Nota: suggerisco di mantenere i campi interni delle proprietà come "protetti".

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.