หาก statments อยู่ในวิธีด้านในหรือด้านนอก?


17

ข้อใดของการออกแบบเหล่านี้ดีกว่า ข้อดีและข้อเสียของแต่ละข้อคืออะไร? คุณจะใช้อันไหน ข้อเสนอแนะอื่น ๆ ของวิธีการจัดการกับวิธีการที่ชอบจะได้รับการชื่นชม

มีเหตุผลที่จะสมมติว่า Draw () เป็นที่เดียวที่มีวิธีการดึงอื่น ๆ ถูกเรียก สิ่งนี้ต้องการที่จะขยายไปยังเมธอด Draw * และคุณสมบัติ Show * อีกมากมายไม่ใช่แค่สามวิธีที่แสดงไว้ที่นี่

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

หรือ

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

สำหรับการอ้างอิงเพิ่มเติมฉันถามสิ่งนี้ใน SO - stackoverflow.com/questions/2966216/…
Tesserex

1
เมื่อใดก็ตามที่ฉันเห็นวลีเช่น "สิ่งนี้ต้องการขยายไปสู่อีกมากมาย ... " ฉันคิดทันทีว่า "นี่ควรจะเป็นวง"
Paul Butcher

คำตอบ:


28

ฉันไม่คิดว่าคุณจะมีกฎแบบครอบคลุมสำหรับเรื่องแบบนี้มันขึ้นอยู่กับสถานการณ์

ในกรณีนี้ฉันขอแนะนำให้มีคำสั่ง if นอกวิธีการเนื่องจากชื่อของวิธีการวาดหมายความว่าพวกเขาเพิ่งวาดสิ่งโดยไม่มีเงื่อนไขพิเศษใด ๆ

หากคุณพบว่าคุณต้องทำการตรวจสอบก่อนที่จะเรียกวิธีการในหลาย ๆ สถานที่แล้วคุณอาจต้องการที่จะใส่เช็คภายในวิธีการและเปลี่ยนชื่อพวกเขาเพื่อชี้แจงว่าสิ่งนี้เกิดขึ้น


7

ฉันพูดที่สอง

วิธีการควรมีระดับที่เป็นนามธรรมเดียวกัน

ในตัวอย่างที่สองความรับผิดชอบของDraw()เมธอดคือการทำหน้าที่เป็นตัวควบคุมเรียกวิธีการวาดแต่ละวิธี รหัสทั้งหมดในDraw()วิธีนี้อยู่ในระดับเดียวกันของนามธรรม คำแนะนำนี้เข้ามามีบทบาทในสถานการณ์จำลองการใช้งานซ้ำ ตัวอย่างเช่นหากคุณถูกล่อลวงให้ใช้DrawPoints()วิธีการซ้ำอีกครั้งในวิธีสาธารณะอื่น (ลองเรียกว่าSketch()) คุณจะไม่จำเป็นต้องทำซ้ำประโยคของยาม (คำสั่ง if ตัดสินใจว่าจะดึงคะแนน) หรือไม่

ในตัวอย่างแรกแม้ว่าDraw()วิธีการมีหน้าที่พิจารณาว่าจะเรียกแต่ละวิธีแต่ละวิธีหรือไม่แล้วเรียกวิธีการเหล่านั้น Draw()มีวิธีการระดับต่ำบางอย่างที่ทำงานได้ แต่มันมอบหมายงานระดับต่ำอื่น ๆ ให้กับวิธีอื่นและจึงDraw()มีรหัสในระดับที่แตกต่างกันของสิ่งที่เป็นนามธรรม เพื่อที่จะกลับมาใช้DrawPoints()ในการSketch()ที่คุณจะต้องทำซ้ำประโยคยามในSketch()เช่นกัน

แนวคิดนี้ถูกกล่าวถึงในหนังสือ "Clean Code" ของ Robert C. Martin ซึ่งฉันขอแนะนำเป็นอย่างยิ่ง


1
สิ่งที่ดี. ในการพูดถึงจุดที่ทำไว้ในคำตอบอื่นฉันขอแนะนำให้เปลี่ยนชื่อของDrawAxis()et อัล TryDrawAxis()ชี้ให้เห็นว่าการดำเนินการของพวกเขาคือเงื่อนไขที่อาจจะ มิฉะนั้นคุณต้องเลื่อนจากDraw()วิธีการไปยังแต่ละวิธีย่อยเพื่อยืนยันพฤติกรรมของมัน
Josh Earl

6

ความคิดเห็นของฉันเกี่ยวกับเรื่องนี้ค่อนข้างขัดแย้ง แต่ทนกับฉันเพราะฉันเชื่อว่าทุกคนเห็นด้วยกับผลลัพธ์สุดท้าย ฉันมีวิธีการที่แตกต่างจากการไปที่นั่น

ในบทความฟังก์ชั่น Hellฉันอธิบายว่าทำไมฉันไม่ชอบวิธีการแยกเพื่อสร้างวิธีการที่เล็กลง ฉันแยกพวกเขาเมื่อฉันรู้ว่าพวกเขาจะถูกนำมาใช้ซ้ำหรือแน่นอนเมื่อฉันสามารถนำพวกเขากลับมาใช้ใหม่

OP ระบุไว้:

มีเหตุผลที่จะสมมติว่า Draw () เป็นที่เดียวที่มีวิธีการดึงอื่น ๆ ถูกเรียก

สิ่งนี้ทำให้ฉันไปที่ตัวเลือกที่สาม (ระดับกลาง) ที่ไม่ได้กล่าวถึง ฉันสร้าง 'บล็อคโค้ด' หรือ 'ย่อหน้ารหัส' ที่คนอื่นจะสร้างฟังก์ชั่น

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 ยังระบุด้วย:

สิ่งนี้ต้องการที่จะขยายไปยังเมธอด Draw * และคุณสมบัติ Show * อีกมากมายไม่ใช่แค่สามวิธีที่แสดงไว้ที่นี่

... ดังนั้นวิธีนี้จะเติบโตอย่างรวดเร็วมาก เกือบทุกคนเห็นด้วยว่าสิ่งนี้จะลดความสามารถในการอ่าน ในความคิดของฉันการแก้ปัญหาที่เหมาะสมไม่เพียง แต่แบ่งออกเป็นหลายวิธี แต่แยกรหัสออกเป็นคลาสที่สามารถใช้ซ้ำได้ โซลูชันของฉันอาจมีลักษณะเช่นนี้

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();
    }
}

ข้อโต้แย้งของหลักสูตรจะยังคงต้องผ่านและเช่นนี้


แม้ว่าฉันจะไม่เห็นด้วยกับคุณเกี่ยวกับ Function นรก แต่โซลูชันของคุณคือ (IMHO) สิ่งที่ถูกต้องและสิ่งที่จะเกิดขึ้นจากการใช้ Clean Code & SRP ที่เหมาะสม
Paul Butcher

4

Draw()สำหรับกรณีที่คุณกำลังตรวจสอบข้อมูลว่าการควบคุมหรือไม่ที่จะวาดมันทำให้ความรู้สึกที่มีที่อยู่ภายใน พ.ศ. เมื่อการตัดสินใจนั้นซับซ้อนมากขึ้นฉันมักจะชอบสิ่งหลัง (หรือแยก) หากคุณต้องการความยืดหยุ่นที่มากขึ้นคุณสามารถขยายได้เสมอ

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

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

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

ขอให้สังเกตว่าวิธีการเพิ่มเติมได้รับการคุ้มครองซึ่งช่วยให้ subclasses สามารถขยายสิ่งที่ต้องการได้ นอกจากนี้ยังช่วยให้คุณบังคับใช้การวาดภาพสำหรับการทดสอบหรือเหตุผลอื่น ๆ ที่คุณอาจมี


นี่เป็นสิ่งที่ดี: ทุกสิ่งที่เกิดขึ้นซ้ำ ๆ เป็นวิธีการของตัวเอง ตอนนี้คุณสามารถเพิ่มDrawPointsIfItShould()วิธีที่ลัดif(should){do}:)
Konerak

4

ในสองทางเลือกนั้นฉันชอบเวอร์ชั่นแรก เหตุผลของฉันคือฉันต้องการวิธีการทำสิ่งที่ชื่อมีความหมายโดยไม่ต้องพึ่งพาที่ซ่อนอยู่ DrawLegend ควรวาดตำนานไม่ใช่บางทีวาดตำนาน

แม้ว่าคำตอบของ Steven Jeuris นั้นดีกว่าสองคำถามในรุ่นนี้


3

แทนที่จะมีShow___คุณสมบัติส่วนบุคคลสำหรับแต่ละส่วนฉันอาจกำหนดเขตข้อมูลบิต ที่จะทำให้มันง่ายขึ้นเล็กน้อยเพื่อ:

[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; }
}

นอกเหนือจากนั้นฉันจะโน้มตัวไปตรวจสอบการมองเห็นในวิธีการด้านนอกไม่ใช่ด้านใน มันเป็นหลังจากทั้งหมดและไม่ได้DrawLegend DrawLegendIfShownแต่มันขึ้นอยู่กับชั้นเรียนที่เหลือ หากมีสถานที่อื่น ๆ ที่เรียกว่าDrawLegendวิธีการและพวกเขาต้องการที่จะตรวจสอบมากเกินไปผมอาจจะเพียงแค่ย้ายเข้าตรวจสอบShowLegendDrawLegend


2

ฉันจะไปกับตัวเลือกแรก - ด้วย "ถ้า" นอกวิธี มันอธิบายถึงตรรกะที่กำลังตามมาได้ดียิ่งขึ้นและให้ตัวเลือกในการวาดแกนตัวอย่างเช่นในกรณีที่คุณต้องการวาดหนึ่งโดยไม่คำนึงถึงการตั้งค่า นอกจากนี้จะลบค่าใช้จ่ายของการเรียกใช้ฟังก์ชันเพิ่มเติม (สมมติว่าไม่มีการ inlined) ซึ่งสามารถสะสมได้หากคุณต้องการความเร็ว (ตัวอย่างของคุณดูเหมือนว่ามันอาจเป็นเพียงการวาดกราฟที่ไม่อาจเป็นปัจจัย แต่ในภาพเคลื่อนไหว หรือเกมมันอาจจะเป็น)


1

โดยทั่วไปฉันต้องการให้โค้ดระดับต่ำกว่าสมมติว่ามีเงื่อนไขเบื้องต้นและได้ทำงานที่พวกเขาควรจะทำด้วยการตรวจสอบที่สูงขึ้นใน call stack สิ่งนี้มีประโยชน์ด้านการบันทึกวงจรโดยไม่ทำการตรวจสอบซ้ำซ้อน แต่ก็หมายความว่าคุณสามารถเขียนรหัสที่ดีเช่นนี้:

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

แทน:

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

และแน่นอนว่าสิ่งนี้จะยิ่งน่าสนใจกว่าหากคุณบังคับใช้ทางออกเดียวมีหน่วยความจำที่คุณต้องการฟรีเป็นต้น


0

ทุกอย่างขึ้นอยู่กับบริบท:

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();
    }

} 
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.