Belirtilen özellikler
Spongee API’sinin büyük bir kısmı, nesne erişimcileri üzerinde Java’nın Optional`sistemini kullanır, ancak daha önce hiç ``Optional` kullanmadıysanız biraz zorlanabilirsiniz. *”Çünkü API nesnesinden bir şeyler üretirken hep daha fazlasını yapmak mı gerekir?” Sorusunu hayal edebilirsiniz. *
Bu bölüm, Optional
ın kısa bir özetini verir ve Sponge API boyunca kullanıldığını belirtir.
Biraz eskiye gidelim ve erişimcilerin - özellikle de “getters” - ``Optional``kullanmadan nasıl çalıştıklarına göz atalım.
1. Bahsedilen Nullable Sözleşmeleri ve neden bahsedildiği
Diyelim ki, getFoo()``yöntemiyle ``Entity
adında basit bir API Nesnemiz var ve bu nesne, ``Foo``yu döndürüyor.
Daha önceden, eklentimiz getter
işlevini kullanarak Foo
yı şu şekilde alıp kullanabilirdi:
public void someEventHandler(Entity someEntity) {
Foo entityFoo = someEntity.getFoo();
entityFoo.bar();
}
Sorun çıktığında - API aşamasında - yöntemin null
e döndürülebileceğinin (veya döndürülemeyeceğinin) konusunda``getFoo`` yönteminde * bahsedilen* bir sözleşmeye bağlı kalmak zorundayız. Bu bahsedilen sözleşme iki yoldan biriyle tanımlanabilir:
** Javadoc’da ** - bu, yöntem için javadoc’a erişen eklenti yazarına açık olmayabilir. Yapılan iş çok faydalı değil
** Null olabilecek ek açıklamaları ** - IDE veya derleyici ek açıklamaları işlemek için güvenilir bir araç gerekir, çünkü genel itibarla kullanmak ideal değildir.
getFoo()
yönteminin sözleşmenin bir parçası olarak null döndürdüğünü varsayalım. Burada``entityFoo`` değeri boşsa, NullPointerException
ile sonuçlanabileceği için yukarıdaki kodun güvensiz olduğu anlamına gelir.
public void someEventHandler(Entity someEntity) {
Foo entityFoo = someEntity.getFoo();
entityFoo.bar();
}
Eklenti yazarımızın getFoo
yönteminin geçersiz niteliğini araştırdığını ve sorunun null denetimi ile düzeltilmeye karar verdiğini varsayalım. Bir sabite Foo
tanımladığını varsayarsak, sonuçta ortaya çıkan kod şöyle görünür:
public void someEventHandler(Entity someEntity) {
Foo entityFoo = someEntity.getFoo();
if (entityFoo == null) {
entityFoo = MyPlugin.DEFAULT_FOO;
}
entityFoo.bar();
}
Bu örnekte, eklenti yazarı, yöntemin null döndürebileceğinin ve bunun yerine kullanılabilecek varsayılan bir Foo
örneği ile bir sabitin mevcut olduğunun farkındadır. Elbette eklenti yalnızca aramayı tamamen bozabilir veya başka bir yerden Foo yı almaya çalışabilir. Burada önemli olan konu, basit durumlarda bile boşlukların işlenmesinin spagetti koduna oldukça hızlı bir şekilde yol açabilmesi ve ilk etapta boş bir denetimin gerekli olup olmadığını kontrol etmek için metnin sözleşmesini açıkça bakılması eklenti yazarına güvenmesidir.
Ancak tek dezavantajı bu değildir. API’yi uzun vadede ele alalım ve yazarın eklentiyi yazdığı sırada, javadoc yöntemini ziyaret ettiğini ve yöntemin hiçbir zaman null döndürmeyeceğini garanti ettiğini varsayalım (her`Entity`te daima bir Foo
mevcut). Mükemmel! Artık kontrol gerekli değil!
Ancak şimdi oyunun sonraki bir sürümünde, oyun geliştiricilerin Foo
kavramını kaldırdığını veya reddettiğini varsayalım. API yazarları buna göre API’yi günceller ve bundan böyle getFoo () `` yöntemi ** ``null
döndürebilir ve bunu javadoc yöntemine yazar. Şimdi bir sorun var: yöntem sözleşmesini ilk kez kod yazarken kontrol eden çalışkan eklenti yazarları farkında olmadan metodu hatalı bir şekilde işlemekteler: hiçbir boşluk yerine getFoo
den döndürülen Foo
işlevini kullanan herhangi bir kod yoksa bu NPE’yi artıracaktır.
Dolayısıyla bahsedilen nullable sözleşmelere izin vermenin bizi seçmek için oldukça kötü çözümleri olan bir seçime zorladığını görebiliriz:
Eklenti yazarları, ** ** yöntemlerin boşa dönebileceğini ve buna göre savunucuyu kodlandırabileceğini varsayabilir, bunun zaten spagetti koduna oldukça seri yol açtığını görmüştük.
API yazarları, yalnızca önceki soruyu daha da kötüleştiren eklenti yazarının sorununu boş işleme yapmaya çalışmak için, her API yönteminde örtük bir null sözleşmeyi tanımlayabilir.
API yazarları, tanımladıkları tüm bahsedilen null sözleşmelerini, ileriye dönük olarak değiştirilmeyeceğini iddia edebilirler. Bu, bir oyunun temel oyundan kaldırılmasının gerektiği sonucuna varılacağı anlamına gelir:
Bazı istisnaları saymazsak kolay sayılabilecek bir kod başka yerdeki bir kodu tetikletebilir ve zayıf NPD ortaya çıkartır. Bunu teşhis etmek zor olacaktır
“Sahte” bir nesne veya geçersiz değer döndürürseniz, eklenti kodu çalışmaya devam edeceğini, ancak API geliştiricilerini zorlayıcı unsurlar barındıracağını, kullanımdan kaldırılan her özellikte daha sahte nesnelerin oluşturulması gerekeceği anlamına gelir. Bu durum, kısa süre içinde büyük bir API yığınının, artık kullanılmayan API bölümlerinin dolu duruma gelmesine neden olabilir.
Belirtilen API son derece homojen olmayan bir temel üzerinde olduğundan, daha belirgin hale getirmek için * bahsedilen * null sözleşmelere eklenmiş bazı önemli sorunlara karşı açık olmalıdır. Neyse ki daha iyi bir yol var:
2. İsteğe bağlı ve Açık Anlaşılabilir Sözleşme
Yukarıda belirtildiği üzere, Minecraft API’leri zor durumdalar. Önünde sonunda, anlaşılır bir kararlılık sahibi olmayan bir platform (oyun) üzerine, mantıklı ölçüde anlaşılır kararlılık sahibi bir platform sunmak durumundalar. Bu yüzden, herhangi bir Minecraft API’si, oyunun herhangi bir kısmının herhangi bir zamanda, herhangi bir sebeple ve akla gelebilecek herhangi bir şekilde değişebileceğine dair tam farkındalıkla tasarlanmalı - buna hep birlikte kaldırılma seçeneği de dahil!
Bu dalgalanma yukarıda açıklanan boşaltılabilen yöntem sözleşmeleriyle ilgili soruna neden olmaktadır.
İsteğe bağlı, gizli sözleşmeler*i, *açık olanlarla değiştirerek yukarıdaki problemleri çözer. API, asla duyurmaz, “burada nesneniz, kthxbai” yerine “burada, istediğiniz nesneyi içerebilecek veya içermeyebilecek bir kutu olan, ymmv”’e olan erişimcileri sunar.
null
’un açık bir sözleşme olma ihtimalini kodlayarak, * null denetleme* kavramını incelikli var olmayabilir kavramı ile değiştiriyoruz. Ayrıca bu sözleşmeyi günden itibaren şart koşuyoruz.
Yani bunun anlamı ne?
Özetle, artık yazarların null
’un geri gelme ihtimali hakkında endişelenmelerine gerek yok. Buna karşılık belirli bir nesnenin var olmama ihtimali eklenti kodunun dokusuna göre kodlanır hale gelir. Bu null denetimleri sürekli olarak uygulanırken aynı düzeyde güvenlik içerebilir, ancak bunu yapmak kodun okunabilmesi ve düzenli olması açısından çok daha iyi hale gelir.
Bunun nedenini görmek için, yukarıdaki örneğe bir göz atalım: İsteğe bağlı<Foo>
’ya geri dönme yöntemini getFoo
kullanmaya dönüştürelim bunun yerine:
public void someEventHandler(Entity someEntity) {
Optional<Foo> entityFoo = someEntity.getFoo();
if (entityFoo.isPresent()) {
entityFoo.get().bar();
}
}
Bu örnek standart bir sıfırlama denetimi gibi görünmesine rağmen, İsteğe bağlı
kullanımı aslında aynı miktar kodda biraz daha fazla bilgi taşır. Örneğin, yukarıdaki kodu okuyan birinin yöntem sözleşmesini kontrol etmesine gerek yoktur, yöntemin bir değeri geri getiremediği ve değerin yokluğunun işlenmesinin açık ve net olduğu açıktır.
Ne olmuş? Bu davadaki açık sözleşmemiz sıfırlama denetimi ile aynı miktarda kod temel alır - alıcı tarafından sözleşmeye dayalı olarak zorunlu da olsa. “Büyük bir mesele,” sen, “ne olmuş?” diyorsun
İsteğe Bağlı kutulama geleneksel olarak sıfırlama denetiminin daha değişik yönlerinden bazılarını almamıza ve bunları daha iyi hale getirmemize izin verir: aşağıdaki kodu takip edin:
public void someEventHandler(Entity someEntity) {
Foo entityFoo = someEntity.getFoo().orElse(MyPlugin.DEFAULT_FOO);
entityFoo.bar();
}
Hatta kalın! Yukarıdaki örnekte yer alan sıkıcı sıfırlama denetimini ve varsayılan atamayı tek bir kod satırı ile değiştirdik mi? Evet gerçekten yaptık. Aslında, basit kullanım örnekleri için, atamadan bile vazgeçebiliriz:
public void someEventHandler(Entity someEntity) {
someEntity.getFoo().orElse(MyPlugin.DEFAULT_FOO).bar();
}
MyPlugin.DEFAULT_FOO
her zaman kullanılabilir olması şartıyla son derece güvenlidir.
Örneği takip ederek iki birimi göz önünde bulundurun, boşaltılabilen gizli bir sözleşme yapmak için ilk birimden Foo
’yu kullanmanızı istiyoruz, eğer kullanılabilir değilse ikinci birim``den ``Foo
’yu kullanın ya da ikisi de kullanılabilir değilse varsayılanımıza geri dönün:
public void someEventHandler(Entity someEntity, Entity entity2) {
Foo entityFoo = someEntity.getFoo();
if (entityFoo == null) {
entityFoo = entity2.getFoo();
}
if (entityFoo == null) {
entityFoo = MyPlugin.DEFAULT_FOO;
}
entityFoo.bar();
}
İsteğe bağlı
’yı kullanarak bunu çok daha temiz şekilde kodlayabiliriz:
public void someEventHandler(Entity someEntity, Entity entity2) {
someEntity.getFoo().orElse(entity2.getFoo().orElse(MyPlugin.DEFAULT_FOO)).bar();
}
Bu İsteğe bağlı
buzdağının yalnızca görünen kısmı. İsteğe Bağlı'' Java 8'de ayrıca ``Tüketici
ve Sağlayan
arabirimlerini destekler, lambalar bulunmayan yük devretme için kullanılabilir. Bunların kullanım örneklerini Kullanım örnekleri sayfasında bulabilirsiniz.
Not
Geçersiz referanslardan kaçınmanın arkasındaki gerekçelere ilişkin bir başka açıklamayı Guava: Sıfırlama Açıklamasının Kullanılması ve Kullanılmaması Kriteri <https://github.com/google/guava/wiki/UsingAndAvoidingNullExplained/> _ ‘da bulabilirsiniz. Bağlantılı makalede belirtilen guava İsteğe bağlı
sınıfının, javaya ait java.util.Optional
’dan farklı olduğuna ve burada kullanılanlardan farklı metod isimlerine sahip olduğuna dikkat edin.