Java neden paket erişimini varsayılan yaptı?


26

Bu soruyu soruyorum çünkü bunu çok iyi bir sebeple yaptıklarına ve çoğu insanın bunu endüstride edindiğim deneyime göre de doğru kullanmadıklarına inanıyorum. Ama teorim doğruysa, neden özel erişim değiştiriciyi dahil ettiklerini bilmiyorum ...?

Eğer varsayılan erişim düzgün kullanılırsa, kapsüllemeyi korurken gelişmiş test edilebilirlik sağladığını düşünüyorum. Ayrıca özel erişim değiştiricisini gereksiz kılar.

Varsayılan erişim değiştiricisi, dünyanın geri kalanından gizlenmesi gereken yöntemler için benzersiz bir paket kullanarak aynı etkiyi sağlamak için kullanılabilir ve bunu bir test klasöründeki paketler gibi test edilebilirlikten ödün vermeden yapar. Kaynak klasörde belirtilen tüm varsayılan yöntemlere erişebilir.

Java'nın paket erişimini 'varsayılan' olarak kullanmasının nedeni budur. Ancak neden özel erişim de içerdiklerinden emin değilim, geçerli bir kullanım davası olduğundan eminim ...



Daha önce tartışılan ünite özel yöntem testi ile ilgili; nasıl yapılır-birim-test-özel-yöntemler . Cevap; yapmamalısın
Richard Tingle

Yanıtlar:


25

Sanırım ortalama bir programcının ne yapacağı hakkında iyi bir fikirleri vardı. Ve ortalama bir programcı ile kastedilen, programlamada gerçekten iyi olmayan, ancak işi yine de alan, çünkü yeterince iyi olanlar yok ve pahalılar.

Eğer "public" (varsayılan) öntanımlıya erişselerdi, çoğu programcı başka bir şeyi kullanmaktan asla rahatsız olmazdı. Sonuç, her yerde çok sayıda spagetti kodu olacaktır . (Teknik olarak herhangi bir yerden herhangi bir şeyi arayabilmenize izin veriliyorsa, neden bir mantığın bir yöntem içinde kapsanması için uğraşmıyorsunuz?)

"Özel" varsayılan erişim yapmak, sadece biraz daha iyi olurdu. Yeni başlayan programcıların çoğu, basitçe “her yere 'halka açık' yazmanız gerekecek” bir kural kuralı icat eder (ve bloglarında paylaşırlar) ve Java'nın neden her yere “herkese açık” yazmaya zorladıklarından şikayet ederlerdi. Ayrıca çok sayıda spagetti kodu da üretiyorlardı.

Paket erişimi, programcıların tek bir paket içinde kod yazarken özensiz programlama tekniklerini kullanmalarına izin veren bir şeydir, ancak daha sonra daha fazla paket yaparken yeniden gözden geçirmeleri gerekir. İyi iş uygulamaları ile çirkin gerçeklik arasında bir uzlaşmadır. Gibi: ısrar ediyorsanız spagetti kodunu yazın, ancak lütfen çirkin pisliği paketin içinde bırakın; en azından paketler arasında daha hoş arayüzler yarat.

Muhtemelen başka sebepler de var, ama bunu küçümsemem.


14

Varsayılan erişim özel erişim değiştiricisini gereksiz kılmaz.

Bunun üzerine dil tasarımcıları resmi öğreticiye yansıtılır - Bir Sınıfın Üyelerine Erişimi Kontrol Etme ve oldukça açıktır (kolaylık sağlamak için, kalın alıntıdan ilgili ifade ):

Erişim Seviyesi Seçmekle İlgili İpuçları:

Diğer programcılar sınıfınızı kullanıyorsa, yanlış kullanımdan kaynaklanan hataların oluşmamasını sağlamak istersiniz. Erişim seviyeleri bunu yapmanıza yardımcı olabilir.

  • Belirli bir üye için anlamlı olan en kısıtlayıcı erişim seviyesini kullanın. Olmaması için iyi bir nedeniniz yoksa, özel kullanın.
  • Sabitler dışındaki ortak alanlardan kaçının. (Eğitimdeki örneklerin çoğu ortak alanları kullanır. Bu, bazı noktaları tam olarak göstermeye yardımcı olabilir, ancak üretim kodu için önerilmez.) Genel alanlar, sizi belirli bir uygulamaya bağlama ve kodunuzu değiştirme esnekliğinizi sınırlama eğilimindedir.

