Swift 2 ile Hata Yönetimi (Error Handling)

By -

WWDC 2015’de duyurulan Swift 2’nin getirdiği en kapsamlı özellik yeni bir “error handling” mekanizması. Programda beklenmeyen durumları yakalayıp gerekli şeyleri yaptığımız “error handling”‘i kaba bir şekilde Türkçe’ye “hata yönetimi” veya “hata yakalama” olarak çevirebiliriz. Swift 2 öncesi yazdığınız programın uç durumlarda doğru çalıştığından emin olmak istiyorsanız izleyebileceğiniz iki yol vardı; bunların ilki Apple’ın önerdiği NSError sınıfını kullanmak. Bu yöntem ile şöyle bir kod yazmanız gerekliydi;

Bu yaklaşımdaki en büyük sorun NSError nesnesinin kontrol edilip edilmediğini derleyicinin takip edemiyor olması. Programcı basit bir kontrolü atladığında daha önce testlerde yaşanmayan ve pek beklenmeyen bir durumda uygulamanın patlaması çok olası.  Apple Swift 2 ile bunun önüne geçmek için NSError‘ün bu kullanımı kaldırarak daha bilindik try-catch yapısını dile ekliyor. Aynı kodu Swift 2 ile yazarsak;

Böylece hem kod kısa kısaldı hem de programcı olarak hata yapma olasılığınız neredeyse ortadan kalktı. Ancak ve lakin başta da belirttiğim gibi NSError tek yöntem değil. Çoğu kişi kendi kodunda NSError ile uğraşmak yerine dönen değeri kontrol etmeyi veya daha karışık ve projeye özel yapılar kullanmayı tercih ediyor ki bu herkes için tam bir kaos. Özellikle başkasının kodunu kullandığınız durumlarda yanlış şeyler yapmanız veya bir şeyleri atlamanız çok olası. Pek kullanışlı olmayan ancak basit bir örneğe göz atarsak;

Fonksiyon eğer bölen 0 ise nil, değilse bölme işleminin sonucunu döndürüyor. Ancak geriye nil dönmesi kodu ilk kez kullanan kişi açısından pek yararlı değil, eğer dokümantasyon da yoksa niye nil döndüğünü anlaması için kodu okuması şart. Elbette geriye hata mesajı döndürmek vs. gibi olaylara girilebilir ancak bunlar da kodun iyice dağılmasına ve karışık hale gelmesine sebep olabilir. Swift 2 neyse ki bizi bu tarz dertlerden kurtarıyor;

ErrorType Swift 2 ile gelen yeni bir protokol, catch ile yakaladığımız hataların bu protokolü desteklemesi gerekiyor. Bölme sırasındaki olabilecek hataları tanımladıktan sonra fonksiyonu “throws” olarak tanımlıyoruz ve hataların oluştuğu noktalarda “throw” kelimesi ile ilgili hataları fırlatıyoruz. Önceki örneğe göre biraz daha fazla kod içerse de bu şekilde yazmak hem daha temiz hem daha değişikliklere açık.

O zaman temel yapıdan bahsettiğimize göre yeni yöntemin yararlarını anlayabilmek için önce daha karışık bir örnekten yola çıkarak koda önce klasik hata düzeltme yöntemlerini eklemeyi deneyelim ve daha sonra Swift 2’ye dönüştürelim;

Bu hayali tahsilat programında kullanıcı su parasını ödemek istediğinde öncelikle suParasınıYatır fonksiyonu çalışıyor ve gerekli bağlantıları oluşturuyor, ardından sisteme kullanıcının borç miktarını soruyor ve işlemi onaylıyor, hepsi sona erince de bağlantıları bitirip para üstünü geriye döndürüyor. Herhangi bir sıkıntı oluştuğu anda ise tüm fonksiyonlar nil döndürüyor ki bu büyük bir problem, kullanıcıya bu durumda neyin yanlış gittiğini aktarmanız veya programcı olarak kullanıcıya çaktırmadan bunları halletmeniz mümkün değil.

Eğer koda Swift 2 öncesindeki gibi bir hata yönetimi yaparsak yaklaşık şöyle bir kod elde ederiz;

Kodun anlaşılır olması çok önemli

