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

25

Sto facendo questa domanda perché credo che lo abbiano fatto per una buona ragione e che la maggior parte della gente non la usi correttamente, comunque dalla mia esperienza nel settore fino ad ora. Ma se la mia teoria è vera, non sono sicuro del motivo per cui hanno incluso il modificatore di accesso privato ...?

Credo che se l'accesso predefinito viene utilizzato correttamente, fornisce una testabilità avanzata pur 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 unico per i metodi che devono essere nascosti dal resto del mondo, e lo fa senza compromettere la testabilità, come i pacchetti in una cartella di test, con gli stessi sono in grado di accedere a tutti i metodi predefiniti dichiarati in una cartella sorgente.

Credo che questo sia il motivo per cui Java usa l'accesso al pacchetto come 'predefinito'. Ma non sono sicuro del perché abbiano incluso anche l'accesso privato, sono sicuro che c'è un caso d'uso valido ...

    
posta newlogic 02.12.2013 - 14:32
fonte

6 risposte

24

Credo che avessero una buona idea di cosa avrebbe fatto 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 costosi.

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

Rendere "privato" l'accesso predefinito sarebbe solo leggermente migliore. Molti programmatori principianti semplicemente inventerebbero (e condivideranno sui loro blog) una regola empirica secondo cui "è necessario scrivere 'pubblico' ovunque", e quindi si lamenterebbero del perché Java sia così grave da costringerli a scrivere "pubblico" ovunque. E avrebbero anche prodotto un sacco di codice spaghetti.

L'accesso al pacchetto è qualcosa che permette ai programmatori di usare le loro tecniche di programmazione sciatta mentre scrivono il codice all'interno di un pacchetto, ma poi devono riconsiderarlo quando creano più pacchetti. È un compromesso tra buone pratiche commerciali e la brutta realtà. Ad esempio: scrivi un codice per spaghetti, se insisti, ma per favore lascia il brutto pasticcio all'interno del pacco; almeno crea delle interfacce migliori tra i pacchetti.

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

    
risposta data 02.12.2013 - 16:51
fonte
13

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

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

Tips on Choosing an Access Level:

If other programmers use your class, you want to ensure that errors from misuse cannot happen. Access levels can help you do this.

  • Use the most restrictive access level that makes sense for a particular member. Use private unless you have a good reason not to.
  • Avoid public fields except for constants. (Many of the examples in the tutorial use public fields. This may help to illustrate some points concisely, but is not recommended for production code.) Public fields tend to link you to a particular implementation and limit your flexibility in changing your code.

Il tuo ricorso alla testabilità come giustificazione per il completamente eliminazione del modificatore privato è errato, come evidenziato ad esempio dalle risposte in Novità di TDD. Dovrei evitare i metodi privati ora?

Of course you can have private methods, and of course you can test them.

Either there is some way to get the private method to run, in which case you can test it that way, or there is no way to get the private to run, in which case: why the heck are you trying to test it, just delete the damn thing...

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

You should bundle these classes and the interface in a package for several reasons, including the following:

  • You and other programmers can easily determine that these types are related...
  • You can allow types within the package to have unrestricted access to one another yet still restrict access for types outside the package...

< rant "Penso di aver sentito abbastanza lamentarsi. Indovina che è ora di dire strong e chiaro ..." >

I metodi privati sono utili per il test dell'unità.

La seguente nota presuppone che tu abbia familiarità con la >> copertura del codice . In caso contrario, dedica del tempo all'apprendimento, poiché è abbastanza utile per coloro che sono interessati al test delle unità e ai test.

Bene, quindi ho ottenuto il metodo privato e i test delle unità, e l'analisi della copertura mi dice che c'è un divario, il mio metodo privato non è coperto dai test. Ora ...

Che guadagno dal mantenerlo privato

Poiché il metodo è privato, l'unico modo per procedere è studiare il codice per sapere come viene utilizzato tramite API non privata. In genere, uno studio di questo tipo rivela che la ragione del divario è che nei test non è presente 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 motivi di completezza, altre (meno frequenti) ragioni per tali lacune nella copertura potrebbero essere bug nella specifica / progettazione. Non mi tufferò in profondità qui, per mantenere le cose semplici; basti dire che se si indebolisce la limitazione di accesso "solo per rendere testabile il metodo", perderai la possibilità di apprendere che questi errori esistono del tutto.

Bene, per aggiustare il divario, aggiungo un test unitario per lo scenario mancante, ripeto l'analisi della copertura e verifica che non ci sia spazio. Che cosa ho ora? Ho un nuovo test unitario per l'utilizzo specifico dell'API non privata.

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

  2. Un lettore esterno può esaminare questo test e impara come dovrebbe essere usato e comportarsi (qui, il lettore esterno include il mio sé futuro, dato che tendo a dimenticare il codice un mese o due dopo che ho finito con esso).

  3. Il nuovo test è tollerante al refactoring (faccio a refactoring metodi privati? si scommette!) Qualunque cosa io faccia a privateMethod , voglio sempre testare nonPrivateMethod(true) . Non importa cosa faccio a privateMethod , non sarà necessario modificare il test perché il metodo non viene invocato direttamente.

Non male? Ci puoi scommettere.

