Hangisini ve neden İstisnaları veya Dönüş Kodlarını tercih ediyorsunuz?


92

Sorum, çoğu geliştiricinin hata işleme, İstisnalar veya Hata Dönüş Kodları için neyi tercih ettiği. Lütfen dile (veya dil ailesine) özgü olun ve neden birini diğerine tercih ettiğinizi belirtin.

Bunu meraktan soruyorum. Şahsen ben Hata Dönüş Kodlarını daha az patlayıcı olduklarından ve istemiyorlarsa istisna performans cezasını ödemeye zorlamadıkları için tercih ediyorum.

güncelleme: tüm cevaplar için teşekkürler! İstisnalar dışında kod akışının öngörülemezliğinden hoşlanmama rağmen söylemeliyim. Dönüş kodu (ve onların ağabeyleri) hakkındaki cevap, koda çok fazla Gürültü ekler.


1
Yazılım dünyasından bir tavuk veya yumurta problemi ... sonsuza kadar tartışılabilir. :)
Gishu

Bunun için üzgünüm, ama umarım çeşitli fikirlere sahip olmak insanların (ben de dahil) uygun şekilde seçim yapmasına yardımcı olur.
Robert Gould

Yanıtlar:


107

Bazı diller için (yani C ++) Kaynak sızıntısı bir sebep olmamalıdır

C ++, RAII'ye dayanmaktadır.

Başarısız olabilecek, geri dönebilecek veya fırlatabilecek (yani, çoğu normal kod) kodunuz varsa, işaretçinizin akıllı bir işaretçinin içine sarılması gerekir ( nesnenizin yığın üzerinde oluşturulmaması için çok iyi bir nedeniniz olduğunu varsayarak ).

Dönüş kodları daha ayrıntılıdır

Bunlar ayrıntılıdır ve aşağıdaki gibi bir şeye dönüşme eğilimindedirler:

if(doSomething())
{
   if(doSomethingElse())
   {
      if(doSomethingElseAgain())
      {
          // etc.
      }
      else
      {
         // react to failure of doSomethingElseAgain
      }
   }
   else
   {
      // react to failure of doSomethingElse
   }
}
else
{
   // react to failure of doSomething
}

Sonunda, kodunuz tanımlanmış talimatların bir koleksiyonudur (bu tür bir kodu üretim kodunda gördüm).

Bu kod şu şekilde çevrilebilir:

try
{
   doSomething() ;
   doSomethingElse() ;
   doSomethingElseAgain() ;
}
catch(const SomethingException & e)
{
   // react to failure of doSomething
}
catch(const SomethingElseException & e)
{
   // react to failure of doSomethingElse
}
catch(const SomethingElseAgainException & e)
{
   // react to failure of doSomethingElseAgain
}

Hangi temiz ayrı bir kod ve hata işleme, olabilir bir olmak iyi bir şey.

Dönüş kodları daha kırılgandır

Bir derleyiciden gelen belirsiz bir uyarı değilse ("phjr" nin açıklamasına bakın), kolayca göz ardı edilebilirler.

Yukarıdaki örneklerle, birinin olası hatasını ele almayı unuttuğunu varsayın (bu olur ...). Hata "döndürüldüğünde" göz ardı edilir ve muhtemelen daha sonra patlayacaktır (yani bir NULL işaretçisi). Aynı sorun istisna ile olmaz.

Hata göz ardı edilmeyecek. Bazen, patlamamasını istersiniz, gerçi ... Bu yüzden dikkatli seçmelisiniz.

Dönüş Kodları bazen çevrilmelidir