Artık neyin ters gittiğini bilsek de sürekli olarak NSError ile uğraşmak zorundayız, özellikle suParasınıYatır fonksiyonunun içi korkunç. Hele hele daha farklı hata mesajları veya hata mesajına göre işlemler yapmak istediğinizde kodun iyice içinden çıkılamaz bir hale gelmesi çok olası. Elbette bu kod en ideali değil ve iyileştirmek için bir şeyler yapılabilir ancak Swift 2 öncesi genel olarak takip edeceğiniz yol da bu. (NSError kullanmazsanız her fonksiyondan hata kodu veya nil döndürüp onları kontrol edebilirsiniz veya kendi hata sınıflarınızı oluşturabilirsiniz). Peki Swift 2’de bu çok mu farklı? try-catch’in o kadar ciddi bir katkısı var mı? Kendiniz karar verin;

  1. Enumlar: Farklı hata tipleri için farklı enumlarımız var, böylece hata nedenini takip etmemiz kolaylaşıyor.
  2. Yan Fonksiyonlar: Hata çıkartma ihtimali olan tüm fonksiyonlar throws olarak tanımlı ve hata oluştuğu anda alakalı hata tipini fırlatıyorlar.
  3. Ana Fonksiyon: Bu örnek için hatalarla suParasınıYatır fonksiyonunun değil onu çağıran kod parçacığının ilgilenmesini istiyoruz, o yüzden suParasınıYatır kendisine gelen tüm hataları ilettiği gibi ödeme sırasında para miktarı yetmezse hata fırlatıyor.
  4. Fonksiyonun Kullanımı: Fonksiyonu do – try – catch yapısı içerisinde kullanıp gelen hataları yakalıyoruz. Her hata tipine farklı farklı tanım yapabileceğimiz gibi (BorçYokParaYetersiz) bir hata tipindeki tüm durumlara aynı muameleyi yapabiliyoruz (SistemHatası).  Gerektiği noktada enum’ların esnekliğinden yararlanarak ParaYetersiz durumunda olduğu gibi enuma çeşitli değerler ilişkilendirmemiz de mümkün.
  5. Defer: Bazı durumlarda fonksiyon nasıl sona ererse ersin belli işlemleri yapmak isteriz. Örneğin bu örnek için sorguBitir fonksiyonunu çalıştırarak arkamızı  temiz bırakmak istiyoruz. defer tam olarak bu işe yarıyor, fonksiyonun nasıl bittiğinden bağımsız olarak tanımlanan kodu her seferinde çalıştırıyor. Burada not etmek gereken bir detay var, defer içerisinde return veya throw kullanmanız mümkün değil, ancak oluşabilecek hataları yine de yakalayabilir ve duruma göre gerekli olan şeyleri bu örnekte olduğu gibi yapabilirsiniz.

Peki hata düzeltmeyle ilgili bilinmesi gereken başka neler var?

  • Bazı durumlarda (özellikle hata ayıklarken) programın çalışmaya devam etmesindense kendini imha edip kapatmasını tercih edebilirsiniz. Swift’de bunu hali hazırda assert fonksiyonu ile yapmak mümkün, ancak try-catch blokları için daha hızlı bir çözüm getirilmiş, try yerine try! kullandığınızda hata oluştuğu durumlarda uygulama otomatik olarak sonlanıyor. Örneğin ilk örneğimizi do-catch olmadan try! ile kullanırsak uygulama dosyayı bulamadığında otomatik olarak kapanır;

  • Swift 2’nin yeni özelliklerinden olan guard hata yakalayacağınız durumlarda epey kullanışlı. Bu yazıda iyice her şeyi birbirine sokmamak için kasten es geçsem de ufak bir örnek vermiş olayım;

  • Son olarak belirtmiş olayım, Objective-C’deki NSException ile Swift’in try-catch yapısı uyumlu değil, eğer aynı projede hem Swift hem Objective-C kullanıyorsanız bunu aklınızdan çıkartmamalısınız.

Son not: Yazıdaki ilki hariç tüm kod parçalarını Xcode 7 Playgrounds ile deneyebilirsiniz. Ayrıca Koddit’in kod editörü Swift için Türkçe karaktere izin vermediği için kod örneklerinin çoğunu Swift yerine XHTML ile işaretlemek durumunda kaldım, renksiz oldu biraz kusura bakmayın.

Yüksek lisanstan vakit bulabildiğinde iOS uygulama geliştirmekle ilgili bildiklerini yazmaya çalışıyor.

  • cihan

    Güzel bir makale olmuş emeğinize sağlık teşekkürler