İstisna işleme mekanizması olmadan neden modern bir dil tasarladım?


47

Birçok modern dil zengin istisna işleme özellikleri sağlar, ancak Apple'ın Swift programlama dili bir istisna işleme mekanizması sağlamaz .

Benim gibi istisnalar dışında, aklımı bunun ne anlama geldiği etrafına sarmakta güçlük çekiyorum. Swift'in iddiaları ve elbette geri dönüş değerleri var; Ancak istisna temelli düşünme tarzımın istisnasız bir dünyaya nasıl eşleştiğini (ve bu nedenle , neden böyle bir dünyanın arzu edildiğini ) hayal etmekte zorlanıyorum . İstisnalarla yapabileceğim Swift gibi bir dilde yapamadığım şeyler var mı? İstisnaları kaybederek bir şey mi kazanıyorum?

Nasıl örneğin ben iyi ifade edilebileceğini gibi bir şey

try:
    operation_that_can_throw_ioerror()
except IOError:
    handle_the_exception_somehow()
else:
     # we don't want to catch the IOError if it's raised
    another_operation_that_can_throw_ioerror()
finally:
    something_we_always_need_to_do()

istisna yönetimi olmayan bir dilde (örneğin Swift)?


11
Sadece görmezden gelirsek , listeye Git eklemek isteyebilirsiniz , panichangisi aynı değildir. Buna ek olarak, orada söyleneni, bir istisna, GOTOkimse bunu böyle söylemese de, bariz nedenlerden ötürü, sofistike (ama rahat) bir yöntemden daha fazlası değildir .
JensG

3
Sorunuza verilen sıralama yanıtı, yazmak için istisnalar için dil desteğine ihtiyacınız olmasıdır. Dil desteği genellikle hafıza yönetimi içerir; bir istisna herhangi bir yere atılabildiğinden ve herhangi bir yere yakalanabildiğinden, kontrol akışına dayanmayan nesneleri elden çıkarmanın bir yolu olması gerekir.
Robert Harvey,

1
Seni tam olarak takip etmiyorum, @Robert. C ++, çöp toplamadan istisnaları desteklemeyi başarır.
Karl Bielefeldt

3
@KarlBielefeldt: Ne pahasına olursa olsun, büyük bir pahasına. Sonra tekrar, en azından çaba ve gerekli alan bilgisi olmak üzere, C ++ 'da büyük bir masraf olmadan üstlenilen herhangi bir şey var mı?
Robert Harvey,

2
@RobertHarvey: İyi nokta. Ben bu şeyler hakkında yeterince düşünmeyen insanlardan biriyim. ARC'nin GC olduğunu düşünmekten bıktım, ama elbette değil. Yani, temelde (kabaca kavrarsam), istisnalar, nesnelerin elden çıkarılmasının kontrol akışına dayandığı bir dilde dağınık bir şey (C ++ olmasına rağmen) olur mu?
orome

Yanıtlar:


33

Gömülü programlamada, istisnalara geleneksel olarak izin verilmedi, çünkü yapmak istemediğiniz istifin yükü, gerçek zamanlı performansı korumaya çalışırken kabul edilemez bir değişkenlik olarak kabul edildi. Akıllı telefonlar teknik olarak gerçek zamanlı platformlar olarak görülebilse de, gömülü sistemlerin eski sınırlamalarının artık geçerli olmadığı yerlerde yeterince güçlüler. Ben sadece iyiliği uğruna getirdim.

İstisnalar genellikle işlevsel programlama dillerinde desteklenir, ancak nadiren kullanıldıkları şekilde kullanılırlar. Bunun bir nedeni, varsayılan olarak tembel olmayan dillerde bile zaman zaman yapılan tembel değerlendirmedir. Çalıştırmak için sıraya yerleştirilenden farklı bir yığınla çalışan bir işleve sahip olmak, istisna işleyicinizi nereye koyacağınızı belirlemeyi zorlaştırır.

Diğer bir nedeni de birinci sınıf fonksiyonlar, size istisnaların daha esnek bir şekilde sözdizimsel faydalarını sağlayan seçenekler ve gelecekler gibi yapılara izin verir. Başka bir deyişle, dilin geri kalanı, istisnaların size bir şey almaması için yeterince açıklayıcıdır.

