27 Ekim 2011 Perşembe

Hatasız Programcı Olmaz: Java'da Hata Kotarımı

Yazılım gerçek dünyada var olan somut/soyut süreçlerin bilgisayar donanımı üzerinde çalıştırılan benzetimleridir. Amaç, sürecin, etkileşimde bulunulan diğer süreçler ile birlikte daha verimli ve sağlıklı bir şekilde işletilmesini sağlamaktır. Mesela, bir otomasyon yazılımı zayiatı azaltmak ve bakımı hızlandırmak suretiyle parçası olduğu üretim sürecinin daha verimli olmasını sağlayacaktır. Doğal olarak, bir yazılımın başarılı kabul edilmesindeki en önemli ölçüt, bilgisayarda oluşturulan hesaplama sürecinin benzetimi yapılmakta olan sürecin algılanışına1 sadık kalıp kalmadığı şeklinde ifade edilebilecek doğruluk (İng., correctness) özelliğidir. Peşinden koşulması gereken bir diğer özellik, yazılımın öngörülmeyen koşullar altında da kabul edilebilir bir tepki göstermesi olarak da tanımlayabileceğimiz dayanıklılıktır (İng., robustness). Bu beklentiler yazılım sektörünün rekabete dayalı pragmatik bir dünya olması gerçeği ile birlikte ele alındığında, yazılım üreticisinin birincil görevinin düzgün bir peformansa sahip, doğru ve dayanıklı—yani, güvenilir (İng., reliable)—programların makul bir hızda geliştirilmesi olduğu söylenebilir. İşte bu yazıda, ifade edilen görev tanımındaki güvenilirlik özelliğine yönelik olarak Java programlama dili tarafından sağlanan desteğe değineceğiz.

Kötü bir haber vererek başlayalım: Java, programlarınızın doğruluğunu sağlamak bağlamında kaynak kodunuzun dilin yazım kurallarına uygunluğunu denetlemek dışında pek bir destek sağlamaz; sözdizimsel hataların ötesinde Java derleyicisinin yapacağı çok şey yoktur. Aşağıdaki örnek üzerinden ne demek istediğimizi açalım.

Faktöryel.java
import static java.lang.System.out;
...

public class Faktöryel {
  public static void main(String[] ksa) {
    byte b = Byte.parseByte(ksa[0]);
    out.println(b + "!: " + fakt(b));
    ...
  } // void main(String[]) sonu
 
  public static long fakt(byte n) {
    long çarpım = 1;
    for (byte i = 2; i <= n; i++) çarpım += i;
  
    return çarpım;
  } // long fakt(byte) sonu
  ...
} // Faktöryel sınıfının sonu
$ javac -encoding UTF-8 Faktöryel.java
$ java Faktöryel 5
5!: 15 ✘
Görüldüğü gibi, sözdizimsel bir hata bulunmamakla birlikte, faktöryel kavramının hatalı gerçekleştiriminden kaynaklanan bir mantıksal hata ortaya çıkmıştır. Derleyicinin müdahele ederek işaretli satırdaki + yerine * yazılması gerektiği yorumunu yapması olanaksızdır. Bu gibi hataların, kodun sınanması sırasında keşfedilerek programcı tarafından ortadan kaldırılması gerekir. Bir an için bunun yapıldığını ve hatanın düzeltilerek doğru bir gerçekleştirimin sağlandığını düşünelim. Bu durumda, programımızın çalıştırılması aşağıdaki sonuçları verecektir.
$ java Faktöryel 5
5!: 120 ✔
$ java Faktöryel 20
20!: 2432902008176640000 ✔
$ java Faktöryel 21
21!: -4249290049419214848 ✘
Bir ihtimal Java'daki tamsayı türlerinin taşma davranışını bilmeyenleriniz, herhangi bir sayının eksi değerli bir faktöryele sahip olduğuna şaşırabilir. Bu gruptaki arkadaşlara önerim, daha fazla ilerlemeden şu yazıya🔎 bir göz atmaları. Diğer arkadaşlar ise, bunun sebebinin çarpım sonucunun long değerler için söz konusu olan aralığa düşmemesi nedeniyle kırpılması olduğunu bileceklerdir. Peki bu durumda, faktöryel metodumuzun doğru olmadığını söyleyebilir miyiz? Faktöryelin tanımını bilen herkesin bu soruya hayır yanıtı vermesi gerekir; ortada olan şey bir hatadan çok yetersizliktir ve muhtemelen metodun kullanıcısı ile iletişim eksikliğinden kaynaklanmaktadır. Soruna değişik şekillerde çözüm getirilebilir.
  1. Gerçekleştirime dokunulmaz ve kullanıcıya sağlanan belgelerde metodun 0 ile 20 arasındaki tamsayılar için doğru çözümler ürettiği ve dolayısıyla sağlanması beklenen argüman değerinin bu aralıkta olması gerektiği söylenir.
  2. Duruma has bir başka çözüm sağlanarak eksiklik giderilir. Örneğimizde, dönüş türü olarak long yerine java.math.BigInteger türünün kullanılması ve kodda buna göre değişikliklerin yapılması işimizi görecektir.
  3. Geçirilen argüman değerinin beklenen aralıkta olup olmadığı denetlenir. Bu, basit bir if komutuyla yapılabileceği gibi doğruluk savı (İng.; assertion) denetimiyle de yapılabilir. İlk yolun seçilmesi durumunda, görülen olumsuzluk dönüşte [0 gibi] özel olarak yorumlanan bir değerle belirtilebileceği gibi söz konusu durumu özetleyen bir ayrıksı durum (İng., exception(al condition)) nesnesi ile de bildirilebilir.
