21 Şubat 2025 Cuma

Ruby Temelleri 4

https://ujk-ujk.blogspot.com/2025/02/ruby-temelleri-4.html
İçindekiler +

 

Merhaba, Ruby dilinin temel tekniklerini incelediğimiz yazımıza devam ediyoruz.


String Değerler

-- Deyim yapıları

"Bir string" # tek tırnak kullanarak string nesne üretimi
"Bir string" # çift tırnak kullanarak string nesne üretimi
String.new("Bir string") # String sınıfı 'new' metodu ile
%q(Bir string) # tek tırnakla string üretmenin alternatif şekli
%Q(Bir string) # çift tırnakla string üretmenin alternatif şekli



-- Tek tırnaklı ve çift tırnaklı string'ler arası farklar

Ana fark , çift tırnaklı string'ler enterpolasyonu ve tüm escape karakterleri kabul ederler. Örneğin enterpolasyon içinde Ruby kodu çalıştırabilirler. 

# tek tırnaklılar enterpolasyonu kabul etmez
puts 'Tarih-zaman değerimiz #{Time.now}'
# Tarih-zaman değerimiz #{Time.now}

# çift tırnaklılar enterpolasyonu kabul eder
puts "Tarih-zaman değerimiz #{Time.now}"
# Tarih-zaman değerimiz 2025-02-21 13:39:21 +0300


Çift tırnak ile tanımlanan string'ler \n veya \t gibi escape sequence de kabul eder (ayrıca bakınız backslash notasyonu). 

puts 'Merhaba\nDünya'
# Merhaba\nDünya

puts "Merhaba\nDünya"
# Merhaba
# Dünya