Tamamen özel bırakma özelliğinin düşürülmesinin gerekçesi olarak test edilebilirliğe başvurmanız , örneğin TDD’deki Yeni’deki cevaplarda da belirtildiği gibi yanlıştır . Şimdi özel yöntemlerden kaçınmalı mıyım?

Elbette özel yöntemlere sahip olabilirsiniz ve elbette bunları test edebilirsiniz.

Ya orada bazı bu şekilde test edebilirsiniz ya yoktur ki bu durumda vadede özel yöntem almanın yolu, hiçbir çalıştırmak için özel almanın yolu, bu durumda: neden halt sadece, bunu test etmek çalışıyoruz lanet şeyi sil ...


Dil tasarımcılarının paket düzeyinde erişimin amacı ve kullanımı konusundaki konumu, başka bir resmi derste, Paketlerin Oluşturulması ve Kullanılması bölümünde açıklanmıştır ve özel değiştiricileri bırakma fikri ile ortak bir ilgisi yoktur (rahatınız için, teklifinize koyulan ifade ) :

Bu sınıfları ve arayüzü, aşağıdakiler de dahil olmak üzere çeşitli nedenlerle bir pakette paketlemelisiniz:

  • Siz ve diğer programcılar bu türlerin birbiriyle ilişkili olduğunu kolayca belirleyebilirsiniz ...
  • Paket içindeki türlerin birbirine sınırsız erişime sahip olmasına izin verebilirsiniz ancak paket dışındaki türlerin erişimini kısıtlayabilirsiniz ...

rant "Sanırım yeterince sızlanan duydum. Yüksek sesle ve net söylemenin zamanı geldiğini tahmin ediyorum ...">

Özel yöntemler, birim testlerinde faydalıdır.

Aşağıdaki not, kod kapsamı hakkında bilgi sahibi olduğunuzu varsaymaktadır . Olmazsa, öğrenmek için zaman ayırın, ünite testi ve hiç test yapmak isteyenler için oldukça kullanışlıdır.

Tamam, bu yüzden özel yöntem ve ünite testlerini aldım ve bir boşluk olduğunu söyleyen kapsama analizi, özel yöntemim testlerin kapsamına girmedi. Şimdi ...

Özel tutmaktan ne kazanırım?

Yöntem özel olduğundan, ilerlemenin tek yolu, özel olmayan API üzerinden nasıl kullanıldığını öğrenmek için kodu incelemektir. Tipik olarak, böyle bir çalışma, boşluğun nedeninin testlerde belirli kullanım senaryosunun eksik olduğunu ortaya koymaktadır.

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

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

Tamamlanma açısından, bu kapsama boşluklarının diğer (daha az sıklık) nedenleri, spesifikasyon / tasarımdaki hatalar olabilir. Her şeyi basit tutmak için burada derinlere dalmayacağım; Erişim sınırlamasını "sadece metodu test edilebilir hale getirmek için" zayıflatırsanız, bu hataların var olduğunu öğrenme şansını kaçırırsınız.

Tamam, açığı düzeltmek için eksik senaryo için bir birim testi ekliyorum, kapsam analizini tekrarlıyorum ve aralığın gittiğini doğruladım. Şimdi neyim var? Özel olmayan API'nin özel kullanımı için yeni bir test olarak aldım.

  1. Yeni test, bu kullanım için beklenen davranışın önceden haber verilmeksizin değişmemesini sağlar, çünkü değişirse test başarısız olur.

  2. Bir dış okuyucu bu teste bakabilir ve nasıl kullanması ve davranması gerektiğini öğrenebilir (burada, dış okuyucu, kendimden sonraki bir veya iki ay sonra kodu unutmaya meyilli olduğum için gelecekteki benliğimi içerir).

  3. Yeni test yeniden yapılanmaya toleranslıdır (özel yöntemleri yeniden değerlendirebilir miyim? Bahse girerim!) Ne yaparsam yapayım privateMethod, her zaman test etmek isteyeceğim nonPrivateMethod(true). Ne yaparsam yapayım privateMethod, testi direkt olarak başlatılmadığı için testi değiştirmeye gerek kalmayacak.