Diyelim ki aşağıdaki işlevlere sahibiz:

  • doSomething, NOT_FOUND_ERROR adlı bir int döndürebilir
  • doSomethingElse, bool "false" döndürebilir (başarısız olduğu için)
  • doSomethingElseAgain, bir Error nesnesi döndürebilir (hem __LINE__, __FILE__ hem de yığın değişkenlerinin yarısı ile.
  • do TryToDoSomethingWithAllThisMess ki bu iyi ... Yukarıdaki işlevleri kullanın ve bir tür hata kodu döndürür ...

Çağrılan işlevlerinden biri başarısız olursa, do TryToDoSomethingWithAllThisMess'in dönüşünün türü nedir?

İade Kodları evrensel bir çözüm değildir

Operatörler bir hata kodu döndüremez. C ++ kurucuları da yapamaz.

Dönüş Kodları, ifadeleri zincirleyemeyeceğiniz anlamına gelir

Yukarıdaki noktanın doğal sonucu. Ya yazmak istersem:

CMyType o = add(a, multiply(b, c)) ;

Yapamam çünkü dönüş değeri zaten kullanılıyor (ve bazen değiştirilemez). Böylece dönüş değeri referans olarak gönderilen ilk parametre olur ... Veya değil.

İstisna yazılır

Her istisna türü için farklı sınıflar gönderebilirsiniz. Kaynak istisnaları (yani hafıza yetersizliği) hafif olmalıdır, ancak diğer her şey gerektiği kadar ağır olabilir (Java İstisnasının bana tüm yığını vermesini seviyorum).

Her yakalama daha sonra özelleştirilebilir.

Yeniden fırlatmadan asla yakalama (...) kullanmayın

Genellikle bir hatayı gizlememelisiniz. Yeniden atmazsanız, en azından, hatayı bir dosyaya kaydedin, bir mesaj kutusu açın, her neyse ...

İstisnalar ... NUKE

İstisnai sorun, aşırı kullanılmasının deneme / yakalama ile dolu kod üretmesidir. Ancak sorun başka bir yerde: STL konteynerini kullanarak kodunu kim yakalamaya çalışıyor? Yine de bu kaplar bir istisna gönderebilir.

Elbette, C ++ 'da, bir istisnanın yıkıcıdan çıkmasına asla izin vermeyin.

İstisnalar ... senkronize

İş parçacığınızı dizlerinin üzerine indirmeden veya Windows ileti döngünüzün içine yaymadan önce onları yakaladığınızdan emin olun.

Çözüm onları karıştırmak olabilir mi?

Sanırım Yani çözüm şey gerekirken atılmasıdır değil olur. Ve bir şey olduğunda, kullanıcının buna tepki vermesini sağlamak için bir dönüş kodu veya parametre kullanın.

Öyleyse, tek soru "olmaması gereken şey nedir?"

Görevinizin sözleşmesine bağlıdır. İşlev bir işaretçi kabul ederse, ancak işaretçinin NULL olmaması gerektiğini belirtirse, kullanıcı bir NULL işaretçi gönderdiğinde bir istisna atılması uygundur (soru, C ++ 'da, işlev yazarı bunun yerine referanslar kullanmadığında işaretçiler, ama ...)

Başka bir çözüm, hatayı göstermek olacaktır.

Bazen sorunun, hata istememektir. İstisnalar veya hata dönüş kodları kullanmak harika, ama ... Bunu bilmek istiyorsunuz.

İşimde bir tür "İddia" kullanıyoruz. Hata ayıklama / yayınlama derleme seçenekleri ne olursa olsun, bir yapılandırma dosyasının değerlerine bağlı olarak:

  • hatayı günlüğe kaydet
  • "Hey, bir sorunun var" yazan bir mesaj kutusu açın
  • "Hey, bir sorunun var, hata ayıklamak istiyor musun" yazan bir mesaj kutusu açın

Hem geliştirme hem de testte, bu, kullanıcının problemi tam olarak tespit edildiğinde ve sonra değil (bazı kodlar dönüş değerini önemsediğinde veya bir yakalama içinde) tam olarak saptamasına olanak tanır.

Eski koda eklemek kolaydır. Örneğin:

void doSomething(CMyObject * p, int iRandomData)
{
   // etc.
}

şuna benzer bir tür kod açar:

void doSomething(CMyObject * p, int iRandomData)
{
   if(iRandomData < 32)
   {
      MY_RAISE_ERROR("Hey, iRandomData " << iRandomData << " is lesser than 32. Aborting processing") ;
      return ;
   }

   if(p == NULL)
   {
      MY_RAISE_ERROR("Hey, p is NULL !\niRandomData is equal to " << iRandomData << ". Will throw.") ;
      throw std::some_exception() ;
   }

   if(! p.is Ok())
   {
      MY_RAISE_ERROR("Hey, p is NOT Ok!\np is equal to " << p->toString() << ". Will try to continue anyway") ;
   }

   // etc.
}

(Yalnızca hata ayıklamada etkin olan benzer makrolarım var).

Üretimde konfigürasyon dosyasının mevcut olmadığını, dolayısıyla istemcinin bu makronun sonucunu asla görmediğini unutmayın ... Ancak gerektiğinde etkinleştirmek kolaydır.

Sonuç

Dönüş kodlarını kullanarak kod yazdığınızda, kendinizi başarısızlığa hazırlıyorsunuz ve test kalenizin yeterince güvenli olmasını umuyorsunuz.

İstisna kullanarak kod yazdığınızda, kodunuzun başarısız olabileceğini bilirsiniz ve genellikle kodunuzda seçilen stratejik konuma karşı ateş yakalama koyarsınız. Ancak genellikle, kodunuz daha çok "ne yapması gerektiği" ve sonra "ne olacağından korktuğum" ile ilgilidir.

Ancak kod yazdığınızda, emrinizde olan en iyi aracı kullanmanız gerekir ve bazen bu "Bir hatayı asla gizleme ve mümkün olan en kısa sürede gösterme" dir. Yukarıda bahsettiğim makro bu felsefeyi takip ediyor.


3
Evet, AMA ilk örneğinizle ilgili olarak, bu kolayca şöyle yazılabilir: if( !doSomething() ) { puts( "ERROR - doSomething failed" ) ; return ; // or react to failure of doSomething } if( !doSomethingElse() ) { // react to failure of doSomethingElse() }
bobobobo

Neden olmasın ... Ama sonra, hala doSomething(); doSomethingElse(); ...daha iyi buluyorum çünkü eğer / while / etc eklemem gerekiyorsa. normal yürütme amaçlı ifadelerin if / while / etc ile karıştırılmasını istemiyorum. İstisnai amaçlar için ifadeler eklendi ... Ve istisnaları kullanmanın gerçek kuralı yakalamak değil , atmak olduğu için , dene / yakala ifadeleri genellikle istilacı değildir.
paercebal

1
İlk noktanız, istisnalarla ilgili sorunun ne olduğunu gösterir. Kontrol akışınız tuhaflaşıyor ve gerçek problemden ayrılıyor. Bazı kimlik seviyelerinin yerini bir dizi yakalama ile değiştiriyor. Olası hatalar ve beklenmeyen şeyler için istisnalar için her iki dönüş kodunu da kullanırdım (veya ağır bilgiler içeren nesneleri döndürür) .
Peter

2
@Peter Weber: Tuhaf değil. Normal yürütme akışının bir parçası olmadığı için asıl sorundan ayrılmıştır . Bir olan olağanüstü bir uygulama. Ve sonra, yine, istisna ile ilgili nokta, istisnai hata durumunda, sık sık fırlatmak ve hiç değilse nadiren yakalamaktır . Bu nedenle, yakalama blokları nadiren kodda görünür.
paercebal

Bu tür tartışmalardaki örnek çok basittir. Genellikle, "doSomething ()" veya "doSomethingElse ()" aslında bir nesnenin durumunu değiştirmek gibi bir şey gerçekleştirir. İstisna kodu, nesnenin önceki duruma döndürülmesini garanti etmez ve yakalama atıştan çok uzak olduğunda daha da azdır ... Örnek olarak, doSomething'in iki kez çağrıldığını ve fırlatmadan önce bir sayacı artırdığını hayal edin. Bir veya iki kez azaltmanız gerektiği istisnasını yakalarken nasıl anlarsınız? Genel olarak, oyuncak örneği olmayan herhangi bir şey için istisnai güvenlik kodu yazmak çok zordur (imkansız mı?).
xryl669

36

Aslında ikisini de kullanıyorum.

Bilinen, olası bir hata ise dönüş kodlarını kullanıyorum. Olabileceğini ve olacağını bildiğim bir senaryo ise, o zaman geri gönderilen bir kod var.

İstisnalar yalnızca beklemediğim şeyler için kullanılıyor.


Çok basit bir yol için +1. En azından Yeterince kısa olduğundan çok hızlı okuyabilirim. :-)
Ashish Gupta

1
" İstisnalar yalnızca beklemediğim şeyler için kullanılıyor. " Onları beklemiyorsanız, neden yapıyorsunuz veya nasıl kullanabilirsiniz?
anar khalilov

5
Bunun olmasını beklemiyor olmam, nasıl olabileceğini göremediğim anlamına gelmez. SQL Server'ımın açılmasını ve yanıt vermesini bekliyorum. Ancak, beklenmedik bir kesinti meydana gelirse, sorunsuz bir şekilde başarısız olabilmek için yine de beklentilerimi kodluyorum.
Stephen Wrighton

Yanıt vermeyen bir SQL Server kolayca "bilinen, olası hata" altında kategorize edilemez mi?
tkburbidge

21

Framework Design Guidelines: Conventions, Idioms ve Patterns for Reusable .NET Libraries'deki "Exceptions" başlıklı Bölüm 7'ye göre , C # gibi OO çerçeveleri için dönüş değerleri yerine istisnaların kullanılmasının neden gerekli olduğuna dair çok sayıda gerekçe verilmiştir.

Belki de en ikna edici neden budur (sayfa 179):

"İstisnalar, nesne yönelimli dillerle iyi bütünleşir. Nesne yönelimli diller, üye imzalarına OO olmayan dillerdeki işlevler tarafından empoze edilmeyen kısıtlamalar getirme eğilimindedir. Örneğin, yapıcılar, operatör aşırı yüklemeleri ve özellikler söz konusu olduğunda geliştirici dönüş değerinde seçenek yoktur.Bu nedenle, nesne yönelimli çerçeveler için getiri değerine dayalı hata raporlamasını standartlaştırmak mümkün değildir. İstisnalar gibi, yöntem imzasının bant dışında kalan bir hata raporlama yöntemi tek seçenektir. "


10

Tercihim (C ++ ve Python'da) istisnaları kullanmaktır. Dil tarafından sağlanan olanaklar, istisnaları hem yükseltmek, yakalamak hem de (gerekirse) yeniden atmak için iyi tanımlanmış bir süreç haline getirerek modelin görülmesini ve kullanılmasını kolaylaştırır. Kavramsal olarak, dönüş kodlarından daha temizdir, çünkü belirli istisnalar adlarıyla tanımlanabilir ve bunlara eşlik eden ek bilgiler olabilir. Bir dönüş koduyla, yalnızca hata değeriyle sınırlandırılırsınız (bir ReturnStatus nesnesi veya başka bir şey tanımlamak istemediğiniz sürece).

Yazdığınız kod zaman açısından kritik olmadığı sürece, yığının çözülmesiyle ilişkili ek yük endişelenecek kadar önemli değildir.


2
İstisnaların kullanılmasının program analizini zorlaştırdığını unutmayın.
Paweł Hajdan

7

İstisnalar, yalnızca beklemediğiniz bir şey olduğunda iade edilmelidir.

Tarihsel olarak bir diğer istisna noktası, dönüş kodlarının doğası gereği özeldir, bazen başarıyı göstermek için bir C işlevinden 0 döndürülebilir, bazen -1 veya bunlardan biri başarı için 1 ile başarısız olursa. Numaralandırıldıklarında bile, numaralandırmalar belirsiz olabilir.

İstisnalar ayrıca çok daha fazla bilgi sağlayabilir ve özellikle 'Bir Şeyler Yanlış Gidebilir, işte bu, bir yığın izleme ve bağlam için bazı destekleyici bilgiler' şeklinde iyi bir şekilde ifade edilebilir.

Bununla birlikte, iyi numaralandırılmış bir dönüş kodu, bilinen bir sonuç kümesi için yararlı olabilir, basit bir 'işlevin sonucu ve bu şekilde çalışıyor'


6

Java'da kullanıyorum (aşağıdaki sırayla):

  1. Sözleşme bazında tasarım ( başarısız olabilecek herhangi bir şeyi denemeden önce ön koşulların karşılanmasını sağlamak ). Bu çoğu şeyi yakalar ve bunun için bir hata kodu döndürürüm.

  2. İşi işlerken hata kodlarını döndürme (ve gerekirse geri alma gerçekleştirme).

  3. İstisnalar, ancak bunlar yalnızca beklenmedik şeyler için kullanılır .


1
Sözleşmeler için iddiaları kullanmak biraz daha doğru olmaz mıydı? Sözleşme bozulursa, sizi kurtaracak hiçbir şey yoktur.
Paweł Hajdan

@ PawełHajdan, iddiaların varsayılan olarak devre dışı bırakıldığına inanıyorum. Bu, asserther zaman iddialarla çalıştırmadığınız sürece üretim kodundaki sorunları yakalayamayacağı için C'nin malzemeleriyle aynı soruna sahiptir . Ben geliştirme sırasında yakalamak sorunlara bir yolu olarak iddialarını görme eğiliminde ancak sadece (örneğin, sabit, ile malzeme olarak değil, assert sürekli iddia veya olacak şeyler için değil değişkenler veya çalışma zamanında değiştirebilirsiniz başka bir şey şeyler).
paxdiablo

Ve sorunuzu yanıtlamak için on iki yıl. Ben bir yardım masası :-) çalışmalıdır
paxdiablo

6

Dönüş kodları neredeyse her seferinde benim için " Pit of Success " testinde başarısız oluyor .

  • Bir dönüş kodunu kontrol etmeyi unutmak ve daha sonra kırmızı ringa balığı hatası almak çok kolaydır.
  • Dönüş kodları, çağrı yığını, iç istisnalar gibi onlar hakkında büyük hata ayıklama bilgilerine sahip değildir.
  • Dönüş kodları, yukarıdaki nokta ile birlikte, tek bir merkezi yerde (uygulama ve iş parçacığı düzeyi istisna işleyicileri) günlük kaydı yapmak yerine aşırı ve iç içe geçmiş tanılama günlüğünü tetikleme eğiliminde olan yayılmaz.
  • Dönüş kodları, iç içe geçmiş 'if' blokları biçimindeki dağınık kodu yönlendirme eğilimindedir
  • Geliştiricinin zamanı, aksi takdirde bariz bir istisna (başarı çukuru) olacak bilinmeyen bir sorunu ayıklamak için harcanan zaman pahalıdır.
  • C # arkasındaki ekip kontrol akışını yönetmek için istisnalar istemezse, yürütmeler yazılmaz, catch ifadelerinde "when" filtresi olmaz ve parametresiz 'throw' ifadesine gerek olmaz. .

Performans ile ilgili olarak:

  • İstisnalar, hiç atmamak için hesaplama açısından pahalı olabilir, ancak bunlara bir nedenle İSTİSNALAR denir. Hız karşılaştırmaları her zaman% 100 istisna oranını varsaymayı başarır ki bu asla böyle olmamalıdır. Bir istisna 100 kat daha yavaş olsa bile, zamanın sadece% 1'inde gerçekleşirse, bu gerçekten ne kadar önemli?
  • Grafik uygulamaları veya benzer bir şey için kayan nokta aritmetiğinden bahsetmediğimiz sürece, CPU döngüleri geliştiricinin zamanına kıyasla ucuzdur.
  • Zaman açısından maliyet aynı argümanı taşır. Veritabanı sorguları veya web hizmeti çağrıları veya dosya yüklemelerine göre normal uygulama süresi, istisna süresini gölgede bırakacaktır. 2006'da istisnalar neredeyse MICRO saniyenin altındaydı
    • .Net'te çalışan herkesi, hata ayıklayıcınızı tüm istisnaları kıracak ve sadece kodumu devre dışı bırakacak şekilde ayarlamaya ve sizin bile bilmediğiniz kaç istisna olduğunu görmeye cesaret ediyorum.

4

The Pragmatic Programmer'dan aldığım harika bir tavsiye , "programınız, istisnalar kullanmadan tüm ana işlevlerini yerine getirebilmelidir."


4
Yanlış yorumluyorsun. Demek istedikleri, "programınız normal akışında istisnalar atıyorsa, bu yanlıştır" idi. Başka bir deyişle "istisnai şeyler için yalnızca istisnaları kullanın".
Paweł Hajdan

4

Bir süre önce bununla ilgili bir blog yazısı yazdım .

Bir istisna atmanın performans yükü, kararınızda herhangi bir rol oynamamalıdır. Doğru yapıyorsanız, sonuçta istisnai bir durumdur .


Bağlantılı blog yazısı, atlamadan önce bakmakla (kontroller), af istemek izin istemekten daha kolaydır (istisnalar veya dönüş kodları). Orada bu konuyla ilgili düşüncelerimle yanıt verdim (ipucu: TOCTTOU). Ancak bu soru, farklı bir konuyla, yani özel niteliklerle bir değer döndürmek yerine bir dilin istisna mekanizmasının hangi koşullarda kullanılacağıyla ilgilidir.
Damian Yerrick

Tamamen katılıyorum. Görünüşe göre son dokuz yılda bir veya iki şey öğrendim;)
Thomas

4

Dönüş kodlarından hoşlanmıyorum çünkü kodunuz boyunca aşağıdaki kalıbın mantar gibi görünmesine neden oluyorlar

CRetType obReturn = CODE_SUCCESS;
obReturn = CallMyFunctionWhichReturnsCodes();
if (obReturn == CODE_BLOW_UP)
{
  // bail out
  goto FunctionExit;
}

Yakında 4 işlev çağrısından oluşan bir yöntem çağrısı, 12 satırlık hata işleme ile şişirir. Bazıları asla gerçekleşmeyecek. If ve switch vakaları çoktur.

İstisnalar, istisnai olayları işaret etmek için iyi kullanırsanız daha temizdir .. bundan sonra yürütme yolu devam edemez. Genellikle hata kodlarından daha açıklayıcı ve bilgilendiricidirler.

Farklı şekilde işlenmesi gereken (ve istisnai durumlar olmayan) bir yöntem çağrısından sonra birden fazla durumunuz varsa, hata kodlarını veya parametreleri kullanın. Personaly bunu nadir bulsam da ..

'Performans cezası' karşı argümanını biraz araştırdım .. C ++ / COM dünyasında daha fazla ama yeni dillerde, bence fark o kadar da değil. Her durumda, bir şey patladığında, performans endişeleri arka plana atılır :)