İkinci ve üçüncü maddelerdeki yaklaşımların nasıl gerçekleştirilebileceğini aşağıdaki alternatif çözümü izleyerek görelim. Özyinelemeli olan yeni metodumuzda, argümanın eksi olması hali, ki bu beklenilmeyen bir kullanıma işaret eder, çağırıcıya IllegalArgumentException türündeki bir ayrıksı durum nesnesi ile bildirilirken, argümanın 0 veya 1 olması halinde 1, geri kalan durumlarda ise matematikten tanıdık n * (n-1)! değeri döndürülmektedir. java.lang paketinde tanımlanan IllegalArgumentException, sağlanan argüman değerinin, -5'in faktöryelinin bulunmaya çalışılmasında olduğu gibi, uygunsuz olduğuna işaret eder.
...
import java.math.BigInteger;

public class Faktöryel {
  public static void main(String[] ksa) {
    ...
    long l = Long.parseLong(ksa[1]);
    try { out.println(l + "!: " + fakt2(l)); }
      catch(IllegalArgumentException a) {
        out.println(l + "!: " + fakt2(Math.abs(l)) + "!!!");
      }
  } // void main(String[]) sonu
  ...
  public static BigInteger fakt2(long n) {
    if (n < 0)
      throw new IllegalArgumentException(String.valueOf(n));
    if (n < 2)
      return BigInteger.ONE;
      else return BigInteger.valueOf(n)
                            .multiply(fakt2(n - 1));
  } // BigInteger fakt2(long) sonu
} // Faktöryel sınıfının sonu
$ java Faktöryel 21 21
21!: -4249290049419214848 ✘
21!: 51090942171709440000 ✔
$ java Faktöryel 5 -5
5!: 120 ✔
-5!: 120!!!
Beklenmedik koşulların oluştuğu noktada, özet bilgi içeren bir ayrıksı durum nesnesinin yaratılıp throw komutu ile fırlatılması gerekir. Bunun sonrasında, ortaya çıkan durumun çözümünü bilen birisinin duruma el atıp bir şeyler yapması beklenir. Biraz düşünüldüğünde, çözümün içine düşülen duruma sebep olanlar tarafından sağlanabileceği görülecektir. Bu ise, ayrıksı durumun ortaya çıktığı noktaya gelinene kadar izlenen çağrı zinciri üzerindeki noktalar demektir. Bir diğer deyişle, çare ya sorunun ortaya çıktığı noktada ya da o noktaya gelinmesi ile son bulan çağrı yığıtındaki diğer metotlarda bulunabilir. Çözüme dair bir şeyler yapabileceğini düşünenlerin bu iddialarını try-catch yapısı içinde bildirmeleri beklenir. try, takip eden kıvrımlı ayraç çifti arasındaki komutların sorun çıkarabileceğini bildirirken, kotarıcı olarak adlandırılan catch bloğu/blokları iliştirildikleri korumalı bölgede ortaya çıkabilecek sorunların tümü veya bazıları için çözümler sunar. Çağrı zinciri üzerindeki noktaların hiçbirinde çözüm önerilmemesi halinde ise, top denetim akışı geriye sarılarak programı başlatan JSM'ye atılacak ve JSM de ortaya çıkan durumu oluştuğu yerden başlayarak nerelere uğrayarak çözmeye çalıştığını söyleyen bir raporu standart hata ortamına2 yazarak programı sonlandıracaktır. Bunun nasıl olduğuna aşağıdaki çıktıya göz atarak bir bakalım.
...
public class Faktöryel {
  public static void main(String[] ksa) {
    ...
    long l = Long.parseLong(ksa[1]);
    out.println(l + "!: " + fakt2(l));
  } // void main(String[]) sonu
  ...
  public static BigInteger fakt2(long n) {
    if (n < 0)
      throw new IllegalArgumentException(String.valueOf(n));
    ...
  } // BigInteger fakt2(long) sonu
} // Faktöryel sınıfının sonu
$ java Faktöryel 5 -5
5!: 120 ✔
Exception in thread "main" java.lang.IllegalArgumentException: -5
 at Faktöryel.fakt2(Faktöryel.java:20)
 at Faktöryel.main(Faktöryel.java:9)
