Dlaczego Java ustawiła domyślny dostęp do pakietu?

26

Zadaję to pytanie, ponieważ uważam, że zrobili to z bardzo dobrego powodu i że większość ludzi nie używa go właściwie, dobrze z mojego dotychczasowego doświadczenia w branży. Ale jeśli moja teoria jest prawdziwa, to nie jestem pewien, dlaczego zawarli modyfikator dostępu prywatnego ...?

Uważam, że prawidłowe użycie domyślnego dostępu zapewnia większą testowalność przy zachowaniu enkapsulacji. I sprawia, że ​​modyfikator dostępu prywatnego jest zbędny.

Domyślnego modyfikatora dostępu można użyć, aby zapewnić ten sam efekt, używając unikalnego pakietu dla metod, które muszą być ukryte przed resztą świata, i robi to bez uszczerbku dla testowalności, ponieważ pakiety w folderze testowym z tym samym są w stanie uzyskać dostęp do wszystkich domyślnych metod zadeklarowanych w folderze źródłowym.

Uważam, że właśnie dlatego Java używa dostępu do pakietu jako „domyślnego”. Ale nie jestem pewien, dlaczego obejmowały one również dostęp prywatny, jestem pewien, że istnieje ważny przypadek użycia ...

newlogic
źródło
Istotne w odniesieniu do wcześniej omawianych prywatnych metod testowania jednostek; How-Do-You-Unit-Test-Private-Method . Odpowiedź; nie powinieneś
Richard Tingle

Odpowiedzi:

25

Myślę, że mieli dobry pomysł na to, co zrobiłby przeciętny programista. Przez przeciętnego programistę mam na myśli kogoś, kto nie jest naprawdę dobry w programowaniu, ale i tak dostaje pracę, ponieważ nie ma wystarczającej liczby dobrych i są one drogie.

Gdyby „upublicznili” dostęp do domyślnego, większość programistów nigdy nie zawracałaby sobie głowy korzystaniem z czegokolwiek innego. Rezultat byłby bardzo duży wszędzie kodu spaghetti . (Bo jeśli technicznie możesz dzwonić z dowolnego miejsca, po co zawracać sobie głowę logiką w ramach metody?)

Ustawienie domyślnego dostępu jako „prywatnego” byłoby tylko nieco lepsze. Większość początkujących programistów po prostu wymyśla (i dzieli się na swoich blogach) ogólną zasadę, że „musisz pisać wszędzie„ publicznie ”, a następnie narzekają, dlaczego Java jest tak zła, że ​​zmusza ich do pisania wszędzie„ publicznie ”. I produkują też dużo kodu spaghetti.

Dostęp do pakietu jest czymś, co pozwala programiście korzystać ze swoich niechlujnych technik programowania podczas pisania kodu w jednym pakiecie, ale następnie muszą ponownie rozważyć to przy tworzeniu większej liczby pakietów. Jest to kompromis między dobrymi praktykami biznesowymi a brzydką rzeczywistością. Na przykład: napisz kod spaghetti, jeśli nalegasz, ale zostaw brzydki bałagan w paczce; przynajmniej stworzyć lepsze interfejsy między pakietami.

Prawdopodobnie są też inne powody, ale nie doceniłbym tego.

Viliam Búr
źródło
14

Dostęp domyślny nie powoduje, że modyfikator dostępu prywatnego jest zbędny.

Stanowisko projektantów języka znajduje odzwierciedlenie w oficjalnym samouczku - Kontrolowanie dostępu do członków klasy i jest dość jasne (dla Twojej wygody, odpowiednie oświadczenie w cytacie pogrubione ):

Wskazówki dotyczące wyboru poziomu dostępu:

Jeśli inni programiści używają twojej klasy, chcesz mieć pewność, że nie wystąpią błędy wynikające z niewłaściwego użycia. Poziomy dostępu mogą ci w tym pomóc.

  • Użyj najbardziej restrykcyjnego poziomu dostępu, który ma sens dla konkretnego członka. Używaj prywatnego, chyba że masz dobry powód, aby tego nie robić.
  • Unikaj pól publicznych oprócz stałych. (Wiele przykładów w tym samouczku używa pól publicznych. Może to pomóc zwięźle zilustrować niektóre punkty, ale nie jest zalecane w przypadku kodu produkcyjnego.) Pola publiczne mają tendencję do łączenia cię z określoną implementacją i ograniczają twoją elastyczność w zmienianiu kodu.

Twoje odwołanie do testowalności jako uzasadnienia całkowitego usunięcia prywatnego modyfikatora jest błędne, o czym świadczą np. Odpowiedzi w New to TDD. Czy powinienem teraz unikać prywatnych metod?

Oczywiście możesz mieć metody prywatne i oczywiście możesz je przetestować.

Albo jest jakiś sposób na uruchomienie prywatnej metody, w którym to przypadku możesz to przetestować, albo nie ma sposobu na uruchomienie prywatnej, w takim przypadku: dlaczego, do cholery, próbujesz ją przetestować, po prostu usuń tę cholerną rzecz ...


Stanowisko projektantów języków na temat celu i korzystania z dostępu na poziomie pakietu zostało wyjaśnione w innym oficjalnym samouczku, Tworzenie i korzystanie z pakietów i nie ma nic wspólnego z pomysłem porzucenia prywatnych modyfikatorów (dla Twojej wygody odpowiednie oświadczenie w pogrubionej wycenie ) :

Powinieneś spakować te klasy i interfejs w pakiet z kilku powodów, w tym:

  • Ty i inni programiści możecie łatwo ustalić, że te typy są powiązane ...
  • Możesz zezwolić typom w pakiecie na nieograniczony dostęp do siebie, a jednocześnie ograniczyć dostęp dla typów spoza pakietu ...

<rant "Myślę, że słyszałem dość marudzenia. Chyba czas powiedzieć głośno i wyraźnie ...">

Metody prywatne są korzystne dla testów jednostkowych.

Uwaga poniżej zakłada, że ​​znasz zakres kodu . Jeśli nie, poświęć trochę czasu na naukę, ponieważ jest to bardzo przydatne dla osób zainteresowanych testowaniem jednostkowym i testowaniem w ogóle.

W porządku, więc mam tę prywatną metodę i testy jednostkowe, a analiza zasięgu mówi mi, że jest luka, moja prywatna metoda nie jest objęta testami. Teraz...

Co zyskuję, zachowując prywatność

Ponieważ metoda jest prywatna, jedynym sposobem na kontynuację jest przestudiowanie kodu, aby dowiedzieć się, jak jest on używany przez nieprywatny interfejs API. Zazwyczaj takie badanie ujawnia, że ​​przyczyną luki jest brak konkretnego scenariusza użycia w testach.

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

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

Ze względu na kompletność, innymi (rzadziej) przyczynami takich luk w zasięgu mogą być błędy w specyfikacji / projekcie. Nie zagłębię się tutaj głęboko, aby wszystko było proste; wystarczy powiedzieć, że jeśli osłabisz ograniczenie dostępu „tylko po to, aby metoda była testowalna”, stracisz szansę dowiedzenia się, że te błędy w ogóle istnieją.

W porządku, aby naprawić lukę, dodaję test jednostkowy na brakujący scenariusz, powtarzam analizę pokrycia i sprawdzam, czy luka zniknęła. Co mam teraz Mam jako nowy test jednostkowy do konkretnego użycia nieprywatnego API.

  1. Nowy test gwarantuje, że oczekiwane zachowanie dla tego użycia nie zmieni się bez powiadomienia, ponieważ jeśli się zmieni, test się nie powiedzie.

  2. Zewnętrzny czytelnik może przyjrzeć się temu testowi i dowiedzieć się, jak powinien on używać i zachowywać się (tutaj, zewnętrzny czytelnik obejmuje moje przyszłe ja, ponieważ zwykle zapominam o kodzie miesiąc lub dwa po tym, jak skończę z nim).

  3. Nowy test jest odporny na refaktoryzację (czy refaktoryzuję metody prywatne? Założycie się!) Cokolwiek zrobię privateMethod, zawsze będę chciał przetestować nonPrivateMethod(true). Bez względu na to, co zrobię privateMethod, nie będzie potrzeby modyfikowania testu, ponieważ metoda nie jest wywoływana bezpośrednio.