3

Herhangi bir düzgün derleyici veya çalıştırma ortamı istisnası önemli bir cezaya neden olmaz. Aşağı yukarı, istisna işleyiciye atlayan bir GOTO ifadesi gibidir. Ayrıca, bir çalışma zamanı ortamı (JVM gibi) tarafından yakalanan istisnalara sahip olmak, bir hatayı çok daha kolay izole etmeye ve düzeltmeye yardımcı olur. Herhangi bir gün C'deki bir segfault yerine Java'da NullPointerException alacağım.


2
İstisnalar son derece pahalıdır. Olası istisna işleyicileri bulmak için yığında yürümeleri gerekir. Bu yığın yürüyüşü ucuz değil. Bir yığın izleme oluşturulursa, daha da pahalıdır, çünkü o zaman tüm yığının ayrıştırılması gerekir .
Derek Park

Derleyicilerin en azından bazen istisnanın nerede yakalanacağını belirleyememesine şaşırdım. Artı, bir istisnanın kod akışını değiştirmesi gerçeği, bence bir hatanın tam olarak nerede oluştuğunu belirlemeyi kolaylaştırır, bir performans cezası oluşturur.
Kyle Cronin

Çağrı yığınları, çalışma zamanında son derece karmaşık olabilir ve derleyiciler genellikle bu tür bir analiz yapmaz. Olsalar bile, iz almak için yine de yığını yürümek zorunda kalırsınız. Ayrıca, yığın finallytahsisli nesneler için bloklar ve yıkıcılarla başa çıkmak için yığını çözmeniz gerekir.
Derek Park