JSM'ye bakılacak olursa, programın [main adına sahip] ana izleğinde Faktöryel sınıfının main metodu içinden 9. satırda aynı sınıftaki fakt2 metodu çağrılmış ve denetim akışı bu metottta iken 20. satırda IllegalArgumentException ayrıksı durumu ortaya çıkmış. Anlamadıysanız ikinci bir örnekle açıklık getirmeye çalışalım; anlayanlar da bu sayede pekiştirmiş olurlar.
import java.io.BufferedReader;
import java.io.FileReader;
import java.util.Scanner;

public class Örnek {
  public static void main(String[] ksa) {
    // ...
    m1();
    // ...
  } // void main(String[]) sonu

  private static void m1() {
    // ...
    m2();
    // ...
  } // void m1() sonu

  private static void m2() {
    String dosyaAdı;
    Scanner grdKanalı = new Scanner(System.in);
    System.out.print("Dosya adını giriniz: ");
    dosyaAdı = grdKanalı.nextLine();
    // ...
    m3(dosyaAdı);
    // ...
  } // void m2() sonu

  private static void m3(String dosyaAdı) {
    BufferedReader dosya =
      new BufferedReader(new FileReader(dosyaAdı));
    // ...
  } // void m3(String) sonu
} // Örnek sınıfının sonu
İşaretli satırlarda, argümanda geçirilen ada sahip ve o anki çalışma dizini içinde bulunan bir dosyanın tamponlanarak ve karakter yönelimli bir biçimde okunması için uygun türden bir akak nesnesi yaratılıyor. Bu işlem, sağlanan ada sahip bir dosyanın bulunmaması halinde java.io.FileNotFoundException ayrıksı durumu ile sonlanacaktır. Dolayısıyla, programın derlenip var olmayan bir dosyanın adı verilerek çalıştırılması, yukardakine benzer bir çağrı yığıtı özetinin üretilmesine neden olacaktır diye düşünebiliriz. Ancak, gelin görün ki, program derlendiğinde durumun hiç de öyle olmadığı görülür.
$ javac -encoding UTF-8 Örnek.java 
Örnek.java:29: error: unreported exception FileNotFoundException; must be caught or declared to be thrown
      new BufferedReader(new FileReader(dosyaAdı));
                         ^
1 error
Yukarıdaki çıktıdan da görülebileceği gibi, IllegalArgumentException için sesini bile çıkarmayan derleyici, iş FileNotFoundException'a geldiğinde yaygarayı basmaktadır. Çifte standardın sebebi, ayrıksı durumların iki kategoriye ayrılmasıdır:
  • RuntimeException köklü sınıf sıradüzeninde olanlar: Bu kategoriye giren ayrıksı durumlar, ya ortaya çıktıkları noktada yakalanıp kotarılmalı ya da çağrı yığıtındaki diğer metotlardan birinde kotarılması gerektiğini hatırlatmak için metot imzasında ilan edilmelidirler.
  • Diğerleri: Bu ayrıksı durumların ne yakalanması ne de, yakalanmadıkları takdirde, metot başlığında ilan edilmeleri zorunludur.
