Deneme / Yakalama / Günlük / Tekrar Et - Anti Desen mi?


19

Merkezi konumda veya süreç sınırında istisna işlemenin öneminin denemek / yakalamak etrafında her kod bloğunu çöp yerine iyi bir uygulama olarak vurgulandığı birkaç yazı görebiliyorum. Çoğumuzun önemini anladığımıza inanıyorum, ancak insanların genellikle herhangi bir istisna sırasında sorun gidermeyi kolaylaştırmak için catch-log-rethrow anti modeliyle son bulduklarını görüyorum. geçti) ve yol, try / catch / log / rethrow yöntemini sarmaktır.

public static bool DoOperation(int num1, int num2)
{
    try
    {
        /* do some work with num1 and num2 */
    }
    catch (Exception ex)
    {
        logger.log("error occured while number 1 = {num1} and number 2 = {num2}"); 
        throw;
    }
}

İstisna yönetimi iyi uygulamalarını sürdürürken bunu başarmanın doğru yolu var mı? Bunun için PostSharp gibi AOP çerçevesini duydum, ancak bu AOP çerçeveleriyle ilişkili herhangi bir olumsuz veya büyük performans maliyeti olup olmadığını bilmek istiyorum.

Teşekkürler!


6
Her bir yöntemi dene / yakala sarma, özel durumu günlüğe kaydetme ve kodun değişmesine izin verme arasında büyük bir fark vardır. Ve ek bilgileri kullanarak istisnayı yeniden yakalamaya çalışın. Birincisi korkunç bir uygulamadır. İkincisi, hata ayıklama deneyiminizi geliştirmenin mükemmel bir yoludur.
Euphoric

Ben denemek / yakalamak her yöntemi yakalamak ve sadece yakalamak blok ve yeniden giriş oturum - söylüyorum yapmak için bu tamam mı?
rahulaga_dev

2
Amon'un işaret ettiği gibi. Dilinizde yığın izleri varsa, her yakalamada oturum açmak anlamsızdır. Ancak istisnayı sarmak ve ek bilgi eklemek iyi bir uygulamadır.
Euphoric

1
Bakınız @ Liath'ın cevabı. Verdiğim herhangi bir cevap hemen hemen onu yansıtır: istisnaları mümkün olduğu kadar erken yakalayın ve eğer bu aşamada yapabileceğiniz tek şey bazı yararlı bilgileri günlüğe kaydetmekse, o zaman yapın ve yeniden düşünün. Bunu bir antipattern olarak görmek bence saçma.
David Arno

1
Liath: küçük kod snippet'i eklendi. Ben c # kullanıyorum
rahulaga_dev

Yanıtlar:


19

Sorun yerel catch bloğu değil, sorun log ve rethrow . İstisnayı ele alın veya ek bağlam ekleyen yeni bir istisna ile sarın ve atın. Aksi takdirde, aynı istisna için birkaç yinelenen günlük girişi ile karşılaşacaksınız.

Buradaki fikir , uygulamanızda hata ayıklama yeteneğini geliştirmektir .

Örnek 1 - Ele alın

try
{
    doSomething();
}
catch (Exception e)
{
    log.Info("Couldn't do something", e);
    doSomethingElse();
}

Kural dışı durumu ele alırsanız, kural dışı durum günlüğü girdisinin önemini kolayca azaltabilirsiniz ve bu kural dışı durumu zincirlemek için bir neden yoktur. Zaten halledildi.

Bir istisnayı ele almak, kullanıcılara bir sorunun olduğunu bildirmeyi, olayı günlüğe kaydetmeyi veya basitçe yok saymayı içerebilir.

NOT: Bir istisnayı kasıtlı olarak yoksayarsanız, boş catch yan tümcesinde bunun nedenini açıkça belirten bir yorum sağlamanızı öneririm. Bu, gelecekteki bakıcıların bunun bir hata veya tembel programlama olmadığını bilmelerini sağlar. Misal:

try
{
    context.DrawLine(x1,y1, x2,y2);
}
catch (OutOfMemoryException)
{
    // WinForms throws OutOfMemory if the figure you are attempting to
    // draw takes up less than one pixel (true story)
}

2. Örnek: Ek bağlam ekleyin ve atın