Swift'e aşina değilim, ancak hata işleme hakkında okuduğum küçükler, daha işlevsel stil kalıplarını takip etmek için hata işlemeyi amaçladıklarını gösteriyor. Kod örnekleri ile successve failuregeleceklere çok benzeyen bloklar gördüm .

İşte kullanıldığı bir örnek Futuregelen bu Scala öğretici :

val f: Future[List[String]] = future {
  session.getRecentPosts
}
f onFailure {
  case t => println("An error has occured: " + t.getMessage)
}
f onSuccess {
  case posts => for (post <- posts) println(post)
}

İstisnaları kullanarak kabaca örneğin ile aynı yapıya sahip olduğunu görebilirsiniz. futureBloğu gibidir try. onFailureBlok bir istisna işleyicisi gibidir. Scala'da, çoğu işlevsel dilde Futureolduğu gibi, dilin kendisi kullanılarak tamamen uygulanır. İstisnalar gibi özel bir sözdizimi gerektirmez. Bu, kendi benzer yapılarınızı tanımlayabileceğiniz anlamına gelir. Belki bir timeoutblok ekleyebilir , örneğin, veya günlük kaydı işlevi.

Ek olarak, geleceği etrafa aktarabilir, fonksiyondan geri döndürebilir, bir veri yapısında saklayabilirsiniz veya her neyse. Bu birinci sınıf bir değer. Yığına kadar yayılması gereken istisnalar gibi sınırlı değilsiniz.

Seçenekler hata yönetimi problemini biraz farklı şekilde çözer ve bu bazı kullanım durumlarında daha iyi sonuç verir. Sadece bir yöntemle sıkışıp kalmadın.

Bunlar "istisnaları kaybederek kazandığınız" şeyler.


1
Bu Future, aslında bir fonksiyondan dönen değeri, onu beklemeden durmadan incelemenin bir yoludur. Swift gibi, geri dönüş değeri bazlıdır, ancak Swift'den farklı olarak, geri dönüş değerine verilen yanıt daha sonra ortaya çıkabilir (biraz istisnalar gibi). Sağ?
orome

1
Doğru bir Futureşekilde anlıyorsun , ama Swift'i yanlış karakterize ediyorsun. Örneğin, bu yığın akışı yanıtının ilk bölümüne bakın .
Karl Bielefeldt

Hmm, Swift için yeniyim, bu yüzden bu cevabı ayrıştırmam biraz zor. Ama yanılmıyorsam eğer: esasen bir işleyici geçtiği olabilir daha sonraki bir zamanda çağrılabilir; sağ?
orome

Evet. Temel olarak bir hata oluştuğunda geri arama oluşturuyorsunuz.
Karl Bielefeldt

Eitherdaha iyi bir örnek olurdu IMHO
Paweł Prażak

19

İstisnalar, kodun nedenini zorlaştırır. Goto kadar güçlü olmasalar da, yerel olmayan doğaları nedeniyle aynı sorunların çoğuna neden olabilirler. Örneğin, bunun gibi bir zorunluluk koduna sahip olduğunuzu varsayalım:

cleanMug();
brewCoffee();
pourCoffee();
drinkCoffee();

Bir bakışta bu prosedürlerden herhangi birinin bir istisna atabilip atmayacağını söyleyemezsiniz. Bunu anlamak için bu prosedürlerin her birinin belgelerini okumalısınız. (Bazı diller bu bilgiyle tür imzasını artırarak bunu biraz daha kolaylaştırır.) Yukarıdaki kod, işlemlerden herhangi birinin atılıp atılmadığına bakılmaksızın, bir istisna ele almayı gerçekten unutmayı kolaylaştıracak şekilde, sadece düzgün şekilde derlenir.

Ayrıca, istisnayı arayana geri iletmek niyetinde olsa bile, işlerin tutarsız bir durumda bırakılmasını önlemek için genellikle ek bir kod eklemek gerekir (örneğin, kahve makineniz bozulursa, yine de pisliği temizlemeniz ve geri göndermeniz gerekir). Kupa!). Bu nedenle, çoğu durumda istisnalar kullanan kod, gereken ekstra temizleme nedeniyle yapmayan kodlar kadar karmaşık görünür.