Yine de, istisnaların hata ayıklama faydalarının genellikle performans maliyetlerini oluşturduğuna katılıyorum.
Derek Park

1
Derak Park, istisna olduklarında pahalıdır. Fazla kullanılmamalarının nedeni budur. Ama olmadıklarında neredeyse hiçbir maliyeti yoktur.
paercebal

3

Bir dizi basit kuralım var:

1) Hemen arayan kişinin tepki vermesini beklediğiniz şeyler için dönüş kodlarını kullanın.

2) Kapsam olarak daha geniş olan ve arayanın üstündeki bir şey tarafından ele alınması makul olan istisnalar kullanın, böylece hatanın farkında olunması birçok katmandan geçerek kodu daha karmaşık hale getirmek zorunda kalmaz.

Java'da sadece kontrol edilmemiş istisnalar kullandım, kontrol edilen istisnalar sadece başka bir dönüş kodu formuna dönüştü ve benim deneyimlerime göre bir yöntem çağrısı tarafından "döndürülebilecek" şeyin ikiliği genellikle bir yardımdan çok bir engeldi.


3

Python'da İstisnaları hem İstisnai hem de İstisnai olmayan durumlarda kullanıyorum.

Hata değeri döndürmenin aksine, "istek gerçekleştirilemedi" yi belirtmek için bir İstisna kullanabilmek çoğu zaman güzeldir. Bu, dönüş değerinin keyfi olarak Yok veya NotFoundSingleton veya başka bir şey yerine doğru tür olduğunu / her zaman / bildiğiniz anlamına gelir. Burada, dönüş değeri için bir koşul yerine bir istisna işleyicisi kullanmayı tercih ettiğim yerlere iyi bir örnek.