try
{
    doSomething(line);
}
catch (Exception e)
{
    throw new MyApplicationException(filename, line, e);
}

Ek bağlam eklemek (ayrıştırma kodundaki satır numarası ve dosya adı gibi), giriş dosyalarında hata ayıklama yeteneğini geliştirmeye yardımcı olabilir - sorunun orada olduğu varsayılarak. Bu bir tür özel durumdur, bu nedenle "ApplicationException" içinde bir istisnayı yeniden paketlemek yalnızca hata ayıklamanıza yardımcı olmaz. Ek bilgi eklediğinizden emin olun.

Örnek 3: İstisna dışında hiçbir şey yapmayın

try
{
    doSomething();
}
finally
{
   // cleanup resources but let the exception percolate
}

Bu son durumda, istisnanın dokunmadan ayrılmasına izin verirsiniz. En dış katmandaki özel durum işleyici, günlüğe kaydetmeyi işleyebilir. finallyFıkra kadar temizlenir sizin yöntemiyle gerekli emin tüm kaynakları yapmak için kullanılan, ancak bu istisna atıldı oturum açmak için yer değildir.


Ben sevdim " Sorun yerel catch bloğu değil, sorun günlük ve tekrar " Ben bu temiz günlüğü sağlamak için mükemmel mantıklı olduğunu düşünüyorum. Ama nihayetinde tüm yöntemlere dağılmaya çalış / yakala ile iyi olmak demek, öyle değil mi? Bence her yöntemin uygulanmasından ziyade bu uygulamanın mantıklı bir şekilde takip edilmesini sağlayacak bazı kurallar olmalı.
rahulaga_dev

Cevabımda bir rehber hazırladım. Bu sorularınızı cevaplamıyor mu?
Berin Loritsch

@rahulaga_dev Ben bir kılavuz / gümüş kurşun olduğunu sanmıyorum, bu yüzden büyük ölçüde bağlama bağlı olduğu için bu sorunu çözmek. Bir istisnanın nerede ele alınacağını veya ne zaman yeniden ele alınacağını söyleyen genel bir kılavuz olamaz. Gördüğüm tek kılavuz IMO'yu günlüğe kaydetmeyi / işlemeyi mümkün olan en son süreye ertelemeyi ve gereksiz bağımlılıklar oluşturmamanız için yeniden kullanılabilir kodda oturum açmayı önlemektir. Bir şeyleri (yani istisnaları ele aldılar) kendi yollarıyla işleme fırsatı vermeden kaydettiyseniz, kodunuzun kullanıcıları çok eğlenmezlerdi. Sadece iki sent :)
andreee

7

Yerel yakalamaların bir anti-desen olduğuna inanmıyorum, aslında doğru hatırlıyorsam aslında Java'da zorlanıyor!

Hata işlemeyi uygularken benim için kilit nokta genel stratejidir. Servis sınırındaki tüm istisnaları yakalayan bir filtre isteyebilirsiniz, bunları manuel olarak ele almak isteyebilirsiniz - her ikisi de, takımlarınızın kodlama standartlarına girecek genel bir strateji olduğu sürece iyidir.

Şahsen ben aşağıdakilerden birini yapabildiğimde bir fonksiyon içindeki hataları yakalamak istiyorum:

  • Bağlamsal bilgiler ekleyin (nesnelerin durumu veya olan bitenler gibi)
  • Kural dışı durumu güvenli bir şekilde yönetme (TryX yöntemi gibi)
  • Sisteminiz bir hizmet sınırını aşıyor ve harici bir kitaplığa veya API'ya çağrı yapıyor
  • Farklı bir istisna türünü yakalamak ve yeniden düzenlemek istersiniz (belki orijinali bir iç istisna olarak)
  • İstisna, bazı düşük değerli arka plan işlevlerinin bir parçası olarak atıldı

Bu durumlardan biri değilse, yerel bir try / catch eklemem. Öyleyse, senaryoya bağlı olarak istisnayı (örneğin, yanlış döndüren bir TryX yöntemi) işleyebilir veya istisna genel strateji tarafından ele alınabilir.

Örneğin:

public bool TryConnectToDatabase()
{
  try
  {
    this.ConnectToDatabase(_databaseType); // this method will throw if it fails to connect
    return true;
  }
  catch(Exception ex)
  {
     this.Logger.Error(ex, "There was an error connecting to the database, the databaseType was {0}", _databaseType);
    return false;
  }
}

Veya yeniden inceleme örneği:

public IDbConnection ConnectToDatabase()
{
  try
  {
    // connect to the database and return the connection, will throw if the connection cannot be made
  }
  catch(Exception ex)
  {
     this.Logger.Error(ex, "There was an error connecting to the database, the databaseType was {0}", _databaseType);
    throw;
  }
}

Sonra yığının üstündeki hatayı yakalar ve kullanıcıya hoş bir kullanıcı dostu mesaj sunar.

Hangi yaklaşımı kullanırsanız kullanın, bu senaryolar için daima birim testleri oluşturmaya değer; böylece işlevin daha sonraki bir tarihte projenin akışını değiştirmediğinden ve akışını bozmadığından emin olabilirsiniz.

Hangi dilde çalıştığınızdan bahsetmediniz, ancak bir .NET geliştiricisi olduğunuzu ve bunu birçok kez görmediğinizi gördünüz.

YAZMA:

catch(Exception ex)
{
  throw ex;
}

kullanın:

catch(Exception ex)
{
  throw;
}

Birincisi yığın izini sıfırlar ve en üst seviyenizi tamamen işe yaramaz hale getirir!

TLDR

Yerel olarak yakalamak bir anti-desen değildir, genellikle bir tasarımın parçası olabilir ve hataya ek bağlam eklemeye yardımcı olabilir.


3
Üst düzey istisna işleyicide aynı günlükçünün ne zaman kullanılacağını yakalamada günlüğe kaydetme noktası nedir?
Euphoric

Yığının en üstünde erişemeyeceğiniz ek bilgilere (yerel değişkenler gibi) sahip olabilirsiniz. Örneklemek için örneği güncelleyeceğim.
Liath

2
Bu durumda, ek veriler ve iç istisna ile yeni bir istisna atın.
Euphoric

2
@Euphoric yep, ben de gördüm, şahsen ben bir hayran değilim ama çok yükü olduğunu hissediyorum hemen hemen her yöntem / senaryo için yeni istisna türleri oluşturmanızı gerektirir çünkü. Buraya günlük satırını eklemek (ve muhtemelen üstte bir tane daha) sorunları teşhis ederken kod akışını göstermeye yardımcı olur
Liath

4
Java sizi istisnayı ele almaya zorlamaz, bunun farkında olmaya zorlar. ya onu yakalamak ve ne olursa olsun yapabilir ya da sadece fonksiyon atmak ve fonksiyon onunla bir şey yapmak değil bir şey olarak ilan .... Aksi takdirde oldukça iyi bir cevap küçük nitpicking!
Newtopian

4

Bu büyük ölçüde dile bağlıdır. Örneğin, C ++ istisna hata mesajlarında yığın izleri sunmaz, bu nedenle istisnaları sık sık yakalama-log-rethrow ile izlemek faydalı olabilir. Buna karşılık, Java ve benzer diller çok iyi yığın izleri sunar, ancak bu yığın izlerinin biçimi çok yapılandırılamayabilir. Gerçekten önemli bir bağlam ekleyemediğiniz sürece (örn. Bir alt düzey SQL istisnasını bir iş mantığı işlemi bağlamıyla bağlama) bu dillerdeki istisnaları yakalamak ve yeniden düzenlemek tamamen anlamsızdır.

Yansıtma yoluyla uygulanan herhangi bir hata işleme stratejisi, dilde yerleşik işlevlerden neredeyse daha az verimlidir. Ayrıca, yaygın günlük kaydı kaçınılmaz performans yüküne sahiptir. Bu nedenle, bu yazılım için diğer gereksinimlerle elde ettiğiniz bilgi akışını dengelemeniz gerekir. Bununla birlikte, derleyici düzeyinde enstrümantasyon üzerine inşa edilen PostSharp gibi çözümler genellikle çalışma zamanı yansımasından çok daha iyi sonuç verecektir.

