Perché Clean Code suggerisce di evitare le variabili protette?


168

Clean Code suggerisce di evitare le variabili protette nella sezione "Distanza verticale" del capitolo "Formattazione":

I concetti strettamente correlati dovrebbero essere tenuti verticalmente vicini l'uno all'altro. Chiaramente questa regola non funziona per i concetti che appartengono a file separati. Ma allora i concetti strettamente correlati non dovrebbero essere separati in file diversi a meno che tu non abbia una buona ragione. In effetti, questo è uno dei motivi per cui le variabili protette dovrebbero essere evitate .

Qual è il ragionamento?


7
mostrare un esempio in cui si pensa di bisogno che
moscerino

63
Non prenderei molto in quel libro come Vangelo.
Rig

40
@Rig sei al limite della blasfemia da queste parti con un commento del genere.
Ryathal,

40
Sto bene con quello. Ho letto il libro e posso avere la mia opinione al riguardo.
Rig

10
Ho letto il libro e anche se sono d'accordo con la maggior parte di ciò che è lì, non direi che lo considero anche evangelico. Penso che ogni situazione sia diversa ... ed essere in grado di attenersi alle linee guida esatte rappresentate nel libro sono piuttosto difficili per molti progetti.
Simon Whitehead,

Risposte:


277

Le variabili protette dovrebbero essere evitate perché:

  1. Tendono a portare a problemi di YAGNI . A meno che tu non abbia una classe discendente che effettivamente fa cose con il membro protetto, rendilo privato.
  2. Tendono a portare a problemi LSP . Le variabili protette generalmente hanno qualche invarianza intrinseca ad esse associata (altrimenti sarebbero pubbliche). Gli ereditari devono quindi mantenere quelle proprietà, che le persone possono rovinare o violare volontariamente.
  3. Tendono a violare l' OCP . Se la classe base fa troppe ipotesi sul membro protetto, o l'erede è troppo flessibile con il comportamento della classe, può comportare che il comportamento della classe base venga modificato da quell'estensione.
  4. Tendono a portare all'eredità per estensione piuttosto che per composizione. Ciò tende a portare a un accoppiamento più stretto, a più violazioni dell'SRP , a test più difficili e a una serie di altre cose che rientrano nella discussione sulla composizione del favore rispetto all'eredità.

Ma come vedi, tutti questi sono "tendenti a". A volte un membro protetto è la soluzione più elegante. E le funzioni protette tendono ad avere meno di questi problemi. Ma ci sono un certo numero di cose che li fanno trattare con cura. Con tutto ciò che richiede quel tipo di cura, le persone commettono errori e nel mondo della programmazione ciò significa bug e problemi di progettazione.


Grazie per molte buone ragioni. Tuttavia, il libro lo menziona specificamente in un contesto di formattazione e distanza verticale, è quella parte che mi chiedo.
Matsemann,

2
Sembra che lo zio Bob si riferisca alla distanza verticale quando qualcuno si riferisce a un membro protetto tra le classi e non nella stessa classe.
Robert Harvey,

5
@Matsemann - certo. Se ricordo bene il libro, quella sezione si concentra sulla leggibilità e la capacità di scoprire il codice. Se le variabili e le funzioni funzionano su un concetto, dovrebbero essere vicine nel codice. I membri protetti verranno utilizzati dai tipi derivati, che non possono necessariamente essere vicini al membro protetto poiché si trovano in un file completamente diverso.
Telastyn,

2
@ steve314 Non riesco a immaginare uno scenario in cui la classe base e tutti i suoi eredi vivranno in un solo file. Senza un esempio, sono propenso a ritenerlo un abuso di eredità o una cattiva astrazione.
Telastyn,

3
YAGNI = Non ne avrai bisogno LSP = Principio di sostituzione di Liskov OCP = Principio di apertura / chiusura SRP = Principio di responsabilità singola
Bryan Glazer,

54

È davvero la stessa ragione per cui eviti i globi, solo su scala minore. È perché è difficile trovare ovunque una variabile venga letta, o peggio, scritta e difficile rendere coerente l'uso. Se l'utilizzo è limitato, come essere scritto in un punto ovvio nella classe derivata e letto in un punto ovvio nella classe base, le variabili protette possono rendere il codice più chiaro e conciso. Se sei tentato di leggere e scrivere una variabile volenti o nolenti su più file, è meglio incapsularla.


26

Non ho letto il libro, ma posso capire cosa significava zio Bob.

Se metti protectedqualcosa, significa che una classe può ereditarla. Ma le variabili membro dovrebbero appartenere alla classe in cui sono contenute; questo fa parte dell'incapsulamento di base. L'inserimento di protecteduna variabile membro interrompe l'incapsulamento perché ora una classe derivata ha accesso ai dettagli di implementazione della classe base. È lo stesso problema che si verifica quando si effettua una variabile publicsu una classe ordinaria.

Per correggere il problema, è possibile incapsulare la variabile in una proprietà protetta, in questo modo:

protected string Name
{
    get { return name; }
    private set { name = value; }
}

