Try-catch-nihayet bloğundaki ilgisiz kod “ne kadar kötü”?


11

Bu ilgili bir S: Sonunda kötü stil / tehlikeli dönüş sonra iş yapmak için madde kullanımı tehlikeli mi?

Referans verilen Q'da, nihayet kod kullanılan yapı ve ön getirme gerekliliği ile ilgilidir. Sorum biraz farklı ve daha geniş bir kitleye açıklık getirdiğine inanıyorum. Benim özel örneğim bir C # winform uygulaması, ancak bu C ++ / Java nihayet kullanım için de geçerli olacaktır.

İstisnalar ve istisna işleme / blok içinde gömülü temizleme ile ilgili olmayan bir çok kod var oldukça birkaç try-catch-nihayet blokları fark ediyorum. Ve istisna ve işleme ile yakından ilgili kod ile çok sıkı try-catch-nihayet bloklara sahip olmanın önyargılarını kabul edeceğim. İşte gördüğüm bazı örnekler.

Try bloklarında çok sayıda ön çağrı ve atanabilecek koda giden değişkenler ayarlanacaktır. Günlüğe kaydetme bilgileri de kurulur ve try bloğunda çalıştırılır.

Son olarak, bloklar form / modül / kontrol biçimlendirme çağrılarına (uygulama, yakalama bloğunda ifade edildiği gibi sona ermek üzere olmasına rağmen) yanı sıra paneller gibi yeni nesneler oluşturacaktır.