Nie jest zły? Stawiasz zakład

Co tracę z osłabienia ograniczenia dostępu

Teraz wyobraź sobie, że zamiast wyżej, po prostu osłabiam ograniczenie dostępu. Pomijam przestudiowanie kodu używającego tej metody i przechodzę od razu do testu, który mnie wywołuje exPrivateMethod. Świetny? Nie!

  1. Czy dostaję test na określone użycie nieprywatnego API wymienionego powyżej? Nie: wcześniej nie było testu nonPrivateMethod(true)i teraz nie ma takiego testu.

  2. Czy czytelnicy z zewnątrz mają szansę lepiej zrozumieć wykorzystanie klasy? Nie. "- Hej, jaki jest cel testowanej tutaj metody? - Zapomnij, to jest wyłącznie do użytku wewnętrznego. - Ups."

  3. Czy toleruje refaktoryzację? Nie ma mowy: cokolwiek się zmieni exPrivateMethod, prawdopodobnie przerwie test. Zmień nazwę, połącz się z inną metodą, zmień argumenty i test po prostu przestanie się kompilować. Bół głowy? Obstawiasz!

Podsumowując , trzymanie się metody prywatnej przynosi mi użyteczne, niezawodne ulepszenie w testach jednostkowych. W przeciwieństwie do tego, osłabienie ograniczeń dostępu „do testowalności” daje mi tylko niejasny, trudny do zrozumienia fragment kodu testowego, który dodatkowo jest narażony na stałe ryzyko złamania przez jakiekolwiek drobne refaktoryzacja; szczerze mówiąc, to, co dostaję, wygląda podejrzanie jak dług techniczny .

</rant>

komar
źródło
7
Nigdy nie znalazłem przekonującego argumentu do testowania metod prywatnych. Cały czas przekierowujesz kod na metody z powodów, które nie mają nic wspólnego z publicznym interfejsem API klasy, i bardzo miło jest móc testować te metody niezależnie, bez angażowania żadnego innego kodu. To jest powód, dla którego dokonałeś refaktoryzacji, prawda? Aby uzyskać niezależną funkcjonalność. Ta funkcjonalność powinna być również testowana niezależnie.
Robert Harvey,
Odpowiedź @RobertHarvey została poszerzona o odpowiedź na to pytanie. „Prywatne metody są korzystne dla testów jednostkowych ...”
komenda
Rozumiem, co mówisz o prywatnych metodach i pokryciu kodu, ale tak naprawdę nie opowiadałem się za upublicznieniem tych metod, abyś mógł je przetestować. Popierałem możliwość samodzielnego przetestowania ich, nawet jeśli są prywatne. Jeśli chcesz, możesz nadal mieć testy publiczne, które dotyczą metody prywatnej. I mogę dostać moje prywatne testy.
Robert Harvey,
@RobertHarvey Rozumiem. Zgadnij, że sprowadza się to do stylu kodowania. Przy ilości prywatnych metod, które zazwyczaj produkuję, i przy ich refaktoryzacji, testowanie ich jest po prostu luksusem, na który mnie nie stać. Nieprywatny interfejs API to inna sprawa, mam tendencję do dość niechętnego i powolnego modyfikowania go
gnat
Być może jesteś lepszy w pisaniu metod, które działają po raz pierwszy ode mnie. Dla mnie muszę przetestować metodę, aby upewnić się, że najpierw działa, zanim będę mógł przejść dalej, a testowanie jej pośrednio przez wypalenie szeregu innych metod byłoby niezręczne i niewygodne.
Robert Harvey,
9

