Muhtemelen kod çalıştırdığınız ve sonunda NullPointerException , ClassCastException veya daha kötüsü gibi bir durumla karşılaştığınız bir durumla karşılaşmışsınızdır... Bunu, uzun bir hata ayıklama, analiz, googling vb. süreci takip eder. İstisnalar olduğu gibi harikadır: sorunun doğasını ve nerede meydana geldiğini gösterirler. Hafızanızı tazelemek ve biraz daha fazla bilgi edinmek istiyorsanız şu makaleye göz atın: İstisnalar: işaretli, işaretsiz ve özel .

Bununla birlikte, kendi istisnanızı yaratmanız gereken durumlar olabilir. Örneğin, kodunuzun herhangi bir nedenle kullanılamayan bir uzak hizmetten bilgi istemesi gerektiğini varsayalım. Ya da birisinin bir banka kartı başvurusunu doldurduğunu ve tesadüfen ya da değil, sistemdeki başka bir kullanıcıyla ilişkilendirilmiş bir telefon numarası verdiğini varsayalım.

Tabii ki, burada doğru davranış yine de müşterinin gereksinimlerine ve sistemin mimarisine bağlıdır, ancak telefon numarasının zaten kullanımda olup olmadığını kontrol etmek ve kullanılıyorsa bir istisna atmakla görevlendirildiğinizi varsayalım.

Bir istisna oluşturalım:


public class PhoneNumberAlreadyExistsException extends Exception {

   public PhoneNumberAlreadyExistsException(String message) {
       super(message);
   }
}
    

Ardından, kontrolümüzü gerçekleştirirken kullanacağız:


public class PhoneNumberRegisterService {
   List<String> registeredPhoneNumbers = Arrays.asList("+1-111-111-11-11", "+1-111-111-11-12", "+1-111-111-11-13", "+1-111-111-11-14");

   public void validatePhone(String phoneNumber) throws PhoneNumberAlreadyExistsException {
       if (registeredPhoneNumbers.contains(phoneNumber)) {
           throw new PhoneNumberAlreadyExistsException("The specified phone number is already in use by another customer!");
       }
   }
}
    

Örneğimizi basitleştirmek için, bir veri tabanını temsil edecek birkaç kodlanmış telefon numarası kullanacağız. Ve son olarak, istisnamızı kullanmaya çalışalım:


public class CreditCardIssue {
   public static void main(String[] args) {
       PhoneNumberRegisterService service = new PhoneNumberRegisterService();
       try {
           service.validatePhone("+1-111-111-11-14");
       } catch (PhoneNumberAlreadyExistsException e) {
           // Here we can write to logs or display the call stack
		e.printStackTrace();
       }
   }
}
    

Ve şimdi Shift+F10'a basma zamanı (eğer IDEA kullanıyorsanız), yani projeyi çalıştırın. Konsolda göreceğiniz şey bu:

istisna.CreditCardIssue
istisna.PhoneNumberAlreadyExistsException: Belirtilen telefon numarası zaten başka bir müşteri tarafından kullanılıyor!
istisnada.PhoneNumberRegisterService.validatePhone(PhoneNumberRegisterService.java:11)

Kendine bak! Kendi istisnanızı yarattınız ve hatta biraz test ettiniz. Bu başarı için tebrikler! Nasıl çalıştığını daha iyi anlamak için kodu biraz denemenizi tavsiye ederim.

Başka bir kontrol ekleyin — örneğin, telefon numarasının harf içerip içermediğini kontrol edin. Muhtemelen bildiğiniz gibi, Amerika Birleşik Devletleri'nde telefon numaralarının hatırlanmasını kolaylaştırmak için harfler sıklıkla kullanılır, örneğin 1-800-MY-APPLE. Çekiniz, telefon numarasının yalnızca sayıları içermesini sağlayabilir.

Tamam, kontrol edilmiş bir istisna oluşturduk. Her şey güzel olurdu ama...

