it-swarm.asia

Yuvalanmış IF Deyimlerini nasıl yeniden düzenlersiniz?

GOTO'lar hakkında bu yazının başına geldiğimde programlama blogosferi etrafında dolaşıyordum:

http://giuliozambon.blogspot.com/2010/12/programmers-tabu.html

Burada yazar, "GOTO'ların daha okunabilir ve daha sürdürülebilir kodlar için durumların olduğu sonucuna varılması gerektiğine" değiniyor ve sonra buna benzer bir örnek göstermeye devam ediyor:

if (Check#1)
{
    CodeBlock#1
    if (Check#2)
    {
        CodeBlock#2
        if (Check#3)
        {
            CodeBlock#3
            if (Check#4)
            {
                CodeBlock#4
                if (Check#5)
                {
                    CodeBlock#5
                    if (Check#6)
                    {
                        CodeBlock#6
                        if (Check#7)
                        {
                            CodeBlock#7
                        }
                        else
                        {
                            rest - of - the - program
                        }
                    }
                }
            }
        }
    }
}

Yazar daha sonra GOTO'ları kullanmanın bu kodu okumayı ve sürdürmeyi çok daha kolay hale getireceğini önermektedir.

Şahsen bunu düzleştirmenin ve bu kodu akış kırıcı GOTO'lara başvurmadan daha okunabilir hale getirmenin en az 3 farklı yolunu düşünebilirim. İşte benim iki favorim.

1 - İç İçe Küçük Fonksiyonlar. Her if ve kod bloğunu alın ve bir fonksiyona dönüştürün. Boole kontrolü başarısız olursa, geri dönün. Başarılı olursa, zincirdeki bir sonraki işlevi çağırın. (Çocuk, özyineleme gibi görünüyor, bunu işlev işaretçileriyle tek bir döngüde yapabilir misiniz?)

2 - Sentinal Değişken. Bana göre bu en kolayı. Sadece bir blnContinueProcessing değişkeni kullanın ve eğer if çekinizde hala doğru olup olmadığını kontrol edin. Sonra kontrol başarısız olursa, değişkeni false olarak ayarlayın.

Yuvalamayı azaltmak ve sürdürülebilirliği artırmak için bu tür kodlama sorunu kaç farklı yolla yeniden düzenlenebilir?

34
saunderl

Farklı kontrollerin nasıl etkileştiğini bilmeden söylemek gerçekten zor. Sıkı yeniden düzenleme sırası olabilir. Türlerine bağlı olarak doğru bloğu yürüten nesnelerin bir topolojisi oluşturmak, aynı zamanda bir strateji modeli veya durum modeli de işe yarayabilir.

Neyin en iyi şekilde yapılacağını bilmeden, daha fazla yöntem çıkarılarak daha fazla yeniden düzenlenebilen iki olası basit yeniden düzenleme düşünülmelidir.

İlk olarak sevmediğim, çünkü her zaman bir yöntemde litle çıkış noktaları olarak seviyorum (tercihen bir)

if (!Check#1)
{ 
    return;
}
CodeBlock#1

if (!Check#2)
{
    return;
}
CodeBlock#2
...

İkincisi, çoklu dönüşleri kaldırır, ancak aynı zamanda çok fazla gürültü ekler. (temel olarak yalnızca yuvalamayı kaldırır)

bool stillValid = Check#1
if (stillValid)
{
  CodeBlock#1
}

stillValid = stillValid && Check#2
if (stillValid)
{
  CodeBlock#2
}

stillValid = stillValid && Check#3
if (stillValid)
{
  CodeBlock#3
}
...

Bu sonuncusu işlevlere güzelce yeniden düzenlenebilir ve onlara iyi isimler verdiğinizde sonuç makul olabilir ';

bool stillValid = DoCheck1AndCodeBlock1()
stillValid = stillValid && DoCheck2AndCodeBlock2()
stillValid = stillValid && DoCheck3AndCodeBlock3()

public bool DoCheck1AndCodeBlock1()
{
   bool returnValid = Check#1
   if (returnValid)
   {
      CodeBlock#1
   }
   return returnValid
}

Sonuçta daha iyi seçenekler vardır

33
KeesDijk

Uygun girintili kodun şekli nedeniyle buna "Ok Kodu" denir.

Jeff Atwood, Coding Horror hakkında okları nasıl düzeltebileceğiniz konusunda iyi bir blog yazısı yayınladı:

Düzleştirici Ok Kod

Tam tedavi için makaleyi okuyun, ancak burada önemli noktalar var ..

  • Koşulları koruyucu maddelerle değiştirin
  • Koşullu blokları ayrı işlevlere ayırın
  • Negatif kontrolleri pozitif kontrollere dönüştürün
  • Her zaman fırsatçı bir şekilde işlevden en kısa sürede geri dönün
40
JohnFx

Bazı insanların bunun bir goto olduğunu iddia edeceğini biliyorum, ama return; bariz bir yeniden düzenleme, yani.

if (!Check#1)
{ 
        return;
}
CodeBlock#1
if (!Check#2)
{
    return;
}
CodeBlock#2
.
.
.
if (Check#7)
{
    CodeBlock#7
}
else
{
    rest - of - the - program
}

Kodu çalıştırmadan önce gerçekten bir sürü bekçi kontrolü varsa, bu iyi çalışır. Bundan daha karmaşıksa, bu sadece biraz daha basit hale gelir ve diğer çözümlerden birine ihtiyacınız vardır.

26
Richard Gadsden

Bu spagetti kodu, onu bir devlet makinesine yeniden yansıtmak için mükemmel bir aday gibi görünüyor.

10
Pemdas

Bu zaten belirtilmiş olabilir, ancak burada gösterilen "ok ucu antipattern" e "go-to" (pun amaçlı) cevabım ifs tersine çevirmektir. Mevcut koşulların tersini test edin (operatör olmayan bir cihazla yeterince kolay) ve doğruysa yöntemden dönün. Gotos yok (bilgili bir şekilde konuşsa da, boş bir dönüş, bir çerçeveyi yığının dışına atmanın önemsiz ekstra adımıyla, çağrı kodu hattına atlamaktan biraz daha fazlasıdır).

Vaka olarak, işte orijinal:

if (Check#1)
{
    CodeBlock#1
    if (Check#2)
    {
        CodeBlock#2
        if (Check#3)
        {
            CodeBlock#3
            if (Check#4)
            {
                CodeBlock#4
                if (Check#5)
                {
                    CodeBlock#5
                    if (Check#6)
                    {
                        CodeBlock#6
                        if (Check#7)
                        {
                            CodeBlock#7
                        }
                        else
                        {
                            rest - of - the - program
                        }
                    }
                }
            }
        }
    }
}

Refactored:

if (!Check#1) return;

CodeBlock#1

if (!Check#2) return;

CodeBlock#2

if (!Check#3) return;

CodeBlock#3

if (!Check#4) return;

CodeBlock#4

if (!Check#5) return;

CodeBlock#5

if (!Check#6) return;

CodeBlock#6

if (Check#7)
    CodeBlock#7
else
{
    //rest of the program
}

Her bir durumda, temel olarak devam edip etmeyeceğimizi kontrol ederiz. Yalnızca bir seviye yuvalama ile orijinal ile aynı şekilde çalışır.

Bu snippet'in sonunun ötesinde de çalıştırılması gereken bir şey varsa, bu snippet'ten sonra gelen koda geçmeden önce bu kodu kendi yöntemine çıkarın ve bu kodun şu anda bulunduğu yerden çağırın. Snippet'in kendisi, her bir kod bloğunda yeterli gerçek LOC verildiğinde, birkaç yöntemi daha bölmeyi haklı çıkarmak için yeterince uzun, ama ben kazıyorum.

6
KeithS

Eğer rutin olarak bu if çek piramidini gerektiren bir mantığa sahipseniz, muhtemelen (metaforik olarak) çivileri çekiçlemek için bir anahtar kullanıyorsunuzdur. Doğrusaldan daha iyi yapılara sahip bu tür bükümlü ve karmaşık mantığı destekleyen bir dilde bu tür bükümlü, karmaşık mantığı yapmak size daha iyi hizmet ederdi if/else if/else tarzı yapılar.

Bu tür yapıya daha uygun olabilecek diller arasında SNOBOL4 (tuhaf çift GOTO tarzı dallanma ile) veya Prolog ve Mercury (birleşmeleriyle) gibi mantık dilleri yer alabilir ve karmaşık izleme kararlarının kısa ve öz ifadeleri için DCG'lerinden bahsetmemek için geri izleme yetenekleri).

Tabii ki bu bir seçenek değilse (çoğu programcı trajik bir şekilde poliglotlar değildir), başkalarının ortaya koyduğu fikirler çeşitli [~ # ~] oop [~ # ~ ]yapıları veya cümleleri fonksiyonlara bölüyor veya hatta, çaresizseniz ve durum makinesini kullanarak çoğu insanın okunamaz bulduğu gerçeğine aldırmayın.

Ancak benim çözümüm, ifade etmeye çalıştığınız mantığı (kodunuzda olağan olduğunu varsayarak) daha kolay ve daha okunabilir bir şekilde ifade etmenize izin veren bir dile ulaşmaya devam ediyor.

burada :

Oldukça sık bir pac-man setine baktığımda, ilgili tüm koşulların doğruluk tablosu gibi bir şey çizersem, sorunu çözmek için çok daha iyi bir yol çalışabileceğimi görürüm.

Bu şekilde daha iyi bir yöntem olup olmadığını, onu nasıl daha fazla parçalayabileceğinizi ve (ve bu tür bir kodla büyük bir yöntemdir) mantıkta herhangi bir delik olup olmadığını da değerlendirebilirsiniz.

Bunu yaptıktan sonra, muhtemelen birkaç anahtar deyimine ve birkaç yönteme ayırabilir ve bir sürü problemden geçmek zorunda olan bir sonraki kötü mook'u kurtarabilirsiniz.

1
Jim G.

Şahsen, eğer if ifadeleri fonksiyon başarılı olursa bir bool döndüren ayrı fonksiyonlara kaydırmayı seviyorum.

Yapı aşağıdaki gibi görünür:


if (DoCheck1AndCodeBlock1() && 
    DoCheck2AndCodeBlock2() && 
    DoCheck3AndCodeBlock3()) 
{
   // ... you may perform the final operation here ....
}

Tek dezavantajı, bu işlevlerin referans veya üye değişkenler tarafından iletilen nitelikleri kullanarak veri çıkışı yapmak zorunda olmasıdır.

1
gogisan

OO) yaprağın basit koşul ve bileşenin basit unsurlardan oluşan birleşik bir yaklaşımı kullanılması, bu tür kodları genişletilebilir ve uyarlanabilir hale getirir

1
guiman

Olası bir nesne yönelimli çözüm istiyorsanız, kod sorumluluk zinciri tasarım deseni kullanarak yeniden düzenleme için standart bir örnek gibi görünür.

0
FinnNk

Bunları işlevlere bölmek yardımcı olabilir mi?

İlk işlevi çağırırsınız ve tamamlandığında bir sonraki işlevi çağırır, işlevin yapacağı ilk şey, bu işlevin kontrolünü sınamaktır.

0
Toby

Her kod bloğunun boyutuna ve toplam boyuta çok bağlıdır, ancak bir blokta düz bir "çıkarma yöntemi" ile ya da koşulsuz parçaları ayrı yöntemlere taşıyarak bölme eğilimindeyim. Ancak @KeesDijk'te belirtilen uyarılar geçerlidir. Eski yaklaşım size her biri gibi bir dizi işlev sunar

if (Check#1)
{
    CodeBlock#1
    NextFunction
}

Hangi iyi çalışır ama kod bloat ve "yöntem sadece bir kez kullanılır" antipattern yol açar. Bu yüzden genellikle bu yaklaşımı tercih ederim:

if (CheckFunctionOne)
{
    MethodOneWithDescriptiveName
    if (CheckFunctionTwo)
    {
        MethodTwoWithDescriptiveName
        ....

Özel değişkenlerin uygun kullanımı ve parametre geçişi ile yapılabilir. Okuryazar programlamaya göz atmak burada yardımcı olabilir.

0
Мסž