try:
    dataobj = datastore.fetch(obj_id)
except LookupError:
    # could not find object, create it.
    dataobj = datastore.create(....)

Bunun yan etkisi, bir datastore.fetch (obj_id) çalıştırıldığında, dönüş değerinin None olup olmadığını kontrol etmek zorunda kalmazsınız, bu hatayı hemen ücretsiz alırsınız. Bu, "programınız tüm ana işlevlerini istisnalar kullanmadan gerçekleştirebilmelidir" argümanına aykırıdır.

Yarış koşullarına tabi olmayan dosya sistemi ile başa çıkmak için kod yazmak için istisnaların 'istisnai olarak' yararlı olduğu başka bir örnek.

# wrong way:
if os.path.exists(directory_to_remove):
    # race condition is here.
    os.path.rmdir(directory_to_remove)

# right way:
try: 
    os.path.rmdir(directory_to_remove)
except OSError:
    # directory didn't exist, good.
    pass

İki yerine bir sistem çağrısı, yarış durumu yok. Bu kötü bir örnektir, çünkü açıkçası bu, dizinin mevcut olmadığından daha fazla durumda bir OSError ile başarısız olacaktır, ancak çok sıkı kontrol edilen birçok durum için 'yeterince iyi' bir çözümdür.


İkinci örnek yanıltıcıdır. Sözde yanlış yol yanlıştır çünkü os.path.rmdir kodu istisna atmak için tasarlanmıştır. Dönüş kodu akışında doğru uygulama 'eğer rmdir (...) == BAŞARISIZ ise: pass' olacaktır
MaR

