Perché Java ha reso predefinito l'accesso al pacchetto?


26

Sto ponendo questa domanda perché credo che l'abbia fatto per una ragione molto buona e che la maggior parte delle persone non la usi correttamente, benché la mia esperienza nel settore finora abbia funzionato. Ma se la mia teoria è vera, allora non sono sicuro del perché includessero il modificatore di accesso privato ...?

Credo che se l'accesso predefinito viene utilizzato correttamente, fornisce una migliore testabilità mantenendo l'incapsulamento. Inoltre, rende ridondante il modificatore di accesso privato.

Il modificatore di accesso predefinito può essere utilizzato per fornire lo stesso effetto utilizzando un pacchetto univoco per i metodi che devono essere nascosti dal resto del mondo, e lo fa senza compromettere la testabilità, poiché i pacchetti in una cartella di test, con lo stesso sono in grado di accedere a tutti i metodi predefiniti dichiarati in una cartella di origine.

Credo che questo sia il motivo per cui Java utilizza l'accesso al pacchetto come "predefinito". Ma non sono sicuro del perché includessero anche l'accesso privato, sono sicuro che esiste un caso d'uso valido ...



Rilevante per quanto riguarda i metodi privati ​​di unit test precedentemente discussi; come-fai-tu-unità-test-metodi-privati . Risposta; non dovresti
Richard Tingle il

Risposte:


25

Immagino che avessero una buona idea di cosa farebbe un programmatore medio. E per programmatore medio intendo uno che non è veramente bravo a programmare, ma ottiene comunque il lavoro perché non ce ne sono abbastanza buoni e sono costosi.