Najbardziej prawdopodobnym powodem jest to, że ma to związek z historią. Przodek Javy, Oak, miał tylko trzy poziomy dostępu: prywatny, chroniony, publiczny.

Tyle że private w Oak było odpowiednikiem pakietu private w Javie. Możesz przeczytać rozdział 4.10 specyfikacji językowej Oaka (moje podkreślenie):

Domyślnie wszystkie zmienne i metody w klasie (w tym konstruktory) są prywatne. Dostęp do zmiennych prywatnych i metod można uzyskać tylko metodami zadeklarowanymi w klasie, a nie przez jej podklasy lub inne klasy ( z wyjątkiem klas w tym samym pakiecie ).

Tak więc, twoim zdaniem, prywatny dostęp, znany w Javie, początkowo nie istniał. Ale kiedy masz więcej niż kilka klas w pakiecie, nie posiadanie prywatnego, jak wiemy, doprowadziłoby to do koszmaru kolizji nazw (na przykład java.until.concurrent ma prawie 60 klas), prawdopodobnie dlatego przedstawił to.

Jednak semantyka domyślnego dostępu do pakietu (pierwotnie zwana prywatnym) nie została zmieniona między Oak i Java.

assylias
źródło
5

Uważam, że prawidłowe użycie domyślnego dostępu zapewnia większą testowalność przy zachowaniu enkapsulacji. I sprawia, że ​​modyfikator dostępu prywatnego jest zbędny.

Są sytuacje, w których chcielibyśmy mieć dwie oddzielne klasy, które są ściślej związane niż ujawnianie wszystkiego publicznie. Ma to podobne pomysły na „przyjaciół” w C ++, gdzie inna klasa, która jest zadeklarowana jako „przyjaciel” innej osoby, może uzyskać dostęp do swoich prywatnych członków.