Şahsen her şeyin günlüğe kaydedilmesinin yararlı olmadığına inanıyorum, çünkü bir sürü alakasız bilgi içeriyor. Bu nedenle otomatik çözümlere şüpheliyim. İyi bir günlük kaydı çerçevesi göz önüne alındığında, ne tür bilgileri kaydetmek istediğinizi ve bu bilgilerin nasıl biçimlendirilmesi gerektiğini tartışan üzerinde anlaşmaya varılmış bir kodlama kılavuzuna sahip olmak yeterli olabilir. Ardından, önemli olduğu yerde günlük kaydı ekleyebilirsiniz.

İş mantığında oturum açmak, yardımcı program işlevlerinde oturum açmaktan çok daha önemlidir. Ve gerçek dünya kilitlenme raporlarının yığın izlerini toplamak (yalnızca bir işlemin en üst düzeyinde günlüğe kaydetmeyi gerektirir) kodun günlüğe kaydetmenin en değerli olacağı alanları bulmanızı sağlar.


4

try/catch/logHer yöntemde gördüğümde , geliştiricilerin uygulamalarında ne olabileceği veya olmayabileceği hakkında hiçbir fikre sahip olmadıkları, en kötüsünü aldıkları ve bekledikleri tüm hatalar nedeniyle her yerde önleyici olarak her şeyi kaydettikleri endişeleri ortaya çıkıyor.

Bu, birim ve entegrasyon testlerinin yetersiz olduğunu ve geliştiricilerin hata ayıklayıcıdaki çok sayıda koddan geçmeye alışık olduklarını ve bir sürü günlük kaydının, buggy kodunu bir test ortamında dağıtmalarına ve sorunlara bakarak bulmalarını umduklarını gösteren bir belirtidir. günlükleri.

Kod atar istisnalar gereksiz kod o yakalar ve günlükleri istisnalar daha yararlı olabilir. Bir yöntem beklenmedik bir argüman aldığında anlamlı bir mesajla bir istisna atarsanız (ve hizmet sınırında günlüğe kaydederseniz), geçersiz argümanın bir yan etkisi olarak atılan istisnayı hemen günlüğe kaydetmekten ve buna neden olduğunu tahmin etmekten çok daha yararlıdır. .

Sıfırlar buna bir örnektir. Bağımsız değişken veya yöntem çağrısının sonucu olarak bir değer alırsanız ve bu değerin boş olmaması gerekiyorsa, istisnayı atın. NullReferenceExceptionNull değeri nedeniyle sonuçta atılan beş satırı daha sonra günlüğe kaydetmeyin. Her iki durumda da bir istisna alırsınız, ancak biri size bir şey söylerken, diğeri sizi bir şey aramanızı sağlar.

Başkaları tarafından söylendiği gibi, istisnaları hizmet sınırında veya bir istisna yeniden gözden geçirilmediğinde günlüğe kaydedildiği için günlüğe kaydetmek en iyisidir. En önemli fark bir şeyle hiçbir şey arasındadır. İstisnalarınız kolayca ulaşılabilecek tek bir yere kaydedilirse, ihtiyacınız olan bilgileri ihtiyacınız olduğunda bulabilirsiniz.


Teşekkürler Scott. " Bir yöntem beklenmedik bir argüman aldığında anlamlı bir mesajla bir istisna atarsanız (ve hizmet sınırında günlüğe kaydederseniz) " gerçekten çarptı ve yöntem argümanları bağlamında etrafımda dolaşan durumu görselleştirmeme yardımcı oldu. Bence güvenlik görevlisi maddeye sahip olmak ve argüman ayrıntılarını yakalamak ve günlüğe kaydetmek yerine ArgumentException'ı kullanmak mantıklıdır
rahulaga_dev

Scott, bende aynı his var. Ne zaman günlük görmek ve sadece bağlamı günlüğe yeniden görüntülemek, geliştiriciler sınıfın değişmezleri kontrol edemez veya yöntem çağrısı korumak mümkün değil gibi hissediyorum. Bunun yerine her yöntemin tamamı benzer bir try / catch / log / throw içine sarılır. Ve bu sadece korkunç.
Maksimum

2

Zaten istisna dışında olmayan bağlam bilgilerini kaydetmeniz gerekiyorsa, bunu yeni bir istisnaya sarın ve orijinal istisnayı olarak sağlayın InnerException. Bu şekilde orijinal yığın izini koruyabilirsiniz. Yani:

public static bool DoOperation(int num1, int num2)
{
    try
    {
        /* do some work with num1 and num2 */
    }
    catch (Exception ex)
    {
        throw new Exception("error occured while number 1 = {num1} and number 2 = {num2}", ex);
    }
}

Yapıcıya ikinci parametre Exceptionbir iç istisna sağlar. Daha sonra tüm istisnaları tek bir yerde kaydedebilirsiniz ve yine de tüm yığın izlemesini ve bağlamsal bilgileri aynı günlük girişinde alabilirsiniz.

Özel bir istisna sınıfı kullanmak isteyebilirsiniz, ancak nokta aynı.

kafa karıştırıcı günlüklere yol açacağı için denemek / yakalamak / log / rethrow bir karışıklıktır - örneğin, bağlamsal bilgilerin kaydedilmesi ile asıl istisnanın üstündeki gerçek istisnaların kaydedilmesi arasında başka bir iş parçacığında farklı bir istisna olursa ne olur? Yeni istisna orijinal belgeye bilgi eklerse try / catch / throw gayet iyi.


Orijinal istisna türü ne olacak? Sarırsak, gitti. Sorun mu? Birisi örneğin SqlTimeoutException'a güveniyordu.
Maksimum

@ Maks: Orijinal istisna türü hala iç istisna olarak kullanılabilir.
JacquesB

Demek istediğim bu! Şimdi SqlException için yakalayan çağrı yığını kadar herkes asla elde edemez.
Maksimum

1

İstisnanın kendisi, mesaj, hata kodu ve ne dahil olmak üzere uygun günlük kaydı için gerekli tüm bilgileri sağlamalıdır. Bu nedenle, yalnızca yeniden değerlendirmek veya farklı bir istisna atmak için bir istisna yakalamaya gerek yoktur.

Genellikle, DatabaseConnectionException'ı, InvalidQueryException'ı ve InvalidSQLParameterException'ı yakalamak ve bir DatabaseException'ı yeniden yazmak gibi yaygın bir istisna olarak yakalanan ve yeniden oluşturulan birkaç özel durum modelini görürsünüz. Buna rağmen, tüm bu özel istisnaların ilk olarak DatabaseException'dan türetilmesi gerektiğini savunuyorum, bu yüzden yeniden düzenleme gerekli değildir.

Gereksiz deneme yakalama hükümlerinin kaldırılmasının (yalnızca günlük kayıt amacıyla kullanılanlar için bile) işi daha kolay değil, daha kolay hale getireceğini göreceksiniz. Yalnızca programınızdaki özel durumu işleyen yerler özel durumu günlüğe kaydetmelidir ve diğer her şey başarısız olursa, programdan zarif bir şekilde çıkmadan önce özel durumu günlüğe kaydetmeye yönelik son bir girişim için program çapında bir özel durum işleyicisi. Kural dışı durum, kural dışı durumun atıldığı noktayı gösteren tam bir yığın izine sahip olmalıdır, bu nedenle genellikle "bağlam" günlüğü sağlamak gerekli değildir.

Bu, AOP'nin sizin için hızlı bir çözüm olabileceğini, ancak genel olarak hafif bir yavaşlama gerektirdiğini söyledi. Bunun yerine, hiçbir değerin eklenmediği yerdeki gereksiz deneme yakalama maddelerini tamamen kaldırmanızı öneririm.


1
" İstisna, mesaj, hata kodu ve ne dahil olmak üzere uygun günlük kaydı için gerekli tüm bilgileri sağlamalıdır. " Ancak, pratikte klasik bir durum olan Null referans istisnalarını kullanmazlar. Örneğin, ona karmaşık bir ifadede neden olan değişkeni anlatacak herhangi bir dilin farkında değilim.
David Arno

1
@DavidArno Doğru, ancak sağlayabileceğiniz herhangi bir bağlam da o kadar özel olamazdı. Aksi halde sahip olursunuz try { tester.test(); } catch (NullPointerException e) { logger.error("Variable tester was null!"); }. Yığın izi çoğu durumda yeterlidir, ancak eksiklik genellikle hata türünün yeterli olduğunu gösterir.
Neil
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.