Ders 11 / 12
İstisnalar
rescue, ensure ve retry yapılarının dilin kendi sözdizimi olduğu, raise'in ise bir ileti olduğu ve rescue dalındaki eşleşmenin bir sınıf iletisiyle yapıldığı ölçülür.
Yayın Güncelleme
Bu derste
- rescue ile bir istisnayı yakalar ve e.class ile türünü okur
- Kendi istisna sınıfını StandardError'dan türetir ve raise ile fırlatır
- ensure ile kaynak temizleme ve retry ile yeniden deneme yazar
- rescue yapısının sözdizimi, raise'in ise ileti olduğunu ölçer
İçindekiler
Önceki dersin sonunda bir lambda yanlış sayıda argümanla çağrıldığında ArgumentError fırlattı ve bu istisna rescue ile yakalandı. Bu ders o yapıyı konu yapar. İstisna (exception), normal akışı kesen bir olaydır; fırlatma (raise) onu üretir, yakalama (rescue) onu karşılar. M12/K01 PHP’de hata kanallarını ölçmüştü; burada hata kanalı sorusu sorulmuyor, yalnız yapının kim tarafından yanıtlandığı ölçülüyor.
Bu dersin yanıtlayanı sözdizimidir: rescue bir yöntem değil, dilin kendi yapısıdır.
rescue Bir İfadedir
Çekirdek gözlemi defined? sorusuna dayanır: bir begin ... rescue ... end bloğu için defined? ne diyor? Yöntem çağrısı olsaydı "method" dönerdi.
# yanit.rb — bir iletiyi kimin yanitladigini siniflandiran yardimcilar
# Siniflandirma sirayla sorar: once ifade bir ileti mi (defined?),
# sonra iki farkli blok iki farkli sonuc veriyor mu, sonra yontemin sahibi kim.
def yanitlayan(tanim, sahip: nil, sonuc: nil, bloklu: nil)
return ["sozdizimi", "defined? #{tanim}"] unless tanim == "method"
if bloklu
birinci, ikinci = bloklu.call
return ["blok", "#{birinci.inspect} / #{ikinci.inspect}"] unless birinci == ikinci
end
tur = sahip.instance_of?(Class) ? "sinif" : "modul"
[tur, "#{sahip}, sonuc #{sonuc.inspect}"]
end
# kurtarma.rb — rescue yapisini kim yanitliyor
require_relative "yanit"
tanim = defined?(begin; Integer("x"); rescue ArgumentError; 0; end)
tur, kanit = yanitlayan(tanim)
puts "#{tur} #{kanit}"
deger = Integer("x") rescue 0
puts deger
puts Integer("x", exception: false).inspect
sozdizimi defined? expression 0 nil
İlk satır çekirdeğin kurtarma gözlemidir: yanıtlayan sözdizimi, defined? "expression" verdi. İkinci satır deyim değiştirici biçimi gösterir: Integer("x") rescue 0 istisnayı yakalayıp 0 verdi. Üçüncü satır aynı işi exception: false anahtarlı argümanıyla, istisna fırlatmadan yaptı ve nil döndürdü. KB15. Bu ders değiştirici rescue’ü yalnız ArgumentError ile ölçer; başka istisna sınıflarını yakalayıp yakalamadığı ölçülmez.
İkinci satırda Integer("x") rescue 0 önce bir değişkene atandı, sonra basıldı; değiştirici biçim bir yöntem argümanının ya da defined?’ın içine doğrudan yazılmadı. Bu konumlarda nasıl çözüldüğü bu derste ölçülmez.
İstisna Sınıfları
Her istisna bir sınıfın örneğidir. e.class onun türünü verir; iletisi (e.message) yerine sınıf adı kullanılır, çünkü ileti metni sürümle değişebilir. Dersin kendi raise metni bu kuralın dışındadır.
# siniflar.rb — istisna siniflari
def dene
yield
rescue => e
e.class
end
puts dene { 1 / 0 }
puts dene { raise "x" }
puts dene { Integer("x") }
puts 1.0 / 0
class StokBitti < StandardError; end
begin
raise StokBitti, "stok bitti"
rescue StokBitti => e
puts "#{e.class} #{e.message}"
end
ZeroDivisionError RuntimeError ArgumentError Infinity StokBitti stok bitti
Sıfıra tam sayı bölmesi ZeroDivisionError fırlattı; ondalık bölme ise istisna değil Infinity verdi. Düz raise "x" bir RuntimeError üretti. Kendi sınıfımız StokBitti, StandardError’dan türedi ve rescue StokBitti => e yalnız onu yakaladı. İleti burada dersin kendi raise metni olduğu için basılabildi.
ensure ve retry
ensure dalı istisna olsa da olmasa da koşar; kaynak temizleme için kullanılır. retry ise begin bloğunu baştan çalıştırır.
# deneme.rb — ensure ve retry
deneme = 0
begin
deneme += 1
raise "henuz degil" if deneme < 3
puts "basardi #{deneme}"
rescue
retry
end
olaylar = []
begin
olaylar << 1
raise "hata"
rescue
olaylar << :kurtarildi
ensure
olaylar << 2
end
puts olaylar.inspect
basardi 3 [1, :kurtarildi, 2]
retry her başarısız denemede bloğu yeniden başlattı ve üçüncü denemede iş tamamlandı. İkinci blokta ensure dalı, istisna yakalandıktan sonra da koştu. KB16. Yeniden deneme sayısı dersin kendi sayacıdır; gerçek bir yeniden deneme senaryosunda sonsuz döngüyü önlemek için bir üst sınır gerekir ve bu burada ölçülmez.
Dallar ve else
Bir begin bloğu birden çok rescue dalı taşıyabilir; istisna ilk eşleşen dala gider. else dalı yalnız hiçbir istisna olmadığında koşar. Bir sınıfın üst sınıfı superclass ile sorulur.
# dallar.rb — rescue dallari ve else
def sinifla(deger)
Integer(deger)
rescue ArgumentError
"gecersiz"
rescue TypeError
"tip"
else
"tamam"
end
puts sinifla("5")
puts sinifla("x")
puts sinifla(nil)
puts ZeroDivisionError.superclass
puts StandardError.superclass
tamam gecersiz tip StandardError Exception
"5" istisna doğurmadığı için else dalı koştu. "x" ArgumentError dalına, nil ise TypeError dalına gitti: ilk eşleşen dal kazandı. ZeroDivisionError’ın üst sınıfı StandardError, onun üst sınıfı ise Exception çıktı; çıplak rescue bu yüzden ZeroDivisionError’ı yakalar. KB18. Dalların sırası önemlidir: geniş bir sınıf önce yazılırsa ondan sonraki dallar hiç koşmaz, ama bu davranış burada ölçülmez.
StandardError ve Exception
Parametresiz rescue ve rescue => e yalnız StandardError ve alt sınıflarını yakalar. Hiyerarşide StandardError’ın dışında kalan istisnalar bu yakalayıcıdan geçer. Aşağıdaki ölçüm bir ArgumentError’ın yakalandığını, bir NotImplementedError’ın yakalanmadığını ve ilgili üst sınıfları gösterir.
# hiyerarsi.rb — rescue hangi istisnalari yakalar
def sar
yield
"yok"
rescue => e
"yakalandi #{e.class}"
end
puts sar { raise ArgumentError }
begin
sar { raise NotImplementedError }
rescue NotImplementedError => e
puts "disarida #{e.class}"
end
puts NotImplementedError.superclass
puts ScriptError.superclass
puts KeyError.superclass
puts IndexError.superclass
yakalandi ArgumentError disarida NotImplementedError ScriptError Exception IndexError StandardError
ArgumentError yakalandı ve yakalandi ArgumentError basıldı. NotImplementedError ise sar’ın rescue’undan geçti ve dıştaki begin bloğunda yakalandı. Üst sınıf zinciri bunu açıklar: NotImplementedError ScriptError’ın, o da Exception’ın altındadır; StandardError’a ulaşmaz. KeyError ise IndexError üzerinden StandardError’a bağlıdır. KB34. Yalnız bu dört sınıfın üst zinciri basıldı.
ensure Dönüş Değerini Değiştirmez
ensure dalı, yöntem nasıl biterse bitsin koşar. Ancak bu dalın son ifadesi yöntemin dönüş değeri olmaz. return ile erken çıkıldığında da ensure koşar.
# ensure_deger.rb — ensure dali ve donus degeri def deger 1 ensure 2 end def erken return 1 ensure puts "temizlik" end puts deger puts erken
1 temizlik 1
deger 1 döndürdü; ensure içindeki 2 dönüş değerini değiştirmedi. erken return 1 ile çıktı, ama ensure önce temizlik satırını bastı ve ardından 1 döndü. KB35. Yalnız bu iki biçim ölçüldü; ensure içinde return yazılması ölçülmedi.
Yeniden Fırlatma
rescue içinde argümansız raise, yakalanan istisnayı olduğu gibi yeniden fırlatır. Böylece bir katman olayı kaydeder, ama kararı üst katmana bırakır.
# yeniden.rb — raise ile yeniden firlatma
def disari
yield
rescue ArgumentError => e
puts "kayit #{e.class}"
raise
end
begin
disari { Integer("x") }
rescue => e
puts "yeniden #{e.class}"
end
kayit ArgumentError yeniden ArgumentError
İç rescue önce kayit ArgumentError satırını bastı ve argümansız raise ile aynı istisnayı fırlattı. Dıştaki rescue aynı sınıfı gördü: yeniden ArgumentError. KB36. İstisna iletisi basılmadı; yalnız sınıf ölçüldü.
retry Sınırı ve Neden Zinciri
retry bloğu baştan koşturur; sınırsız bırakılırsa sonsuz döngüye girer. Bu yüzden her zaman bir sayaçla birlikte yazılır. Bir rescue içinde yeni bir istisna fırlatılırsa, önceki istisna cause ile yeni olanın üzerinde kalır.
# sinir.rb — retry sayaci ve cause
deneme = 0
begin
deneme += 1
raise "x" if deneme < 10
rescue
retry if deneme < 3
puts "vazgecti #{deneme}"
end
begin
begin
Integer("x")
rescue => e
raise RuntimeError, "ust"
end
rescue => ust
puts ust.cause.class
end
vazgecti 3 ArgumentError
İlk iki turda retry bloğu yeniden koşturdu; üçüncü turda koşul sağlanmadığı için vazgecti 3 basıldı. İkinci bölümde Integer("x") bir ArgumentError fırlattı, rescue içinde yeni bir RuntimeError fırlatıldı ve dıştaki ust.cause.class ArgumentError verdi. KB37. retry yalnız begin ve rescue biçiminde ölçüldü.
Çoklu Sınıf ve Dal Sırası
Bir rescue birden çok sınıfı virgülle sayabilir. Birden çok rescue dalı varsa, ilk eşleşen dal koşar ve sonrakilere bakılmaz.
# siralama.rb — rescue listesi ve dal sirasi
def sinifla
yield
rescue ZeroDivisionError, TypeError
"ikisi"
rescue StandardError
"genel"
end
puts sinifla { 1 / 0 }
puts sinifla { 1 + nil }
puts sinifla { Integer("x") }
def ters
yield
rescue StandardError
"genel"
rescue ZeroDivisionError
"ozel"
end
puts ters { 1 / 0 }
ikisi ikisi genel genel
rescue ZeroDivisionError, TypeError hem 1 / 0’ı hem 1 + nil’i yakaladı: ikisi de ikisi basıldı. Integer("x") bu listede yoktu ve genel dalına düştü. ters yönteminde StandardError önce yazıldı; 1 / 0 bu dala takıldı ve ozel dalına hiç ulaşılmadı. Özel sınıf, genel olandan önce yazılmalıdır. KB48. Ölçüm bu üç istisna sınıfıyla yapıldı.
begin, else ve ensure Sırası
Bir begin bloğundaki dallar belirli bir sırayla koşar. Dalların hangi sırayla çalıştığı, bir diziye eklenen sembollerle okunur.
# sira.rb — dallarin kosma sirasi sira = [] begin sira << :govde rescue sira << :kurtarma else sira << :else ensure sira << :ensure end puts sira.inspect sira = [] begin sira << :govde raise "x" rescue sira << :kurtarma else sira << :else ensure sira << :ensure end puts sira.inspect
[:govde, :else, :ensure] [:govde, :kurtarma, :ensure]
Hata yokken else gövdeden sonra, ensure en son koştu. Hata fırlatılınca rescue devreye girdi, else atlandı ve ensure yine koştu. ensure iki durumda da son sırada yer aldı. KB49.
Alanlı İstisna Sınıfı
Kendi istisna sınıfınız ek bilgi taşıyabilir. initialize içinde alanı saklar, super ile iletiyi verirsiniz.
# alanli.rb — kendi alani olan istisna sinifi
class StokHatasi < StandardError
attr_reader :adet
def initialize(adet)
@adet = adet
super("stok yetersiz")
end
end
begin
raise StokHatasi.new(3)
rescue StokHatasi => e
puts "#{e.class} adet=#{e.adet} ileti=#{e.message}"
end
puts StokHatasi.superclass
puts StokHatasi.instance_method(:adet).owner
StokHatasi adet=3 ileti=stok yetersiz StandardError StokHatasi
StokHatasi StandardError’dan türedi, adet alanını taşıdı ve super ile kendi ileti metnini verdi. rescue alana e.adet ile eriştiği için adet=3 basıldı; ileti=stok yetersiz dersin kendi yazdığı metindir. adet okuyucusunun sahibi StokHatasi’dır: bu yöntemi yanıtlayan sınıftır. KB50.
catch ve throw
catch ve throw etiketli bir çıkış sağlar. throw, aynı etiketle açılmış en yakın catch bloğuna kadar çıkar ve ona bir değer taşır.
# etiket.rb — catch ve throw
r = catch(:bitti) do
[1, 2, 3].each { |n| throw :bitti, n * 10 if n == 2 }
:hic
end
puts r
puts "sahip=#{method(:throw).owner}"
20 sahip=Kernel
throw :bitti, 20 iç each bloğunu ve dıştaki catch bloğunu bitirdi; catch :hic yerine 20 döndürdü. throw’un sahibi Kernel olarak basıldı. KB51. Yalnız bu tek etiketli örnek ölçüldü.
İç İçe ensure ve rescue Eki
İç bir begin bloğunda ensure varsa ve istisna orada yakalanmıyorsa, istisna dışarı çıkmadan önce ensure koşar. Kısa bir yazım olarak ifade rescue değer, ifade hata verirse değeri kullanır; bu yazım parantezle bir değişkene atanabilir.
# ic_ice.rb — ic ice ensure ve rescue eki
kayit = []
begin
begin
kayit << :ic
raise ArgumentError
ensure
kayit << :temizlik
end
rescue ArgumentError
kayit << :dis
end
puts kayit.inspect
kurtarilan = (Integer("x") rescue -1)
puts kurtarilan
[:ic, :temizlik, :dis] -1
İç blokta istisna yakalanmadı; ama dışa çıkmadan önce ensure koştu ve sıra [:ic, :temizlik, :dis] oldu. Yani ensure, dıştaki rescue’dan önce çalışır. Integer("x") hata verince ek yazımdaki -1 kullanıldı. KB55. Ek yazımın yalnız StandardError ailesini yakaladığı ölçülmedi; bu derste yalnız bu tek örnek denendi.
Sınırlayıcı Ölçüm
raise bir ileti, yani bir yöntem çağrısıdır; rescue ise sözdizimidir. İkisinin yanıtlayanı farklıdır. Bir de rescue ArgumentError => e dalının kendi içinde bir eşleşme yaptığı sorulabilir: dal, istisnayı ArgumentError sınıfına bir iletiyle mi soruyor?
# eslesme.rb — rescue dalinin eslesmesi
puts "raise sahibi=#{method(:raise).owner}"
begin
Integer("x")
rescue ArgumentError => e
puts ArgumentError === e
puts "=== sahibi=#{ArgumentError.method(:===).owner}"
end
raise sahibi=Kernel true === sahibi=Module
raise bir yöntemdir ve sahibi Kernel modülüdür. Dalın eşleşmesi === iletisiyle yapılır: ArgumentError === e true verdi ve ===’in sahibi Class değil Module’dür, yani sınıf kendi ===’ini üst yapıdan alır. Bu, ayrımı netleştirir: rescue anahtar sözcüğünü dil çözer, ama hangi dalın istisnayı alacağını sınıfın === yanıtı belirler. Dil yapıyı kurar, sınıf eşleşmeyi karar verir. Aynı rescue satırında yazılan sınıf bu yüzden sıradan bir sınıf nesnesidir ve kendi eşleşme iletisini yanıtlar. KB17. Bu ölçüm yalnız ArgumentError dalı için yapıldı; farklı istisna sınıflarının aynı iletiyi aynı sahipten aldığı varsayılmaz.
Sonuç, çekirdeğin sınıflandırmasıyla uyumludur: defined? ifadeyi "expression" olarak adlandırdı, yani rescue anahtar sözcüğü bir ileti değildir; dal eşleşmesi ise yukarıda görüldüğü gibi === iletisiyle yapılır. Fırlatan taraf ise farklıdır; raise sıradan bir yöntemdir, Kernel modülünden gelir ve istisna sınıfını ya da metni argüman olarak alır. Böylece bir istisna olayında iki yanıtlayan yan yana durur: fırlatmayı bir modül, yakalamayı dilin kendisi yanıtlar. Dersin yanıtlayanı olarak sözdiziminin seçilmesinin nedeni de budur: ölçülen soru, yakalama yapısının kim tarafından kurulduğudur.
Özet
- İstisna, normal akışı kesen bir olaydır;
rescueyakalar,raisefırlatır,ensureher durumda koşar,retrybloğu yeniden başlatır. - Bu dersin yanıtlayanı sözdizimidir:
defined?begin ... rescue ... endiçin"expression"döndürür. - Deyim değiştirici
rescueveexception: falseaynı işi iki yolla görür: istisna yerine bir değer. raisebir yöntemdir ve sahibiKernelmodülüdür;rescuedalının eşleşmesi===iletisiyle yapılır.- Sınırlayıcı ölçüm:
rescuesözdizimidir ama dalının içindeki eşleşme bir sınıf iletisidir.
Sonraki Adım
Bu derste istisna fırlatan işlemlerin hepsi bellekte çalıştı. Son derste dosyalar ve girdi çıktı gelir: bir dosyayı açan blok mu, yöntem mi, ve ensure bu işte hangi yapıyı temizler.
İlerlemeni kaydetmek ve not almak için Giriş yap
Notlarım
Not almak için giriş yapmalısın.