3

Dönüş kodlarının kod gürültüsüne katkıda bulunduğuna inanıyorum. Örneğin, dönüş kodları nedeniyle COM / ATL kodunun görünümünden her zaman nefret ettim. Her kod satırı için bir HRESULT kontrolü olmalıydı. Hata dönüş kodunun COM mimarları tarafından verilen kötü kararlardan biri olduğunu düşünüyorum. Kodun mantıksal gruplamasını zorlaştırır, bu nedenle kod incelemesi zorlaşır.

Her satırda dönüş kodu için açık bir kontrol olduğunda performans karşılaştırmasından emin değilim.


1
COM wad, istisnaları desteklemeyen diller tarafından kullanılmak üzere tasarlanmıştır.
Kevin

Bu iyi bir nokta. Kodlama dilleri için hata kodlarıyla uğraşmak mantıklıdır. En azından VB6, hata kodu ayrıntılarını Err nesnesinde kapsayarak gizler, bu da daha temiz kodda yardımcı olur.
rpattabi

Katılmıyorum: VB6 yalnızca son hatayı kaydeder. Kötü şöhretli "bir sonraki hatada devam et" ile birleştiğinde, sorununuzu gördüğünüzde kaynağını tamamen özleyeceksiniz. Bunun Win32 APII'deki hata işlemenin temeli olduğunu unutmayın (GetLastError işlevine bakın)
paercebal

2

Bir işlevin normal sonucu olarak hata işleme ve dönüş değerleri (veya parametreler) için istisnaları kullanmayı tercih ederim. Bu, kolay ve tutarlı bir hata işleme planı sağlar ve doğru yapılırsa çok daha temiz görünen kod sağlar.


2

En büyük farklardan biri, istisnaların sizi bir hatayı işlemeye zorlamasıdır, oysa hata dönüş kodları kontrol edilmeyebilir.

Hata dönüş kodları, yoğun şekilde kullanılırsa, bu forma benzer çok sayıda if testiyle çok çirkin koda neden olabilir:

if(function(call) != ERROR_CODE) {
    do_right_thing();
}
else {
    handle_error();
}

Şahsen ben, çağıran kod tarafından ETKİLENMESİ GEREKEN veya ZORUNLU olan hatalar için istisnalar kullanmayı ve sadece bir şeyi döndürmenin gerçekten geçerli ve mümkün olduğu "beklenen hatalar" için hata kodlarını kullanmayı tercih ederim.


En azından C / C ++ ve gcc'de bir işleve dönüş değeri göz ardı edildiğinde uyarı oluşturacak bir öznitelik verebilirsiniz.
Paweł Hajdan

phjr: "Hata kodu döndür" kalıbına katılmasam da, yorumunuz belki de tam bir cevap olmalıdır. Yeterince ilginç buluyorum. En azından bana faydalı bir bilgi verdi.
paercebal

2

Dönüş kodu yerine İstisnaları tercih etmenin birçok nedeni vardır:

  • Genellikle okunabilirlik için insanlar bir yöntemdeki dönüş ifadesinin sayısını en aza indirmeye çalışır. Bunu yaparken, istisnalar, incoorect durumundayken fazladan iş yapılmasını engeller ve böylece daha fazla veriye zarar verme potansiyeline sahiptir.
  • İstisnalar genellikle daha ayrıntılıdır ve dönüş değerinden daha kolay genişletilebilirdir. Bir yöntemin doğal sayı döndürdüğünü ve bir hata oluştuğunda dönüş kodu olarak negatif sayılar kullandığınızı varsayalım, yönteminizin kapsamı değişirse ve şimdi tamsayılar döndürürse, sadece biraz ince ayar yapmak yerine tüm yöntem çağrılarını değiştirmeniz gerekir. istisna.
  • İstisnalar, hata işlemeyi normal davranıştan daha kolay ayırmaya izin verir. Bazı işlemlerin bir şekilde atomik bir işlem olarak gerçekleştirilmesini sağlarlar.

