İfadeler iç veya dış yöntemde olmalı mı?


17

Bu tasarımlardan hangisi daha iyi? Her birinin artıları ve eksileri nelerdir? Hangisini kullanırdınız? Gibi yöntemlerle nasıl başa çıkılacağı ile ilgili diğer öneriler takdir edilmektedir.

Diğer çizim yöntemlerinin çağrıldığı tek yer Draw () 'un olduğunu kabul etmek mantıklıdır. Bunun, yalnızca burada gösterilen üç taneye değil, daha birçok Draw * yöntemine ve Show * özelliğine genişletilmesi gerekir.

public void Draw()
{
    if (ShowAxis)
    {
        DrawAxis();
    }

    if (ShowLegend)
    {
        DrawLegend();
    }

    if (ShowPoints && Points.Count > 0)
    {
        DrawPoints();
    }
}

private void DrawAxis()
{
    // Draw things.
}

private void DrawLegend()
{
    // Draw things.
}

private void DrawPoints()
{
    // Draw things.
}

Veya

public void Draw()
{
    DrawAxis();
    DrawLegend();
    DrawPoints();
}

private void DrawAxis()
{
    if (!ShowAxis)
    {
        return;
    }

    // Draw things.
}

private void DrawLegend()
{
    if (!ShowLegend)
    {
        return;
    }

    // Draw things.
}

private void DrawPoints()
{
    if (!ShowPoints ||  Points.Count <= 0))
    {
        return;
    }

    // Draw things.
}

Daha fazla referans için bunu SO - stackoverflow.com/questions/2966216/…
Tesserex

1
"Bu çok daha genişlemeli ..." gibi bir ifade gördüğümde hemen "Bu bir döngü olmalı" diye düşünüyorum.
Paul Butcher

Yanıtlar:


28

Bu tür şeyler için battaniye kuralı olabileceğini sanmıyorum, duruma bağlı.

Bu durumda, çizim yöntemlerinin dışında if cümlelerinin kullanılmasını öneririm çünkü Draw yöntemlerinin isimleri, herhangi bir özel koşul olmadan sadece şeyleri çizdiklerini ima eder.

Birçok yerde yöntemleri çağırmadan önce kontroller yapmanız gerektiğini fark ederseniz, kontrolü yöntemlerin içine koymak ve bunun olduğunu açıklığa kavuşturmak için bunları yeniden adlandırmak isteyebilirsiniz.


7

İkincisi diyorum.

Yöntemler aynı soyutlama seviyesine sahip olmalıdır.

İkinci örnekte, Draw()yöntemin sorumluluğu , münferit çizim yöntemlerinin her birini çağıran bir denetleyici gibi davranmaktır. Bu Draw()yöntemdeki tüm kodlar aynı soyutlama düzeyindedir. Bu kılavuz yeniden kullanım senaryolarında devreye girer. Örneğin, DrawPoints()yöntemi başka bir genel yöntemde yeniden kullanmanız istendiyse (diyelim ki Sketch()), koruma yan tümcesini (noktaların çizilip çizilmeyeceğine karar veren if ifadesi) tekrarlamanız gerekmez.

Bununla birlikte, ilk örnekte Draw()yöntem , her bir yöntemin her birinin çağrılıp çağrılmayacağının belirlenmesinden ve daha sonra bu usullerin çağrılmasından sorumludur. Draw()işleyen bazı düşük seviyeli yöntemler vardır, ancak diğer düşük seviyeli çalışmaları diğer yöntemlere devreder ve bu nedenle Draw()farklı soyutlama düzeylerinde koda sahiptir. Yeniden kullanımı için DrawPoints()de Sketch(), içeri bekçi maddesini çoğaltmak gerekir Sketch()de.

Bu fikir Robert C. Martin'in tavsiye ettiği “Clean Code” kitabında tartışılmıştır.


1
İyi şeyler. Başka bir cevapta yapılan bir noktayı ele almak için, DrawAxis()et. ark. belki de idamlarının şartlı olduğunu belirtmek TryDrawAxis(). Aksi takdirde Draw(), davranışını onaylamak için yöntemden her bir alt yönteme geçmeniz gerekir .
Josh Earl

6

Konu hakkındaki fikrim oldukça tartışmalı, ancak benimle birlikte, neredeyse herkesin sonuçta hemfikir olduğuna inanıyorum. Oraya ulaşmak için farklı bir yaklaşımım var.

İşlev Cehennemi makalemde , neden sadece daha küçük yöntemler oluşturmak için bölme yöntemlerini sevmediğimi açıklıyorum. Onları sadece bildiğim zaman bölebilirim , yeniden kullanılabilirler veya tabi ki onları tekrar kullanabileceğim zaman.

OP şunları söyledi:

Diğer çizim yöntemlerinin çağrıldığı tek yer Draw () 'un olduğunu kabul etmek mantıklıdır.

Bu beni (orta) üçüncü bir seçeneğe götürür. Başkalarının işlevler yaratacağı 'kod blokları' veya 'kod paragrafları' oluşturuyorum.

public void Draw()
{
    // Draw axis.
    if (ShowAxis)
    {
        // Drawing code ...
    }

    // Draw legend.
    if (ShowLegend)
    {
        // Drawing code ...
    }

    // Draw points.
    if (ShowPoints && Points.Count > 0)
    {
        // Drawing code ...
    }
}

OP ayrıca şunları söyledi:

Bunun, yalnızca burada gösterilen üç taneye değil, daha birçok Draw * yöntemine ve Show * özelliğine genişletilmesi gerekir.