Ciò consente namedi essere impostato in modo sicuro da una classe derivata utilizzando un argomento del costruttore, senza esporre i dettagli di implementazione della classe base.


Hai creato una proprietà pubblica con setter protetto. Il campo protetto deve essere risolto con una proprietà protetta con setter privato.
Andy,

2
+1 ora. Anche il setter potrebbe essere protetto, suppongo, ma gli ID punto che la base può applicare se il nuovo valore è valido o meno.
Andy,

Giusto per offrire un punto di vista opposto, proteggo tutte le mie variabili in una classe base. Se è possibile accedervi solo tramite un'interfaccia pubblica, non dovrebbe essere una classe derivata, dovrebbe contenere solo un oggetto baseclass. Ma evito anche le gerarchie più profonde di 2 classi
Martin Beckett il

2
C'è un'enorme differenza tra protectede publicmembri. Se BaseTypeespone un membro pubblico, ciò implica che tutti i tipi derivati ​​devono avere lo stesso membro allo stesso modo, poiché DerivedTypeun'istanza può essere passata al codice che riceve un BaseTyperiferimento e si aspetta di utilizzare quel membro. Al contrario, se BaseTypeespone un protectedmembro, DerivedTypepotrebbe aspettarsi di accedervi base, ma basenon può essere altro che un BaseType.
supercat

18

L'argomento di zio Bob è principalmente di distanza: se hai un concetto che è importante per una classe, raggruppa il concetto insieme alla classe in quel file. Non separato su due file sul disco.

Le variabili dei membri protetti sono sparse in due punti e, in qualche modo, sembra una magia. Fai riferimento a questa variabile, ma non è definita qui ... dove è definita? E così inizia la caccia. Meglio evitare del protectedtutto il suo argomento.

Ora non credo che questa regola debba essere obbedita continuamente alla lettera (come: non userai protected). Guarda lo spirito di ciò che sta ottenendo ... raggruppa le cose correlate in un unico file - usa le tecniche di programmazione e le caratteristiche per farlo. Consiglierei di non analizzare troppo e di rimanere invischiato nei dettagli.


9

Alcune ottime risposte già qui. Ma una cosa da aggiungere:

Nella maggior parte dei linguaggi OO moderni è una buona abitudine (in Java AFAIK è necessario) mettere ogni classe nel proprio file (per favore non avere niente di strano in C ++ dove spesso hai due file).

Quindi è praticamente impossibile mantenere le funzioni in una classe derivata accedendo a una variabile protetta di una classe base "verticalmente chiusa" nel codice sorgente alla definizione della variabile. Ma idealmente dovrebbero essere mantenuti lì, poiché le variabili protette sono destinate all'uso interno, il che rende le funzioni che le accolgono spesso "un concetto strettamente correlato".


1
Non in Swift. È interessante notare che Swift non ha "protetto" ma ha "fileprivate" che sostituisce sia "protetto" che C ++ "amico".
gnasher729,

@ gnasher729 - ovviamente, Swift ha un sacco di Smalltalk nella sua eredità. Smalltalk non ha altro che variabili private e metodi pubblici. E privato in Smalltalk significa privato: due oggetti anche della stessa classe non possono accedere l'un l'altro alle variabili.
Jules,

4

L'idea di base è che un "campo" (variabile a livello di istanza) che viene dichiarato protetto è probabilmente più visibile di quanto non debba essere e meno "protetto" di quanto si possa desiderare. Non esiste un modificatore di accesso in C / C ++ / Java / C # che è l'equivalente di "accessibile solo da classi figlio all'interno dello stesso assembly", garantendo così la possibilità di definire i propri figli che possono accedere al campo nel proprio assembly, ma non consentire ai bambini creati in altre assemblee lo stesso accesso; C # ha modificatori interni e protetti, ma la loro combinazione rende l'accesso "interno o protetto", non "interno e protetto". Quindi, un campo protetto è accessibile a qualsiasi bambino, sia che tu l'abbia scritto o che lo abbia fatto qualcun altro. Protected è quindi una porta aperta per un hacker.

Inoltre, i campi per loro definizione non hanno praticamente alcuna validazione inerente alla loro modifica. in C # puoi crearne uno in sola lettura, il che rende i tipi di valore effettivamente costanti e i tipi di riferimento non possono essere reinizializzati (ma comunque molto mutabili), ma questo è tutto. Come tale, anche protetto, i tuoi figli (di cui non puoi fidarti) hanno accesso a questo campo e possono impostarlo su qualcosa di non valido, rendendo incoerente lo stato dell'oggetto (qualcosa da evitare).

Il modo accettato di lavorare con i campi è renderli privati ​​e accedervi con una proprietà e / o un metodo getter e setter. Se tutti i consumatori della classe hanno bisogno del valore, rendi pubblico (almeno) pubblico. Se solo i bambini ne hanno bisogno, proteggi il getter.