Se rendessero l'accesso "pubblico" a quello predefinito, la maggior parte dei programmatori non si preoccuperebbe mai di usare nient'altro. Il risultato sarebbe un sacco di codice spaghetti ovunque. (Perché se tecnicamente ti è permesso chiamare qualsiasi cosa da qualsiasi luogo, perché mai preoccuparti di incapsulare una logica all'interno di un metodo?)

Rendere "privato" l'accesso predefinito sarebbe solo leggermente migliore. La maggior parte dei programmatori principianti semplicemente inventerebbe (e condividerà sui loro blog) una regola empirica secondo cui "è necessario scrivere" pubblico "ovunque", e poi si lamenterebbero del fatto che Java è così cattivo che li costringe a scrivere "pubblico" ovunque. E avrebbero anche prodotto un sacco di codice spaghetti.

L'accesso al pacchetto è qualcosa che consente ai programmatori di utilizzare le loro tecniche di programmazione sciatta durante la scrittura del codice all'interno di un pacchetto, ma devono quindi riconsiderarlo quando creano più pacchetti. È un compromesso tra buone pratiche commerciali e la brutta realtà. Tipo: scrivi un po 'di spaghetti code, se insisti, ma per favore lascia il brutto pasticcio nel pacchetto; almeno creare alcune interfacce più belle tra i pacchetti.

Probabilmente ci sono anche altri motivi, ma non lo sottovaluterei.


14

L'accesso predefinito non rende ridondante il modificatore di accesso privato.

La posizione dei progettisti linguistici su questo si riflette nel tutorial ufficiale - Controllo dell'accesso ai membri di una classe ed è abbastanza chiaro (per tua comodità, la dichiarazione pertinente nella citazione diventa grassetto ):

Suggerimenti sulla scelta di un livello di accesso:

Se altri programmatori usano la tua classe, vuoi assicurarti che non si possano verificare errori da uso improprio. I livelli di accesso possono aiutarti a farlo.

  • Utilizzare il livello di accesso più restrittivo che ha senso per un determinato membro. Usa privato a meno che tu non abbia una buona ragione per non farlo.
  • Evita i campi pubblici ad eccezione delle costanti. (Molti degli esempi del tutorial utilizzano campi pubblici. Questo può aiutare a illustrare alcuni punti in modo conciso, ma non è raccomandato per il codice di produzione.) I campi pubblici tendono a collegarti a una particolare implementazione e limitare la tua flessibilità nella modifica del codice.

Il tuo appello alla testabilità come giustificazione per eliminare completamente il modificatore privato è sbagliato, come evidenziato ad esempio dalle risposte in Nuovo al TDD. Devo evitare i metodi privati ​​adesso?

Ovviamente puoi avere metodi privati ​​e ovviamente puoi provarli.

O c'è un modo per far funzionare il metodo privato, nel qual caso puoi provarlo in quel modo, o non c'è modo di far funzionare il privato, nel qual caso: perché diamine stai provando a provarlo, solo elimina la dannata cosa ...


La posizione dei progettisti linguistici sullo scopo e sull'uso dell'accesso a livello di pacchetto è spiegata in un altro tutorial ufficiale, Creazione e utilizzo di pacchetti e non ha nulla in comune con l'idea di abbandonare modificatori privati ​​(per comodità, la dichiarazione pertinente nella citazione diventa grassetto ) :

È necessario raggruppare queste classi e l'interfaccia in un pacchetto per diversi motivi, tra cui:

  • Tu e altri programmatori potete facilmente determinare che questi tipi sono correlati ...
  • Puoi consentire ai tipi all'interno del pacchetto di avere accesso illimitato l'uno all'altro, ma ancora limitare l'accesso per i tipi al di fuori del pacchetto ...

<rant "Penso di aver sentito abbastanza piagnistei. Suppongo sia giunto il momento di dire forte e chiaro ...">

I metodi privati ​​sono utili per i test unitari.

La nota che segue presuppone che tu abbia familiarità con la copertura del codice . In caso contrario, prenditi del tempo per imparare, dal momento che è abbastanza utile per coloro che sono interessati ai test unitari e ai test.

Va bene, quindi ho quel metodo privato e unit test e l'analisi di copertura mi dice che c'è un divario, il mio metodo privato non è coperto da test. Adesso...

Cosa ottengo dal mantenerlo privato

Poiché il metodo è privato, l'unico modo per procedere è studiare il codice per scoprire come viene utilizzato tramite API non private. Tipicamente, un tale studio rivela che la ragione del divario è che nei test manca uno scenario di utilizzo particolare.

    void nonPrivateMethod(boolean condition) {
        if (condition) {
            privateMethod();
        }
        // other code...
    }

    // unit tests don't invoke nonPrivateMethod(true)
    //   => privateMethod isn't covered.

Per completezza, altri motivi (meno frequenti) per tali lacune di copertura potrebbero essere errori nella specifica / progettazione. Non mi immergerò in profondità qui, per semplificare le cose; basti dire che se si indebolisce la limitazione dell'accesso "solo per rendere testabile il metodo", si perderà la possibilità di apprendere che questi bug esistono affatto.

Bene, per correggere il divario, aggiungo un test unitario per lo scenario mancante, ripeto l'analisi della copertura e verifico che il divario è scomparso. Cosa ho adesso? Ho come nuovo test unitario per l'utilizzo specifico di API non private.

  1. Il nuovo test garantisce che il comportamento previsto per questo utilizzo non cambierà senza preavviso poiché se cambia, il test fallirà.

  2. Un lettore esterno può esaminare questo test e imparare come dovrebbe usare e comportarsi (qui, il lettore esterno include il mio sé futuro, poiché tendo a dimenticare il codice un mese o due dopo aver finito con esso).

  3. Il nuovo test è tollerante al refactoring (refactoring metodi privati? Scommetti!) Qualunque cosa io faccia privateMethod, voglio sempre testare nonPrivateMethod(true). Indipendentemente da ciò che faccio privateMethod, non sarà necessario modificare il test perché il metodo non viene richiamato direttamente.

Non male? Scommetti.

Cosa perdo dall'indebolimento della limitazione dell'accesso

Ora immagina che invece di sopra, ho semplicemente indebolito la limitazione dell'accesso. Salto lo studio del codice che utilizza il metodo e procedo direttamente con il test che invoca il mio exPrivateMethod. Grande? Non!

  1. Ottengo un test per l'utilizzo specifico dell'API non privata di cui sopra? No: non è stato effettuato alcun test per nonPrivateMethod(true)prima e non esiste un test del genere ora.

  2. I lettori esterni hanno la possibilità di comprendere meglio l'uso della classe? No. "- Hey, qual è lo scopo del metodo testato qui? - Dimenticalo, è strettamente per uso interno. - Oops."

  3. È tollerante al refactoring? Assolutamente no: qualunque cosa io cambi exPrivateMethod, probabilmente interromperà il test. Rinomina, unisci in qualche altro metodo, modifica gli argomenti e il test smetterà di compilare. Mal di testa? Scommetti!

Riassumendo , attenersi al metodo privato mi porta a un utile, affidabile miglioramento nei test unitari. Al contrario, l'indebolimento delle limitazioni di accesso "per testabilità" mi dà solo un codice di test oscuro, difficile da capire, che è inoltre a rischio permanente di essere infranto da qualsiasi refactoring minore; francamente ciò che ottengo sembra sospettosamente un debito tecnico .

</ Rant>


7
Non ho mai trovato convincente quell'argomento per testare metodi privati. Rifatti il ​​codice in metodi continuamente per ragioni che non hanno nulla a che fare con l'API pubblica di una classe ed è una cosa molto bella poter testare quei metodi in modo indipendente, senza coinvolgere altri codici. Questo è il motivo per cui hai effettuato il refactoring, giusto? Per ottenere un pezzo di funzionalità indipendente. Tale funzionalità dovrebbe essere verificabile anche in modo indipendente.
Robert Harvey,

La risposta di @RobertHarvey si è espansa con un rant affrontando questo. "I metodi privati ​​sono utili ai test unitari ..."
moscerino del

Capisco quello che stai dicendo sui metodi privati ​​e sulla copertura del codice, ma non stavo davvero sostenendo di renderli pubblici in modo da poterli testare. Stavo sostenendo di avere un modo per testarli indipendentemente, anche se sono privati. Puoi comunque avere i tuoi test pubblici che tocchino il metodo privato, se vuoi. E posso avere i miei test privati.
Robert Harvey,

@RobertHarvey Capisco. Immagino che si riduca allo stile di programmazione. In base alla quantità di metodi privati ​​che di solito produco e alla velocità con cui li refactoring, avere dei test per loro è semplicemente un lusso che non posso permettermi. L'API non privata è un'altra questione, tendo ad essere abbastanza riluttante e lento a modificarlo
moscerino

Forse sei più bravo a scrivere metodi che funzionano la prima volta di me. Per me, ho bisogno di testare il metodo per assicurarmi che funzioni prima di poter andare avanti, e doverlo testare indirettamente sparando una serie di altri metodi sarebbe goffo e imbarazzante.
Robert Harvey,

9

La ragione più probabile è: ha a che fare con la storia. L'antenato di Java, Oak, aveva solo tre livelli di accesso: privato, protetto, pubblico.

Tranne che private in Oak era l'equivalente del pacchetto private in Java. Puoi leggere la sezione 4.10 di Oak's Language Specification (sottolineatura mia):

Per impostazione predefinita, tutte le variabili e i metodi in una classe (inclusi i costruttori) sono privati. È possibile accedere alle variabili e ai metodi privati ​​solo tramite i metodi dichiarati nella classe e non dalle sue sottoclassi o da qualsiasi altra classe (ad eccezione delle classi nello stesso pacchetto ).

Quindi, a tuo avviso, l'accesso privato, come noto in Java, inizialmente non era presente. Ma quando hai più di un paio di classi in un pacchetto, non avere un privato come lo conosciamo ora porterebbe a un incubo di collisione di nomi (ad esempio, java.until.concurrent ha quasi 60 classi), motivo per cui probabilmente introdotto.

Tuttavia, la semantica di accesso al pacchetto predefinito (originariamente chiamata privata) non è stata modificata tra Oak e Java.


5

Credo che se l'accesso predefinito viene utilizzato correttamente, fornisce una migliore testabilità mantenendo l'incapsulamento. Inoltre, rende ridondante il modificatore di accesso privato.

Ci sono situazioni in cui si vorrebbero due classi separate che sono più strettamente legate che esporre tutto con il pubblico. Ciò ha idee simili di "amici" in C ++ in cui un'altra classe dichiarata come "amica" di un altro può accedere ai propri membri privati.

Se osservi le viscere di classi come BigInteger , troverai una serie di protezioni predefinite del pacchetto su campi e metodi (tutto ciò che è triangolo blu nell'elenco). Ciò consente ad altre classi nel pacchetto java.math di avere un accesso più ottimale alle loro viscere per operazioni specifiche (puoi trovare questi metodi chiamati in BigDecimal - BigDecimal è supportato da BigInteger e salva di nuovo la reimplementazione di BigInteger . Che questi sono a livello di pacchetto non è ' t un problema di protezione perché java.math è un pacchetto sigillato e non è possibile aggiungere altre classi ad esso.

D'altra parte, ci sono molte cose che sono davvero private, come dovrebbero essere. La maggior parte dei pacchetti non è sigillata. Senza privato, il tuo collega potrebbe inserire un'altra classe in quel pacchetto e accedere al campo (non più) privato e interrompere l'incapsulamento.

Da notare che i test unitari non erano qualcosa a cui si pensava quando venivano costruiti gli ambiti di protezione. JUnit (il framework di test XUnit per Java) non è stato concepito fino al 1997 (un racconto della storia può essere letto su http://www.martinfowler.com/bliki/Xunit.html ). Le versioni Alpha e Beta del JDK erano nel 1995 e JDK 1.0 nel 1996, anche se i livelli di protezione non erano stati inchiodati fino al JDK 1.0.2 (prima di ciò si poteva avere un private protectedlivello di protezione).

Alcuni sostengono che il valore predefinito non dovrebbe essere a livello di pacchetto, ma privato. Altri sostengono che non ci dovrebbe essere una protezione predefinita ma tutto dovrebbe essere esplicitamente dichiarato - alcuni follower di questo scriveranno codice come:

public class Foo {
    /* package */ int bar = 42;
    ...
}

Nota il commento lì.

Il vero motivo per cui la protezione a livello di pacchetto è l'impostazione predefinita è probabilmente perso nelle note di progettazione di Java (sto scavando e non riesco a trovare alcun motivo per cui , molte persone spiegano la differenza, ma nessuno dice "questo è il motivo ").

Quindi ci proverò. E questo è tutto: un'ipotesi.

Prima di tutto, le persone stavano ancora cercando di capire il design del linguaggio. Le lingue da allora hanno imparato dagli errori e dai successi di Java. Questo potrebbe essere elencato come un errore per non avere qualcosa che deve essere definito per tutti i campi.

  • I progettisti hanno visto la necessità occasionale di un rapporto "amico" di C ++.
  • I progettisti hanno voluto dichiarazioni esplicite per public, e privatepoiché questi hanno i maggiori impatti sull'accesso.

Pertanto, privato non è predefinito e bene, tutto il resto è andato a posto. L'ambito predefinito è stato lasciato come predefinito. Esso può essere stato il primo ambito che è stato creato e nell'interesse di una rigorosa compatibilità all'indietro con il vecchio codice, è stato lasciato in quel modo.

Come ho già detto, queste sono tutte ipotesi .


Ah, se solo la funzione amico potesse essere applicata ai membri su base individuale, quindi si potrebbe dire che voF someFunction () può essere visto solo dalla classe X ... ciò renderebbe davvero più forte l'incapsulamento!
Newlogic,

Oh aspetta, puoi farlo in C ++, ne abbiamo bisogno in Java!
Newlogic

2
@ user1037729 crea un accoppiamento più stretto tra le due classi: la classe con il metodo ora deve conoscere le altre classi che sono sue amiche. Questo tende ad andare contro la filosofia di progettazione fornita da Java. La serie di problemi risolvibili con gli amici ma non con la protezione a livello di pacchetto non è così ampia.

2

L'incapsulamento è un modo di dire "non devi pensarci".

                 Access Levels
Modifier    Class   Package  Subclass   World
public        Y        Y        Y        Y
protected     Y        Y        Y        N
no modifier   Y        Y        N        N
private       Y        N        N        N

Mettendoli in termini non tecnici.

  1. Pubblico: tutti devono pensarci.
  2. Protetto: impostazione predefinita e sottoclassi devono saperlo.
  3. Di default, se stai lavorando sul pacchetto, lo saprai (è proprio lì davanti ai tuoi occhi) potrebbe anche permetterti di avvantaggiarlo.
  4. Privato, devi solo saperlo se stai lavorando su questa classe.

Non penso che esista una lezione privata o protetta ...
Koray Tugay,

@KorayTugay: guarda docs.oracle.com/javase/tutorial/java/javaOO/innerclasses.html . La classe interna è privata. Leggi l'ultimo paragrafo.
jmoreno,

0

Non penso che si siano concentrati sui test, semplicemente perché i test non erano molto comuni all'epoca.

Quello che penso che volessero ottenere era l'incapsulamento a livello di pacchetto. Come sapete, una classe può avere metodi e campi interni ed esponendone solo attraverso membri pubblici. Allo stesso modo un pacchetto può avere classi, metodi e campi interni ed esporne solo una parte. Se la pensi in questo modo, un pacchetto ha un'implementazione interna in un insieme di classi, metodi e campi, insieme a un'interfaccia pubblica in un altro insieme (possibilmente parzialmente sovrapposto) di classi, metodi e campi.

I programmatori più esperti che conosco la pensano in questo modo. Prendono l'incapsulamento e lo applicano a un livello di astrazione superiore alla classe: un pacchetto o un componente, se lo desideri. Mi sembra ragionevole che i progettisti di Java fossero già così maturi nei loro progetti, considerando quanto Java regge ancora.


Hai ragione sul fatto che i test automatici non erano molto comuni laggiù. Ma è un design immaturo.
Tom Hawtin - tackline il
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.