Tek tırnaklı ifadeler her ne kadar escape karakterleri kabul etmese de değer içindeki tek tırnağı ifade etmek için ( \' ) ve bu ikisi string içinde böyle denk gelirse, ters slash'ı ifade için de ( \\ ) kullanımı mecburen olmak zorunda kalınılmıştır. 

puts 'Ümit\'in arabası'
#=> Ümit'in arabası
puts 'c:\belgeler\\\'deki dosyalar'
#=> c:\belgeler\'deki dosyalar

gibi.



-- Bir String nesnesi üretmek

Biliyoruz ki "Ruby'de her şey bir nesnedir", ve string bir değer de tabii ki bir nesnedir, o yüzden "string değer tanımlamak" yerine "string nesnesi üretmek" diyoruz. Ruby'de String nesnesi üretmenin değişik yolları vardır. İlk ve en çok kullanılan yöntem, tek tırnak ya da çift tırnak içinde bir string değer ifadesi kullanmak.

s1 = 'Hello'
s2 = "Hello"


İkisinin arasındaki ana farkı az önce söyledik, çift tırnaklılar enterpolasyon ve escape sequence kabul eder. 

Bir diğer yöntem de birbirini karşılayan string sınırlayıcıları içinde tanımlamaktır.

%(Bir string)
%{Bir string}
%<Bir string>
%|Bir string|
%!Bir string!

gibi. Birbirini karşılayan parantez, büyük -küçük veya köşeli parantezlerde string içinde de birbirini karşılayan aynı karakterlerde Ruby işi hemen anlar.

>> %(a (b) c)
=> "a (b) c"
>> %[a b[0] c]
=> "a b[0] c"

gibi.

Son olarak da sırasıyla tek tırnağa ve çift tırnağa eşlenik olan %q ve %Q deyimleri.

puts %q(Bir string)
#=> Bir string
puts %q(Zaman değerimiz: #{Time.now})
#=> Zaman değerimiz: #{Time.now}

puts %Q(Bir string)
#=> Bir string
puts %Q(Zaman değerimiz: #{Time.now})
#=> Zaman değerimiz: 2025-02-21 14:09:21 +0300


%q veya %Q yapıları özellikle string'ler içinde karışık olarak tek tırnak veya çift tırnakların bol olduğu yerlerde habire escape etmeye uğraşmamak için faydalıdır. 

%Q(<a href="/profile">User's profile<a>)

gibi. Sınırlayıcı karakter olarak da eşleşen karakterler kullanılabilir. Yukarıda % ile yaptıklarımız aslında %Q ile yapılmış gibi kabul görür. 

%q(tek tırnak\n içinde)
%Q{çift tırnak\n içinde}
%<çift tırnak\n içinde>



-- Harf büyüklüğü değiştirme

"string".upcase     # => "STRING"
"STRING".downcase   # => "string"
"StrinG".swapcase   # => "sTRINg"
"string".capitalize # => "String"


Bu dört metod orjinal değeri değiştirmez, manipüle edilmiş değerde yeni bir string üretir. 

>> str = "Merhaba"
>> str.upcase
=> "MERHABA"
>> str
=> "Merhaba"


Bu metodların dördünün de orjinali değiştiren metodları aynı ismin sonuna ünlem işareti ekleyerek tanımlıdır - bu demek değil ki her metodun sonuna ünlem konunca orjinal değişecek. 

"string".upcase!     # => "STRING"
"STRING".downcase!   # => "string"
"String".swapcase!   # => "sTRING"
"string".capitalize! # => "String"

Örneğin,

>> str = "Merhaba"
>> str.upcase!
>> str
=> "MERHABA"



-- String'i parçalamak

String parçalamak atomu parçalamak kadar zor değil. String#split (metodu böyle diyez ile yazınca o sınıfın oluşum metodu olduğu ifade edilmek istenir, sınıf metodu olsa direk String.split yazılırdı) metodu kullanarak string değer belirtilen ayırıcı ile parçalanmış şekilde bir array'a dönüştürülür.

>> "alfa,beta".split(",")
=> ["alfa", "beta"]

Ayırıcı değerine istediğimizi girebiliriz. 

>> "geliyoring gidiyoring yapıyoring".split("ing")
=> ["geliyor", " gidiyor", " yapıyor"]



-- String pozsiyonlama

Ruby string'ler sola yaslanmış, sağa yaslanmış ve ortalanmış olarak şekillendirilebilir. 

Sola yaslanmış yazılar için ljust metodu kullanılır. İki parametre alır, oluşturulacak string'in karakter sayısı, ve geri kalan boşlukların doldurulacağı şablon. 

Eğer verilen karakter sayısı, string nesnenin karakter sayısından fazlaysa geri kalan karakterler şablona göre doldurulur. Şablon verilmemişse boşluk bırakılır. 

>> str ="abcd"
>> str.ljust(4)
=> "abcd"
>> str.ljust(10)
=> "abcd      "
>> str.ljust(10, "-")
=> "abcd------"
>> str.ljust(10, "0-")
=> "abcd0-0-0-"


Sağa yaslanmış yazı için rjust (right justify) metodu kullanılır. Kullanım ve parametreler olarak ljust ile aynıdır. 

>> str ="abcd"
>> str.rjust(4)
=> "abcd"
>> str.rjust(10)
=> "      abcd"


Yazıyı ortalamak için center metodu kullanılır. 

>> str ="abcd"
>> str.center(4)
=> "abcd"
>> str.center(10)
=> "   abcd   "
>> str.center(10, "*")
=> "***abcd***"




-- Array'lerde birleştirerek string yapmak

String#split metodu string'i string'lerden oluşan array olarak parçalıyordu. Array#join metodu da tersini yapar, yani string elemanlardan oluşan bir array'i elemanlarını bir birleştirici ile arka arkaya dizerek stringe dönüştürür. 

>> ["alfa", "beta"].join(",")
=> "alfa,beta"


Birleştirici değer opsiyoneldir, verilmezse boş string olarak kabul edilir.

>> ["alfa", "beta"].join
=> "alfabeta"


Boş bir array birleştiricinin ne olduğuna bakılmaksızın boş bir string verir.

>> [].join ","
=> ""



-- String#start_with? metodu

Bir stringin verilen şablonla başladığını test etmek için start_with? metodu kullanılır. 

>> str = "bugün Pazar"
>> str.start_with?("bu")
=> true


Şablonun başladığı index değerinin sıfır olmasını kontrol ederek de başlangıcında şablonun olduğunu test edebiliriz.

>> str.index("bu").zero?
=> true




-- String enterpolasyonu 

Enterpolasyon ile string değer içine hesaplanmış başka değerler enjekte edilir. Çift tırnaklı, %Q kullanan ya da % sınırlayıcılar içinde ifade edilen string'ler enterpolasyon kabul eder. Enterpolasyon ifade etmek için #{ruby_ifadesi} yapısı kullanılır. 

puts "Zaman değerimiz: #{Time.now}"
# Zaman değerimiz: 2025-02-22 12:30:56 +0300

puts %Q(Zaman değerimiz: #{Time.now})
# Zaman değerimiz: 2025-02-22 12:30:56 +0300

puts %[Zaman değerimiz: #{Time.now}]
# Zaman değerimiz: 2025-02-22 12:30:56 +0300


Süslü parantez içinde verilen Ruby kodu çalıştırılıp, dönen değer o noktaya enjekte edilir. Stringleri birleştirmek için + operatörü de kullanabiliriz, enterpolasyon da kullanabiliriz.

isim = "Ümit"
puts "Merhaba " + isim + "!"

# ya da

puts "Merhaba #{isim}!"


Enterpolayon kodu daha anlaşılabilir yapıyor, birleştirme şeklinde gidersek boşlukları falan kaçırmak gibi hatalar çok oluyor.



-- String end_with?

Bir string'in verilen şablonla bittiğini test için end_with? metodu kullanılır. 

>> str = "bugüm Pazar"
>> str.end_with? "azar"
=> true




-- Formatlanmış string

Bir array içinde verilen değerleri bir string içinde format ifadeleri ile gösterilen yerlere enjekte etmek mümkündür. 

>> "Merhaba %s, benim adım %s." % ["dünya", "Ümit"]
=> "Merhaba dünya, benim adım Ümit."

String içinde formatı %s (string) olarak verilen yerlere sırası ile array içinde verilen değerler enjekte edilir.

Format belirleyiciler:

  • %s - string değer
  • %d - tamsayı değer
  • %04d - tamsayı soluna 4 rakamı tamamlayana kadar sıfır eklenir.
  • %4d - tamsayıyı 4 karakterlik yere solunda boşluk bırakarak yazar 
  • %-4d - tamsayıyı 4 karakterlik yere sağında boşluk bırakarak yazar
  • %f - kayan noktalı sayı
  • %0.2f - Noktadan sonra sadecce 2 basamak gösterilen noktalı sayı
  • %x - sayıyı hex'e dönüştürerek yazar
  • %b - değeri binary'ye dönüştürerek yazar
  • %08b - soluna 8 rakama tamamlayana kadar sıfır ekler
  • %o - sayıyı octal değere dçnüştürerek yazar

puts "Adım %s, yaşım %s" % ["Ümit", 59]
#=> Adım Ümit, yaşım 59
puts "Adım %s, yaşım %d" % ["Ümit", 59]
#=> Adım Ümit, yaşım 59
puts "|%04d|%4d|%-4d|" % [17,17,17]
#=> |0017|  17|17  |
puts "PI = %f // %.2f" % [Math::PI, Math::PI]
#=> PI = 3.141593 // 3.14
puts "%b|%08b|%#b" % [14,14,14]
#=> 1110|00001110|0b1110
puts "%x|%04x|%#x" % [14,14,14]
#=> e|000e|0xe
puts "%o|%04o|%#o|%#o" % [14,14,14,-14]
#=> 16|0016|016|..762


Daha da ayrıntı isteyen Ruby Doc sayfasına göz atabilir. 



-- String içinde parça değiştirmek

Bir string içinde istediğimiz karakterleri değiştirmek amacıyla tr metodunu kullanabiliriz. 

>> "string ring".tr("r", "l")
=> "stling ling"
>> "string ring".tr("r", "ly")
=> "stling ling"
>> "string ring".tr("ri", "l")
=> "stllng llng"
>> "string ring".tr("ri", "ly")
=> "stlyng lyng"


Sadece ilk bulunan eşleşmeyi değiştirmek için sub metodu kullanılır. 

>> "string ring".sub("r", "l")
=> "stling ring"
>> "string ring".sub("r", "ly")
=> "stlying ring"
>> "string ring".sub("ri", "l")
=> "stlng ring"
>> "string ring".sub("ri", "ly")
=> "stlyng ring"


Dikkat ederseniz sub metodu tr metodundan biraz farklı davranıyor. Bulunan değeri verilen yeni değerle değiştirirken karakter sayılarına bakmıyor, aynen verildiği şekilde değiştiriyor.

Bu davranışı tüm eşleşmelerde uygulamak için gsub metodu kullanılır. 

>> "string ring".gsub("r", "l")
=> "stling ling"
>> "string ring".gsub("r", "ly")
=> "stlying lying"
>> "string ring".gsub("ri", "l")
=> "stlng lng"
>> "string ring".gsub("ri", "ly")
=> "stlyng lyng"


Bir parçayı silmek için ikinci argümanda boş string verebilirsiniz. 

Tüm bu metodlarda regex de kullanılabilir.

>> "şifrem 12345 ve arkasından 54321".gsub(/\d/, "*")
=> "şifrem ***** ve arkasından *****"


Bu metodlar yeni bir string kopyası oluşturur, mevcut string'i değiştirmek için tr!, sub! ve gsub! metodları kullanılır. 




-- String içindeki veriyi anlamak

Ruby'de bir string değer,  karakterleri ifade eden bir byte dizisi ve kodlama adından (UTF-8, US-ASCII gibi) oluşur.  

Ruby string'leri genelde yazıları saklamak için kullanılır. Standart kodlama UTF-8'dir.

>> "ümit".bytes
=> [195, 188, 109, 105, 116]
>> "abc".bytes
=> [97, 98, 99]
>> "ümit".bytes
=> [195, 188, 109, 105, 116]
>> "i".bytes
=> [105]
>> "ü".bytes
=> [195, 188]
>> "ümit".encoding.name
=> "UTF-8"


Gördüğümüz üzere standart ASCII kod tablosunda bulunmayan ü harfi iki byte yer işgal ediyor. Diğer Türkçe karakterlere de bir bakalım.

>> "ş".bytes
=> [197, 159]
>> "ç".bytes
=> [195, 167]
>> "ğ".bytes
=> [196, 159]
>> "ı".bytes
=> [196, 177]


Eski telefonlarda mesaj atarken Türkçe karakter kullanınca max harf sayısı hemen doluyor ve fazladan mesaj parası ödüyorduk. Bu yüzden İngilizce harfler kullanırdık, o saçmalık geldi aklıma.. 

Sayısal değerleri bir string içine ASCII-8BIT kodlama olarak bytelar şeklinde saklayabilirsiniz. 

>> [42].pack("i")
=> "*\x00\x00\x00"
>> [42].pack("i").bytes
=> [42, 0, 0, 0]
>> [42].pack("i").encoding.name
=> "ASCII-8BIT"


Integer'a dönüştürülmüş 4 byte (32 bit) veri olarak string içine paketlenmiş bir sayı. 

Bir string içinde byte değerler oluşturduğumuzda geçerli bir encoding vermiyorsa o string'de işlem yapmak hata verir.

>> "\x00 \x00 \xFF".bytes
=> [0, 32, 0, 32, 255]
>> "\x00 \x00 \xFF".valid_encoding?
=> false
>> "\x00 \x00 \xFF".split(" ")
=> "invalid byte sequence in UTF-8" (ArgumentError)




-- Çok satırlı string

Çok satırlı string olulturmanın en kolay yolu çift tırnak içinde çok satırlı olarak yazmaktır. 

reçete = "2 bardak un, 1.5 bardak süt, 1 yumurta
Malzemeler bir kapta çırpılarak karıştırılır
185 dereceye ısıtılmış fırnda 35 dakika pişirilir"


Böyle uzun yazıları yazarken en büyük problem içinde de tırnaklar olursa string'i kapatmasın diye ters slash ile escape etmek gerekiyor. Benzer şekilde string içinde her türlü karşılaşılabilecek kısıtlamalardan kurtulmak için here döküman kullanılır. 

puts <<HERE
Yolda karşılaştık , bana "merhaba" dedi,
   Ahmet'in yolda bulduğu mektubun sahibini aramış
Bulamayınca götürüp bakkala bırakmış.
HERE


VSCode editör bile ne renk vereceğini şaşırdı.. % stringleri de çok satırlı string değeri girmek için idealdir. 

puts %q(
Bir zamanlar, "Gramola" adında bir dağın yamacında
bir köy varmış. Bu köyde yaşayan insanlar, her sabah
dağın kenarından akan derenin verdiği enerjiyle mutlu
ve güçlü bir #{hayat}\\ sürerlermiş.
)


%q tek tırnak string ifade ettiği için enterpolasyon almasa da iki ters slash arka arkaya gelince bir tanesini yazdı. Enterpolasyon ve escape sequence'lerden korunmak için birkaç ilke var.

  • Çift tırnak yerine tek tırnak kullanın, '\n carriage return demektir.'
  • Yüzde string'lerinde küçük q tek tırnak demektir. %q[#{bir-değişken-değil}]
  • Here döküman etiket adını tek tırnak içine alın. 

puts <<-'CODE'
   puts 'Hello world!'\\
CODE

gibi.





Semboller

-- Sembol ifadeleri

>> :sembol          #=> :sembol
>> :'sembol'        #=> :sembol
>> :'sembol a'      #=> :"sembol a"
>> :"sembol"        #=> :sembol
>> "sembol".to_sym  #=> :sembol
>> %s{sembol}       #=> :sembol



-- String yerine sembol kullanma avantajı

-- -- Aynı değerdeki semboller aynı nesneyi gösterir

String'ler her biri ayrı bir nesnedir, semboller ise aynı olan semboller aynı nesneyi gösterir. 

>> a = "string"
>> b = "string"
>> a == b       #=> true
>> a.object_id  #=> 100480
>> b.object_id  #=> 106800

>> a = :sembol  #=> :sembol
>> b = :sembol  #=> :sembol
>> a == b       #=> true
>> a.object_id  #=> 2485148
>> b.object_id  #=> 2485148


String'leri karşılaştırırken içlerindeki tüm karakterleri karşılaştırmak zorundayız, bu da N karakterli bir string'de N+1 karşılaştıma yapacağımız anlamına gelir.

def string_compare str1, str2
  if str1.length != str2.length
    return false
  end
  for i in 0...str1.length
    return false if str1[i] != str2[i]
  end
  return true
end
string_compare "foobar", "foobar"


Ancak :foobar sembolünün her oluşumu aynı nesneyi gösterdiği için eşitliklerini sadece object_id değerlerine bakarak test edebiliriz.

def symbol_compare sym1, sym2
  sym1.object_id == sym2.object_id
end
symbol_compare :foobar, :foobar



-- -- Bir sembol enümerasyon değeri olarak kullanılabilir

C++ dilinde birbiri ile bağlantılı sabitleri enümerasyon ile ifade ederiz. 

enum BugStatus { OPEN, CLOSED };
BugStatus original_status = OPEN;
BugStatus current_status  = CLOSED;

gibi. Fakat Ruby dinamik bir dil olduğu için BugStatus tipini tanımlamak zorunda da değiliz, girilen değerin veri doğruluğunu da garantilemek zorunda değiliz. Enümerasyon yerine rahatlıkla sembolleri kullanabiliriz.

original_status = :open
current_status  = :closed


-- -- Sembol biricik adı olan bir sabittir

Ruby'de bir string'in değerini değiştirebiliriz.

"foo"[0] = ?b # "boo"


Ama bir sembolü değiştirmeye kalkarsak hata verir.

:foo[0] = ?b # hata verir



-- -- İsimlendirilmiş parametreler kullanılırken semboller anahtar değer olur

İsimlendirilmiş parametreleri olan bir Ruby metodu çağırırken anahtar değerleri (parametre isimlerini) sembol olarak gireriz. Bunu özellikle Rails'de çok kullanırlar.

# Rails kullanarak 'bug' sayfasının URL değeri
url_for :controller => 'bug',
        :action => 'show',
        :id => bug.id



-- -- Ruby sembolleri hash key değerleri için idealdir

Bir hash tablosunda anahtar değerleri genellikle sembol ifadelerle yazarız.

opsiyonlar = {}
opsiyonlar[:otomatik_kayıt] = true
opsiyonlar[:yorumları_göster] = false



-- Sembol Tanımlamaları

Tipik tanımlama sembol ismi başına iki nokta üstüste koymaktır. 

>> :bir_sembol        #=> :bir_sembol
>> :bir_sembol.class  #=> Symbol


Bir string değer kullanarak sembol tanımlamanın alternatif yolları var.

>> :"bir_sembol"        #=> :bir_sembol
>> "bir_sembol".to_sym  #=> :bir_sembol


Sembolleri ayrıca %q veya %Q gibi sınırlayıcı karakterler arasında %s kullanarak da tanımlayabiliriz.

>> %s(bir_sembol) #=> :bir_sembol
>> %s{bir_sembol} #=> :bir_sembol


%s şekli özellikle içinde boşluk bulunan isimlerle sembol tanımlarken faydalıdır.

>> %s(bir sembol) #=> :"bir sembol"


:/, :[], :^ gibi özel karakterlerle de sembol tanımlayabilirken nümerik değerlerle sembol tanımlanamaz.

:1 # => syntax error, unexpected tINTEGER, ...
:0.3 # => syntax error, unexpected tFLOAT, ...


Semboller bir string değer ifadesi içine konmadan tek bir ? ya da ! ile bitebilir. 

:hello?  # :"hello?" gerekmez.
:world!  # :"world!" gerekmez.


Tüm bu farklı tanımlama şekilleri aynı sembol için aynı nesneyi gösterir.

:symbol.object_id == "symbol".to_sym.object_id
:symbol.object_id == %s{symbol}.object_id


Ruby 2.0 sonrasında array içindeki değerlerden bir sembol array oluşturmanın kısa yolu vardır.

%i(numerator denominator) == [:numerator, :denominator]



-- String'den sembole dönüştürme

Bir string verildiğinde.

s = "birşey"


Bunu sembole çevirmenin değişik yolları vardır.

>> s.to_sym #=> :birşey
>> :"#{s}"  #=> :birşey

gibi.



-- Sembol'den String'e dönüştürme

En klasik yolu to_s metodu kullanmaktır.

>> s = :birşey  #=> :birşey
>> s.to_s       #=> "birşey"


Diğer bir yöntem Symbol#id2name metodunu kullanmaktır. Aslında aynı işi yapar ama id2name metodu Symbol sınıfınına özel bir metoddur, yani sembolden başka bir değere uygulanmaya kalkarsanız hata verir. 

>> s.id2name
=> "birşey"





Ruby'de hata ayıklama

Bir exception sıradışı bir durum oluştuğunu bildiren bir nesnedir. Diğer bir deyişle bir şeylerin ters gittiğini gösterir. 

Ruby'de sıradışı durumlar error olarak adlandırılır. Sebebi standart Exception sınıfı sıra dışı durum nesnesinin tepe elemanıyken , kullanıcı tanımlı sıradışı durumlar StandardError  sınıfı ya da onun alt sınıflarından birine aittir. 



-- Kendi sıra dışı durumunuzu tanımlamak

Normalde bunu Exception sınıfından alt sınıf tanımlayarak yapmanız beklenir. Ancak Ruby'de Exception sınıfı standart sistem hataları ya da sanal makine hataları için kullanılır. Bu yüzden onları kullanmak standart bir hatanın karşılığında olması beklenen davranışı etkileyebilir. Kendi sıra dışı durumlarımızı StandardError  veya türevlerinden birini kullanarak tanımlarız.

# FileNotFound adında bir sıra dışı durum tanımlaması
class FileNotFound < StandardError
end

def read_file(path)
  File.exist?(path) || raise(FileNotFound, "#{path} dosyası bulunamadı")
  File.read(path)
end

read_file("valid.txt")    #=> dosya vardır ve içeriği hatasız okunur
read_file("missing.txt")  #=> "missing.txt dosyası bulunamadı (FileNotFound)"


Genelde isimlendirme yaparken sıra dışı durum açıklaması arkasına Error eklemek tavsiye edilir.

class ConnectionError < StandardError
class DontPanicError < StandardError

gibi.

Ancak isim oldukça açıksa Error eklemeseniz daha kısa olur.

class FileNotFound < StandardError
class DatabaseExploded < StandardError

gibi.



-- Sıra dışı durumu işlemek

Bir hatayı yakalamak için begin/rescue blok yapısı kullanılır.

begin
    # hata verebilecek kod
rescue
    # hata oluşunca çalışacak kod
end


Diyelim kodumuzda hata üretecek bir şey var.

a = 0
b = 5 / a


5 / 0 işlemi kodu çalıştırınca ZeroDivisionError hatası (sıfıra bölme hatası) verecektir. Kodumuzu bu sıra dışı durumda hata vermeyecek şekilde ayarlayabiliriz.

begin
  a = 0
  b = 5 / a
rescue
  puts "Oppss, bişeyler ters gitti"
end


Bu kod hata vermez sadece biz öyle davranmasını istediğimiz için ekrana bir mesaj yazar ve çalışmaya devam eder. Bu rescue yapısı C# ve Java'daki catch bloğuna benzer. 

Buradaki gibi yalın bir rescue bloğu StandartError sınıfından hataları işlemek içindir. 

Default StandartError yerine Exception sınıfı hataları işlemeye çalışmaktan kaçının. Çünkü Exception sınıfında SystemExit , NoMemoryError ve benzeri ciddi sistem hataları işlenir ve onları işlemeye kalkmak çok pahalıya mal olabilir. 

Ayrıca işlemek istediğimiz sıra dışı durumu da belirtebiliriz. 

begin
  a = 0
  b = 5 / a
rescue ZeroDivisionError
  puts "Oppss, sıfıra bölme oldu"
end


Ama bu hata kodumuzdaki sıfıra bölme harici hatalarda işi yine sisteme bırakır, mesela

  Math::sqrt(-2)

satırımız olsa hata bizim rescue bloğumuzun odağı dışında kalır ve programımız hata verir. 

Ayrıca sıra dışı durum bilgisini bir değişkende saklayabiliriz.

begin
  a = 0
  b = 5 / a
rescue ZeroDivisionError => err
  puts "Oppss, sıfıra bölme oldu"
  puts err.message
  puts err.backtrace.inspect
end


Eğer hata işlemeyi başarıyla halledemediysek orjinal sistem hatasını hala ateşleyebiliriz. 

begin
  a = 0
  b = 5 / a
rescue => err
  puts "Oppss, bişeyler ters gitti"
  raise err
end


Eğer kurtarmaya çalıştığımız kod içinde de yine aynı hata oluşuyorsa sonsuz bir döngüye girebiliriz. Bunu engellemek için bir hata sayacı yapabiliriz. 

tekrar_sayısı = 0
begin
  # hata verebilecek kod
rescue
  if tekrar_sayısı < 5
    tekrar_sayısı += 1
    # hataya düşebilecek kurtarma kodu
  else
    #maks hata oluştu, başka bir şey yap
  end
end


Ayrıca hata işleme yapımızı else ve ensure blokları ile destekleyebiliriz. 

begin
  # hata verebilecek kod
rescue
  # hata olunca çalışacak kod
else
  # hata olmazsa çalışacak kod
ensure
  # her koşulda en son çalışacak kod
end


Bu ensure bloğu C#'taki Finally bloğu gibi hata olsa da olmasa da bloktan çıkmadan önce çalışmasını istediklerimizi içerir. 

Eğer bir def, module ya da class bloğu içindeysek ayrıca begin bloğu yapmadan rescue kullanabiliriz. 

def foo
  ...
rescue
  ...
end



-- Çoklu hata işlemek

Aynı rescue bloğunda bir'den fazla hatayı işleyebiliriz. 

begin
  # hata verebilecek kod
rescue İlkError, İkinciError => e
  # İlkError ya da İkinciError oluşursa çalışan kod
end


İstersek bir'den fazla rescue bloğumuz da olabilir.

begin
  # hata verebilecek kod
rescue İlkError => e
  # İlkError oluşursa çalışan kod
rescue İkinciError => e
  # İkinciError oluşursa çalışan kod
rescue => e
  # Başka bir StandardError oluşursa
end


Burada rescue blokları hangisi önce işlem görürse diğerlerine bakılmaz, bu yüzden en sondaki bloğu en üste koyarsanız İlkError ve İkinciError bloklarına asla ulaşamazsınız.



-- Bir hatayı ateşlemek

Özellikle kendi yazdığımız hata sınıfları için hata oluştuğunu da sisteme bildirmek zorundayız. Ayrıca istediğimiz anda sistemde tanımlı olan hataları da ateşleyebiliriz. Bunu Kernel#raise metoduna hata sınıfımızı ve/veya mesajımızı vererek yapabiliriz. 

>> raise StandardError
=> (irb):1:in `<main>': StandardError (StandardError)`
>> raise StandardError, "hata mesajı"
=> (irb):2:in `<main>': hata mesajı (StandardError)


Sadece mesaj girerek ateşlediğimizde RuntimeError sınıfı olarak kabul edilir.

>> raise "hata mesajı"
=> (irb):3:in `<main>': hata mesajı (RuntimeError)


Bir örnek

def merhaba(özne)
  raise ArgumentError, "`özne` değeri yok" if özne.to_s.empty?
  puts "Merhaba #{özne}"
end

merhaba("Ümit") # => "Merhaba Ümit"
merhaba "" # => ArgumentError: `özne` değeri yok



-- Kendi sıra dışı durumumuza bilgi eklemek

Kendi tanımladığımız sıra dışı durumlara kayıt için vs. bilgiler eklemek yardımcı olabilir. 

class ÖzError < StandardError
  attr_reader :tekrarlanabilir

  def initialize(tekrarlanabilir = false, mesaj = 'Bir şeyler ters gitti')
    @tekrarlanabilir = tekrarlanabilir
    super(mesaj)
  end
end


Kod akışımızda rescue bloğunda bu tekrarlanabilir bilgisini  kullanabiliriz.

begin
  # bir kısım kod
  raise ÖzError.new(true)
rescue ÖzError => e
  puts "tekrarlanabilir" if e.tekrarlanabilir
end





Ruby Thread

Thread birkaç değişik kodu paralelde çalıştırmak için kullanılır. 

thr = Thread.new {
  sleep 1 # alt thread 1 saniye bekler
  puts "1 saniye sonrası"
}


Bu kod yeni akışı çalıştırır, ancak bloğun ilk satırında bir beklemeye girdiği için diğer akışlara geçer. Başka akış yoksa ana akışa döner, bekleme işlemi bitene kadar o kodda dolaşır. Kodumuz şöyle olsa.

thr = Thread.new {
  sleep 1 # alt thread 1 saniye bekler
  puts "1 saniye sonrası"
}
puts "thr sonrası"


Bu kod sadece "thr sonrası" yazar ve program biter. Yeni tanımladığımız thr akışı çalışmaya başlar ama o daha sleep 1 satırında süre dolmasını beklerken ana akışa geri dönüldüğü için kod aşağıdan çalışmaya devam eder ve program ana akış bittiği için biter. 

Ana akışın yazdığımız yeni akış bitene kadar çalışmasını durdurması için 

thr.join

metodu çağrılır.

thr = Thread.new {
  sleep 1 # alt thread 1 saniye bekler
  puts "1 saniye sonrası"
}
thr.join
puts "thr sonrası"

çıktısı

1 saniye sonrası
thr sonrası


Ana akışın beklemesi için yazdığımız akış daha bitmeden join etmemiz gerekir. Zaten yeni akış bittiyse join bir işe yaramaz kod olduğu gibi devam eder. Eğer join yapılmazsa ana akış bitince program biter. 

thr = Thread.new {
  puts "yeni akış başı"
  sleep 1 # alt thread 1 saniye bekler
  puts "1 saniye sonrası"
}
# thr.join
sleep 0.1   # thr'ye akış geçsin diye
puts "thr sonrası"


Bu kodun çıktısında "1 saniye sonrası" yazısı yazılamadan ana akış biteceği için thr akışının o satırına gelmeden programdan çıkılır. Yani çıktısı

yeni akış başı
thr sonrası

olur. 



-- Paylaşılan değerlere erişmek

Diyelim tüm akışlarda paylaşılan bir değişken kullanmak istiyoruz. 

counter = 0
3.times.map { |i|
  Thread.new {
    puts "[Thread #{i}] Before: #{counter}"
    counter += 1
    puts "[Thread #{i}] After: #{counter}"
  }
}.each(&:join) # tüm akışları 'join'


Bu kod çalışırken her akışın counter değişkenine erişmesi başka zamanda olacağı için rastgele bir sonuç oluşur. 

[Thread 0] Before: 0
[Thread 0] After: 1
[Thread 2] Before: 0
[Thread 2] After: 2
[Thread 1] Before: 0
[Thread 1] After: 3

gibi. Bu gibi durumlarda birçok akıştan değişkene senkronize erişim için Mutex nesnesi kullanılır.

counter = 0
counter_mutex = Mutex.new
3.times.map { |i|
  Thread.new {
    counter_mutex.synchronize {
      puts "[Thread #{i}] Before: #{counter}"
      counter += 1
      puts "[Thread #{i}] After: #{counter}"
    }
  }
}.each(&:join) # tüm akışları 'join'' 


Mutex nesnesinin synchronize metodu akışı arkasından verilen blok kod işlenene kadar kilitler. Böylece istediğimiz sağlıklı çalışma oluşur. Ortak veriye ulaşırken kod başka akışlara kaymasın diye Mutex nesnesi kullanbiliriz. Bu kodun çıktısı.

[Thread 0] Before: 0
[Thread 0] After: 1
[Thread 1] Before: 1
[Thread 1] After: 2
[Thread 2] Before: 2
[Thread 2] After: 3



-- Bir thread nasıl yok edilir

Ya Thread.kill ya da Thread.terminate metodu kullanılır

thr = Thread.new { ... }
Thread.kill(thr)



-- Bir thread kendi bitmesi

Thread bloğu kodu sona ulaşınca thread bitecektir zaten. Ancak bunu kendiniz yönetmek isterseniz bir örnek verelim.

class CounterThread < Thread
  def initialize
    @count = 0
    @continue = true

    super do
      while @continue
        @count += 1
        puts "İşim bitmeden önce #{@count} sayısına ulaştım"
      end
    end
  end

  def stop
    @continue = false
  end
end

counter = CounterThread.new
sleep 0.1
counter.stop


Kod while döngüsü içinde dönerken dışarıdan counter.stop ile @countinue değeri false yapılarak thread döngüden çıkarılıyor.


Bu bölüm de bu kadar yeter, bir sonraki bölümde metodlarla devam edeceğiz inşallah. Şimdilik kalın sağlıcakla..









Hiç yorum yok:

Yorum Gönder