Ders 13 / 13
İstisna Tasarımı
Aynı iş üç sözleşmeyle kurulur: denetlenen istisna, denetlenmeyen istisna ve sonuç tipi. Ölçüm çağıran tarafta derleyicinin zorladığı karar noktası sayısını ve boş depoda dikkatsiz çağıranın ne aldığını gösterir; üç sözleşmenin hiçbiri kusuru yok etmez, yalnız göründüğü yeri değiştirir.
İçindekiler
Önceki dört ders istisnayı verili aldı: fırlatıldı, bildirildi, yakalandı, bastırıldı. Hiçbirinde bir olayın istisnayla mı bildirileceği sorulmadı. Aynı iş üç ayrı sözleşmeyle yazılabilir — denetlenen istisna, denetlenmeyen istisna, ya da hiç istisna kullanmayıp sonucu bir tiple döndürmek.
Bu ders üçünü aynı iş üzerinde kurar: boş olabilen bir depodan öğe almak. Kursun sorusu burada tasarım sorusuna dönüşür, ama ölçü aynı kalır. Sözleşmeyi seçen, çağıranın ne yapmak zorunda olduğunu seçer; ve zorlamayı yapan taraf her zaman derleyicidir.
Derleyicinin Zorladığı Karar Noktası
- GN27 — Üç sözleşme aynı depo üzerinde, aynı üç katmanlı çağrı zinciriyle ölçülür:
icişi yapar,ortaonu çağırır,disdaortayı. - GN28 — Sekiz deneme, bildirimin üç noktadan hangilerine konduğu kümesidir. Sonuç tipi için konulacak bir bildirim olmadığından o satırın alt küme sayımı boş bırakılır.
// Olcek.java — ortak ölçüm çekirdeği: verilen kaynağı derler
import java.io.PrintWriter;
import java.io.Writer;
import java.lang.classfile.*;
import java.nio.file.*;
import java.util.spi.ToolProvider;
class Olcek {
static ClassModel derle(String kaynak, String sinif) throws Exception {
Path d = Files.createTempDirectory("olcek");
Path k = d.resolve(sinif + ".java");
Files.writeString(k, kaynak);
PrintWriter bos = new PrintWriter(Writer.nullWriter());
if (ToolProvider.findFirst("javac").orElseThrow()
.run(bos, bos, "-d", d.toString(), k.toString()) != 0)
throw new IllegalStateException("derlenmedi: " + sinif);
return ClassFile.of().parse(d.resolve(sinif + ".class"));
}
}
// Zorlama.java — üç sözleşme çağıran zincirinde kaç noktayı zorluyor
public class Zorlama {
static final String TIPLER = """
class Olay extends Exception { Olay(String m) { super(m); } }
class SessizOlay extends RuntimeException { SessizOlay(String m) { super(m); } }
record Sonuc(String deger, String hata) { }
class Depo {
String denetlenen() throws Olay { throw new Olay("bos"); }
String denetlenmeyen() { throw new SessizOlay("bos"); }
Sonuc sonuc() { return new Sonuc(null, "bos"); }
}
""";
static String zincir(String tip, String cagri, String[] bildirim) {
return TIPLER + "class C {\n"
+ " static " + tip + " ic(Depo d) " + bildirim[0]
+ " { return d." + cagri + "(); }\n"
+ " static " + tip + " orta(Depo d) " + bildirim[1] + " { return ic(d); }\n"
+ " static " + tip + " dis(Depo d) " + bildirim[2] + " { return orta(d); }\n}\n";
}
static boolean derlenirMi(String kaynak) {
try {
Olcek.derle(kaynak, "C");
return true;
} catch (Exception e) {
return false;
}
}
static void olc(String ad, String tip, String cagri, String bildirim) {
boolean dokunulmamis = derlenirMi(zincir(tip, cagri, new String[] {"", "", ""}));
int enAz = -1, gecen = 0;
for (int m = 0; m < 8; m++) {
String[] b = new String[3];
int nokta = 0;
for (int i = 0; i < 3; i++) {
boolean var = (m >> i & 1) == 1;
b[i] = var ? bildirim : "";
if (var) nokta++;
}
if (derlenirMi(zincir(tip, cagri, b))) {
gecen++;
if (enAz < 0 || nokta < enAz) enAz = nokta;
}
}
System.out.printf("%-16s %-20s %-10d %s%n", ad,
dokunulmamis ? "derlendi" : "derlenmedi", enAz,
bildirim.isEmpty() ? "-" : gecen + " / 8");
}
public static void main(String[] args) {
System.out.printf("%-16s %-20s %-10s %s%n",
"sozlesme", "dokunulmamis zincir", "en az nokta", "derlenen alt kume");
olc("denetlenen", "String", "denetlenen", "throws Olay");
olc("denetlenmeyen", "String", "denetlenmeyen", "throws SessizOlay");
olc("sonuc tipi", "Sonuc", "sonuc", "");
}
}
sozlesme dokunulmamis zincir en az nokta derlenen alt kume denetlenen derlenmedi 3 1 / 8 denetlenmeyen derlendi 0 8 / 8 sonuc tipi derlendi 0 -
İlk satır denetlenen istisnanın çağırana yazdığı bedeli sayıyor. Hiçbir yere dokunulmamış zincir derlenmiyor, ve zinciri derlenir kılan en küçük bildirim kümesinin büyüklüğü üç — yani her katman. Sekiz alt kümeden yalnız biri derleniyor: hepsinin bildirdiği küme. Zorunluluk aradaki bir katmanda durdurulamaz, çünkü bir katman bildirmezse onu çağıran katman istisnanın varlığını hiç öğrenemez.
İkinci ve üçüncü satırlar sıfır veriyor. Denetlenmeyen istisnada zincire hiç dokunulmadan derleniyor; sekiz alt kümenin sekizi de geçerli, yani bildirim tümüyle isteğe bağlı. Sonuç tipinde ise konulacak bir bildirim bile yok.
Buradan ölçülmüş bir karşılaştırma çıkıyor. Denetlenen istisna, hata yolunun varlığını zincirin her katmanına yazdırır; bu bir belgelendirmedir ve derleyici tarafından uygulanır. Diğer iki sözleşmede hata yolu ara katmanların hiçbirinde görünmez. Ne var ki zorlanan şey yalnız bildirmektir, işlemek değil — ve bir sonraki ölçüm bunun ne anlama geldiğini gösteriyor.
Aynı İş, Üç Sözleşme
- GN29 — Her sözleşme için iki çağıran yazılır: derleyicinin kabul ettiği en kısa yazım ile hata yolunu gerçekten işleyen yazım. İkisi de aynı boş depoyla ve aynı dolu depoyla çalıştırılır.
- GN30 — Çağıranın sonucundan yalnız dönen değer ya da çıkan istisnanın sınıf adı basılır.
// Sozlesme.java — aynı iş üç sözleşmeyle, dikkatsiz ve dikkatli çağıranla
import java.util.function.Function;
class Olay extends Exception {
Olay(String m) { super(m); }
}
class SessizOlay extends RuntimeException {
SessizOlay(String m) { super(m); }
}
record Sonuc(String deger, String hata) { }
class Depo {
private final String oge;
Depo(String oge) { this.oge = oge; }
String denetlenen() throws Olay {
if (oge == null) throw new Olay("bos depo");
return oge;
}
String denetlenmeyen() {
if (oge == null) throw new SessizOlay("bos depo");
return oge;
}
Sonuc sonuc() {
return oge == null ? new Sonuc(null, "bos depo") : new Sonuc(oge, null);
}
}
public class Sozlesme {
static String dikkatsizDenetlenen(Depo d) {
try {
return d.denetlenen();
} catch (Olay e) {
return "";
}
}
static String dikkatliDenetlenen(Depo d) {
try {
return d.denetlenen();
} catch (Olay e) {
return "yedek (" + e.getMessage() + ")";
}
}
static String dikkatliDenetlenmeyen(Depo d) {
try {
return d.denetlenmeyen();
} catch (SessizOlay e) {
return "yedek (" + e.getMessage() + ")";
}
}
static String dikkatliSonuc(Depo d) {
Sonuc s = d.sonuc();
return s.hata() == null ? s.deger() : "yedek (" + s.hata() + ")";
}
static String calistir(Function<Depo, String> f, Depo d) {
try {
return "deger \"" + f.apply(d) + "\"";
} catch (RuntimeException e) {
return "istisna " + e.getClass().getSimpleName();
}
}
static void satir(String ad, Function<Depo, String> dikkatsiz, Function<Depo, String> dikkatli) {
Depo bos = new Depo(null), dolu = new Depo("kuzey");
System.out.printf("%-16s %-26s %-26s %s%n", ad,
calistir(dikkatsiz, bos), calistir(dikkatli, bos), calistir(dikkatsiz, dolu));
}
public static void main(String[] args) {
System.out.printf("%-16s %-26s %-26s %s%n",
"sozlesme", "dikkatsiz / bos depo", "dikkatli / bos depo", "dikkatsiz / dolu depo");
satir("denetlenen", Sozlesme::dikkatsizDenetlenen, Sozlesme::dikkatliDenetlenen);
satir("denetlenmeyen", d -> d.denetlenmeyen(), Sozlesme::dikkatliDenetlenmeyen);
satir("sonuc tipi", d -> String.valueOf(d.sonuc().deger()), Sozlesme::dikkatliSonuc);
}
}
sozlesme dikkatsiz / bos depo dikkatli / bos depo dikkatsiz / dolu depo denetlenen deger "" deger "yedek (bos depo)" deger "kuzey" denetlenmeyen istisna SessizOlay deger "yedek (bos depo)" deger "kuzey" sonuc tipi deger "null" deger "yedek (bos depo)" deger "kuzey"
İlk sütun üç ayrı kusur biçimi veriyor ve üçü de aynı boş depodan çıkıyor.
Denetlenen sözleşmede dikkatsiz çağıran boş bir dizgi döndürüyor. Derleyici bir yakalama yazmaya zorladı, ama yazılan yakalamanın içine ne konacağını sormadı; boş bir gövde zorunluluğu tümüyle karşılıyor. Denetlenen istisnanın güvencesi hata yolunun görülmesidir, işlenmesi değil.
Denetlenmeyen sözleşmede dikkatsiz çağıran çalışma zamanında düşüyor; kusur burada en
gürültülü biçimini alıyor. Sonuç tipinde ise ne derleme zamanında durduruluyor ne de çalışma
zamanında düşüyor; null metnini bir değer olarak döndürüyor. Üç biçim arasında en
sessiz olanı budur, çünkü hata yolu bir istisna değil, sıradan bir veri hâline gelmiştir.
İkinci sütun üç sözleşmenin ortak noktasını yazıyor: hata yolu gerçekten işlendiğinde üçü de aynı sonucu veriyor. Üçüncü sütun ise ayrımın nerede yaşadığını gösteriyor — depo dolu olduğunda üç sözleşme ayırt edilemez. Sözleşme seçimi doğru yolda hiçbir şeyi değiştirmez; tamamı hata yoluna ilişkin bir karardır.
Kusur Yok Olmaz, Yer Değiştirir
Bu dersin sınırlayıcı ölçümü iki tablonun birlikte okunmasıdır ve üç sözleşmenin hiçbirini üstün çıkarmaz.
Kusur tektir: boş bir depodan öğe istenmiştir. Üç sözleşme bu kusuru ortadan kaldırmıyor, yalnız görüneceği yeri seçiyor. Denetlenen istisna onu çağıranın derlemesine taşıyor — üç katman da hata yolunu yazmak zorunda, ama yazdıklarının doğru olması zorunlu değil. Denetlenmeyen istisna onu çalışma zamanına taşıyor; kaynakta hiçbir katman kirlenmiyor, buna karşılık hangi çağrının düşebileceği kaynağa bakarak görülemiyor. Sonuç tipi onu veri katmanına taşıyor; hiçbir yerde düşmüyor, ama yanlış değer akmayı sürdürüyor.
Üç yerin üçünde de bedeli ödeyen ayrı taraflardır: birincisinde yazan, ikincisinde çalıştıran, üçüncüsünde sonucu okuyan. Bu kursun ilk dersinden beri sürüp gelen ölçü burada son biçimini alıyor — bir kararın nerede verildiği, kusurun nerede görüneceğini belirler ve hiçbir seçim kusuru yok etmez.
Özet
- Denetlenen istisna çağrı zincirinin her katmanında bildirim zorunlu kılar: sekiz alt kümeden yalnız biri derlenir ve en küçük bildirim kümesinin büyüklüğü üçtür.
- Denetlenmeyen istisna ile sonuç tipi hiçbir noktayı zorlamaz; zincire dokunulmadan derlenir ve hata yolu ara katmanların kaynağında görünmez.
- Zorlanan şey bildirmektir, işlemek değil: dikkatsiz çağıran boş bir yakalama yazarak zorunluluğu karşılar ve boş bir değer döndürür.
- Aynı boş depo üç ayrı kusur biçimi üretir: boş değer, çalışma zamanı istisnası, ve bir veri
hâline gelmiş
null. Hata yolu işlendiğinde üç sözleşme aynı sonucu verir; depo dolu olduğunda ayırt bile edilemezler. - Sınırlayıcı ölçüm: üç sözleşmenin hiçbiri kusuru yok etmez. Kusur tektir ve sözleşme yalnız onun görüneceği yeri seçer — derleme, fırlatma ya da okuma.
Kurs Kapanışı
Kurs tek bir soruyla açıldı: bir ad iki tip taşıdığında, kararı hangisi verir? On üç ders bu soruyu on üç ayrı dilsel karara sordu ve yanıtı her seferinde çalıştırılmış bir ölçümden aldı. Toplandığında ortaya tek bir kural çıkıyor: çağrılan davranış çalışma zamanı tipine, seçilen ad bildirilen tipe bakar — ve iki taraf arasındaki sınır sözdiziminde görünmez.
| Ders | Ölçülen karar | Kararı veren taraf | Sınırlayıcı ölçüm |
|---|---|---|---|
| Sınıf, Nesne ve Yapıcı | yapıcı zincirinden çağrılan yeniden tanımlı yöntem | çalışma zamanı tipi | özel ve final yöntemde tuzak doğmaz, üçü aynı komuta derlenir |
| Erişim Belirteçleri | görünürlük: 16 çiftin 10’u derlenir | bildirilen tip (derleme zamanı) | üye çalışma zamanında yerinde durur, ayrımı yalnız bir bayrak taşır |
| Kalıtım ve Yeniden Tanımlama | sekiz kararın dördü ve dördü | dördü çalışma zamanı, dördü bildirilen tip | imza denk düşmediğinde yeniden tanımlama doğmaz, karar taraf değiştirir |
| Soyut Sınıflar ve Arayüzler | aynı yeteneğin sekiz kuruluşu, üçünde derleme durur | derleyici, tarafı sormadan | durum taşıyan bir yetenek arayüzle kurulamaz |
| Statik Üyeler ve Başlatma Blokları | statik yöntem seçimi ve sınıf düzeyi durum | bildirilen tip | iki taraf çakıştığında ayrım gözlenemez |
final Anahtar Sözcüğü |
yeniden atama, yeniden tanımlama, genişletme kısıtları | bildirilen tip (derleme zamanı) | final referans nesneyi dondurmaz |
| İç ve Yerel Sınıflar | üretilen sınıf dosyaları ve gizli alanlar | derleyici, kaynakta yazılmadan | statik iç içe sınıf gizli alan taşımaz |
| Kayıtlar ve Numaralandırmalar | üretilen üyelerin hangi tarafta yanıt verdiği | çalışma zamanı tipi | kaydın koruması sığdır, dizi bileşeni dışarıdan değişir |
| Genel Tipler | tip parametresinin çalışma zamanında bıraktığı | bildirilen tip, tamamen | imza özniteliği kalır: silinen bilgi değil, denetimdir |
| Sınırlı Joker Karakterler | dört kap biçimi × iki işlem, sekizin dördü | bildirilen tip, tamamen | dördünden de Object okunur, dördüne de boş değer yazılır |
| İstisna Hiyerarşisi | denetlenen ayrımının zorladığı: dokuz yazımın biri reddedilir | yazımı bildirilen tip, dalı çalışma zamanı tipi | sanal makine bildirimi denetlemez |
| Kaynakla Deneme | kapatma sırası ve bastırılan istisna | derleyici, bildirilen tipe bakarak | bastırma yönü tektir, kapatma gövdedekini bastırmaz |
| İstisna Tasarımı | üç sözleşmenin zorladığı karar noktası: 3, 0, 0 | bildirilen tip (derleme zamanı) | üç sözleşme de kusuru yok etmez, yerini değiştirir |
Tablonun üçüncü sütunu kursun yöntemidir; dördüncü sütun ise her dersin kendi tezini
sınırladığı yerdir. İkisi birlikte okunduğunda kursun asıl iddiası görünüyor: fark
sözdiziminde görünmez. d.ad() ile d.etiket iki
karakterlik bir farkla yazılır ve ayrı taraflardan yanıt alır; Depo<Ust> ile Depo<String>
ayrı yazılır ve tek bir sınıf olur; throws bildirimi kaynakta durur ve çalışma zamanında
denetlenmez.
İkinci okuma genelliklerden geliyor ve son beş dersin ortak dersidir. Bir denetim derleme zamanına taşındığında ortadan kalkmaz, yalnız yer değiştirir; ve o denetimi atlatan bir yol açıldığında kusur, üretildiği yerde değil, çok sonra okunduğu yerde görünür. Ham tiple yazılan bir değer okuyan satırda düşer, kaynakla deneme yazılmadığında istisna kapatma satırında kaybolur, sözleşme yanlış seçildiğinde kusur veri hâline gelir.
Bu kursta sınıflar hep yalnız çalıştı: bir depo, bir üst sınıf, bir alt sınıf. Sıradaki kurs olan Standart Kütüphane ve Akışlar aynı tipleri koleksiyonların ve akışların içine koyar. Orada tip parametresi tek bir depoda değil, birbirine zincirlenen işlemlerde taşınır; ve “kararı hangi taraf verdi” sorusu yeniden sorulabilir hâle gelir, çünkü bir akışın her halkası kendi bildirilen tipini taşır.
İlerlemeni kaydetmek ve not almak için Giriş yap
Notlarım
Not almak için giriş yapmalısın.