Fena değil? Emin ol.

Zayıflama erişim sınırlandırmasından ne kaybederim?

Şimdi, yukarıdakilerin yerine erişim sınırlandırmasını zayıflattığımı hayal edin. Yöntemi kullanan kodun çalışmasını atladım ve doğrudan benim başlatan teste devam ettim exPrivateMethod. Harika? Değil!

  1. Yukarıda belirtilen özel olmayan API’nin özel kullanımı için bir test mi kazanıyorum? Hayır: Daha nonPrivateMethod(true)önce hiçbir test yapılmamıştı ve şimdi böyle bir test yok.

  2. Dışarıdaki okuyucular, sınıfın kullanımını daha iyi anlama şansı elde ediyor mu? Hayır. "- Hey, burada test edilen yöntemin amacı nedir? - Unut gitsin, kesinlikle iç kullanım içindir. - Hata!"

  3. Yeniden düzenlemeye toleranslı mı? Hiçbir şekilde: neyi değiştirirsem değiştireyim exPrivateMethod, muhtemelen testi kıracak. Yeniden adlandırın, başka bir yönteme birleştirin, argümanları değiştirin ve test derlemeyi bırakacaktır. Baş ağrısı? Emin ol!

Özetle , özel yöntemlere bağlı kalmak bana ünite testlerinde faydalı, güvenilir bir geliştirme getiriyor. Buna karşılık, "test edilebilirlik için" erişim kısıtlamalarının zayıflatılması, bana yalnızca, küçük bir yeniden düzenleme işleminden dolayı kalıcı olarak kırılma riski altında olan, anlaşılması zor bir test kodu parçası; Açıkçası ne alırım şüpheli teknik borç gibi görünüyor .

</ Rant>


7
Özel metotları sınamak için bu argümanı hiçbir zaman çekici bulmadım. Bir sınıfın genel API'si ile ilgisi olmayan nedenlerden dolayı, her zaman metotlara yeniden kod yazıyorsunuz ve bu metotları bağımsız olarak, başka bir kod içermeden test edebilmek çok hoş bir şey . Refactoring nedeni budur, değil mi? Bir parça bağımsız işlevsellik elde etmek için. Bu işlevsellik de bağımsız olarak test edilebilir olmalıdır.
Robert Harvey,

@RobertHarvey cevabı bu hitap ile bir rantla genişledi. "Özel yöntemler, birim testlerine faydalıdır ..."
gnat

Özel yöntemler ve kod kapsamı hakkında ne söylediğinizi anlıyorum, ancak bu yöntemleri herkese açık hale getirmeyi savunmuyordum, böylece bunları test edebilirsiniz. Özel olsalar bile, onları bağımsız olarak test etmenin bir yolunu savunuyordum. İsterseniz, özel testlere dokunan açık testlerinizi de yapabilirsiniz. Ve özel sınavlarımı alabilirim.
Robert Harvey,

@RobertHarvey görüyorum. Kodlama tarzı aşağı kaynar sanırım. Genelde ürettiğim özel yöntemlerin miktarında ve bunları yeniden azaltma oranım altında, onlar için testler yapmak göze alamayacağım bir lüks. Özel olmayan API başka bir konudur, oldukça isteksiz ve değişiklik yapma konusunda yavaş olma eğilimindeyim
gnat

Belki de ilk defa benden daha işe yarayan yöntemler yazmakta daha iyisindir. Benim için, devam etmeden önce ilk önce çalıştığından emin olmak için yöntemi denemeye ihtiyacım var ve dolaylı olarak test etmek zorunda kalıyorum bir dizi başka yöntemi ateşleyerek beceriksiz ve garip olurdu.
Robert Harvey,

9