Un altro approccio che risponde alla domanda è quello di porsi; perché il codice in un metodo figlio richiede la possibilità di modificare direttamente i miei dati di stato? Che cosa dice questo codice? Questa è l'argomento "distanza verticale" sulla sua faccia. Se esiste un codice in un figlio che deve modificare direttamente lo stato genitore, allora forse quel codice dovrebbe appartenere al genitore in primo luogo?


4

È una discussione interessante.

Ad essere sinceri, buoni strumenti possono mitigare molti di questi problemi "verticali". In effetti, secondo me la nozione di "file" è in realtà qualcosa che ostacola lo sviluppo del software - qualcosa su cui sta lavorando il progetto Light Table (http://www.chris-granger.com/2012/04/12/light- tavolo --- a-new-ide-concept /).

Elimina i file dall'immagine e l'ambito protetto diventa molto più attraente. Il concetto protetto diventa più chiaro in lingue come SmallTalk - dove qualsiasi eredità estende semplicemente il concetto originale di una classe. Dovrebbe fare tutto e avere tutto ciò che ha fatto la classe genitore.

Secondo me, le gerarchie di classi dovrebbero essere il più superficiali possibile, con la maggior parte delle estensioni provenienti dalla composizione. In tali modelli, non vedo alcun motivo per cui le variabili private non vengano invece protette. Se la classe estesa dovrebbe rappresentare un'estensione che include tutti i comportamenti della classe genitore (cosa che credo dovrebbe, e quindi ritengo che i metodi privati ​​siano intrinsecamente cattivi), perché la classe estesa non dovrebbe rappresentare anche l'archiviazione di tali dati?

Per dirla in altro modo - in ogni caso in cui ritengo che una variabile privata si adatterebbe meglio di una protetta, l'ereditarietà non è in primo luogo la soluzione al problema.


0

Come dice lì, questo è solo uno dei motivi, vale a dire, il codice logicamente collegato dovrebbe essere collocato all'interno di entità fisiche collegate (file, pacchetti e altri). Su una scala più piccola, questo è lo stesso del motivo per cui non si inserisce una classe che esegue query DB all'interno del pacchetto con classi che visualizzano l'interfaccia utente.

La ragione principale, tuttavia, che le variabili protette non sono raccomandate è perché tendono a rompere l'incapsulamento. Le variabili e i metodi dovrebbero avere la visibilità quanto più limitata possibile; per ulteriori riferimenti, consultare l' efficace Java di Joshua Bloch , elemento 13 - "Ridurre al minimo l'accessibilità di classi e membri".

Tuttavia, non dovresti prendere tutto questo annuncio litteram, solo come linea di gilda; se le variabili protette sono pessime, non sarebbero state inserite nella lingua in primo luogo. Un posto ragionevole per i campi protetti imho è all'interno della classe base da un framework di test (le classi di test di integrazione lo estendono).


1
Non dare per scontato che, poiché una lingua lo consente, va bene farlo. Non sono sicuro che ci sia davvero una ragione divina per consentire campi protetti; in realtà li vietiamo al mio datore di lavoro.
Andy,

2
@Andy Non hai letto quello che ho detto; Ho detto che i campi protetti non sono raccomandati e le ragioni di ciò. Esistono chiaramente scenari in cui sono necessarie variabili protette; potresti desiderare un valore finale costante solo nelle sottoclassi.
m3th0dman,

Quindi potresti voler riformulare alcuni dei tuoi post, dato che sembri usare variabili e campi in modo intercambiabile e rendere esplicito che considereresti cose come i costi protetti un'eccezione.
Andy,

3
@Andy È abbastanza ovvio che da quando mi riferivo a variabili protette non parlavo di variabili locali o di parametri ma di campi. Ho anche menzionato uno scenario in cui sono utili variabili protette non costanti; imho, è sbagliato per un'azienda far rispettare il modo in cui i programmatori scrivono il codice.
m3th0dman,

1
Se fosse ovvio non sarei stato confuso e votato a fondo. La regola è stata fatta dai nostri sviluppatori senior, così come la nostra decisione di eseguire StyleCop, FxCop e richiedere test unitari e revisioni del codice.
Andy,

0

Trovo che usare variabili protette per i test JUnit possa essere utile. Se li rendi privati, non puoi accedervi durante i test senza riflessione. Questo può essere utile se stai testando un processo complesso attraverso molti cambiamenti di stato.

Potresti renderli privati, con metodi getter protetti, ma se le variabili verranno utilizzate solo esternamente durante un test JUnit, non sono sicuro che sia una soluzione migliore.


-1

Sì, sono d'accordo che sia un po 'strano dato che (come ricordo) il libro tratta anche tutte le variabili non private come qualcosa da evitare.

Penso che il problema con il modificatore protetto sia che non solo il membro è esposto a sottoclassi ma è anche reso visibile all'intero pacchetto. Penso che sia questo duplice aspetto a cui lo zio Bob fa eccezione. Certo, non si può sempre aggirare con metodi protetti e quindi la qualificazione variabile protetta.

Penso che un approccio più interessante sia quello di rendere tutto pubblico, che ti costringerà ad avere piccole classi, il che a sua volta rende l'intera metrica della distanza verticale piuttosto controversa.

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.