Jeśli zajrzysz do wnętrza klas takich jak BigInteger , znajdziesz szereg domyślnej ochrony pakietów dla pól i metod (wszystko, co jest niebieskim trójkątem na liście). Pozwala to innym klasom w pakiecie java.math na bardziej optymalny dostęp do ich wewnętrznych funkcji dla określonych operacji (można znaleźć te metody nazywane w BigDecimal - BigDecimal jest wspierany przez BigInteger i zapisuje ponowne wdrożenie BigInteger ponownie . To, że są one na poziomie pakietu, nie jest ' Problem ochrony, ponieważ java.math jest zapieczętowanym pakietem i nie można do niego dodawać żadnych innych klas.

Z drugiej strony istnieje wiele rzeczy, które są rzeczywiście prywatne, tak jak powinny. Większość paczek nie jest zamknięta. Bez prywatnego twój współpracownik mógłby umieścić inną klasę w tym pakiecie i uzyskać dostęp do (już nie) prywatnego pola i przerwać enkapsulację.

Warto zauważyć, że testowanie jednostkowe nie było czymś, o czym myślano wstecz, gdy budowano lunety ochronne. JUnit (środowisko testowe XUnit dla Javy) powstało dopiero w 1997 r. (O jednym opowiadaniu można przeczytać na stronie http://www.martinfowler.com/bliki/Xunit.html ). Wersje JDK w wersji Alpha i Beta były w 1995 r., A JDK 1.0 w 1996 r., Ale poziomy ochrony tak naprawdę nie zostały obniżone aż do JDK 1.0.2 (wcześniej można było mieć private protectedpoziom ochrony).

Niektórzy twierdzą, że domyślnie nie powinien to być poziom pakietu, ale prywatny. Inni twierdzą, że nie powinna istnieć domyślna ochrona, ale wszystko powinno być wyraźnie określone - niektórzy z tych osób będą pisać kod, taki jak:

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

Zanotuj tam komentarz.

Prawdziwy powód, dla którego ochrona na poziomie pakietu jest domyślna, prawdopodobnie został utracony w notatkach projektowych Java (kopałem i nie mogę znaleźć żadnego powodu, dla którego artykuły, wiele osób wyjaśnia różnicę, ale nikt nie mówi „to jest powód „).

Więc zgadnę. I to wszystko - zgadnij.

Po pierwsze, ludzie wciąż próbowali rozgryźć język. Od tego czasu języki uczyły się na błędach i sukcesach Javy. Może to być wymienione jako błąd polegający na braku czegoś, co należy zdefiniować dla wszystkich pól.

  • Od czasu do czasu projektanci zauważyli potrzebę „przyjacielskiej” znajomości C ++.
  • Projektanci chcieli wyraźnych oświadczeń dla public, a privateponieważ mają one największy wpływ na dostęp.

Tak więc prywatność nie jest domyślna i dobrze, wszystko inne ułożyło się na swoim miejscu. Domyślny zakres pozostawiono jako domyślny. Być może był to pierwszy zakres, który został stworzony i w celu zachowania ścisłej kompatybilności wstecznej ze starszym kodem, tak zostało.

Jak już powiedziałem, są to zgadywanki .


źródło
Ach, gdyby tylko funkcja przyjaciela mogła być zastosowana do członków na zasadzie indywidualnej, można by powiedzieć, że void someFunction () można zobaczyć tylko w klasie X ... to naprawdę zaostrzyłoby hermetyzację!
newlogic
Och, czekaj, możesz to zrobić w C ++, potrzebujemy tego w Javie!
newlogic
2
@ user1037729 zapewnia ściślejsze połączenie między dwiema klasami - klasa z tą metodą musi teraz wiedzieć o innych klasach, które są jej przyjaciółmi. Jest to sprzeczne z filozofią projektowania, którą zapewnia Java. Zestaw problemów, które można rozwiązać ze znajomymi, ale nie ochrona na poziomie pakietu, nie jest tak duży.
2

Enkapsulacja to sposób na powiedzenie „nie musisz o tym myśleć”.

                 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

Ujęcie ich w nietechnicznych terminach.

  1. Publiczne: każdy musi o tym pomyśleć.
  2. Zabezpieczony: domyślny i podklasy muszą o tym wiedzieć.
  3. Domyślnie, jeśli pracujesz nad pakietem, dowiesz się o nim (jest tuż przed twoimi oczami), a także pozwoli ci go wykorzystać.
  4. Prywatne, musisz o tym wiedzieć tylko, jeśli pracujesz nad tą klasą.
jmoreno
źródło
Nie sądzę, aby istniała coś takiego jak klasa prywatna lub klasa chroniona.
Koray Tugay
@KorayTugay: spójrz na docs.oracle.com/javase/tutorial/java/javaOO/innerclasses.html . Klasa wewnętrzna jest prywatna. Przeczytaj ostatni akapit.
jmoreno
0

Nie sądzę, żeby skupili się na testowaniu, po prostu dlatego, że testowanie nie było wtedy zbyt powszechne.

Myślę, że chcieli osiągnąć enkapsulację na poziomie pakietu. Klasa może, jak wiesz, mieć wewnętrzne metody i pola i ujawniać tylko niektóre z nich przez członków publicznych. W ten sam sposób pakiet może mieć wewnętrzne klasy, metody i pola i ujawniać tylko niektóre z nich. Jeśli tak myślisz, pakiet ma wewnętrzną implementację w zestawie klas, metod i pól, wraz z interfejsem publicznym w innym (być może częściowo pokrywającym się) zestawie klas, metod i pól.

Najbardziej doświadczeni koderzy, których znam, myślą w ten sposób. Biorą enkapsulację i stosują ją na poziomie abstrakcji wyższym niż klasa: pakiet lub komponent, jeśli chcesz. Wydaje mi się rozsądne, że projektanci Javy byli już tak dojrzali w swoich projektach, biorąc pod uwagę, jak dobrze Java wciąż wytrzymuje.

Alexander Torstling
źródło
Masz rację, że zautomatyzowane testy nie były tam zbyt częste. Ale to niedojrzały projekt.
Tom Hawtin - tackline