En olası sebep şudur: tarihle ilgisi var. Java'nın atası Oak, yalnızca üç erişim seviyesine sahipti: özel, korumalı, genel.

Bunun dışında Oak’daki özel, Java’daki özel paketin eşdeğeri idi. Oak'ın Dil Belirtiminin 4.10 bölümünü okuyabilirsiniz (vurgu madeni):

Varsayılan olarak, bir sınıftaki tüm değişkenler ve yöntemler (yapıcılar dahil) özeldir. Özel değişkenler ve yöntemlere yalnızca sınıfta bildirilen yöntemlerle erişilebilir, alt sınıfları veya diğer sınıflar ( aynı paketteki sınıflar hariç) ile erişilebilir .

Demek istediğim, Java’da bilinen özel erişim aslında orada değildi. Ancak, bir pakette birkaç dersten daha fazlası varken, bildiğimiz kadar özel olmamak, ad çarpışmasının kabusuna yol açacaktır (örneğin, java.until.concurrent 60 sınıfa yakındır), bu yüzden muhtemelen tanıttım.

Ancak, varsayılan paket erişimi (başlangıçta özel olarak adlandırılır) anlambilimi, Oak ve Java arasında değişmedi.


5

Eğer varsayılan erişim düzgün kullanılırsa, kapsüllemeyi korurken gelişmiş test edilebilirlik sağladığını düşünüyorum. Ayrıca özel erişim değiştiricisini gereksiz kılar.

İnsanın her şeyi halka açıklamaktan daha sıkı bağlı iki ayrı sınıf isteyeceği durumlar vardır. Bu, C ++ 'ta benzer bir' arkadaş 'fikrine sahiptir; burada başka birinin' arkadaşı 'olarak ilan edilen başka bir sınıf, özel üyelerine erişebilir.