İstisnalar, yeterince güçlü bir tür sistemle taklit edilebilir. İstisnalardan kaçınan dillerin çoğu aynı davranışı elde etmek için dönüş değerleri kullanır. C'de nasıl yapıldığına benzer, ancak modern tip sistemler genellikle daha şık ve hata durumuyla başa çıkmayı unutmayı zorlaştırır. Ayrıca işleri daha az hantal hale getirmek için sözdizimsel şeker sağlayabilirler, bazen de neredeyse istisnalar dışında olduğu kadar temizdirler.

Özellikle, ayrı bir özellik olarak uygulamak yerine hata işleme tür sistemine gömülmesi, "istisnalar", hatalarla bile ilgili olmayan diğer şeyler için kullanılmasına izin verir. (İstisna idaresinin aslında bir monad mülkü olduğu iyi bilinmektedir.)


Opsiyonlar da dahil olmak üzere Swift'in sahip olduğu bu tip bir sistemin, bunu başarabilen "güçlü tip sistem" türü olduğu doğru mu?
orome

2
Evet, opsiyonel ve daha genel olarak, toplam türler (Swift / Rust'ta "enum" olarak anılır) bunu başarabilir. Bununla birlikte, kullanımlarını keyifli hale getirmek için bazı ekstra çalışmalar gerekir, ancak: Swift'de isteğe bağlı zincirleme sözdizimi ile bu başarılır; Haskell'de bu, monadik not yazımı ile başarılır.
Rufflewind

"Yeterince güçlü bir tipte bir sistem" bir yığın iz
bırakabilir

1
İstisnaların kontrol akışını engellediğine işaret etmek için +1. Sadece bir Yardımcı not olarak: Bu istisnalar dışında aslında daha kötü olup olmadığını nedenle duruyor goto: gotooldukça küçük bir aralık sağlanan fonksiyon küçük gerçekten tek fonksiyonu ile sınırlıdır, istisna daha bazılarının haç gibi davranır gotove come from(bkz en.wikipedia.org/wiki/INTERCAL ;-)). Herhangi bir iki kod parçasını birbirine bağlayabilir, muhtemelen bazı üçüncü fonksiyonları kodun üzerinden atlayabilir. Yapamayacağı tek şey goto, geri dönüş olacak.
cmaster

2
@ PawełPrażak Çok sayıda üst düzey işlevle çalışırken, yığın izleri neredeyse kadar değerli değildir. Girdiler ve çıktılar hakkında güçlü garantiler ve yan etkilerin önlenmesi, bu dolaylı uygulamanın kafa karıştırıcı hatalara neden olmasını önler.
Jack,

11

Burada bazı büyük cevaplar var, ancak bence önemli bir neden yeterince vurgulanmadı: İstisnalar oluştuğunda, nesneler geçersiz durumlarda bırakılabilir. Bir istisna "yakalayabilir" ise, istisna işleyici kodunuz bu geçersiz nesnelere erişebilir ve onlarla çalışabilir. Bu nesnelerin kodları mükemmel bir şekilde yazılmadıkça, bu çok yanlış bir şekilde yanlış gidecektir.

Örneğin, Vector uygulamasını uyguladığınızı hayal edin. Birisi Vector'unuzu bir nesne kümesiyle başlatırsa, ancak başlatma sırasında bir istisna ortaya çıkarsa (belki, örneğin, nesnelerinizi yeni ayrılmış belleğe kopyalarken), Vector uygulamasını doğru bir şekilde kodlamak çok zor hafıza sızdırılmış. Stroustroup'un bu kısa makalesi Vector örneğini kapsar .

Ve bu sadece buzdağının görünen kısmı. Örneğin, bazı öğelerin üzerine kopyaladıysanız, ancak tüm öğelerin üzerine kopyalamazsanız ne olur? Vector gibi bir kabı doğru bir şekilde uygulamak için, yaptığınız her işlemi geri alınabilir hale getirmelisiniz, bu nedenle tüm işlem atomiktir (bir veritabanı işlemi gibi). Bu karmaşık bir işlemdir ve çoğu uygulama bunu yanlış anlar. Doğru yapıldığında bile, kabın uygulama işlemini büyük ölçüde karmaşıklaştırır.