Dolayısıyla, ilk kategoriye giren FileNotFoundException ayrıksı durumunun ortaya çıkması olasılığının belirmesiyle derleyici bizim kotarmak ya da bu görevi ihale etmek yönünde bir karar verdiğimizi görmek ister. Bu istemin karşılanmaması halinde ise söz konusu eksikliğe dikkat çekerek derlemenin başarısızlıkla sonlandığını bildirir. Burada bir noktanın özellikle vurgulanması yerinde olacaktır: derleyici, kodu okuyup var olmayan bir dosyanın girileceğinden emin olduğu için değil, böyle bir olasılığın var olduğu için itiraz etmektedir. Derleyicinin gözünde, FileReader sınıfının kullanılan yapıcısı FileNotFoundException türlü bir ayrıksı durum nesnesi fırlatabilir ve programcının da buna karşı hazırlıklı olduğunu kanıtlaması gerekir.

Kotarımın sorunun çıktığı nokta yerine çağrı yığıtındaki diğer metotlara bırakıldığı metot başlığına eklenen throws bildirimi ile ilan edilir. Buna bir örnek aşağıda verilmiştir: m3'te ortaya çıkabilecek sorun öngörülmüş fakat metot içinde çözülmektense metot başlığında belirtilmek suretiyle çağrı yığıtındaki diğer metotlara bırakılmıştır. Daha kesin ifade edecek olursak, m3'te ortaya çıkabilecek sorun için top önce m2'ye ve oradan da m1'e atılmış ve hal çaresi bu metot içinde sağlanmıştır.
...
import java.io.FileNotFoundException;

public class Örnek {
  public static void main(String[] ksa) {
    // ...
    m1();
    // ...
  } // void main(String[]) sonu

  private static void m1() {
    // ...
    try { m2(); } catch(FileNotFoundException a) { ... }
    // ...
  } // void m1() sonu

  private static void m2() throws FileNotFoundException {
    ...
    // ...
    m3(dosyaAdı);
    // ...
  } // void m2() sonu

  private static void m3(String dosyaAdı)
    throws FileNotFoundException {
    BufferedReader dosya =
      new BufferedReader(new FileReader(dosyaAdı));
    // ...
  } // void m3(String) sonu
} // Örnek sınıfının sonu
Kotarıcıların iliştirildikleri korumalı bölgeye özel oldukları unutulmamalıdır. Bundan dolayı, aşağıdaki kod parçası derleyici tarafından kabul görmeyecektir; kotarıcı m2 metodunun sadece birinci kullanımına çözüm sağlamaktadır.
private static void m1() {
    // ...
    try { m2(); } catch(FileNotFoundException a) { ... }
    // ...
    m2(); // ⇒ Derleme hatası!
    // ...
  } // void m1() sonu
Bu noktada, neden iki kategori olduğunu soruyor olabilirsiniz. Aşağıdaki örnekle anlamaya çalışalım. Belli ki, bu basit programı yazan arkadaş Java'da dizilerin 0-temelli indislere sahip olduğunu unutmuş ve üç elemanlı bir int dizisi yarattıktan sonra üçüncü eleman yerine 3 indisiyle gösterilen elemanın değerini 4 olarak güncellemek istemiş. Yani, Java programlama bilgisi eksik olan arkadaş derleme zamanında yakalanamayan bir hata yapmış. Takdir edersiniz ki, böylesine bir hatanın kotarıcıda düzeltilmesi ve programa devam edilmesi olanaklı değildir. Yapılması gereken, programcı arkadaşımızın dizileri daha iyi öğrenmesi ve kullanıcıdan 0..2 aralığında değer istemesi gerektiğini farketmesidir. Çünkü, ayrıksı durum düzeneği programın beklenmedik durumlarda daha sağlıklı bir tepki vermesini sağlamak için kullanılır, kötü programcıların programlama hatalarını düzeltmesi gibi erişilmesi olanaksız bir amaç için değil.
import java.util.Scanner;

public class DiziKullanımı {
  public static void main(String[] ksa) {
    int[] iDz = {1, 2, 3};
    Scanner grdKanalı =  new Scanner(System.in);
    System.out.print("1..3 aralığında bir sayı giriniz: ");
    iDz[grdKanalı.nextInt()] = 4;
  } // void main(String[]) sonu
} // DiziKullanımı sınıfının sonu
$ javac -encoding UTF-8 DiziKullanımı.java
$ java DiziKullanımı
1..3 aralığında bir sayı giriniz: 3
Exception in thread "main" java.lang.ArrayIndexOutOfBoundsException: 3
 at DiziKullanımı.main(DiziKullanımı.java:8)