... yani bu yöntem çok hızlı büyüyecek. Hemen hemen herkes bunun okunabilirliği azalttığını kabul ediyor. Bence uygun çözüm sadece birkaç yönteme ayrılmakla kalmıyor, aynı zamanda kodu tekrar kullanılabilir sınıflara bölüyor. Benim çözümüm muhtemelen böyle bir şey olurdu.

private void Initialize()
{               
    // Create axis.
    Axe xAxe = new Axe();
    Axe yAxe = new Axe();

    _drawObjects = new List<Drawable>
    {
        xAxe,
        yAxe,
        new Legend(),
        ...
    }
}

public void Draw()
{
    foreach ( Drawable d in _drawObjects )
    {
        d.Draw();
    }
}

Tabii ki tartışmaların hala geçilmesi gerekecekti.


Size İşlev cehennemi konusunda katılmama rağmen, çözümünüz (IMHO) doğru ve Temiz Kod ve SRP'nin doğru uygulanmasından doğacak çözümdür.
Paul Butcher

4

Çizilip çizilmeyeceğini kontrol eden bir alanı kontrol ettiğiniz durumlarda, bunun içinde olması mantıklıdır Draw(). Bu karar daha karmaşık hale geldiğinde, ikincisini tercih etme eğilimindeyim (veya ikiye bölerim). Daha fazla esnekliğe ihtiyacınız varsa, bunu her zaman genişletebilirsiniz.

private void DrawPoints()
{
    if (ShouldDrawPoints())
    {
        DoDrawPoints();
    }
}

protected void ShouldDrawPoints()
{
    return ShowPoints && Points.Count > 0;
}

protected void DoDrawPoints()
{
    // Draw things.
}

Alt sınıfların ihtiyaç duyduklarını genişletmelerini sağlayan ek yöntemlerin korunduğuna dikkat edin. Bu aynı zamanda çizimi test için zorlamanıza veya başka herhangi bir nedenden dolayı zorlamanızı sağlar.


Bu güzel: tekrarlanan her şey kendi yönteminde. Şu anda bile bir ekleyebilir DrawPointsIfItShould()olduğunu kısayolları yöntemi if(should){do}:)
Konerak

4

Bu iki alternatif arasından ilk sürümü tercih ederim. Benim nedenim, herhangi bir gizli bağımlılık olmadan, adın ne anlama geldiğini yapmak için bir yöntem istiyorum. DrawLegend bir efsane çizmeli, belki bir efsane çizmemelidir .

Yine de Steven Jeuris'in cevabı, sorunun iki versiyonundan daha iyi.


3

Show___Her bölüm için ayrı özelliklere sahip olmak yerine, muhtemelen bir bit alanı tanımlarım. Bu biraz basitleştirecekti:

[Flags]
public enum DrawParts
{
    Axis = 1,
    Legend = 2,
    Points = 4,
    // More...
}

public class MyClass
{
    public void Draw() {
        if (VisibleParts.HasFlag(DrawPart.Axis))   DrawAxis();
        if (VisibleParts.HasFlag(DrawPart.Legend)) DrawLegend();
        if (VisibleParts.HasFlag(DrawPart.Points)) DrawPoints();
        // More...
    }

    public DrawParts VisibleParts { get; set; }
}

Bunun dışında, iç yöntemde değil, dış yöntemde görünürlüğü kontrol etmeye eğilmiştim. Ne de olsa öyle, DrawLegenddeğil DrawLegendIfShown. Ama bu sınıfın geri kalanına bağlıdır. Bu DrawLegendyöntemi çağıran başka yerler varsa ve onlar da kontrol etmeleri gerekiyorsa ShowLegend, muhtemelen sadece çekleri taşıyacağım DrawLegend.


2

Ben ilk seçeneği ile - yöntemin dışında "if" ile gider. İzlenen mantığı daha iyi açıklar, ayrıca örneğin bir ayardan bağımsız olarak bir tane çizmek istediğiniz durumlarda, aslında bir eksen çizme seçeneği sunar. Ayrıca, ek bir işlev çağrısının yükünü kaldırır (satır içi değil varsayarak), hız için giderseniz birikebilir (örneğin, sadece bir faktör değil, bir animasyonda bir grafik çiziyor gibi görünüyor) veya oyun olabilir).


1

Genel olarak, önkoşulların karşılandığını varsayan ve kodların çağrı yığınında daha yüksek bir şekilde yapıldıkları her işi yapmaları gerektiğini düşündüğüm daha düşük kod seviyelerine sahip olmayı tercih ederim. Bu, gereksiz denetimler yapmadan döngüleri kaydetmenin yan avantajına sahiptir, ancak aynı zamanda böyle güzel bir kod yazabileceğiniz anlamına gelir:

int do_something()
{
    sometype* x;
    if(!(x = sometype_new())) return -1;
    foo(x);
    bar(x);
    baz(x);
    return 0;
}

onun yerine:

int do_something()
{
    sometype x* = sometype_new();
    if(ERROR == foo(x)) return -1;
    if(ERROR == bar(x)) return -1;
    if(ERROR == baz(x)) return -1;
    return 0;
}

Ve tek çıkış zorlarsanız, boşaltmanız gereken bellek varsa, vb.


0

Her şey bağlama bağlıdır:

void someThing(var1, var2)
{
    // If input fails validation then return quickly.
    if (!isValid(var1) || !isValid(var2))
    {   return;
    }


    // Otherwise do what is logical and makes the code easy to read.
    if (doTaskConditionOK())
    {   doTask();
    }

    // Return early if it is logical
    // This is OK in C++ but not C like languages.
    // You have to be careful of cleanup in C like languages while RIAA will do
    // that auto-magically in C++. 
    if (allFinished)
    {   return;
    }

    doAlternativeIfNotFinished();

    // -- Alternative to the above for C
    if (!allFinished)
    {   
        doAlternativeIfNotFinished();
    }

} 
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.