Bu yaygın bir durumdur ve bununla başa çıkmanın birçok yaygın yolu vardır. İşte kanonik bir cevap verme girişimim. Bir şey kaçırırsam lütfen yorum yapın ve bu yayını güncel tutacağım.
Bu bir ok
Tartıştığınız konu ok anti-paterni olarak bilinir . Buna ok adı verilir, çünkü iç içe ifs zinciri, kod düzenleyici bölmesinin sağ tarafını "gösteren" görsel bir ok oluşturarak sağa ve daha sonra sola doğru genişleyen kod blokları oluşturur.
Muhafızla Oku Düzleştir
Oktan kaçınmanın bazı yaygın yolları burada tartışılmıştır . En yaygın yöntem, kodun önce istisna akışlarını ve ardından temel akışı işlediği bir koruma düzeni kullanmaktır , örneğin
if (ok)
{
DoSomething();
}
else
{
_log.Error("oops");
return;
}
... kullanırsınız ....
if (!ok)
{
_log.Error("oops");
return;
}
DoSomething(); //notice how this is already farther to the left than the example above
Uzun bir koruma serisi olduğunda, tüm korumalar sola kadar göründüğü ve ifs'leriniz iç içe olmadığından bu kodu önemli ölçüde düzleştirir. Ayrıca, mantık koşulunu ilişkili hatayla görsel olarak eşleştiriyorsunuz, bu da neler olduğunu anlatmayı çok daha kolay hale getiriyor:
Ok:
ok = DoSomething1();
if (ok)
{
ok = DoSomething2();
if (ok)
{
ok = DoSomething3();
if (!ok)
{
_log.Error("oops"); //Tip of the Arrow
return;
}
}
else
{
_log.Error("oops");
return;
}
}
else
{
_log.Error("oops");
return;
}
koru:
ok = DoSomething1();
if (!ok)
{
_log.Error("oops");
return;
}
ok = DoSomething2();
if (!ok)
{
_log.Error("oops");
return;
}
ok = DoSomething3();
if (!ok)
{
_log.Error("oops");
return;
}
ok = DoSomething4();
if (!ok)
{
_log.Error("oops");
return;
}
Bu objektif ve ölçülebilir bir şekilde okunması daha kolaydır çünkü
- Belirli bir mantık bloğunun {ve} karakterleri birbirine daha yakın
- Belirli bir çizgiyi anlamak için gerekli zihinsel bağlam miktarı daha azdır
- Bir if koşuluyla ilişkili mantığın bütününün bir sayfada olması daha olasıdır
- Kodlayıcının sayfayı / göz parçasını kaydırma ihtiyacı büyük ölçüde azalır
Sonunda ortak kod nasıl eklenir
Muhafız modelindeki sorun, "fırsatçı dönüş" veya "fırsatçı çıkış" olarak adlandırılan şeye dayanmasıdır. Başka bir deyişle, her bir işlevin tam olarak bir çıkış noktasına sahip olması gereken deseni kırar. Bu iki nedenden dolayı bir sorundur:
- Bazı insanları yanlış bir şekilde ovuyor, örneğin Pascal'a kod yazmayı öğrenen insanlar bir fonksiyonun = bir çıkış noktası olduğunu öğrendiler.
- Ne olursa olsun , eldeki konu ne olursa olsun çıkışta çalışan bir kod bölümü sağlamaz .
Aşağıda, dil özelliklerini kullanarak veya sorunu tamamen önleyerek bu kısıtlamaya bir çözüm bulmak için bazı seçenekler sağladım.
Seçenek 1. Bunu yapamazsınız: kullanın finally
Ne yazık ki, bir c ++ geliştiricisi olarak bunu yapamazsınız. Ancak bu, nihayetinde anahtar kelime içeren diller için bir numaralı yanıttır, çünkü tam olarak bunun için kullanılır.
try
{
if (!ok)
{
_log.Error("oops");
return;
}
DoSomething(); //notice how this is already farther to the left than the example above
}
finally
{
DoSomethingNoMatterWhat();
}
2. Seçenek. Sorunu önleyin: İşlevlerinizi yeniden yapılandırın
Kodu iki işleve bölerek sorunu önleyebilirsiniz. Bu çözüm, herhangi bir dil için çalışma avantajına sahiptir ve ek olarak, hata oranınızı azaltmanın kanıtlanmış bir yolu olan ve herhangi bir otomatik birim testinin özgüllüğünü geliştiren siklomatik karmaşıklığı azaltabilir.
İşte bir örnek:
void OuterFunction()
{
DoSomethingIfPossible();
DoSomethingNoMatterWhat();
}
void DoSomethingIfPossible()
{
if (!ok)
{
_log.Error("Oops");
return;
}
DoSomething();
}
Seçenek 3. Dil hilesi: Sahte bir döngü kullanın
Gördüğüm bir başka yaygın hile, diğer cevaplarda gösterildiği gibi while (true) ve break kullanmaktır.
while(true)
{
if (!ok) break;
DoSomething();
break; //important
}
DoSomethingNoMatterWhat();
Bu, kullanmaktan daha az "dürüst" goto
olsa da, yeniden düzenleme sırasında dağılmaya daha az eğilimlidir, çünkü mantık kapsamının sınırlarını açıkça işaretler. Etiketlerinizi veya etiketlerinizi kesen ve yapıştıran saf bir kodlayıcıgoto
ifadelerinizi büyük sorunlara neden olabilir! (Ve açıkçası desen çok yaygın şimdi bence niyetini açıkça iletiyor ve bu nedenle "dürüst olmayan" değil).
Bu seçeneklerin başka çeşitleri de vardır. Örneğin, switch
bunun yerine kullanılabilir while
. Bir break
anahtar kelimeye sahip herhangi bir dil yapısı muhtemelen işe yarar.
Seçenek 4. Nesne yaşam döngüsünü kullanın
Diğer bir yaklaşım nesne yaşam döngüsünü güçlendirir. Parametrelerinizi taşımak için bir bağlam nesnesi kullanın (naif örneğimizde şüpheli olmayan bir şey) ve işiniz bittiğinde imha edin.
class MyContext
{
~MyContext()
{
DoSomethingNoMatterWhat();
}
}
void MainMethod()
{
MyContext myContext;
ok = DoSomething(myContext);
if (!ok)
{
_log.Error("Oops");
return;
}
ok = DoSomethingElse(myContext);
if (!ok)
{
_log.Error("Oops");
return;
}
ok = DoSomethingMore(myContext);
if (!ok)
{
_log.Error("Oops");
}
//DoSomethingNoMatterWhat will be called when myContext goes out of scope
}
Not: Seçtiğiniz dilin nesne yaşam döngüsünü anladığınızdan emin olun. Bunun çalışması için bir çeşit deterministik çöp toplamaya ihtiyacınız var, yani yıkıcının ne zaman çağrıldığını bilmelisiniz. Bazı dillerde kullanmanız gerekecekDispose
yıkıcı yerine .
Seçenek 4.1. Nesne yaşam döngüsünü kullanın (sarma deseni)
Nesneye yönelik bir yaklaşım kullanacaksanız, doğru da yapabilirsiniz. Bu seçenek, temizleme gerektiren kaynakları ve diğer işlemlerini "sarmak" için bir sınıf kullanır.
class MyWrapper
{
bool DoSomething() {...};
bool DoSomethingElse() {...}
void ~MyWapper()
{
DoSomethingNoMatterWhat();
}
}
void MainMethod()
{
bool ok = myWrapper.DoSomething();
if (!ok)
_log.Error("Oops");
return;
}
ok = myWrapper.DoSomethingElse();
if (!ok)
_log.Error("Oops");
return;
}
}
//DoSomethingNoMatterWhat will be called when myWrapper is destroyed
Yine, nesne yaşam döngünüzü anladığınızdan emin olun.
Seçenek 5. Dil hilesi: Kısa devre değerlendirmesini kullanma
Diğer bir teknik kısa devre değerlendirmesinden faydalanmaktır .
if (DoSomething1() && DoSomething2() && DoSomething3())
{
DoSomething4();
}
DoSomethingNoMatterWhat();
Bu çözüm, && operatörünün çalışma şeklinden yararlanır. && öğesinin sol tarafı yanlış olarak değerlendirildiğinde, sağ taraf asla değerlendirilmez.
Bu hile en çok kompakt kod gerektiğinde ve kodun çok fazla bakım görmesi mümkün olmadığında yararlıdır, örneğin iyi bilinen bir algoritma uyguluyorsunuz. Daha genel kodlama için bu kodun yapısı çok kırılgandır; mantıktaki küçük bir değişiklik bile toplam yeniden yazmayı tetikleyebilir.