ฉันจะแก้ไขเชนของ if-else ได้อย่างไรถ้าข้อความที่เป็นไปตามหลักการ Clean Code ของลุงบ็อบ?


45

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

ฉันพบว่าตัวเองไม่สามารถตัดทอนตรรกะนี้ได้แม้ว่า:

if (checkCondition()) {addAlert(1);}
else if (checkCondition2()) {addAlert(2);}
else if (checkCondition3()) {addAlert(3);}
else if (checkCondition4()) {addAlert(4);}

ฉันไม่สามารถลบ elses และแยกสิ่งทั้งหมดออกเป็นบิตขนาดเล็กทำให้ "else" ใน "else if" ช่วยประสิทธิภาพการทำงาน - การประเมินเงื่อนไขเหล่านั้นมีราคาแพงและถ้าฉันสามารถหลีกเลี่ยงการประเมินเงื่อนไขด้านล่างได้ เป็นจริงฉันต้องการหลีกเลี่ยงพวกเขา

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


แก้ไข: คำถามนี้ถูกระบุว่าเป็นที่ซ้ำกันเป็นไปได้ของวิธีที่สง่างามในการจับถ้า (ถ้าอื่น) อื่น

ฉันเชื่อว่านี่เป็นคำถามที่แตกต่างกัน (คุณสามารถเห็นได้ด้วยการเปรียบเทียบคำตอบของคำถามเหล่านั้น)

  • คำถามของฉันคือการตรวจสอบสำหรับเงื่อนไขแรกยอมรับที่จะจบได้อย่างรวดเร็ว
  • คำถามที่เชื่อมโยงนั้นกำลังพยายามยอมรับเงื่อนไขทั้งหมดเพื่อที่จะทำอะไรสักอย่าง (เห็นได้ดีขึ้นในคำตอบสำหรับคำถามนี้: https://softwareengineering.stackexchange.com/a/122625/96955 )

46
มีอะไรผิดปกติหรือไม่ชัดเจนเกี่ยวกับรหัสนี้ในบริบทของมัน ฉันไม่เห็นว่ามันจะสั้นลงหรือง่ายขึ้นได้อย่างไร! รหัสที่ประเมินเงื่อนไขนั้นปรากฏว่าได้รับปัจจัยดีเช่นเดียวกับวิธีการที่เรียกว่าเป็นผลมาจากการตัดสินใจ คุณต้องดูที่คำตอบบางข้อด้านล่างซึ่งทำให้รหัสนั้นซับซ้อน!
Steve

38
ไม่มีอะไรผิดปกติกับรหัสนี้ มันอ่านง่ายและง่ายต่อการติดตาม สิ่งที่คุณทำเพื่อลดขนาดลงไปอีกคือการเพิ่มทางอ้อมและทำให้เข้าใจยากขึ้น
17 ของ 26

20
รหัสของคุณใช้ได้ นำพลังงานที่เหลืออยู่ของคุณไปสู่สิ่งที่มีประสิทธิผลมากกว่าการพยายามทำให้มันสั้นลงอีก
Robert Harvey

5
ถ้ามันเป็นเพียงแค่ 4 เงื่อนไขนี่เป็นเรื่องปกติ หากเป็นเช่น 12 หรือ 50 คุณอาจต้องการ refactor ในระดับที่สูงกว่าวิธีนี้
JimmyJames

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

คำตอบ:


81

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

{
    addAlert(GetConditionCode());
}

และคุณมี GetConditionCode () encapsulate ตรรกะสำหรับการตรวจสอบเงื่อนไข อาจจะดีกว่าถ้าใช้ Enum มากกว่าเลขเวทย์

private AlertCode GetConditionCode() {
    if (CheckCondition1()) return AlertCode.OnFire;
    if (CheckCondition2()) return AlertCode.PlagueOfBees;
    if (CheckCondition3()) return AlertCode.Godzilla;
    if (CheckCondition4()) return AlertCode.ZombieSharkNado;
    return AlertCode.None;
}

2
ถ้าเป็นไปได้ที่จะแค็ปซูลเหมือนที่คุณอธิบาย (ฉันคิดว่ามันอาจจะไม่ใช่ฉันคิดว่า OP ทิ้งตัวแปรไว้เพื่อความง่าย) มันไม่เปลี่ยนรหัสซึ่งตัวมันเองก็ใช้ได้ แต่เพิ่มการยศาสตร์รหัสและอ่านง่าย + 1
opa

17
ด้วยรหัสการแจ้งเตือนเหล่านั้นฉันขอขอบคุณรหัสเดียวที่สามารถส่งคืนได้ในแต่ละครั้ง
Josh Part

12
ดูเหมือนว่านี่จะเป็นการจับคู่ที่สมบูรณ์แบบสำหรับการใช้คำสั่งสวิตช์ - หากมีให้ในภาษาของ OP
Frank Hopkins

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

6
ประเด็นหนึ่งที่มี reimplementation นี้ก็คือว่ามันทำให้ฟังก์ชั่นจำเป็นต้องตรวจสอบสภาพการแจ้งเตือนปลอมaddAlert AlertCode.None
David Hammen

69

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

ความพยายามใด ๆ ที่จะ "ลดความซับซ้อน" ต่อไปจะทำให้สิ่งต่าง ๆ มีความซับซ้อน

แน่นอนคุณสามารถแทนที่elseคำหลักด้วย a returnตามที่คนอื่นแนะนำ แต่นั่นเป็นเพียงเรื่องของสไตล์ไม่ใช่การเปลี่ยนแปลงในความซับซ้อนใด ๆ


นอกเหนือ:

คำแนะนำทั่วไปของฉันคือไม่เคยเชื่อเรื่องกฎเกณฑ์ใด ๆ เกี่ยวกับโค้ดที่สะอาดเลย: คำแนะนำการเข้ารหัสส่วนใหญ่ที่คุณเห็นบนอินเทอร์เน็ตนั้นดีถ้ามันถูกนำไปใช้ในบริบทที่เหมาะสมIOCCC เคล็ดลับอยู่เสมอที่จะทำให้เกิดความสมดุลที่ช่วยให้มนุษย์ได้อย่างง่ายดายด้วยเหตุผลเกี่ยวกับรหัสของคุณ

ใช้วิธีการที่ใหญ่เกินไปและคุณจะเมา ใช้ฟังก์ชั่นขนาดเล็กเกินไปและคุณจะเมา หลีกเลี่ยงการแสดงออกที่สามและคุณถูกทำให้เมา ใช้สำนวนที่ประกอบไปด้วยทุกที่และคุณจะเมา ตระหนักดีว่ามีสถานที่ที่เรียกใช้ฟังก์ชันหนึ่งบรรทัดและสถานที่ที่เรียกใช้ฟังก์ชัน 50 บรรทัด (ใช่มีอยู่!) ตระหนักดีว่ามีสถานที่ที่เรียกร้องให้มีif()คำสั่งและมีสถานที่ที่เรียกร้องให้?:ผู้ประกอบการ ใช้คลังแสงแบบเต็มรูปแบบที่คุณต้องการและพยายามใช้เครื่องมือที่เหมาะสมที่สุดที่คุณสามารถหาได้ และจำไว้ว่าอย่ารับเรื่องศาสนาแม้แต่เรื่องคำแนะนำนี้เช่นกัน


2
ฉันขอยืนยันว่าการแทนที่else ifด้วยส่วนภายในreturnตามด้วยการเรียบง่ายif(การลบelse) อาจทำให้โค้ดอ่านยากขึ้น เมื่อโค้ดบอกว่าelse ifฉันรู้ทันทีว่าโค้ดในบล็อกถัดไปจะทำงานเฉพาะเมื่อไม่มีรหัสก่อนหน้า ไม่มีความวุ่นวายไม่มีความยุ่งยาก หากเป็นแบบธรรมดาifอาจใช้งานได้หรือไม่ก็ได้โดยไม่คำนึงว่าจะใช้งานครั้งก่อนหรือไม่ returnตอนนี้ผมจะต้องใช้จ่ายจำนวนของความพยายามที่จะแยกจิตบล็อกก่อนหน้านี้ที่จะต้องทราบบางอย่างที่มันจบลงด้วยการ ฉันควรใช้ความพยายามในการแยกวิเคราะห์ตรรกะทางธุรกิจ
CVN

1
ฉันรู้ว่ามันเป็นเรื่องเล็ก แต่อย่างน้อยสำหรับฉันก็รวมelse ifเป็นหนึ่งหน่วยความหมาย (ไม่จำเป็นต้องเป็นหน่วยเดียวสำหรับคอมไพเลอร์ แต่ก็ไม่เป็นไร) ...; return; } if (...ไม่ได้; ถ้ามันกระจายไปหลายบรรทัด else ifว่าสิ่งที่ผมจริงจะต้องมองไปที่จะเห็นสิ่งที่มันทำแทนของความสามารถในการใช้เวลาในการได้โดยตรงโดยเพียงแค่เห็นคู่คำหลัก
CVN

@ MichaelKjörlingแบบเต็ม Ack.I ฉันต้องการelse ifสร้างโดยเฉพาะอย่างยิ่งเนื่องจากรูปแบบที่ถูกล่ามโซ่ของมันเป็นรูปแบบที่รู้จักกันดี อย่างไรก็ตามรหัสของรูปแบบif(...) return ...;นั้นเป็นรูปแบบที่รู้จักกันดีดังนั้นฉันจะไม่กล่าวโทษอย่างเต็มที่ ฉันเห็นว่านี่เป็นปัญหาเล็กน้อยจริง ๆ : ตรรกะการไหลของการควบคุมเหมือนกันในทั้งสองกรณีและการมองอย่างใกล้ชิดเพียงครั้งเดียวที่if(...) { ...; return; }บันไดจะบอกฉันว่ามันแน่นอนเทียบเท่ากับelse ifบันได ฉันเห็นโครงสร้างของคำเดียวฉันอนุมานความหมายของมันฉันรู้ว่ามันซ้ำทุกที่และฉันรู้ว่าเกิดอะไรขึ้น
cmaster

มาจาก JavaScript / Node.js, บางคนจะใช้ "สายพานและ suspenders" รหัสของการใช้ทั้งสอง และelse if returnเช่นelse if(...) { return alert();}
user949300

1
"และอย่าลืมว่าอย่าได้นับถือศาสนาแม้แต่เรื่องคำแนะนำนี้เช่นกัน" +1
คำเช่น Jared

22

มันแย้งว่านี่มัน 'ดีกว่า' ธรรมดาหรือเปล่าถ้า .. สำหรับทุกกรณี แต่ถ้าคุณต้องการลองอย่างอื่นนี่เป็นวิธีการทั่วไป

ใส่เงื่อนไขของคุณในวัตถุและวางวัตถุเหล่านั้นในรายการ

foreach(var condition in Conditions.OrderBy(i=>i.OrderToRunIn))
{
    if(condition.EvaluatesToTrue())
    {
        addAlert(condition.Alert);
        break;
    }
}

หากจำเป็นต้องดำเนินการหลายอย่างตามเงื่อนไขคุณสามารถทำการสอบถามซ้ำที่บ้า

void RunConditionalAction(ConditionalActionSet conditions)
{
    foreach(var condition in conditions.OrderBy(i=>i.OrderToRunIn))
    {
        if(condition.EvaluatesToTrue())
        {
            RunConditionalAction(condition);
            break;
        }
    }
}

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

แต่ตัวอย่างของคุณไม่ได้มีรูปแบบ

กรณีการใช้งานทั่วไปสำหรับรูปแบบนี้จะเป็นการตรวจสอบ แทน :

bool IsValid()
{
    if(condition1 == false)
    {
        throw new ValidationException("condition1 is wrong!");
    }
    elseif(condition2 == false)
    {
    ....

}

กลายเป็น

[MustHaveCondition1]
[MustHaveCondition2]
public myObject()
{
    [MustMatchRegExCondition("xyz")]
    public string myProperty {get;set;}
    public bool IsValid()
    {
        conditions = getConditionsFromReflection()
        //loop through conditions
    }
}

27
นี่เป็นเพียงการย้ายif...elseบันไดไปสู่การสร้างConditionsรายการ กำไรสุทธิเป็นค่าลบเนื่องจากการสร้างConditionsจะใช้รหัสมากเท่ากับรหัส OP แต่การเพิ่มทางอ้อมมาพร้อมกับค่าใช้จ่ายในการอ่าน ฉันต้องการบันไดที่สะอาดและมีรหัสแน่นอน
cmaster

3
@cmaster ใช่ฉันคิดว่าฉันไม่พูดตรงนั้น "แล้วการตั้งค่าสำหรับวัตถุที่จะซับซ้อนเท่าเดิมถ้าคำสั่งที่£
Ewan

7
สิ่งนี้สามารถอ่านได้น้อยกว่าต้นฉบับ เพื่อที่จะเข้าใจว่าเงื่อนไขใดที่จะได้รับการตรวจสอบจริงคุณต้องไปขุดในส่วนอื่น ๆ ของรหัส มันเพิ่มระดับความไม่จำเป็นของการอ้อมที่ทำให้รหัสนั้นยากที่จะเข้าใจ
17 ของ 26

8
การแปลง if .. else หาก .. else .. เชื่อมโยงไปยังตารางของภาคแสดงและการกระทำที่สมเหตุสมผล แต่สำหรับตัวอย่างที่มีขนาดใหญ่กว่ามากเท่านั้น ตารางเพิ่มความซับซ้อนและความไม่แน่นอนทางอ้อมดังนั้นคุณต้องมีรายการมากพอที่จะตัดจำหน่ายค่าโสหุ้ยในแนวคิดนี้ ดังนั้นสำหรับคู่ภาค / แอ็กชั่น 4 คู่ให้เก็บรหัสดั้งเดิมอย่างง่าย ๆ แต่ถ้าคุณมี 100 ให้ไปกับโต๊ะแน่นอน จุดไขว้อยู่ที่ไหนสักแห่งในระหว่าง @cmaster ตารางสามารถเริ่มต้นแบบคงที่ดังนั้นค่าใช้จ่ายที่เพิ่มขึ้นสำหรับการเพิ่มคู่ภาค / การกระทำเป็นหนึ่งบรรทัดที่เพียงแค่ชื่อพวกเขา: ยากที่จะทำดีกว่า
สตีเฟ่นซี. เหล็ก

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

7

พิจารณาใช้return;หลังจากที่หนึ่งเงื่อนไขที่ได้ประสบความสำเร็จก็จะช่วยให้คุณประหยัดทุกelses คุณอาจจะสามารถทำได้return addAlert(1)โดยตรงหากวิธีนั้นมีค่าส่งคืน


3
แน่นอนว่านี้จะไม่มีอะไรเกิดขึ้นหลังจากห่วงโซ่ของifs ... นั่นอาจเป็นข้อสันนิษฐานที่สมเหตุสมผลแล้วอีกครั้งก็อาจจะไม่ใช่
CVN

5

ฉันเคยเห็นสิ่งปลูกสร้างเช่นนี้ถือว่าสะอาดกว่าบางครั้ง:

switch(true) {
    case cond1(): 
        statement1; break;
    case cond2():
        statement2; break;
    case cond3():
        statement3; break;
    // .. etc
}

Ternary ที่มีระยะห่างที่ถูกต้องอาจเป็นทางเลือกที่เรียบร้อย:

cond1() ? statement1 :
cond2() ? statement2 :
cond3() ? statement3 : (null);

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


1
ternary เรียบร้อย
Ewan

6
@Ewan การแก้จุดบกพร่องที่เสีย” ไตรภาคแบบเรียกซ้ำอย่างลึกซึ้ง” อาจเป็นความเจ็บปวดที่ไม่จำเป็น
dfri

5
มันดูเรียบร้อยบนหน้าจอ
Ewan

อืมภาษาใดที่อนุญาตให้ใช้ฟังก์ชันกับcaseป้ายกำกับ
undercat

1
@undercat นั่นเป็น ECMAScript ที่ถูกต้อง / JavaScript afaik
zworek

1

ในฐานะที่แตกต่างจากคำตอบของ @ Ewan คุณสามารถสร้างห่วงโซ่ (แทนที่จะเป็น "รายการแบน") ของเงื่อนไขเช่นนี้:

abstract class Condition {
  private static final  Condition LAST = new Condition(){
     public void alertOrPropagate(DisplayInterface display){
        // do nothing;
     }
  }
  private Condition next = Last;

  public Condition setNext(Condition next){
    this.next = next;
    return this; // fluent API
  }

  public void alertOrPropagate(DisplayInterface display){
     if(isConditionMeet()){
         display.alert(getMessage());
     } else {
       next.alertOrPropagate(display);
     }
  }
  protected abstract boolean isConditionMeet();
  protected abstract String getMessage();  
}

วิธีนี้คุณสามารถนำเงื่อนไขของคุณไปใช้ตามลำดับที่กำหนดและโครงสร้างพื้นฐาน (คลาสนามธรรมที่แสดง) ข้ามการตรวจสอบที่เหลือหลังจากที่ได้พบกันครั้งแรก

นี่คือที่ที่มันเหนือกว่าวิธี "รายการแบบเรียบ" ที่คุณต้องใช้ "การข้าม" ในลูปที่ใช้เงื่อนไข

คุณเพียงตั้งค่าเชนเงื่อนไข:

Condition c1 = new Condition1().setNext(
  new Condition2().setNext(
   new Condition3()
 )
);

และเริ่มประเมินผลด้วยการโทรง่ายๆ:

c1.alertOrPropagate(display);

ใช่ที่เรียกว่ารูปแบบห่วงโซ่ความรับผิดชอบ
สูงสุด

4
ฉันจะไม่แสร้งพูดเพื่อคนอื่น แต่ในขณะที่รหัสในคำถามนั้นสามารถอ่านได้และเห็นได้ชัดในพฤติกรรมของมันฉันจะไม่คิดว่านี่จะชัดเจนทันทีว่ามันทำอะไร
CVN

0

ก่อนอื่นรหัสต้นฉบับไม่น่ากลัว IMO มันค่อนข้างเข้าใจได้และไม่มีอะไรเลวร้ายในตัวมัน

ถ้าคุณไม่ชอบให้สร้างความคิดของ @ Ewan เพื่อใช้รายการ แต่ลบforeach breakรูปแบบที่ไม่เป็นธรรมชาติของเขาออก:

public class conditions
{
    private List<Condition> cList;
    private int position;

    public Condition Head
    {
        get { return cList[position];}
    }

    public bool Next()
    {
        return (position++ < cList.Count);
    }
}


while not conditions.head.check() {
  conditions.next()
}
conditions.head.alert()

ตอนนี้ปรับใช้สิ่งนี้ในภาษาที่คุณเลือกทำให้แต่ละองค์ประกอบของรายการวัตถุสิ่งอันดับและสิ่งที่คุณทำ

แก้ไข: ดูเหมือนว่ามันไม่ชัดเจนอย่างที่ฉันคิดดังนั้นให้ฉันอธิบายเพิ่มเติม conditionsเป็นรายการที่เรียงลำดับของการเรียงลำดับบางอย่าง headเป็นองค์ประกอบปัจจุบันที่ถูกตรวจสอบ - ตอนแรกมันเป็นองค์ประกอบแรกของรายการและแต่ละครั้งnext()ถูกเรียกว่ากลายเป็นองค์ประกอบต่อไปนี้ check()และalert()เป็นcheckConditionX()และaddAlert(X)จาก OP


1
(ไม่ได้ลงคะแนน แต่) ฉันไม่สามารถติดตามได้ หัวคืออะไร
Belle-Sophie

@ เบลฉันแก้ไขคำตอบเพื่ออธิบายเพิ่มเติม มันเป็นความคิดเช่นเดียวกับอีแวนส์ แต่มีแทนwhile not foreach break
Nico

วิวัฒนาการที่ยอดเยี่ยมของความคิดที่ยอดเยี่ยม
Ewan

0

คำถามไม่มีลักษณะเฉพาะบางอย่าง หากเงื่อนไขคือ:

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

หรือถ้าเนื้อหาaddAlertมีความซับซ้อนมากขึ้นดังนั้นทางออกที่ดีกว่าในการพูด c # จะเป็น:

//in some central spot
IEnumerable<Tuple<Func<bool>, int>> Conditions = new ... {
  Tuple.Create(CheckCondition1, 1),
  Tuple.Create(CheckCondition2, 2),
  ...
}

//at the original place
var matchingCondition = Conditions.Where(c=>c.Item1()).FirstOrDefault();
if(matchingCondition != null) 
  addAlert(matchingCondition.Item2)

สิ่งอันดับไม่สวยงามมากใน c # <8 แต่เลือกเพื่อความมั่นใจ

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


0

วิธีที่ดีที่สุดในการลดความซับซ้อนของ Cyclomaticในกรณีที่คุณมีจำนวนมากif->then statementsคือการใช้พจนานุกรมหรือรายการ (ขึ้นอยู่กับภาษา) เพื่อจัดเก็บค่าคีย์ (ถ้าค่าของคำสั่งหรือค่าบางส่วน) จากนั้นค่า / ผลการทำงาน

ตัวอย่างเช่นแทนที่จะเป็น (C #):

if (i > 10) { return "Two"; }
else if (i > 8) { return "Four" }
else if (i > 4) { return "Eight" }
return "Ten";  //etc etc say anything after 3 or 4 values

ฉันทำได้ง่ายๆ

var results = new Dictionary<int, string>
{
  { 10, "Two" },
  { 8, "Four"},
  { 4, "Eight"},
  { 0, "Ten"},
}

foreach(var key in results.Keys)
{
  if (i > results[key]) return results.Values[key];
}

หากคุณใช้ภาษาที่ทันสมัยมากขึ้นคุณสามารถจัดเก็บตรรกะได้มากขึ้นเพียงแค่ให้ค่าเช่นกัน (c #) นี่เป็นเพียงฟังก์ชั่นอินไลน์จริงๆ แต่คุณสามารถชี้ไปที่ฟังก์ชั่นอื่น ๆ ได้เช่นกันถ้าตรรกะนั้นคือการวางอินไลน์

var results = new Dictionary<Func<int, bool>, Func<int, string>>
{
  { (i) => return i > 10; ,
    (i) => return i.ToString() },
  // etc
};

foreach(var key in results.Keys)
{ 
  if (key(i)) return results.Values[key](i);
}

0

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

ฉันพบว่าตัวเองไม่สามารถตัดทอนตรรกะนี้ได้แม้ว่า:

if (checkCondition()) {addAlert(1);}
else if (checkCondition2()) {addAlert(2);}
else if (checkCondition3()) {addAlert(3);}
else if (checkCondition4()) {addAlert(4);}

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

if (is_an_apple()) {
  addAlert(1);
}
else if (is_a_banana()) {
  addAlert(2);
}
else if (is_a_cat()) {
  addAlert(3);
}
else if (is_a_dog()) {
  addAlert(4);
}

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


0

หากว่ามีการใช้งานฟังก์ชั่นทั้งหมดในองค์ประกอบเดียวกันคุณสามารถทำให้ฟังก์ชั่นยังคงอยู่ในสถานะบางอย่างเพื่อที่จะกำจัดกิ่งต่าง ๆ ในโฟลว์

EG: checkCondition1()จะกลายเป็นevaluateCondition1()ที่จะตรวจสอบว่าเงื่อนไขก่อนหน้านี้ได้พบกับ; getConditionNumber()ถ้าเป็นเช่นนั้นแล้วมันจะเก็บค่าบางอย่างที่จะดึงข้อมูลโดย

checkCondition2()จะกลายเป็นevaluateCondition2()ที่จะตรวจสอบว่าเงื่อนไขก่อนหน้านี้ได้พบ ถ้าเงื่อนไขก่อนหน้านี้ไม่ได้พบกันแล้วก็จะตรวจสอบสถานการณ์สภาพ 2 getConditionNumber()แคชค่าที่จะดึงข้อมูลโดย และอื่น ๆ

clearConditions();
evaluateCondition1();
evaluateCondition2();
evaluateCondition3();
evaluateCondition4();
if (anyCondition()) { addAlert(getConditionNumber()); }

แก้ไข:

นี่คือวิธีการตรวจสอบเงื่อนไขราคาแพงที่จะต้องดำเนินการเพื่อให้วิธีการนี้ทำงานได้

bool evaluateCondition34() {
    if (!anyCondition() && A && B && C) {
        conditionNumber = 5693;
        return true;
    }
    return false;
}

...

bool evaluateCondition76() {
    if (!anyCondition() && !B && C && D) {
        conditionNumber = 7658;
        return true;
    }
    return false;
}

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

clearConditions();
evaluateCondition10();
evaluateCondition9();
evaluateCondition8();
evaluateCondition7();
...
evaluateCondition34();
...
evaluateCondition76();

if (anyCondition()) { addAlert(getConditionNumber()); }

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


ฉันไม่ชอบคำแนะนำนี้ - มันซ่อนตรรกะการทดสอบไว้ในหลาย ๆ ฟังก์ชั่น วิธีนี้อาจทำให้โค้ดยากต่อการดูแลหากคุณจำเป็นต้องเปลี่ยนคำสั่งซื้อและทำ # 3 ก่อนหน้า # 2
ลอเรนซ์

ไม่คุณสามารถตรวจสอบได้ว่ามีการประเมินเงื่อนไขก่อนหน้านี้หรือanyCondition() != falseไม่
Emerson Cardoso

1
ตกลงฉันเห็นสิ่งที่คุณได้รับ อย่างไรก็ตามถ้า (พูด) เงื่อนไข 2 และ 3 ทั้งคู่ประเมินเป็นtrueค่า OP ไม่ต้องการให้ประเมินเงื่อนไข 3
ลอเรนซ์

สิ่งที่ผมหมายถึงคือการที่คุณสามารถตรวจสอบภายในฟังก์ชั่นanyCondition() != false evaluateConditionXX()นี่เป็นไปได้ที่จะใช้ หากวิธีการใช้สถานะภายในไม่ต้องการฉันเข้าใจ แต่ข้อโต้แย้งว่าสิ่งนี้ไม่ทำงานไม่ถูกต้อง
Emerson Cardoso

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

0

ส่วนคำสั่ง "else" ที่มีมากกว่าสองข้อใด ๆ บังคับให้ผู้อ่านรหัสผ่านห่วงโซ่ทั้งหมดเพื่อค้นหาสิ่งที่น่าสนใจ ใช้วิธีการเช่น: void AlertUponCondition (เงื่อนไขเงื่อนไข) {switch (condition) {case Condition.Con1: ... break; สภาพเคส CON2: ... ตัวแบ่ง ฯลฯ ... } โดยที่ "เงื่อนไข" เป็นค่าที่เหมาะสม หากจำเป็นให้ส่งคืนบูลหรือค่า เรียกว่าเช่นนี้: AlertOnCondition (GetCondition ());

มันไม่สามารถทำให้ง่ายขึ้นได้และมันเร็วกว่าห่วงโซ่ if-else เมื่อคุณเกินสองสามกรณี


0

ฉันไม่สามารถพูดถึงสถานการณ์ของคุณเนื่องจากรหัสไม่เฉพาะ แต่ ...

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

ความแตกต่างอาจจะเหมาะกับคุณดีกว่า

เป็นรหัสที่น่าสงสัยด้วยวิธีการที่ยาวที่มีความยาวหรือซับซ้อนถ้าสร้างแล้ว คุณมักจะต้องการต้นไม้เรียนที่นั่นด้วยวิธีการเสมือนบางอย่าง

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