2

İstisnalar hata işleme için değildir, IMO. İstisnalar sadece bu; beklemediğiniz istisnai olaylar. Dikkatli kullanın diyorum.

Hata kodları tamam olabilir, ancak bir yöntemden 404 veya 200 döndürmek kötüdür, IMO. Bunun yerine, kodu daha okunaklı ve diğer geliştiriciler için daha kolay kullanılmasını sağlayan numaralandırmaları (.Net) kullanın. Ayrıca sayılar ve açıklamalar üzerinde bir tablo tutmanıza gerek yoktur.

Ayrıca; dene-yakala-nihayet kalıbı kitabımda bir anti-modeldir. Try-nihayet iyi olabilir, dene-yakala da iyi olabilir ama dene-yakala-nihayet asla iyi değildir. try-final çoğu zaman bir "using" deyimi (IDispose kalıbı) ile değiştirilebilir, bu daha iyi IMO'dur. Ve gerçekten başa çıkabileceğiniz bir istisnayı yakaladığınız yerde deneyin iyi olur veya bunu yaparsanız:

try{
    db.UpdateAll(somevalue);
}
catch (Exception ex) {
    logger.Exception(ex, "UpdateAll method failed");
    throw;
}

İstisnanın patlamaya devam etmesine izin verdiğiniz sürece sorun değil. Başka bir örnek şudur:

try{
    dbHasBeenUpdated = db.UpdateAll(somevalue); // true/false
}
catch (ConnectionException ex) {
    logger.Exception(ex, "Connection failed");
    dbHasBeenUpdated = false;
}

Burada aslında istisnayı ele alıyorum; güncelleme yöntemi başarısız olduğunda, deneme-yakalama dışında yaptığım şey başka bir hikaye, ama sanırım amacım açıklandı. :)

Öyleyse neden dene-yakala-bir anti-model? İşte nedeni:

try{
    db.UpdateAll(somevalue);
}
catch (Exception ex) {
    logger.Exception(ex, "UpdateAll method failed");
    throw;
}
finally {
    db.Close();
}

Db nesnesi zaten kapatılmışsa ne olur? Yeni bir istisna atıldı ve ele alınması gerekiyor! Bu daha iyi:

try{
    using(IDatabase db = DatabaseFactory.CreateDatabase()) {
        db.UpdateAll(somevalue);
    }
}
catch (Exception ex) {
    logger.Exception(ex, "UpdateAll method failed");
    throw;
}

Veya, db nesnesi IDisposable uygulamıyorsa şunu yapın:

try{
    try {
        IDatabase db = DatabaseFactory.CreateDatabase();
        db.UpdateAll(somevalue);
    }
    finally{
        db.Close();
    }
}
catch (DatabaseAlreadyClosedException dbClosedEx) {
    logger.Exception(dbClosedEx, "Database connection was closed already.");
}
catch (Exception ex) {
    logger.Exception(ex, "UpdateAll method failed");
    throw;
}

Bu benim 2 sentim zaten! :)


Bir nesnede .Close () varsa ancak .Dispose () yoksa garip olacak
abatishchev

Örnek olarak yalnızca Close () kullanıyorum. Bunu başka bir şey olarak düşünmekten çekinmeyin. Dediğim gibi; varsa kullanım modeli kullanılmalıdır (doh!). Elbette bu, sınıfın IDisposable uyguladığını ve dolayısıyla Dispose çağırabileceğiniz anlamına gelir.
noocyte

1

Yalnızca istisnalar kullanıyorum, dönüş kodu yok. Burada Java'dan bahsediyorum.

İzlediğim genel kural şudur: Eğer bir metodu çağırdığımda, doFoo()o zaman olduğu gibi "do foo" olmazsa, o zaman istisnai bir şey olmuştur ve bir İstisna atılmalıdır.


1

İstisnalar hakkında korktuğum bir şey, bir istisna atmanın kod akışını bozacağıdır. Örneğin yaparsan

void foo()
{
  MyPointer* p = NULL;
  try{
    p = new PointedStuff();
    //I'm a module user and  I'm doing stuff that might throw or not

  }
  catch(...)
  {
    //should I delete the pointer?
  }
}

Ya da daha da kötüsü, sahip olmamam gereken bir şeyi sildiysem, ancak temizliğin geri kalanını yapmadan önce yakalamak için fırlatılsaydım. Fırlatma, zayıf kullanıcı IMHO'ya çok fazla ağırlık verdi.


<code> nihayet </code> ifadesi bunun içindir. Ancak, ne yazık ki, C ++ standardında değil ...
Thomas

C ++ 'da temel kurala sadık kalmalısınız "Yapılandırıcıdaki kaynakları edinin ve onları destrucotor'da serbest bırakın. Bu özel durum için auto_ptr tam anlamıyla işe yarayacak.
Serge

Thomas, yanılıyorsun. C ++ buna ihtiyaç duymadığı için nihayet olmadı. Onun yerine RAII var. Serge'nin çözümü, RAII kullanan bir çözümdür.
paercebal