Programlama topluluğu, kontrol edilen istisnalardan yana olanlar ve onlara karşı çıkanlar olmak üzere iki kampa bölünmüştür. Her iki taraf da güçlü argümanlar ileri sürüyor. Her ikisi de birinci sınıf geliştiriciler içerir: Bruce Eckel kontrol edilen istisnaları eleştirirken, James Gosling onları savunur. Görünüşe göre bu mesele asla kalıcı olarak çözülmeyecek. Bununla birlikte, kontrol edilen istisnaları kullanmanın ana dezavantajlarına bakalım.

Kontrol edilen istisnaların ana dezavantajı, bunların ele alınması gerekmesidir. Ve burada iki seçeneğimiz var: ya onu bir try-catch kullanarak yerinde ele alın ya da aynı istisnayı birçok yerde kullanırsak, istisnaları atmak için atışları kullanın ve onları en üst düzey sınıflarda işleyin.

Ayrıca, "boilerplate" koduyla, yani çok fazla yer kaplayan, ancak çok fazla ağır kaldırma yapmayan kodla karşılaşabiliriz.

Pek çok istisna ele alındığında oldukça büyük uygulamalarda sorunlar ortaya çıkar: üst düzey bir yöntemdeki atma listesi kolayca bir düzine istisna içerecek şekilde büyüyebilir.

public OurCoolClass(), FirstException, SecondException, ThirdException, ApplicationNameException fırlatır...

Geliştiriciler genellikle bundan hoşlanmazlar ve bunun yerine bir hile seçerler: işaretli tüm istisnalarının ortak bir atadan miras almasını sağlarlar - ApplicationNameException . Şimdi bir işleyicide şu ( işaretli !) istisnayı da yakalamaları gerekir :


catch (FirstException e) {
    // TODO
}
catch (SecondException e) {
    // TODO
}
catch (ThirdException e) {
    // TODO
}
catch (ApplicationNameException e) {
    // TODO
}
    

Burada başka bir sorunla karşı karşıyayız — son catch bloğunda ne yapmalıyız ? Yukarıda, beklenen tüm durumları zaten işledik, bu nedenle bu noktada ApplicationNameException bizim için " İstisna : anlaşılmaz bir hata oluştu" dan başka bir şey ifade etmiyor. Bunu şu şekilde hallediyoruz:


catch (ApplicationNameException e) {
    LOGGER.error("Unknown error", e.getMessage());
}
    

Ve sonunda, ne olduğunu bilmiyoruz.

Ama her istisnayı aynı anda bu şekilde atamaz mıyız?


public void ourCoolMethod() throws Exception {
// Do some work
}
    

Evet yapabiliriz. Ama "İstisna atar" bize ne anlatıyor? Bir şeyin kırıldığını. Nedenini anlamak için her şeyi baştan aşağı araştırmanız ve hata ayıklayıcıyla uzun süre samimi olmanız gerekecek.

Bazen "istisna yutma" olarak adlandırılan bir yapıyla da karşılaşabilirsiniz:


try {
// Some code
} catch(Exception e) {
   throw new ApplicationNameException("Error");
}
    

Buraya açıklama yoluyla eklenecek pek bir şey yok - kod her şeyi açıklığa kavuşturuyor, daha doğrusu her şeyi belirsizleştiriyor.

Elbette bunu gerçek kodda görmeyeceğinizi söyleyebilirsiniz. Pekala, java.net paketinden URL sınıfının bağırsaklarına (kod) bakalım . Bilmek istiyorsan beni takip et!

İşte URL sınıfındaki yapılardan biri :


public URL(String spec) throws MalformedURLException {
   this(null, spec);
}
    

Gördüğünüz gibi, kontrol edilmiş ilginç bir istisnamız var — MalformedURLException . İşte ne zaman atılabileceği (ve alıntı yapıyorum):
"hiçbir protokol belirtilmezse veya bilinmeyen bir protokol bulunursa veya belirtim boşsa veya ayrıştırılan URL, ilişkili protokolün belirli sözdizimine uymuyorsa."