Kabaca:

    methodName (...)
    {
        Deneyin
        {
            // Yöntem için çok sayıda kod ...
            // atabilecek kod ...
            // Yöntem ve dönüş için daha fazla kod ...
        }
        yakalamak (bir şey)
        {// istisna ele}
        en sonunda
        {
            // istisna nedeniyle bazı temizlik, kapanış işleri
            // oluşturulan şeyler için daha fazla kod (istisnaların atılabileceğini yoksayarak) ...
            // belki başka nesneler de oluşturabilir
        }
    }

Kod çalışır, bu yüzden buna bir değer vardır. İyi kapsüllenmemiş ve mantık biraz kıvrıktır. (Acı verici) kod değiştirme ve yeniden düzenleme gibi risklere aşina değilim, bu yüzden sorum benzer yapılandırılmış kod ile başkalarının deneyimlerini bilmek istemek için kaynar.

Kötü stil değişikliklerin yapılmasını haklı çıkarıyor mu? Benzer bir durumdan kötü bir şekilde yakılan var mı? Bu kötü deneyimin ayrıntılarını paylaşmak ister misiniz? Aşırı tepki gösterdiğim için bırak ve stil o kadar da kötü değil mi? Bir şeyleri toplamanın bakım avantajlarından faydalanabiliyor musunuz?


"C ++" etiketi buraya ait değildir, çünkü C ++ yoktur (ve ihtiyaç duymaz) finally. Tüm iyi kullanımlar RAII / RRID / SBRM (hangi kısaltmayı isterseniz) kapsamındadır.
David Thornley

2
@DavidThornley: 'Nihayet' anahtar kelimenin kullanıldığı örnekten başka, sorunun geri kalanı C ++ için mükemmel bir şekilde geçerlidir. Ben bir C ++ geliştiricisiyim ve bu anahtar kelime beni gerçekten şaşırtmadı. Ve kendi ekibimle yarı düzenli olarak konuştuğum düşünüldüğünde, bu soru C ++
DXM 4:12

@DXM: Bunu görselleştirmekte sorun yaşıyorum (ve finallyC # 'da ne olduğunu biliyorum ). C ++ eşdeğeri nedir? Ne düşünüyorum sonra kod catcholduğunu ve C # sonra kod için aynı geçerlidir finally.
David Thornley

@DavidThornley: nihayet kodu ne olursa olsun yürütülen kod .
orlp

1
@nightcracker, bu gerçekten doğru değil. Bunu yaparsanız çalıştırılmaz Environment.FailFast(); yakalanmamış bir istisnanız varsa yürütülmeyebilir. Ve finallyelle yinelediğiniz bir yineleyici bloğunuz varsa daha da karmaşıklaşır .
22'de svick

Yanıtlar:


10

Ne yaptığını açıkça bilmeyen geliştiriciler tarafından yazılmış korkunç bir eski Windows Forms koduyla uğraşmak zorunda kaldığımda çok benzer bir durum yaşadım.

Her şeyden önce, aşırı aktif değilsiniz. Bu kötü bir kod. Söylediğiniz gibi, yakalama bloğu durdurma ve durdurmaya hazırlanma olmalıdır. Nesne oluşturma zamanı değil (özellikle Paneller). Bunun neden kötü olduğunu açıklamaya bile başlayamıyorum.

Söyleniyor ki...

İlk tavsiyem: eğer kırık değilse, dokunmayın!

Eğer işiniz kodu korumaksa, onu kırmamak için elinizden geleni yapmanız gerekir. Acı verici olduğunu biliyorum (orada bulundum) ama zaten çalışmakta olanı kırmamak için elinden geleni yapmalısın.

İkinci tavsiyem: Daha fazla özellik eklemeniz gerekiyorsa, kodu kırmamak için mevcut kod yapısını mümkün olduğunca tutmaya çalışın.

Örnek: yerine uygun kalıtımla geçilebileceğini düşündüğünüz iğrenç bir anahtar-durum bildirimi varsa, bir şeyleri hareket ettirmeye karar vermeden önce dikkatli olmanız ve iki kez düşünmeniz gerekir.

Kesinlikle bir yeniden düzenlemenin doğru yaklaşım olduğu durumları bulacaksınız, ancak dikkat: yeniden düzenleme kodunun hataları getirme olasılığı daha yüksektir . Bu kararı geliştirici perspektifinden değil, uygulama sahipleri perspektifinden vermelisiniz. Bu nedenle, sorunu çözmek için gereken çabanın (para) yeniden düzenlemeye değer olup olmadığını düşünmelisiniz. Birçok kez bir geliştirici sadece "kod çirkin" olduğunu düşünüyor çünkü gerçekten kırık olmayan bir şey düzeltmek için birkaç gün harcama gördüm.

Üçüncü tavsiyem: Kodu kırırsanız yanacaksınız, hatanızın olup olmadığı önemli değil.

Bakım vermek için işe alındıysanız, başka birinin kötü kararlar vermesi nedeniyle uygulamanın dağılması gerçekten önemli değildir. Kullanıcı açısından daha önce çalışıyordu ve şimdi onu kırdınız. Sen kırdın!

Joel, eski kodu yeniden yazmamanız için çeşitli nedenleri açıklayan makalesinde çok iyi anlaşıyor.

http://www.joelonsoftware.com/articles/fog0000000069.html

Bu yüzden bu tür kodlar için gerçekten kötü hissetmelisiniz (ve asla böyle bir şey yazmamalısınız) ama bunu sürdürmek tamamen farklı bir canavar.

Deneyimlerim hakkında: Kodu yaklaşık 1 yıl boyunca korumak zorunda kaldım ve sonunda bunu sıfırdan yeniden yazabildim, ancak hepsini bir kerede değil.

Olan şey, kodun o kadar kötüydü ki, yeni özelliklerin uygulanması imkansızdı. Mevcut uygulamanın ciddi performans ve kullanılabilirlik sorunları vardı. Sonunda benden 3-4 ay sürecek bir değişiklik yapmam istendi (çoğunlukla bu kodla çalışmak her zamankinden daha fazla zamanımı aldı). Bu parçayı (istenen yeni özelliğin uygulanması dahil) yaklaşık 5-6 ay içinde yeniden yazabileceğimi düşündüm. Bu teklifi paydaşlara getirdim ve yeniden yazmayı kabul ettiler (neyse ki benim için).

Bu parçayı yeniden yazdıktan sonra, sahip olduklarından çok daha iyi şeyler teslim edebileceğimi anladılar. Böylece tüm uygulamayı yeniden yazabildim.

Benim yaklaşımım bunu parça parça yeniden yazmaktı. Önce tüm kullanıcı arayüzünü (Windows Forms) değiştirdim, sonra iletişim katmanını (Web Hizmeti çağrıları) değiştirmeye başladım ve son olarak tüm Sunucu uygulamasını değiştirdim (kalın bir istemci / sunucu türü uygulamasıydı).

Birkaç yıl sonra ve bu uygulama tüm şirket tarafından kullanılan güzel bir anahtar araca dönüştü. Her şeyi yeniden yazmamış olsaydım, bunun asla mümkün olmayacağından% 100 eminim.

Bunu yapabilmeme rağmen, önemli olan paydaşların onayladığı ve paraya değdiğine ikna edebildim. Bu nedenle, mevcut uygulamayı sürdürmek zorunda kalırken, hiçbir şeyi kırmamak için elinizden gelenin en iyisini yapın ve sahipleri yeniden yazma avantajı konusunda ikna edebiliyorsanız, Jack the Ripper: parçaları ile yapmaya çalışın.


1
+1. Ancak son cümle bir seri katile işaret ediyor. Bu gerçekten gerekli mi?
MarkJ

Bunun bir kısmını seviyorum ama aynı zamanda kırık tasarımları yerinde bırakmak ve bir şeyler eklemek genellikle daha kötü bir tasarıma yol açar. Elbette ölçülü bir yaklaşımda olsa da, nasıl düzeltileceğini ve düzeltileceğini çalışmak daha iyidir.
Ricky Clarkson

@RickyClarkson Kabul ediyorum. Aşırıya kaçtığınızda (yeniden düzenleme gerçekten gerekli değildir) ve değişiklikleri ekleyerek uygulamayı gerçekten incittiğinizde bilmek gerçekten zor.
Alex

@RickyClarkson O kadar katılıyorum ki, dahil olduğum projeyi yeniden yazabildim. Ancak uzun bir süre, mümkün olan en düşük hasara neden olmaya dikkatlice bir şeyler eklemem gerekti. İstek, temel nedenin kötü bir tasarım kararı olduğu (genellikle durum böyle değil) bir şeyi düzeltmedikçe, uygulamanın tasarımına dokunulmaması gerektiğini söyleyebilirim.
Alex

@RickyClarkson Bu, topluluğun içine girmek istediğim deneyimin bir parçasıydı. Tüm eski kodların kötü olarak bildirilmesi aynı derecede kötü bir anti-modeldir. Ancak, ben gerçekten sonunda bir blok parçası olarak yapılmaması gereken üzerinde çalışıyorum bu kodda şeyler vardır. Alex'in geri bildirimi ve yanıtı, okumak istediğim şeydi.

2

Kodu değiştirmeye gerek yoksa, olduğu gibi bırakın. Ancak, bir hatayı düzeltmeniz veya bazı işlevleri değiştirmeniz gerektiğinden kodu değiştirmeniz gerekiyorsa, isterseniz ya da değiştirmemeniz gerekir. IMHO genellikle daha güvenli ve daha az hataya eğilimlidir, ilk önce daha küçük, daha iyi anlaşılabilir parçalara tekrar bakarsanız ve daha sonra işlevselliği eklerseniz. Elinizde otomatik yeniden düzenleme araçlarına sahip olduğunuzda, bu nispeten güvenli olabilir ve yalnızca yeni hatalar getirme riski çok düşüktür. Ve Michael Feathers kitabının bir kopyasını almanızı tavsiye ederim

http://www.amazon.com/Working-Effectively-Legacy-Michael-Feathers/dp/0131177052

Bu, size kodu daha test edilebilir hale getirme, birim testleri ekleme ve yeni özellikler eklerken kodu kırmayı önleme konusunda değerli ipuçları verecektir.

Doğru yaparsanız, yönteminiz umarım try-catch-nihayet artık yanlış yerlerde herhangi bir ilgisiz kod içermeyecektir.


2

Altı yönteminiz olduğunu varsayalım, bunlardan dördü sadece ilki hatasız tamamlanırsa çağrılmalıdır. İki alternatif düşünün:

try {
     method1();  // throws
     method2();
     method3();
     method4();
     method5();
 } catch(e) {
     // handle error
 }
 method6();

vs

 try {
      method1();  // throws
 }
 catch(e) {
      // handle error
 }
 if(! error ) {
     method2();
     method3();
     method4();
     method5();
 }
 method6();

İkinci kodda, dönüş kodları gibi istisnaları ele alıyorsunuz. Kavramsal olarak, bu aşağıdakilerle aynıdır:

rc = method1();
if( rc != error ) {
     method2();
     method3();
     method4();
     method5();
 }
 method6();

Bir try / catch bloğuna istisna atabilecek her yöntemi sararsak, bir dil özelliğinde istisnaların olması ne anlama gelir? Faydası yok ve daha fazla yazım var.

İlk etapta dillerde istisnalar bulundurmamızın nedeni, eski iade kodlarının eski kötü günlerinde, genellikle hataları döndürebilecek birden fazla yöntemin olduğu durumlara sahip olmamız ve her hatanın tüm yöntemleri tamamen iptal etmesi anlamına geliyordu. Kariyerimin başlangıcında, eski C günlerinde her yerde böyle bir kod gördüm:

rc = method1();
if( rc != error ) {
     rc = method2();
     if( rc != error ) {
         rc = method3();
         if( rc != error ) {
             rc = method4();
             if(rc != error ) {
                 method5();
             }
         }
     }
 }
 method6();

Bu, inanılmaz derecede yaygın bir modeldi ve istisnalar, yukarıdaki ilk örneğe geri dönmenize izin vererek çok düzgün bir şekilde çözüldü. Her yöntemin kendi istisna bloğuna sahip olduğunu söyleyen bir stil kuralı bunu tamamen pencereden dışarı atar. Öyleyse neden rahatsız ediyorsun?

Her zaman try bloğunu kavramsal olarak bir birim olan kod kümesini aşmaya çalışmalısınız. Kod biriminde herhangi bir hata varsa, kod biriminde başka bir kod çalıştırmanın bir anlamı olmayan kodu çevrelemelidir.

İstisnaların asıl amacına geri dönme: bunların kodun gerçekte meydana geldiği zaman hatalarla kolayca başa çıkamayacağı için oluşturulduğunu unutmayın. İstisnaların amacı, kodda daha sonra veya yığının üstündeki hatalarla önemli ölçüde ilgilenmenize izin vermektir. İnsanlar bunu unutuyor ve çoğu durumda, söz konusu yöntemin bu istisnayı atabildiğini belgelemek için daha iyi olduklarında onları yerel olarak yakalıyorlar. Dünyada şöyle görünen çok fazla kod var:

void method0() : throws MyNewException 
{
    try {
        method1();  // throws MyOtherException
    }
    catch(e) {
        if(e == MyOtherException)
            throw MyNewException();
    }
    method2();
}

Burada sadece katmanlar ekliyorsunuz ve katmanlar karışıklık katıyor. Sadece bunu yap:

void method0() : throws MyOtherException
{
    method1();
    method2();
}

Buna ek olarak, bu kuralı takip ederseniz ve yöntemde birkaç denemeden / bloktan daha fazlasını bulursanız, yöntemin kendisinin çok karmaşık olup olmadığını ve birden çok yönteme bölünmesi gerekip gerekmediğini sorgulamalısınız. .

Paket servisi olan restoran: bir try bloğu, bloğun herhangi bir yerinde bir hata oluşursa iptal edilmesi gereken kod kümesini içermelidir. Bu kod kümesinin bir satır mı yoksa bin satır mı olduğu önemli değildir. (Bir yöntemde bin satırınız olsa da, başka sorunlarınız da var.)


Steven - iyi yanıt verdiğiniz için teşekkür ederim ve düşüncelerinize tamamen katılıyorum. Tek denemede yaşayan makul ilgili veya sıralı tip kodu ile ilgili bir sorunum yok. Gerçek bir kod snippet'i yayınlayabilseydim, ilk atılabilir çağrıdan önce 5 - 10 tamamen ilgisiz çağrı göstermiş olurdu. Sonunda bir blok daha kötüydü - birkaç yeni iş parçacığı döndürülüyordu ve ek, temiz olmayan rutinler çağrıldı. Ve bu nedenle, o bloktaki tüm çağrılar atılabilecek herhangi bir şeyle ilgisizdi.

Bu yaklaşımla ilgili en büyük sorunlardan biri method0, bir istisnanın atılmasını bekleyebilecek durumlarla beklenmeyen durumlar arasında ayrım yapmamasıdır. Örneğin, farz edelim ki method1bazen bir atabilir InvalidArgumentException, ama eğer yapmazsa, bir nesneyi geçici olarak geçersiz bir duruma sokar method2, bu da düzeltilecek ve nesneyi her zaman geçerli bir duruma geri döndürmesi beklenir. Method2 InvalidArgumentException, böyle bir istisnayı yakalamayı bekleyen bir kod atarsa method1bile, onu yakalar ...
supercat

... sistem durumu kodun beklentilerini karşılayacak. Yöntem1'den atılan bir istisna, yöntem2 tarafından atılandan farklı bir şekilde ele alınacaksa, bu, onları sarmadan mantıklı bir şekilde nasıl yapılabilir?
supercat

İdeal olarak, istisnalarınız bu nadir bir durum olacak kadar ayrıntılıdır.
Robotu Gort
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.