Robert, Serge'nin çözümünü kullan ve sorunun ortadan kalktığını göreceksin. Şimdi, attığınızdan daha fazla dene / yakala yazarsanız, o zaman (yorumunuzu değerlendirerek) belki kodunuzda bir probleminiz vardır. Elbette, tekrar atmadan catch (...) kullanmak genellikle kötüdür, çünkü hatayı daha iyi görmezden gelmek için gizler.
paercebal

1

İstisna ve dönüş kodu argümanındaki genel kuralım:

  • Yerelleştirme / uluslararasılaştırmaya ihtiyaç duyduğunuzda hata kodlarını kullanın - .NET'te, bu hata kodlarını, daha sonra hatayı uygun dilde görüntüleyecek bir kaynak dosyasına başvurmak için kullanabilirsiniz. Aksi takdirde istisnaları kullanın
  • İstisnaları yalnızca gerçekten istisnai olan hatalar için kullanın . Oldukça sık meydana gelen bir şeyse, ya bir boole ya da bir enum hata kodu kullanın.

L10n / i18n kullandığınızda bir istisna kullanamamanız için hiçbir neden yok. İstisnalar yerelleştirilmiş bilgiler de içerebilir.
gizmo

1

Dönüş kodlarını istisnalardan daha az çirkin bulmuyorum. İstisna try{} catch() {} finally {}olarak, dönüş kodlarınızda olduğu gibi nereye sahipsiniz if(){}. Gönderide verilen nedenlerden dolayı istisnalardan korkardım; İşaretçinin temizlenmesi gerekip gerekmediğini bilmiyorsun, sende ne var. Ama dönüş kodları söz konusu olduğunda aynı sorunları yaşadığınızı düşünüyorum. Söz konusu işlev / yöntemle ilgili bazı ayrıntıları bilmediğiniz sürece parametrelerin durumunu bilemezsiniz.

Her şeye rağmen, mümkünse hatayı halletmelisiniz. Bir istisnanın en üst seviyeye yayılmasına, bir dönüş kodunu göz ardı edip programın segmentine izin vermesi kadar kolayca izin verebilirsiniz.

Sonuçlar için bir değer (numaralandırma?) Ve istisnai bir durum için bir istisna döndürme fikrini seviyorum.


0

Java gibi bir dil için Exception ile giderdim çünkü istisnalar işlenmezse derleyici derleme zamanı hatası verir.Bu, çağıran işlevi istisnaları işlemeye / atmaya zorlar.

Python için daha çelişkili durumdayım. Derleyici olmadığından, çağıranın, çalışma zamanı istisnalarına yol açan işlev tarafından atılan istisnayı işlememesi mümkündür. Dönüş kodlarını kullanırsanız, doğru şekilde ele alınmazsa beklenmedik davranışlarınız olabilir ve istisnaları kullanırsanız çalışma zamanı istisnaları alabilirsiniz.


0

Genelde dönüş kodlarını tercih ederim çünkü arayanın hatanın istisnai olup olmadığına karar vermesine izin veriyorlar .

Bu yaklaşım Elixir dilinde tipiktir.

# I care whether this succeeds. If it doesn't return :ok, raise an exception.
:ok = File.write(path, content)

# I don't care whether this succeeds. Don't check the return value.
File.write(path, content)

# This had better not succeed - the path should be read-only to me.
# If I get anything other than this error, raise an exception.
{:error, :erofs} = File.write(path, content)

# I want this to succeed but I can handle its failure
case File.write(path, content) do
  :ok => handle_success()
  error => handle_error(error)
end

İnsanlar, dönüş kodlarının çok sayıda iç içe ififadeye sahip olmanıza neden olabileceğinden bahsetti , ancak bu daha iyi sözdizimi ile ele alınabilir. Elixir'de ifade, withbir dizi mutlu yol dönüş değerini herhangi bir başarısızlıktan kolayca ayırmamızı sağlar.

with {:ok, content} <- get_content(),
  :ok <- File.write(path, content) do
    IO.puts "everything worked, happy path code goes here"
else
  # Here we can use a single catch-all failure clause
  # or match every kind of failure individually
  # or match subsets of them however we like
  _some_error => IO.puts "one of those steps failed"
  _other_error => IO.puts "one of those steps failed"
end

Elixir'in hala istisnaları artıran işlevleri vardır. İlk örneğime geri dönersek, dosya yazılamıyorsa bir istisna oluşturmak için bunlardan herhangi birini yapabilirim.

# Raises a generic MatchError because the return value isn't :ok
:ok = File.write(path, content)

# Raises a File.Error with a descriptive error message - eg, saying
# that the file is read-only
File.write!(path, content)

Arayan olarak yazma başarısız olursa bir hata yapmak istediğimi bilirsem, File.write!bunun yerine aramayı seçebilirim File.write. Ya File.writeda olası başarısızlık nedenlerinin her birini farklı şekilde arayıp ele almayı seçebilirim .

Elbette rescue, istersek bir istisna yapmak her zaman mümkündür . Ancak bilgilendirici bir getiri değeriyle başa çıkmakla karşılaştırıldığında, bana garip geliyor. Bir işlev çağrısının başarısız olabileceğini veya hatta başarısız olması gerektiğini bilirsem, başarısızlığı istisnai bir durum değildir.

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.