Diğerleri neden erken atıldığını oldukça iyi özetledi . Bunun yerine neden geç kalan kısmı yakalamak istediğime odaklanmama izin verin , ki bu da zevkime uygun bir açıklama görmedim.
Peki neden istisnalar?
İlk etapta istisnaların neden olduğu konusunda bir karışıklık var gibi görünüyor. Burada büyük sırrı paylaşmama izin verin: istisnaların ve istisnaların ele alınmasının nedeni ... ÖZET .
Böyle bir kod gördün mü:
static int divide(int dividend, int divisor) throws DivideByZeroException {
if (divisor == 0)
throw new DivideByZeroException(); // that's a checked exception indeed
return dividend / divisor;
}
static void doDivide() {
int a = readInt();
int b = readInt();
try {
int res = divide(a, b);
System.out.println(res);
} catch (DivideByZeroException e) {
// checked exception... I'm forced to handle it!
System.out.println("Nah, can't divide by zero. Try again.");
}
}
İstisnalar böyle kullanılmamalıdır. Yukarıdaki gibi kodlar gerçek hayatta var, ama bunlar daha çok sapma ve gerçekten istisnadır (pun). Bölünmenin tanımı, örneğin, saf matematikte bile, koşulludur: giriş alanını sınırlamak için her zaman istisnai sıfır durumuyla başa çıkmak zorunda olan "arayan kod" dur. Çirkin Arayan için her zaman acıdır. Yine de, bu gibi durumlar için yapılacak iş şekli doğal yoldur:
static int divide(int dividend, int divisor) {
// throws unchecked ArithmeticException for 0 divisor
return dividend / divisor;
}
static void doDivide() {
int a = readInt();
int b = readInt();
if (b != 0) {
int res = divide(a, b);
System.out.println(res);
} else {
System.out.println("Nah, can't divide by zero. Try again.");
}
}
Alternatif olarak, aşağıdaki gibi OOP tarzında tam bir komando gidebilirsiniz:
static class Division {
final int dividend;
final int divisor;
private Division(int dividend, int divisor) {
this.dividend = dividend;
this.divisor = divisor;
}
public boolean check() {
return divisor != 0;
}
public int eval() {
return dividend / divisor;
}
public static Division with(int dividend, int divisor) {
return new Division(dividend, divisor);
}
}
static void doDivide() {
int a = readInt();
int b = readInt();
Division d = Division.with(a, b);
if (d.check()) {
int res = d.eval();
System.out.println(res);
} else {
System.out.println("Nah, can't divide by zero. Try again.");
}
}
Gördüğünüz gibi, arayan kodu ön kontrolün yükünü taşıyor, ancak sonrasında istisna işlemesi yapmıyor. Bir ArithmeticException
çağrı yapmaktan herhangi bir şey geliyorsa divide
ya da eval
, o zaman SİZİ unuttum, çünkü istisna yapmak zorundasınız ve kodunuzu düzeltmek zorundasınız check()
. Benzer nedenlerden dolayı, bir yakalamak NullPointerException
hemen hemen her zaman yapılacak yanlış şeydir.
Şimdi yöntem / işlev imzasında istisnai durumları görmek istediklerini, yani çıktı alanını açıkça genişletmek istediklerini söyleyenler var . Bunlar, kontrol edilen istisnaları destekleyenler . Tabii ki, çıktı alanını değiştirmek herhangi bir doğrudan arayan kodunu uyarlamaya zorlamalıdır ve bu gerçekten de kontrol edilen istisnalar ile başarılabilir. Ancak bunun için istisnalara ihtiyacınız yok! Bu yüzden Nullable<T>
genel sınıflara , vaka sınıflarına , cebirsel veri türlerine ve birleşim türlerine sahipsiniz . Bazı OO çalışanları bu gibi basit hata durumları için geri dönmeyi bile tercih edebilir null
:
static Integer divide(int dividend, int divisor) {
if (divisor == 0) return null;
return dividend / divisor;
}
static void doDivide() {
int a = readInt();
int b = readInt();
Integer res = divide(a, b);
if (res != null) {
System.out.println(res);
} else {
System.out.println("Nah, can't divide by zero. Try again.");
}
}
Teknik olarak istisnalar olabilir yukarıdaki gibi bir amaç için kullanılabilir, ancak burada noktasıdır edilmesi: istisnalar bu tür bir kullanım için mevcut değildir . İstisnalar pro soyutlamadır. İstisna dolaylı aktarma ile ilgilidir. İstisnalar, doğrudan müşteri sözleşmelerini bozmadan "sonuç" alanını genişletmeye ve hata işlemeyi "başka bir yere" ertelemeye izin verir . Kodunuz, aynı kodun doğrudan arayanlarda işlenen istisnalar atarsa, aralarında bir soyutlama katmanı yoksa, o zaman YANLIŞ yaparsınız.
NASIL GEÇERLİ OLACAK?
İşte buradayız. Yukarıdaki senaryolarda istisnaları kullanmanın istisnaların nasıl kullanılmadığını gösterme yolunda bulunduğumu tartışmıştım. Ancak, istisnaların ele alınması ile sunulan soyutlama ve dolaysızlığın vazgeçilmez olduğu özgün bir kullanım durumu vardır. Bu tür bir kullanımın anlaşılması, gecikme önerisinin de anlaşılmasına yardımcı olacaktır .
Bu kullanım durumu şudur: Kaynak Soyutlamalarına Karşı Programlama ...
Evet, iş mantığı , somut uygulamalara değil, soyutlamalara karşı programlanmalıdır . Üst düzey IOC "kablolama" kodu, kaynak soyutlamalarının somut uygulamalarını somutlaştırır ve bunları iş mantığına aktarır. Burada yeni bir şey yok. Ancak bu kaynak soyutlamalarının somut uygulamaları potansiyel olarak kendi uygulamalarına özel istisnalar atabilir, değil mi?
Peki bu uygulamaya özel istisnaları kim ele alabilir? İş mantığında herhangi bir kaynağa özgü istisnalar işlenebilir mi? Hayır, değil. İş mantığı, bu uygulamaya özel istisna ayrıntılarının bilgisini dışlayan soyutlamalara karşı programlanmıştır.
“Aha!” Diyebilirsiniz: “ama bu yüzden istisnaları alt sınıflara koyabilir ve istisna hiyerarşileri oluşturabiliriz” ( Bay Bahar'a göz atın !). Sana söyleyeyim, bu bir yanlışlık. İlk olarak, OOP hakkındaki her makul kitap, somut kalıtımın kötü olduğunu, ancak bir şekilde JVM'nin bu istisnai durumun temel bileşeni olan somut kalıtımla yakından ilişkili olduğunu söylüyor. İronik olarak, Joshua Bloch, çalışan bir JVM ile deneyim kazanmadan önce Etkin Java kitabını yazamazdı, değil mi? Yeni nesil için "öğrenilen dersler" kitabından daha fazlası. İkincisi, ve daha önemlisi, üst düzey bir istisna yakalarsanız, nasıl HANDLE edeceksin?PatientNeedsImmediateAttentionException
: Ona bir hap vermemiz veya bacaklarını kesmemiz gerekmiyor mu? Mümkün olan tüm alt sınıflar için bir switch ifadesine ne dersiniz? Polimorfizmine gidiyor, soyutlama gidiyor. Sen anladın.
Öyleyse, kaynağa özgü istisnaları kim ele alabilir? Betonu bilen kişi olmalı! Kaynağı hazırlayan kişi! Elbette "kablolama" kodu! Şuna bir bak:
İş mantığı soyutlamalara karşı kodlandı ... BETON KAYNAKLARI HATASI KULLANIMI YOK!
static interface InputResource {
String fetchData();
}
static interface OutputResource {
void writeData(String data);
}
static void doMyBusiness(InputResource in, OutputResource out, int times) {
for (int i = 0; i < times; i++) {
System.out.println("fetching data");
String data = in.fetchData();
System.out.println("outputting data");
out.writeData(data);
}
}
Bu arada başka bir yerde somut uygulamalar ...
static class ConstantInputResource implements InputResource {
@Override
public String fetchData() {
return "Hello World!";
}
}
static class FailingInputResourceException extends RuntimeException {
public FailingInputResourceException(String message) {
super(message);
}
}
static class FailingInputResource implements InputResource {
@Override
public String fetchData() {
throw new FailingInputResourceException("I am a complete failure!");
}
}
static class StandardOutputResource implements OutputResource {
@Override
public void writeData(String data) {
System.out.println("DATA: " + data);
}
}
Ve nihayet kablolama kodu ... Somut kaynak istisnalarını kim ele alıyor? Onları bilen biri!
static void start() {
InputResource in1 = new FailingInputResource();
InputResource in2 = new ConstantInputResource();
OutputResource out = new StandardOutputResource();
try {
ReusableBusinessLogicClass.doMyBusiness(in1, out, 3);
}
catch (FailingInputResourceException e)
{
System.out.println(e.getMessage());
System.out.println("retrying...");
ReusableBusinessLogicClass.doMyBusiness(in2, out, 3);
}
}
Şimdi benimle kal. Yukarıdaki kod olan basit. Birden fazla IOC konteyner yönetilen kaynağı kapsamına sahip bir işletme uygulamanızın / web konteynerinizin olduğunu ve otomatik olarak yeniden denemeler yapmanız ve oturumun yeniden başlatılması veya kapsam kaynakları istemeniz vb. Olduğunu söyleyebilirsiniz. kaynaklar yaratın, bu nedenle gerçek uygulamaların farkında olmamak. Sadece yüksek seviye kapsamlar, bu düşük seviye kaynakların ne gibi istisnalar yaratabileceğini gerçekten bilecektir. Şimdi bekle!
Maalesef, istisnalar yalnızca çağrı yığını üzerinde yönlendirmeye izin verir ve farklı kardinallikleri olan farklı kapsamlar genellikle birden fazla farklı iş parçacığı üzerinde çalışır. İstisnalarla iletişim kurmanın bir yolu yok. Burada daha güçlü bir şeye ihtiyacımız var. Cevap: zaman uyumsuz mesaj geçiyor . Her istisnayı alt seviye kapsamının kökünden yakalayın. Hiçbir şeyi görmezden gelme, hiçbir şeyin kaymasına izin verme. Bu, mevcut kapsamın arama yığında oluşturulan tüm kaynakları kapatacak ve imha edecektir. Sonra, hataların bilindiği düzeye ulaşana kadar istisna işleme yordamındaki ileti kuyruklarını / kanallarını kullanarak hata mesajlarını daha geniş bir alana yayın. Bununla nasıl başa çıkacağını bilen adam.
SUMMA SUMMARUM
Yani benim yorumlama göre catch geç en uygun yerde istisnaları yakalamak için anlamı SİZ ARTIK soyutlama kırma değildir . Çok erken yakalama! Kaynak soyutlamaların örneklerini atarak , soyutlamaların somutluğunu bilen katman olan somut istisnayı oluşturduğunuz katmandaki istisnaları yakalayın . "Kablolama" katmanı.
HTH. Mutlu kodlama!