BigInteger gibi sınıfların içindekilerine bakarsanız , alanlar ve yöntemler üzerinde bir dizi paket varsayılan koruma bulacaksınız (listedeki mavi üçgen olan her şey). Bu, java.math paketindeki diğer sınıfların belirli işlemler için kendi iç kısımlarına daha fazla erişebilmelerini sağlar ( BigDecimal - BigDecimal olarak adlandırılan bu yöntemleri bulabilirsiniz - BigDecimal, BigInteger tarafından desteklenir ve yeniden BigInteger uygulamasının tekrar uygulanmasını önler . t Koruma için bir sorun çünkü java.math mühürlü bir pakettir ve buna başka bir sınıf eklenemez.

Öte yandan, olması gerektiği gibi gerçekten özel olan birçok şey var. Çoğu paket mühürlü değil. Özel olmadan, iş arkadaşınız o pakete başka bir sınıf koyabilir ve (artık) özel alana erişebilir ve enkapsülasyonu kırabilir.

Unutmayın, birim sınama, koruma kapsamları oluşturulurken geriye dönük bir şey değildi. JUnit (Java için XUnit test çerçevesi) 1997 yılına kadar tasarlanmamıştı (bunun hikayesini anlatan biri http://www.martinfowler.com/bliki/Xunit.html adresinde okunabilir ). JDK'nın Alpha ve Beta sürümleri 1995’te idi ve JDK 1.0 1996’daydı, ancak koruma seviyeleri JDK 1.0.2'ye kadar gerçekten çürütülmemişti (bundan önce bir private protectedkoruma seviyesine sahip olabilirsiniz ).

Bazıları varsayılanın paket düzeyinde değil, özel olması gerektiğini savunuyordu. Diğerleri, varsayılan bir koruma olmaması gerektiğini, ancak her şeyin açıkça belirtilmesi gerektiğini savunuyor - bunun bazı takipçileri aşağıdaki gibi bir kod yazacak:

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

Buradaki yorumu not edin.

Paket düzeyi korumanın varsayılan olmasının gerçek nedeni, muhtemelen Java'nın tasarım notlarında kayboluyor (kazıyorum ve neden herhangi bir yazı bulamıyorum , aradaki farkı açıklayan birçok insan var, ancak hiçbiri "neden ").

Bu yüzden bir tahminde bulunacağım. Ve hepsi bu kadar - bir tahmin.

Öncelikle, insanlar hala dil tasarımını çözmeye çalışıyorlardı. O zamandan beri diller Java'nın hatalarını ve başarılarını öğrendi. Bu, tüm alanlar için tanımlanması gereken bir şey olmaması için bir hata olarak listelenebilir .

  • Tasarımcılar ara sıra C ++ 'arkadaş' ilişkisine ihtiyaç duyuyorlardı.
  • Tasarımcılar için açık ifadeleri istedik publicve privatebu şekilde erişime büyük etkileri vardır.

Böylece, özel varsayılan ve iyi değil, diğer her şey yerine düştü. Varsayılan kapsam, varsayılan olarak kaldı. Bu olabilir olmuş ilk oluşturulduğu ve daha eski kodu ile titiz geriye dönük uyumluluk yararına, bu şekilde bırakıldı kapsamı.

Dediğim gibi, bunların hepsi bir tahmin .


Ah, eğer sadece arkadaşlık özelliği üyelere bireysel olarak uygulanabilseydi, bazı void fonksiyonlarının () sadece X sınıfı tarafından görülebildiğini söyleyebilirsiniz, bu gerçekten enkapsülasyonu sıkılaştırır!
newlogic

Bekle bunu C ++ ile yapabilirsin, Java'da buna ihtiyacımız var!
newlogic

2
@ user1037729, iki sınıf arasında daha sıkı bir bağlantı sağlar - metodun bulunduğu sınıfın artık arkadaşları olan diğer sınıfları bilmesi gerekir. Bu, Java'nın sağladığı tasarım felsefesine aykırı olma eğilimindedir. Arkadaşlarla çözülebilecek ancak paket düzeyinde koruma sağlamayan sorun kümesi o kadar büyük değil .

2

Kapsülleme, "bunun hakkında düşünmeniz gerekmez" demenin bir yoludur.

                 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

Bunları teknik olmayan terimlere koymak.

  1. Halk: Herkesin düşünmesi gerekiyor.
  2. Korumalı: varsayılan ve alt sınıflar bunu bilmek zorunda.
  3. Varsayılan olarak, eğer paket üzerinde çalışıyorsanız, (gözünüzün hemen önündedir) hakkında bir şey bileceksiniz ki, onun avantajından da yararlanabilirsiniz.
  4. Özel, sadece bu sınıf üzerinde çalışıyorsanız, bunu bilmeniz gerekir.

Özel bir sınıf ya da korumalı bir sınıf olduğunu sanmıyorum ..
Koray Tugay

@KorayTugay: docs.oracle.com/javase/tutorial/java/javaOO/innerclasses.html adresine bakın . İç sınıf özeldir. Son paragrafı oku.
jmoreno

0

Teste odaklandıklarını sanmıyorum, çünkü test o zamanlar çok yaygın değildi.

Yapmak istediklerini düşündüğüm şey, paket düzeyinde kapsülleme idi. Bir sınıf bildiğiniz gibi içsel yöntemlere ve alanlara sahip olabilir ve sadece bazılarını ortak üyeler aracılığıyla ortaya çıkarabilir. Aynı şekilde bir paket iç sınıflara, metotlara ve alanlara sahip olabilir ve sadece bir kısmını ortaya çıkarabilir. Bu şekilde düşünürseniz, bir paketin bir sınıf, yöntem ve alan kümesinde içsel bir uygulaması ve bir başka (muhtemelen kısmen örtüşen) sınıf, yöntem ve alan kümesindeki ortak bir arabirim vardır.

Bildiğim en deneyimli kodlayıcılar böyle düşünüyor. Kapsülleme yaparlar ve bunu sınıftan daha yüksek bir soyutlama seviyesine uygularlar: bir paket ya da isterseniz bir bileşen. Java tasarımcılarının, Java'nın ne kadar iyi kaldığını göz önünde bulundurarak, tasarımlarında zaten bu kadar olgun olduklarını düşündüğüm için makul görünüyor.


Otomatik testlerin orada çok yaygın olmadığı konusunda haklısın. Ama olgunlaşmamış bir tasarım.
Tom Hawtin -
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.