Benzer bir gözlem null değerli bir tutacak yoluyla ileti gönderilmeye çalışılması sonrasında ortaya çıkan NullPointerException için de yapılabilir. Sıkça kendini gösteren bu ayrıksı durumun da sebebi programlama hatasıdır ve kotarılması olanaksızdır. Dolayısıyla, yakalanması veya metot başlığında ilan edilmesi pek anlamlı olmayacaktır.3 Buna karşılık, bir dosyanın olmaması kullanıcının dosya adını giren kullanıcının dikkatsizliğinden kaynaklanabilir. Doğal olarak, bu beklenmedik durumun öngörülerek kotarıcıda ikinci bir hak verilerek programın boş yere sona ermesinin önüne geçilmelidir.

Peki, ya sizin ve kullanıcının iradesi dışında gelişen sebepler nedeniyle programınız göçecek olursa. Mesela, arkadaşınız ortaklaşa kullandığınız bilgisayardan programınızın çalışması için gerekli olan bir sınıf dosyasını silecek olursa, ne olur? Ya da, dosyanın diskte kaydedildiği sektörlerden birinin bozulması nedeniyle sınıf dosyasının okunarak içselleştirilmesi söz konusu olamazsa, ne olur? Ya da ya da, programınızın işlemesi için gereksinilen kaynaklar JSM tarafından sağlanamayacak kadar büyük olmaya başlarsa, ne olur? Gelin bu soruların yanıtını Faktöryel sınıfını ikinci komut satırı argümanı çok büyük bir sayı ile deneyerek birlikte görelim.
$ java Faktöryel 5 5827 2> StackOverflowError.txt
5!: 120 ✔       
Standart hata ortamını yönlendirerek hata mesajlarını StackOverflowError.txt dosyasına gönderen yukarıdaki komutun ürettiği bilgi incelendiğinde, fakt2'nin sürekli kendini çağırdığı ve programın StackOverflowError ile sonlandığı görülecektir. Yani, JSM özyinelemeli çağrılar sonucu genişleyen çağrı yığıtını doldurmuş ve taşmanın vuku bulduğu noktada imdat çağrısını yapmıştır. Bu hatanın ortaya çıkışı, JSM'nin yararlandığı yığıt büyüklüğünü -Xss512k veya -Xss1m gibi bir opsiyonla büyüterek ertelenebilir. Ancak; işin özünde çaresiz olan JSM'dir ve belleğin bittiği yerde o da bir şey yapamayacaktır. Bir diğer deyişle, beklenmedik durum dış kaynaklıdır ve programcının yapacağı fazla bir şey yoktur. Örneğimizde, özyinelemeli çözümü for döngülüsüyle değiştirerek belki bir şeyler yapabiliriz ama bu her zaman mümkün olmayacaktır. İş böyle olunca, kotarımdan söz etmek de mantıksızdır.

Bu ana kadarki bilgilerimizi maddeleyerek özetleyecek olursak aşağıdaki listeyi elde ederiz. Java programları, iç ve dış koşullara göre beklenenden farklı bir şekilde davranabilir. Davranış sapması programcının müdahele edemeyeceği dış kaynaklardan ötürüyse, programcı Error köklü sıradüzenindeki sınıflardan birisinin nesnesi ile haberdar edilir. Yok eğer oluşan durum içselse ve programcı tarafından çözüm bulunabilecek gibi gözüküyorsa, programcının ihtiyacının duyduğu bilgi RuntimeException köklü sıradüzenindeki sınıflardan birisinin nesnesi ile sağlanır. Aksi takdirde, programcının yaptığı mantıksal bir hata dönüşerek ayrıksı durum haline gelmiştir ve bu, programcının dersini tamamlaması için, Exception köklü sıradüzenindeki diğer sınıfların nesneleri ile haber verilmelidir.

  • Throwable: Hata ve ayrıksı durumlar
    • Error: JSM'nin göçmesine neden olan hatalar
    • Exception: Ayrıksı durumlar
      • RuntimeException ve altsınıfları: Kotarılması zorunlu olmayanlar
      • Kotarılması zorunlu olanlar