Bu yüzden bazı modern diller buna değmeyeceğine karar verdi. Örneğin, pasın istisnaları vardır, ancak bunlar “yakalanamaz”, dolayısıyla kodun geçersiz durumdaki nesnelerle etkileşime girmesi mümkün değildir.


1
yakalamak, bir hata meydana geldikten sonra bir nesneyi tutarlı kılmak (veya mümkün değilse bir şeyi sonlandırmak).
JoulinRouge

@JoulinRouge biliyorum. Ancak bazı diller size bu fırsatı vermemeye karar verdi, bunun yerine tüm süreci çökertmeye karar verdi. Bu dil tasarımcıları, yapmak istediğiniz temizliği biliyorlar, ancak bunu size vermenin çok zor olduğu ve buna dahil olan takasların buna değmeyeceği sonucuna varmışlardır. Onların seçimi ile aynı fikirde olmayabileceğinizi anlıyorum ... ama bu belirli nedenlerle seçimi bilinçli olarak yaptıklarını bilmek değerli.
Charlie Flowers,

6

Rust diliyle ilgili ilk olarak şaşırdığım bir şey, yakalama istisnalarını desteklememesi. Şunları yapabilirsiniz atmak istisnalar, ancak bir görev yalnızca çalışma zamanı onları yakalamak (iplik düşünüyorum, ama her zaman değil, ayrı bir işletim sistemi iplik) ölür; Bir göreve kendiniz başlarsanız, normal çıkıp çıkmadığını veya çalışıp çalışmadığını sorabilirsiniz fail!().

Gibi failçok sık deyimsel değildir . Bunun gerçekleştiği birkaç durum, örneğin, test donanımında (kullanıcı kodunun neye benzediğini bilmez), bir derleyicinin üst seviyesi (bunun yerine çoğu derleyici çatalı) veya bir geri arama başlatırken kullanıcı girişi üzerinde.

Bunun yerine, ortak deyim, kullanılması gereken hataları açıkça iletmek için Resultşablonu kullanmaktır . Bu önemli ölçüde kolaylaşır makro herhangi bir ifade olduğunu verimleri bir sonucu etrafına sarılmış ve orada biridir ya da başka şekilde erken işlevinden dönerse başarılı kol verir gibi, parçaların.try!

use std::io::IoResult;
use std::io::File;

fn load_file(name: &Path) -> IoResult<String>
{
    let mut file = try!(File::open(name));
    let s = try!(file.read_to_string());
    return Ok(s);
}

fn main()
{
    print!("{}", load_file(&Path::new("/tmp/hello")).unwrap());
}

