24. Hata İşleme: rescue deyimi

Çalıştırılan bir yazılım beklenmeyen sorunlar doğurabilir. Okunmaya çalışılan bir dosya mevcut olmayabilir ya da veri kaydetmemek istediğimiz disk dolu olabilir yada kullanıcı beklenmeyen bir girdi yapabilir.

ruby> file = open("bir_dosya")
ERR: No such file or directory @ rb_sysopen - bir_dosya

Güçlü bir yazılım bu gibi durumları hassasiyetle yakalayacaktır. C yazılımcılarından, hata doğurabilecek her sistem çağrısının sonucunu kontrol etmeleri ve anında ne yapılacağına ilişkin karar vermeleri beklenir:

FILE *file = fopen("bir_dosya", "r");
if (file == NULL) {
  fprintf( stderr, "Dosya mevcut değil.\n" );
  exit(1);
}
bytes_read = fread( buf, 1, bytes_desired, file );
if (bytes_read != bytes_desired ) {
  /* hata giderme işlemleri... */
}
...

Bu yazılımcıları dikkatsizliğe ve ihmalciliğe iten, üstelik hataları tam olarak yakalayamayan bir yazılım geliştirmenize yol açan sıkıcı bir uygulamadır. Öte yandan, işi doğru düzgün yapmak, yakalanabilecek bir çok hata olduğu için yazılımın okunabilirliğini oldukça zorlaştıracaktır.

Güncel bir çok dilde olduğu gibi Ruby'de de, yazılımcıyı ya da sonradan kodumuzu okuyan kişileri sıkıntıya sokmadan, sürprizleri kod bloklarından soyutlayan bir yolla yakalayabiliriz.

begin ile işaretlenmiş kod bloğu bir istisnayla karşılaşana dek çalıştırılır, hata durumunda denetimi rescue ile işaretlenmiş kod bloğuna verir. Eğer hiçbir istisnayla karşılaşılmazsa rescue kodu kullanılmaz. Aşağıdaki yöntem bir metin dosyasının ilk satırını, bir istisna ile karşılaşırsa nil döndürür:

def first_line( filename )
  begin
    file = open("bir_dosya")
    info = file.gets
    file.close
    info  # Değerlendirmeye alınan son şey dönüş değeri
  rescue
    nil   # Dosyayı okuyamıyor musunuz? O zaman bir ileti dönmez.
  end
end

Bir sorunla yaratıcı bir biçimde ilgilenmek istediğimiz zamanlar olacaktır. Örneğin dosyaya erişmek mümkün değilse standart girdi yerine başka bir şey kullanmak isteyebiliriz:

begin
  file = open("bir_dosya")
rescue
  file = STDIN
end

begin
  # ... girdiyi değerlendir ...
rescue
  # ... burada diğer istisnalarla uğraş.
end

begin kodunu tekrar çalıştırmak için rescue'nun içinde retry kullanabiliriz. Bu bize önceki örneğimizi daha kısa şekilde yazmamıza izin verir:

fname = "bir_dosya"
begin
  file = open(fname)
  # ... girdiyi degerlendir ...
rescue
  fname = "STDIN"
  retry
end

Ancak burada bir kusur bulunmaktadır. Hiç olmayan bir dosya bu kodun sonsuz bir döngüde kendisini tekrar etmesini sağlayacaktır. retry kullanırken bu tür durumlara dikkat etmelisiniz.

Her Ruby kütüphanesi, sizin de kendi kodunuzda yapabileceğiniz gibi, herhangi bir hata karşısında bir istisna oluşturur. Bir istisnayı ortaya çıkarmak için raise kullanılır. raise tek değer olarak istisnayı açıklayan bir dizge alır. Bu değer isteğe bağlıdır ancak atlanmaması gereken bir husustur. Özel değişkenlerden olan $! ile sonradan ulaşılabilir.

ruby> raise "deneme hatası"
ERR: deneme hatası
ruby> begin
    |    raise "dnm2"
    | rescue
    |    print "Bir hata meydana geldi: ",$!, "\n"
    | end
Bir hata meydana geldi: dnm2
   nil