Hangi kategoriye giriyor olursa olsun, programcı tüm koşullar altında elinden gelenin en iyisini sergilemelidir. Mümkünse, kotarıcıdaki kod ile programın sağlıklı bir şekilde devamı sağlanmalı, bu mümkün olmuyorsa, nasıl olsa bir şey yapamıyorum, denilerek oturmaktansa programın zarif bir şekilde bitmesi için elden gelen yapılmalıdır. Mesela, kullanılmakta olan dış kaynaklar kotarıcıda döndürülmeli, programın bakıcısının muhtemel bir programlama hatasını bulmasını kolaylaştırmak adına bir seyir defterine neden ile ilgili bilgiler not düşülmelidir. Bu noktada karşımıza, ayrıksı durum oluşsa da oluşmasa da çalıştırılacak kodu içeren bir kotarıcı olarak finally çıkıyor. Bir örnek ile görelim.
public void m() {
  ...
  try {
    FileReader dosya = new FileReader(...);
    ... 
  } catch (java.io.IOException a) { ... }
    catch (ADurumu a) { return; }
    ...
    catch (Exception a) { ... }
    finally { dosya.close(); ... }
  ...
} // void m() sonu
Birden çok kotarıcıya sahip yukarıdaki kod parçasında, try bloğunun işlenmesi sırasında oluşacak duruma göre farklı kotarıcılar devreye girebilir ya da işler yolunda giderse denetim akışı finally sonrasındaki komutla devam eder. Ancak, ne olursa olsun, finally kotarıcısı herhalükâda işlenecektir. Bu, başka hiçbir şey yapmadan çağırıcıya dönen ADurumu ayrıksı durumu için de geçerlidir; çünkü metottan dönüş öncesinde işlenmemiş finally kotarıcıları sırayla işlenir. Dolayısıyla, ortaya çıkan ayrıksı durum nedeniyle işlenen kotarıcıda metottan dönülse bile kapatılamamış olan dosya muhakkak kapatılacaktır.

Kod parçasında dikkat çekilmesi gereken bir diğer nokta, kotarıcıların yerleştiriliş sırasının önemli olduğudur. Bir kotarıcı sadece argümanındaki sınıfa ait nesneleri değil, o sınıfın kökü olduğu sıradüzeni içindeki tüm sınıfların nesnelerini yakalar. Örneğin, yukarıdaki ilk kotarıcı IOException nesnelerini yakaladığı gibi, FileNotFoundException'ın da içinde bulunduğu pek çok sayıda sınıfın nesnelerini de yakalayacaktır. Benzer şekilde, Exception argümanlı kotarıcı, kendisinden önce geçen kotarıcılarda yakalanmayan tüm ayrıksı durum nesnelerini yakalayarak amansız—bir o kadar da anlamsız—bir gümrük memuru görevini görecektir. Böylesine bir kotarıcının başa konulması—genelde, bir üstsınıf türlü argümana sahip kotarıcının altsınıf türlü argümana sahip kotarıcıdan önce gelmesi—her şeyi yakaladığı için takip eden kotarıcıları anlamsız kılacaktır. Kotarıcıların, özelden genele olacak şekilde sıralanması gerekir.

Evet, geldik kendi ayrıksı durumunu kendin pişirmeye. Ama, yazımızın vardığı uzunluğu ve muhtemel konsantrasyon azalmasını düşünerek bunu bir başka yazıya bırakalım. Onun için, şimdilik bu kadar. Unutmayın, hatasız program(cı) olmaz; birazcık defansif programlama ortaya çıkabilecek zararın faturasını azaltır. Onun için sadece doğruluk değil, dayanıklılığa da önem verin. Yani, güvenilir yazılım üretin.


  1. Yazılımın benzetimi yapılan sürecin geliştiriciler (alan uzmanı, gereksinim toplayıcı, mimar, tasarımcı ve gerçekleştirimci) tarafından yorumlanması ile ortaya çıktığı unutulumamalıdır. Dolayısıyla, doğruluk özelliğini sürecin algılanışına göre yapmak daha doğru olur.
  2. Standart hata ortamı, bir programın işleyişi sırasında üretilen hata mesajlarının gönderildiği aygıttır ve yeniden yönlendirme yapılmadığı veya System.setErr metodu kullanılarak değiştirilmediği müddetçe standart çıktının da gönderildiği varsayılan aygıt olan ekrandır.
  3. Yapılan ayrımın bir diğer olumlu yanı, programların fazladan try-catch yapıları ve throws bildirimleri ile doldurulmamasıdır. Örnek olarak verdiğimiz iki ayrıksı durum düşünüldüğünde—tüm dizi erişimi ve ileti gönderimlerinin try-catch yapısı içine alındığını veya bu işlemleri içeren metotların imzasına ayrıksı durum ilanının eklendiğini düşünün—kotarımın zorunlu olmamasının kaynak kodun okunabilirliğine yaptığı katkı takdir edilecektir.