Yani:

  1. Herhangi bir protokol belirtilmemişse.
  2. Bilinmeyen bir protokol bulundu.
  3. Spesifikasyon boş .
  4. URL, ilişkili protokolün belirli sözdizimine uymuyor.

Bir URL nesnesi oluşturan bir yöntem oluşturalım :


public URL createURL() {
   URL url = new URL("https://codegym.cc");
   return url;
}
    

Bu satırları IDE'ye yazar yazmaz (IDEA'da kodluyorum, ancak bu Eclipse ve NetBeans'te bile çalışıyor), şunu göreceksiniz:

Bu, ya bir istisna atmamız ya da kodu bir try-catch bloğuna sarmamız gerektiği anlamına gelir. Şimdilik, neler olduğunu görselleştirmek için ikinci seçeneği seçmenizi öneriyorum:


public static URL createURL() {
   URL url = null;
   try {
       url = new URL("https://codegym.cc");
   } catch(MalformedURLException e) {
  e.printStackTrace();
   }
   return url;
}
    

Gördüğünüz gibi, kod zaten oldukça ayrıntılı. Ve yukarıda buna değindik. Bu, denetlenmeyen istisnaları kullanmanın en bariz nedenlerinden biridir.

Java'da RuntimeException'ı genişleterek denetlenmeyen bir istisna oluşturabiliriz .

Denetlenmeyen istisnalar, Error sınıfından veya RuntimeException sınıfından devralınır . Birçok programcı, program çalışırken kurtarmayı bekleyemeyeceğimiz hataları temsil ettikleri için bu istisnaların programlarımızda ele alınamayacağını düşünür.

Denetlenmeyen bir istisna oluştuğunda, bunun nedeni genellikle kodun yanlış kullanılması, null veya başka bir şekilde geçersiz olan bir bağımsız değişkenin iletilmesidir.

Peki, kodu yazalım:


public class OurCoolUncheckedException extends RuntimeException {
   public OurCoolUncheckedException(String message) {
       super(message);
   }

   public OurCoolUncheckedException(Throwable cause) {
       super(cause);
   }
  
   public OurCoolUncheckedException(String message, Throwable throwable) {
       super(message, throwable);
   }
}
    

Farklı amaçlar için birden çok oluşturucu yaptığımızı unutmayın. Bu, istisnamıza daha fazla yetenek vermemizi sağlar. Örneğin, bir istisnanın bize bir hata kodu vermesini sağlayabiliriz. Başlangıç ​​olarak, hata kodlarımızı temsil edecek bir sıralama yapalım :


public enum ErrorCodes {
   FIRST_ERROR(1),
   SECOND_ERROR(2),
   THIRD_ERROR(3);

   private int code;

   ErrorCodes(int code) {
       this.code = code;
   }

   public int getCode() {
       return code;
   }
}
    

Şimdi istisna sınıfımıza başka bir yapıcı ekleyelim:


public OurCoolUncheckedException(String message, Throwable cause, ErrorCodes errorCode) {
   super(message, cause);
   this.errorCode = errorCode.getCode();
}
    

Ve bir alan eklemeyi unutmayalım (neredeyse unutuyorduk):


private Integer errorCode;
    

Ve tabii ki bu kodu almak için bir yöntem:


public Integer getErrorCode() {
   return errorCode;
}
    

Kontrol etmek ve karşılaştırmak için tüm sınıfa bakalım:

public class OurCoolUncheckedException extends RuntimeException {
   private Integer errorCode;

   public OurCoolUncheckedException(String message) {
       super(message);
   }

   public OurCoolUncheckedException(Throwable cause) {
       super(cause);
   }

   public OurCoolUncheckedException(String message, Throwable throwable) {

       super(message, throwable);
   }

   public OurCoolUncheckedException(String message, Throwable cause, ErrorCodes errorCode) {
       super(message, cause);
       this.errorCode = errorCode.getCode();
   }
   public Integer getErrorCode() {
       return errorCode;
   }
}
    