1
Öyleyse, bunun da (Go'nun yaklaşımı gibi) Swift'e benzer olduğunu söylemesi adil assertdeğil catchmi?
orome

1
Swift'de dene! şu anlama gelir: Evet, bunun başarısız olabileceğini biliyorum, ancak olmayacağına eminim, bu yüzden kullanmıyorum ve başarısız olursa kodum yanlış, bu durumda lütfen çöküyor.
gnasher729

6

Benim görüşüme göre, istisnalar çalışma zamanında kod hatalarını tespit etmek için önemli bir araçtır. Hem testlerde hem de üretimde. Mesajlarını yeterince ayrıntılı hale getirin, böylece bir yığın iziyle birlikte bir günlükten ne olduğunu anlayabilirsiniz.

İstisnalar çoğunlukla bir geliştirme aracı ve beklenmeyen durumlarda üretimden makul hata raporları almanın bir yoludur.

Kaygıların ayrılmasının (sadece beklenen hataların olduğu mutlu bir yolun yanı sıra, beklenmeyen hatalar için genel bir işleyiciye ulaşıncaya kadar düşmenin dışında) iyi bir şey olması, kodunuzu daha okunaklı ve sürdürülebilir hale getirmek, aslında kodunuzu mümkün olan her şeye hazırlamak imkansızdır. Beklenmeyen durumlar, okunamazlığı tamamlamak için hata işleme kodu ile şişirerek bile.

Bu aslında "beklenmedik" nin anlamı.

Btw., Ne bekleniyor ve ne değil sadece çağrı sitesinde verilebilecek bir karar değil. Bu yüzden Java’daki kontrol edilen istisnalar işe yaramadı - bir API geliştirilirken, ne beklendiği ya da beklenmeyen bir şey olmadığı açıkça belirlendi.

Basit örnek: bir karma haritanın API'sinin iki yöntemi olabilir:

Value get(Key)

ve

Option<Value> getOption(key)

ilk bulunmazsa bir istisna atmak, ikincisi size isteğe bağlı bir değer verir. Bazı durumlarda, ikincisi daha anlamlı olur, ancak diğerlerinde, kodunuzun sinmply, verilen bir anahtar için bir değer olmasını beklemelidir, eğer bir tane yoksa, bu kodun düzeltemeyeceği bir hatadır çünkü varsayım başarısız oldu. Bu durumda, çağrının başarısız olması durumunda kod yolundan çıkıp bazı genel işleyicilere düşmek istenen davranış aslında.

Kod asla başarısız temel varsayımlarla başa çıkmaya çalışmamalıdır.

Elbette onları kontrol edip iyi okunabilir istisnalar atmak dışında.

İstisnaları atmak kötü değildir, ama onları yakalamak olabilir. Beklenmeyen hataları düzeltmeye çalışmayın. Bazı döngüler veya işlemlere devam etmek istediğiniz birkaç yerde istisnalar yakalayın, bunları günlüğe kaydedin, belki bilinmeyen bir hata bildirin ve hepsi bu kadar.

Her yerde blokları yakalamak çok kötü bir fikir.

API'lerinizi, niyetinizi ifade etmeyi kolaylaştıracak şekilde tasarlayın, örneğin, bulunmayan bir anahtar gibi, belirli bir durum bekleyip beklemeyeceğinizi beyan edin. API'nizin kullanıcıları yalnızca beklenmedik durumlar için arama çağrısını seçebilir.

İnsanların istisnaları reddetmesinin ve hata yönetimi otomasyonu ve kaygıların yeni dillerden daha iyi ayrılması için bu önemli aracı göz ardı ederek çok ileri gitmelerinin sebebinin kötü deneyimler olduğunu tahmin ediyorum.

Bu ve bazıları için neyin iyi olduğu konusunda yanlış anlaşılma.

Monadic ciltleme yoluyla HER ŞEY yaparak bunları simüle etmek, kodunuzu daha az okunabilir ve sürdürülebilir hale getirir ve bir yığın iz bırakmadan bitirdiniz, bu da bu yaklaşımı WAY'i daha da kötüleştirir.

İşlevsel stil hata yönetimi, beklenen hata durumları için mükemmeldir.

İstisna işlemenin otomatik olarak geri kalanı halletmesine izin verin, bunun için :)


3

Swift, burada daha çok sonuç olarak, burada Amaç-C ile aynı prensipleri kullanıyor. Objective-C'de istisnalar programlama hatalarını gösterir. Kaza raporlama araçları dışında ele alınmazlar. "İstisna işleme" kodu düzeltilerek yapılır. (Bazı istisnalar vardır. Örneğin, süreçler arası iletişimde. Ama bu oldukça nadirdir ve birçok insan asla karşılaşmaz. Ve Objective-C aslında denemeye / yakalamaya / nihayet / fırlatmaya çalıştı, ancak bunları nadiren kullanıyorsunuz). Swift, istisnaları yakalama olanağını kaldırdı.

Swift, istisna işleme gibi görünen bir özelliğe sahiptir, ancak yalnızca zorlamalı hata işleme özelliği vardır. Tarihsel olarak, Objective-C oldukça yaygın bir hata işleme modeline sahipti: Bir yöntem ya bir BOOL (başarı için YES) ya da bir nesne referansı (başarısızlık için sıfır, başarı için sıfır değil) ve "pointer NSError *" parametresine sahip olacaktı. NSError referansını saklamak için kullanılır. Swift otomatik olarak çağrıları böyle bir yönteme istisna işleme gibi görünen bir şeye dönüştürür.

