İçeriğe geç
academia.sh

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; rescue yakalar, raise fırlatır, ensure her durumda koşar, retry bloğu yeniden başlatır.
  • Bu dersin yanıtlayanı sözdizimidir: defined? begin ... rescue ... end için "expression" döndürür.
  • Deyim değiştirici rescue ve exception: false aynı işi iki yolla görür: istisna yerine bir değer.
  • raise bir yöntemdir ve sahibi Kernel modülüdür; rescue dalının eşleşmesi === iletisiyle yapılır.
  • Sınırlayıcı ölçüm: rescue sö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.

Aramak için yazmaya başlayın.

↑↓ ↵ Esc gezin · aç · kapat