Che cosa posso perdere indebolendo la limitazione di accesso

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

  1. Ricerco un test per l'utilizzo specifico di API non private sopra menzionate? No: non c'era nessun test per nonPrivateMethod(true) prima, e non esiste questo test ora.

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

  3. È tollerante per il refactoring? In nessun modo: qualunque cosa cambi in exPrivateMethod , probabilmente interromperà il test. Rinominare, unire in un altro metodo, cambiare argomento e test smetterà di compilare. Mal di testa? Hai scommesso!

Riassumendo , attenersi al metodo privato mi offre un miglioramento utile e affidabile nei test unitari. Al contrario, l'indebolimento dei limiti di accesso "per testabilità" mi fornisce solo un pezzo di codice di prova oscuro, difficile da capire, che è inoltre a rischio permanente di essere interrotto da qualsiasi refactoring minore; sinceramente, quello che ottengo sembra sospetto come >> debito tecnico .

< / rant >

    
risposta data 02.12.2013 - 17:10
fonte
9

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

Tranne che il privato di Oak era l'equivalente del pacchetto privato in Java. Puoi leggere la sezione 4.10 di Specifica della lingua di Oak (enfasi mia):

By default all variables and methods in a class (including constructors) are private. Private variables and methods can be accessed only by methods declared in the class, and not by its subclasses or any other classes (except for classes in the same package).

Quindi, al punto, l'accesso privato, come noto in Java, non era originariamente lì. Ma quando hai più di un corso in un pacchetto, non avere privato come lo conosciamo ora porterebbe a un incubo di collisione di nomi (ad esempio, java.until.concurrent ha quasi 60 classi), che è probabilmente il motivo per cui l'ho introdotto.

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

    
risposta data 03.12.2013 - 00:12
fonte
4

I believe that if default access is properly used it provides enhanced testability whilst maintaining encapsulation. And it also renders the private access modifier redundant.

Ci sono situazioni in cui uno vorrebbe due classi separate che sono più strettamente vincolate di esporre tutto al pubblico. Questo ha idee simili di "amici" in C ++ in cui un'altra classe che viene dichiarata come "amica" di un'altra persona può accedere ai propri membri privati.

Se osservi l'interno di classi come BigInteger troverai una serie di protezione predefinita del pacchetto su campi e metodi (tutto ciò che è un triangolo blu nella lista). Ciò consente ad altre classi nel pacchetto java.math di avere un accesso ottimale alle loro interiora per operazioni specifiche (puoi trovare questi metodi chiamati BigDecimal - BigDecimal è supportato da un BigInteger e salva il reimplementing di BigInteger di nuovo . il livello del pacchetto non è un problema di protezione perché java.math è un pacchetto sigillato e non è possibile aggiungervi altre classi.

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

Da notare, il test unitario non era qualcosa a cui si pensava quando erano stati costruiti gli ambiti di protezione. JUnit (il framework di testing XUnit per Java) non è stato concepito fino al 1997 (un racconto della storia di questo può essere letto su link ). Le versioni Alpha e Beta del JDK erano nel 1995 e JDK 1.0 era nel 1996, anche se i livelli di protezione non sono stati azzerati fino a JDK 1.0.2 (prima di questo si poteva avere un livello di protezione private protected ).

Alcuni sostengono che il valore predefinito non dovrebbe essere il livello del 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ì.

La vera ragione per cui la protezione a livello di pacchetto è l'impostazione predefinita è probabilmente persa per le note di progettazione di Java (ho scavato e non riesco a trovare alcun perché articoli, molte persone che spiegano il differenza, ma nessuno dice "questa è la ragione").

Quindi farò una supposizione. E questo è tutto - una supposizione.

Prima di tutto, le persone stavano ancora cercando di capire il design della lingua. 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 i campi tutti .

  • I progettisti hanno visto occasionalmente la necessità di una relazione "amica" di C ++.
  • I progettisti hanno richiesto istruzioni esplicite per public e private in quanto hanno il maggiore impatto sull'accesso.

Quindi, il privato non è predefinito e bene, tutto il resto è andato a posto. L'ambito predefinito è stato lasciato come predefinito. È può essere stato l'ambito primo che è stato creato e nell'interesse della rigorosa retrocompatibilità con il codice precedente, è stato lasciato in questo modo.

Come ho detto, questi sono tutti guess .

    
risposta data 02.12.2013 - 17:22
fonte
2

L'incapsulamento è un modo per 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

Inserendo questi termini in termini non tecnici.

  1. Pubblico: tutti devono pensarci.
  2. Protetto: le sottoclassi e le impostazioni predefinite devono esserne a conoscenza.
  3. Predefinito, se stai lavorando sul pacchetto, lo saprai (lo è proprio lì davanti ai tuoi occhi) potrebbe anche permetterti di prendere vantaggio di esso.
  4. Privato, devi solo sapere se stai lavorando su questo corso.
risposta data 29.12.2013 - 04:32
fonte
0

Non penso che si siano concentrati sui test, semplicemente perché i test non erano molto comuni a quei tempi.

Quello che penso che volessero ottenere era l'incapsulamento a livello di pacchetto. Una classe può, come sapete, avere metodi e campi interni e solo esporne alcuni attraverso membri pubblici. Allo stesso modo un pacchetto può avere classi, metodi e campi interni e solo esporne alcuni. Se si pensa 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 set (eventualmente parzialmente sovrapposto) di classi, metodi e campi.

I programmatori più esperti che conosco 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 bene ancora regge Java.

    
risposta data 12.12.2013 - 18:05
fonte