Genel olarak, Swift işlevleri kolayca, örneğin bir işlev iyi çalıştıysa sonuç, başarısız olursa hata; Bu hata işleme çok daha kolay hale getirir. Ancak asıl sorunun cevabı: Swift tasarımcıları, dilde istisnalar yoksa güvenli bir dil yaratmanın ve böyle bir dilde başarılı bir kod yazmanın daha kolay olduğunu hissettiler.


Swift için bu doğru cevap, IMO. Swift, mevcut Objective-C sistem çerçeveleriyle uyumlu kalmak zorunda olduğundan, kaputun altında geleneksel istisnalar bulunmuyor. Ben objc hata işleme için nasıl çalıştığına dair süre önce blog yazısı a yazdı: orangejuiceliberationfront.com/...
uliwitness

2
 int result;
 if((result = operation_that_can_throw_ioerror()) == IOError)
 {
  handle_the_exception_somehow();
 }
 else
 {
   # we don't want to catch the IOError if it's raised
   result = another_operation_that_can_throw_ioerror();
 }
 result |= something_we_always_need_to_do();
 return result;

C de yukarıdaki gibi bir şeyle sonuçlanırdın.

Swift'de yapamayacağım istisnalar dışında yapabileceğim şeyler var mı?

Hayır, hiçbir şey yok. İstisnalar yerine sonuç kodlarını kullanmanız yeterlidir.
İstisnalar, kodunuzu yeniden düzenlemenizi sağlar, böylece hata işlemenin mutlu yol kodunuzdan ayrı olması gerekir, ancak bu konudadır.


Ve aynı şekilde, ...throw_ioerror()bunlar istisnalar atmak yerine hata döndürmeye mi çağırıyor ?
orome

1
@raxacoricofallapatorius İstisnaların olmadığını iddia edersek, o zaman programın başarısızlıkta hata kodları ve başarısızlıkta 0 döndürme alışkanlığını izlediğini varsayıyorum.
stonemetal

1
@stonemetal Rust ve Haskell gibi bazı diller, istisnaların yaptığı gibi gizli çıkış noktaları eklemeden, hata kodundan daha anlamlı bir şey döndürmek için type sistemini kullanır. A Pas fonksiyonu, örneğin bir döndürebilir Result<T, E>ya bir olabilir, enum Ok<T>veya bir Err<E>ile TEğer varsa istediği tipini meydana getiren, ve Ebir hata temsil eden bir türü olan. Desen eşleştirme ve bazı özel yöntemler hem başarıların hem de başarısızlıkların ele alınmasını kolaylaştırır. Kısacası, istisnaların eksikliğinin otomatik olarak hata kodları anlamına gelmediğini varsaymayın.
8,

1

Charlie'nin cevabına ek olarak:

Birçok kılavuzda ve kitapta gördüğünüz beyan istisnaların ele alınmasına ilişkin örnekler, yalnızca çok küçük örneklerde çok akıllı görünmektedir.

Geçersiz nesne durumu hakkındaki argümanı bir kenara bıraksanız bile, büyük bir uygulamayla uğraşırken her zaman çok büyük bir acı ortaya çıkarırlar.

Örneğin, GÇ ile uğraşmak zorunda olduğunuzda, bazı şifreleme kullanarak, 50 yöntemden atılabilecek 20 istisna olabilir. İhtiyacınız olacak istisna işleme kodunun miktarını hayal edin. İstisna işleme, kodun kendisinden birkaç kat daha fazla kod alacaktır.

Gerçekte, istisnanın ne zaman ortaya çıkacağını biliyorsunuz ve hiçbir zaman istisnai durumlarla uğraşmak zorunda kalmayacaksınız, bu nedenle bildirilen istisnaları yoksaymak için yalnızca bazı geçici çözümler kullanıyorsunuz. Uygulamamda, bildirilen istisnaların yalnızca% 5'inin güvenilir bir uygulamaya sahip olması için kodda ele alınması gerekir.


Gerçekte, bu istisnalar genellikle sadece bir yerde ele alınabilir. Örneğin, SSL başarısız olursa veya DNS çözülemiyorsa veya web sunucusu bir 404 döndürürse, "veri güncellemesini indir" işlevinde, önemi yoktur, en üstte tutulur ve hatayı kullanıcıya bildirir.
Zan Lynx,
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.