Ta-da! İstisnamız tamamlandı! Gördüğünüz gibi, burada özellikle karmaşık bir şey yok. Eylem halinde kontrol edelim:


   public static void main(String[] args) {
       getException();
   }
   public static void getException() {
       throw new OurCoolUncheckedException("Our cool exception!");
   }
    

Küçük uygulamamızı çalıştırdığımızda konsolda aşağıdakine benzer bir şey göreceğiz:

Şimdi eklediğimiz ekstra işlevsellikten yararlanalım. Önceki koda biraz ekleyeceğiz:


public static void main(String[] args) throws Exception {

   OurCoolUncheckedException exception = getException(3);
   System.out.println("getException().getErrorCode() = " + exception.getErrorCode());
   throw exception;

}

public static OurCoolUncheckedException getException(int errorCode) {
   return switch (errorCode) {
   case 1:
       return new OurCoolUncheckedException("Our cool exception! An error occurred: " + ErrorCodes.FIRST_ERROR.getCode(), new Throwable(), ErrorCodes.FIRST_ERROR);
   case 2:
       return new OurCoolUncheckedException("Our cool exception! An error occurred: " + ErrorCodes.SECOND_ERROR.getCode(), new Throwable(), ErrorCodes.SECOND_ERROR);
   default: // Since this is the default action, here we catch the third and any other codes that we have not yet added. You can learn more by reading Java switch statement
       return new OurCoolUncheckedException("Our cool exception! An error occurred: " + ErrorCodes.THIRD_ERROR.getCode(), new Throwable(), ErrorCodes.THIRD_ERROR);
}

}
    

İstisnalarla, nesnelerle çalıştığınız gibi çalışabilirsiniz. Elbette, Java'daki her şeyin bir nesne olduğunu zaten bildiğinizden eminim.

Ve ne yaptığımıza bakın. İlk olarak, artık fırlatmayan, bunun yerine giriş parametresine bağlı olarak basitçe bir istisna oluşturan yöntemi değiştirdik. Daha sonra, bir switch-case deyimi kullanarak, istenen hata kodu ve mesajıyla bir istisna oluşturuyoruz. Ve main metodunda ise oluşturulan istisnayı alıyoruz, hata kodunu alıyoruz ve atıyoruz.

Bunu çalıştıralım ve konsolda ne gördüğümüze bakalım:

Bakın, istisnadan aldığımız hata kodunu yazdırdık ve ardından istisnanın kendisini fırlattık. Dahası, istisnanın tam olarak nerede atıldığını bile takip edebiliriz. Gerektiğinde, ilgili tüm bilgileri mesaja ekleyebilir, ek hata kodları oluşturabilir ve istisnalarınıza yeni özellikler ekleyebilirsiniz.

Buna ne dersin? Umarım her şey senin için işe yaramıştır!

Genel olarak, istisnalar oldukça kapsamlı bir konudur ve kesin değildir. Üzerinde daha birçok anlaşmazlık olacak. Örneğin, yalnızca Java istisnaları kontrol etti. En popüler diller arasında onları kullanan birini görmedim.

Bruce Eckel, "Java'da Düşünmek" adlı kitabının 12. bölümünde istisnalar hakkında çok iyi yazdı — Okumanızı tavsiye ederim! Ayrıca Horstmann'ın "Çekirdek Java" kitabının ilk cildine de bir göz atın - 7. bölümde de pek çok ilginç şey var.

küçük bir özet

  1. Her şeyi bir günlüğe yaz! İletileri atılan istisnalarda günlüğe kaydedin. Bu genellikle hata ayıklamada çok yardımcı olur ve ne olduğunu anlamanıza izin verir. Bir catch bloğunu boş bırakmayın , aksi takdirde istisnayı "yutar" ve sorunları yakalamanıza yardımcı olacak herhangi bir bilgiye sahip olmazsınız.

  2. İstisnalar söz konusu olduğunda, hepsini bir kerede yakalamak kötü bir uygulamadır (bir meslektaşımın dediği gibi, "bu Pokemon değil, Java"), bu yüzden catch (Exception e) veya daha kötüsü, catch (Throwable t ) kaçının .

  3. İstisnaları olabildiğince erken atın. Bu iyi bir Java programlama pratiğidir. Spring gibi çerçeveleri incelediğinizde, "hızlı başarısız olma" ilkesini izlediklerini göreceksiniz. Yani, hatayı hızlı bir şekilde bulmayı mümkün kılmak için olabildiğince erken "başarısız olurlar". Tabii ki, bu belirli rahatsızlıkları beraberinde getiriyor. Ancak bu yaklaşım, daha sağlam kod oluşturmaya yardımcı olur.

  4. Kodun diğer bölümlerini çağırırken, belirli istisnaları yakalamak en iyisidir. Çağrılan kod birden çok özel durum oluşturursa, bu istisnaların yalnızca üst sınıfını yakalamak kötü bir programlama uygulamasıdır. Örneğin, FileNotFoundException ve IOException atan kodu çağırdığınızı varsayalım . Bu modülü çağıran kodunuzda, tek bir catch istisnasını yakalamak yerine, istisnaların her birini yakalamak için iki catch bloğu yazmak daha iyidir .

  5. İstisnaları yalnızca kullanıcılar için ve hata ayıklama için etkili bir şekilde işleyebildiğiniz zaman yakalayın.

  6. Kendi istisnalarınızı yazmaktan çekinmeyin. Tabii ki, Java'da her durum için bir çok hazır şey vardır, ancak bazen yine de kendi "tekerleğinizi" icat etmeniz gerekir. Ancak bunu neden yaptığınızı net bir şekilde anlamalı ve standart istisna setinde ihtiyacınız olanın zaten bulunmadığından emin olmalısınız.

  7. Kendi istisna sınıflarınızı oluştururken adlandırma konusunda dikkatli olun! Sınıfları, değişkenleri, yöntemleri ve paketleri doğru bir şekilde adlandırmanın son derece önemli olduğunu muhtemelen zaten biliyorsunuzdur. İstisnalar istisna değildir! :) Her zaman İstisna kelimesiyle bitirin ve istisnanın adı, temsil ettiği hatanın türünü açıkça belirtmelidir. Örneğin, FileNotFoundException .

  8. İstisnalarınızı belgeleyin. İstisnalar için bir @throws Javadoc etiketi yazmanızı öneririz. Bu, özellikle kodunuz herhangi bir türde arabirim sağladığında yararlı olacaktır. Ayrıca daha sonra kendi kodunuzu daha kolay anlayacaksınız. Ne düşünüyorsunuz, MalformedURLException'ın ne hakkında olduğunu nasıl belirleyebilirsiniz ? Javadoc'tan! Evet, belge yazma fikri pek cazip gelmiyor ama inanın altı ay sonra kendi kodunuza döndüğünüzde kendinize teşekkür edeceksiniz.

  9. Kaynakları serbest bırakın ve kaynaklarla deneme yapısını ihmal etmeyin .

  10. İşte genel özet: İstisnaları akıllıca kullanın. Bir istisna atmak, kaynaklar açısından oldukça "pahalı" bir işlemdir. Çoğu durumda, istisnalar oluşturmaktan kaçınmak ve bunun yerine, basit ve "daha ucuz" bir if-else kullanarak işlemin başarılı olup olmadığını gösteren bir boolean değişkeni döndürmek daha kolay olabilir .

    Uygulama mantığını, kesinlikle yapmamanız gereken istisnalara bağlamak da cazip gelebilir. İstisnalar yazının başında da söylediğimiz gibi istisnai durumlar içindir, beklenmeyen durumlar içindir ve bunları önlemek için çeşitli araçlar vardır. Özellikle, bir NullPointerException'ı önlemek için İsteğe Bağlı veya read () yönteminin atabileceği bir IOException'ı önlemek için Scanner.hasNext ve benzerleri vardır .