วิธีการที่สง่างามในการจัดการถ้า (ถ้ามี) อย่างอื่น


161

นี่เป็นอาการง่อยเล็กน้อย แต่ทุกครั้งที่ฉันต้องเขียนโค้ดแบบนี้การทำซ้ำจะทำให้ฉันรำคาญใจ แต่ฉันไม่แน่ใจว่าวิธีแก้ปัญหาใด ๆ จะไม่แย่ลง

if(FileExists(file))
{
    contents = OpenFile(file); // <-- prevents inclusion in if
    if(SomeTest(contents))
    {
        DoSomething(contents);
    }
    else
    {
        DefaultAction();
    }
}
else
{
    DefaultAction();
}
  • มีชื่อสำหรับตรรกะชนิดนี้หรือไม่?
  • ฉันเป็น OCD ด้วยหรือเปล่า

ฉันเปิดให้คำแนะนำรหัสชั่วร้ายเพียงเพื่อความอยากรู้อยากเห็นของ ...


8
@Emmad Kareem: สองDefaultActionสายละเมิดหลักการ DRY
Abyx

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

20
ผมคิดว่าปัญหาหลักที่นี่คือที่ที่คุณกำลังทำงานในระดับที่ไม่สอดคล้องกันของนามธรรม make sure I have valid data for DoSomething(), and then DoSomething() with it. Otherwise, take DefaultAction()ระดับนามธรรมที่สูงขึ้นคือ รายละเอียดเล็ก ๆ น้อย ๆ ที่ทำให้แน่ใจว่าคุณมีข้อมูลสำหรับ DoSomething () อยู่ในระดับที่เป็นนามธรรมต่ำกว่าดังนั้นจึงควรอยู่ในฟังก์ชันที่แตกต่างกัน ฟังก์ชั่นนี้จะมีชื่อในระดับที่เป็นนามธรรมสูงกว่าและการใช้งานจะมีระดับต่ำ คำตอบที่ดีด้านล่างแก้ไขปัญหานี้
Gilad Naor

6
กรุณาระบุภาษา วิธีแก้ปัญหาที่เป็นไปได้สำนวนมาตรฐานและบรรทัดฐานทางวัฒนธรรมที่ยาวนานจะแตกต่างกันไปในแต่ละภาษาและจะนำไปสู่คำตอบที่แตกต่างกันสำหรับคำถามของคุณ
Caleb

1
คุณสามารถอ้างอิงหนังสือเล่มนี้ "Refactoring: การปรับปรุงการออกแบบของรหัสที่มีอยู่" มีหลายส่วนเกี่ยวกับโครงสร้าง if-else การปฏิบัติที่มีประโยชน์อย่างแท้จริง
Vacker

คำตอบ:


96

แยกมันเพื่อแยกฟังก์ชั่น (เมธอด) และใช้returnคำสั่ง:

if(FileExists(file))
{
    contents = OpenFile(file); // <-- prevents inclusion in if
    if(SomeTest(contents))
    {
        DoSomething(contents);
        return;
    }
}

DefaultAction();

หรืออาจจะดีกว่าแยกเนื้อหาและการประมวลผล:

contents_t get_contents(name_t file)
{
    if(!FileExists(file))
        return null;

    contents = OpenFile(file);
    if(!SomeTest(contents)) // like IsContentsValid
        return null;

    return contents;
}

...

contents = get_contents(file)
contents ? DoSomething(contents) : DefaultAction();

UPD:

ทำไมไม่มีข้อยกเว้นทำไมOpenFileไม่โยนข้อยกเว้นของ IO:
ฉันคิดว่าเป็นคำถามทั่วไปจริง ๆ ไม่ใช่คำถามเกี่ยวกับไฟล์ IO ชื่อต่างFileExistsOpenFileอาจสร้างความสับสน แต่ถ้าจะแทนที่พวกเขาด้วยFoo, Barฯลฯ - มันจะชัดเจนกว่าที่DefaultActionอาจถูกเรียกบ่อยเท่าDoSomethingดังนั้นจึงอาจไม่ใช่กรณีพิเศษ PéterTörökเขียนเกี่ยวกับเรื่องนี้ในตอนท้ายของคำตอบของเขา

เหตุใดจึงมีโอเปอเรเตอร์ที่มีเงื่อนไขในตัวแปรที่สอง:
หากจะมีแท็ก [C ++] ฉันจะเขียนifข้อความที่มีการประกาศcontentsในส่วนของเงื่อนไข:

if(contents_t contents = get_contents(file))
    DoSomething(contents);
else
    DefaultAction();

แต่สำหรับภาษาอื่น ๆ (เช่น C) if(contents) ...; else ...;เหมือนกับคำสั่ง expression กับตัวดำเนินการเงื่อนไขที่ประกอบไปด้วยสามส่วน แต่นานกว่านั้น เนื่องจากส่วนหลักของรหัสคือget_contentsฟังก์ชั่นฉันเพิ่งใช้รุ่นที่สั้นกว่า (และcontentsประเภทที่ละเว้น) อย่างไรก็ตามมันเกินกว่าคำถามนี้


93
+1 สำหรับผลตอบแทนหลายรายการ - เมื่อวิธีการทำมีขนาดเล็กเพียงพอวิธีนี้ใช้ได้ดีที่สุดสำหรับฉัน
gnat

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

3
เส้นทางการส่งคืนหลายเส้นทางอาจมีผลกระทบด้านลบต่อประสิทธิภาพในโปรแกรม C ++ การเอาชนะความพยายามของเครื่องมือเพิ่มประสิทธิภาพในการใช้ RVO (เช่น NRVO ยกเว้นว่าแต่ละเส้นทางจะส่งคืนวัตถุเดียวกัน)
Functastic

ฉันขอแนะนำให้ย้อนกลับตรรกะในโซลูชันที่ 2: {ถ้า (มีไฟล์อยู่) {ตั้งเนื้อหา; ถ้า (บางครั้ง) {ส่งคืนเนื้อหา; }} คืนค่า null; } มันลดความยุ่งยากในการไหลและลดจำนวนบรรทัด
Wed

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

56

หากภาษาโปรแกรมที่คุณใช้ (0) การเปรียบเทียบแบบไบนารีวงจรสั้น (เช่นถ้าไม่โทรSomeTestถ้าFileExistsคืนเท็จ) และ (1) การมอบหมายส่งคืนค่า (ผลลัพธ์ของการOpenFileกำหนดให้contentsแล้วค่านั้นจะถูกส่งเป็นอาร์กิวเมนต์ ถึงSomeTest) คุณสามารถใช้สิ่งต่อไปนี้ได้ แต่ก็ควรที่จะให้คุณแสดงความคิดเห็นต่อโค้ดโดยสังเกตว่าซิงเกิ้ล=นั้นมีเจตนา

if( FileExists(file) && SomeTest(contents = OpenFile(file)) )
{
    DoSomething(contents);
}
else
{
    DefaultAction();
}

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


นี่คือวิธีที่ฉันจะทำมัน
Anthony

13
ค่อนข้างดีที่จะใส่รหัสลงไปในifคำสั่งในความคิดของฉัน
moteutsch

15
ในทางตรงกันข้ามคำสั่ง "ถ้ามีอยู่และตรงตามเงื่อนไขนี้" ประเภทนี้ +1
Gorpik

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

"ถ้ามีอะไรบางอย่างอยู่และตรงตามเงื่อนไขนี้" ก็ดี "ถ้ามีบางสิ่งอยู่และทำสิ่งที่เกี่ยวข้องกันที่นี่และเป็นไปตามเงื่อนไขนี้" OTOH กำลังสับสน ในคำอื่น ๆ ฉันไม่ชอบผลข้างเคียงในสภาพ
Piskvor

26

อย่างจริงจังมากกว่าการกล่าวซ้ำ ๆ ของการเรียกไปยัง DefaultAction เป็นสไตล์ของตัวเองเพราะรหัสนั้นเขียนขึ้นมาโดยไม่ต้องตั้งฉาก (ดูคำตอบนี้ด้วยเหตุผลที่ดีสำหรับการเขียนแบบมุมฉาก)

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

if(FileExists(file))
{
    if(! OnNetworkDisk(file))
    {
        contents = OpenFile(file); // <-- prevents inclusion in if
        if(SomeTest(contents))
        {
            DoSomething(contents);
        }
        else
        {
            DefaultAction();
        }
    }
    else
    {
        DefaultAction();
    }
}
else
{
    DefaultAction();
}

แต่ก็มีข้อกำหนดที่เราไม่ควรเปิดไฟล์ขนาดใหญ่มากกว่า 2Gb ด้วยเช่นกัน เราเพิ่งอัปเดตอีกครั้ง:

if(FileExists(file))
{
    if(LessThan2Gb(file))
    {
        if(! OnNetworkDisk(file))
        {
            contents = OpenFile(file); // <-- prevents inclusion in if
            if(SomeTest(contents))
            {
                DoSomething(contents);
            }
            else
            {
                DefaultAction();
            }
        }
        else
        {
            DefaultAction();
        }
    else
    {
        DefaultAction();
    }
}
else
{
    DefaultAction();
}

ควรชัดเจนว่ารูปแบบรหัสดังกล่าวจะเป็นปัญหาในการบำรุงรักษาอย่างมาก

ในบรรดาคำตอบที่นี่ซึ่งเขียนอย่างถูกต้อง orthogonally เป็นตัวอย่างที่สองของ Abyxและคำตอบของ Jan Hudecดังนั้นฉันจะไม่ทำซ้ำนั้นเพียงชี้ให้เห็นว่าการเพิ่มข้อกำหนดสองประการในคำตอบเหล่านั้นจะเป็นเพียงแค่

if(! LessThan2Gb(file))
    return null;

if(OnNetworkDisk(file))
    return null;

(หรือgoto notexists;แทนreturn null;) ไม่ส่งผลกระทบต่อโค้ดอื่น ๆ นอกเหนือจากบรรทัดที่เพิ่มเข้ามา เช่นมุมฉาก

เมื่อทำการทดสอบกฎทั่วไปควรจะทดสอบข้อยกเว้นไม่ได้เป็นกรณีปกติ


8
+1 สำหรับฉัน ผลตอบแทนในช่วงต้นช่วยหลีกเลี่ยงรูปแบบการต่อต้านหัวลูกศร ดูcodinghorror.com/blog/2006/01/flattening-arrow-code.htmlและlostechies.com/chrismissal/2009/05/27/…ก่อนที่จะอ่านเกี่ยวกับรูปแบบนี้ฉันสมัครสมาชิก 1 รายการ / ออกต่อหนึ่งฟังก์ชัน ทฤษฎีเนื่องจากสิ่งที่ฉันได้รับการสอนเมื่อ 15 ปีก่อน ฉันรู้สึกว่านี่ทำให้การอ่านโค้ดง่ายขึ้นมากและเมื่อคุณพูดถึงการบำรุงรักษามากขึ้น
Mr Moose

3
@MoMoose: คุณพูดถึงคำตอบการต่อต้านหัวลูกศรของ Benjol คำถามที่ชัดเจนของ Benjol: "มีชื่อของตรรกะชนิดนี้หรือไม่?" โพสต์เป็นคำตอบและคุณจะได้รับการโหวตของฉัน
outis

นี่เป็นคำตอบที่ดีมากขอบคุณ และ @MrMoose: "รูปแบบการต่อต้านหัวลูกศร" อาจตอบกระสุนแรกของฉันดังนั้นใช่โพสต์มัน ฉันไม่สามารถสัญญาได้ว่าฉันจะยอมรับ แต่มันสมควรได้รับการโหวต!
Benjol

@outis ขอบคุณ ฉันได้เพิ่มคำตอบแล้ว รูปแบบการต่อต้านหัวลูกศรมีความเกี่ยวข้องอย่างแน่นอนในโพสต์ของ hlovdal และส่วนคำสั่งของเขาทำงานได้ดีในการรับรอบ ฉันไม่รู้ว่าคุณจะตอบคำถามข้อที่สองได้อย่างไร ฉันไม่มีคุณสมบัติที่จะวินิจฉัยว่า :)
นาย Moose

4
+1 สำหรับ "ข้อยกเว้นการทดสอบไม่ใช่กรณีปกติ"
Roy Tinker

25

เห็นได้ชัดว่า:

Whatever(Arguments)
{
    if(!FileExists(file))
        goto notexists;
    contents = OpenFile(file); // <-- prevents inclusion in if
    if(!SomeTest(contents))
        goto notexists;
    DoSomething(contents);
    return;
notexists:
    DefaultAction();
}

คุณบอกว่าคุณเปิดกว้างถึงการแก้ปัญหาความชั่วดังนั้นใช้การนับ goto ชั่วร้ายใช่ไหม?

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

เมื่อคุณมีข้อยกเว้นพวกเขาจะอ่านง่ายขึ้นโดยเฉพาะถ้าคุณมี OpenFile และ DoSomething เพียงแค่โยนข้อยกเว้นหากเงื่อนไขไม่เป็นที่พอใจดังนั้นคุณไม่จำเป็นต้องตรวจสอบอย่างชัดเจนเลย ในทางกลับกันใน C ++, การโยน Java และ C # ข้อยกเว้นเป็นการดำเนินการช้าดังนั้นจากจุดประสิทธิภาพ, goto ยังคงดีกว่า


หมายเหตุเกี่ยวกับ "evil": C ++ FAQ 6.15กำหนด "evil" เป็น:

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

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


11
เคอร์เซอร์ของฉันลอยอยู่เหนือปุ่มยอมรับ ... เพียงเพื่อให้บริสุทธิ์ทั้งหมด Oooohh the temptation: D
Benjol

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

2
และอีกแง่มุมหนึ่งคือรหัสที่เขียนด้วยวิธีนี้คือฉาก ตัวอย่างเช่นสองบรรทัด "if (! FileExists (file)) \ n \ tgoto notexists;" ตอนนี้จะเกี่ยวข้องเฉพาะกับการจัดการด้านนี้ข้อผิดพลาดเพียงครั้งเดียว (KISS) และที่สำคัญที่สุดก็ไม่ส่งผลกระทบใด ๆ ของสายอื่น stackoverflow.com/a/3272062/23118คำตอบนี้แสดงเหตุผลที่ดีหลายประการในการเก็บรหัสมุมฉาก
hlovdal

5
การพูดของวิธีแก้ปัญหาที่ชั่วร้าย: ฉันสามารถมีทางออกของคุณโดยไม่ต้องข้ามไป:for(;;) { if(!FileExists(file)) break; contents = OpenFile(file); if(!SomeTest(contents)) break; DoSomething(contents); return; } /* broken out */ DefaultAction();
โดย

4
@herby: วิธีการแก้ปัญหาของคุณนั้นชั่วร้ายยิ่งกว่าgotoเพราะคุณถูกทำร้ายbreakในทางที่ไม่มีใครคาดคิดว่าจะถูกทำร้ายดังนั้นคนที่อ่านรหัสจะมีปัญหามากขึ้นในการเห็นว่าตัวแบ่งนำพวกเขาไปที่ใด นอกจากนี้คุณกำลังใช้การวนซ้ำไม่สิ้นสุดที่จะเรียกใช้เพียงครั้งเดียวซึ่งจะค่อนข้างสับสน น่าเสียดายที่do { ... } while(0)ไม่สามารถอ่านได้อย่างแน่นอนเพราะคุณเห็นว่ามันเป็นเพียงบล็อกที่ตลกเมื่อคุณไปถึงจุดสิ้นสุดและ C ไม่รองรับการทำลายจากบล็อกอื่น ๆ (ต่างจาก perl)
Jan Hudec

12
function FileContentsExists(file) {
    return FileExists(file) ? OpenFile(file) : null;
}

...

contents = FileContentExists(file);
if(contents && SomeTest(contents))
{
    DoSomething(contents);
}
else
{
    DefaultAction();
}

หรือไปชายเสริมและสร้างวิธี FileExistsAndConditionMet เพิ่มเติม (ไฟล์) ...
UncleZeiv

@herby SomeTestสามารถมีความหมายเหมือนกับการมีอยู่ของไฟล์หากSomeTestตรวจสอบประเภทไฟล์เช่นตรวจสอบว่า. gif เป็นไฟล์ GIF จริงๆ
Abyx

1
ใช่. ขึ้นอยู่กับ @Benjol รู้ดีกว่า
herby

3
... แน่นอนฉันหมายถึง "go mIle พิเศษ" ... :)
UncleZeiv

2
ที่มีการราวีโอลี่ไปยังแขนขาแม้ฉันไม่ได้ไป (และฉันรู้สึกมากในเรื่องนี้) ... contents && f(contents)ผมคิดว่าตอนนี้มันเป็นอย่างที่อ่านได้พิจารณา ฟังก์ชั่นสองอย่างเพื่อประหยัดอีกอย่างหนึ่ง!
herby

12

ความเป็นไปได้หนึ่งอย่าง:

boolean handled = false;

if(FileExists(file))
{
    contents = OpenFile(file); // <-- prevents inclusion in if
    if(SomeTest(contents))
    {
        DoSomething(contents);
        handled = true;
    }
}
if (!handled)
{
    DefaultAction();
}

แน่นอนว่าสิ่งนี้ทำให้โค้ดซับซ้อนขึ้นเล็กน้อยในทางที่ต่างออกไป ดังนั้นจึงเป็นคำถามสไตล์ส่วนใหญ่

แนวทางที่แตกต่างจะใช้ข้อยกเว้นเช่น:

try
{
    contents = OpenFile(file); // throws IO exception if file not found
    DoSomething(contents); // calls SomeTest() and throws exception on failure
}
catch(Exception e)
{
    DefaultAction();
    // and the exception should be at least logged...
}

สิ่งนี้ดูง่ายกว่า แต่ใช้ได้เฉพาะในกรณีที่

  • เรารู้อย่างแม่นยำว่าจะมีข้อยกเว้นประเภทใดและDefaultAction()เหมาะสมกับแต่ละข้อ
  • เราคาดว่าการประมวลผลไฟล์จะประสบความสำเร็จและไฟล์ที่หายไปหรือความล้มเหลวSomeTest()นั้นเป็นเงื่อนไขที่ผิดพลาดอย่างชัดเจนดังนั้นจึงเหมาะที่จะแสดงข้อยกเว้น

19
noooo ~! ไม่ใช่ตัวแปรแฟล็กมันเป็นวิธีที่ผิดอย่างแน่นอนเพราะมันนำไปสู่ความซับซ้อนและยากที่จะเข้าใจ
Abyx

ไม่ใช่ถ้าคุณ จำกัด ให้อยู่ในขอบเขตที่เป็นไปได้ (function () { ... })()ใน Javascript, { flag = false; ... }ในรูปแบบ C-like และอื่น ๆ
herby

+1 สำหรับตรรกะการยกเว้นซึ่งอาจเป็นโซลูชันที่เหมาะสมที่สุดขึ้นอยู่กับสถานการณ์
Steven Jeuris

4
+1 ซึ่งกันและกัน 'Nooooo!' เป็นเรื่องตลก ฉันคิดว่าตัวแปรสถานะและผลตอบแทนเร็วนั้นเหมาะสมในบางกรณี ในกิจวัตรที่ซับซ้อนมากขึ้นฉันจะไปหาตัวแปรสถานะเพราะแทนที่จะเพิ่มความซับซ้อนสิ่งที่มันทำจริงๆทำให้ตรรกะชัดเจน ไม่มีอะไรผิดปกติกับที่
grossvogel

1
นี่คือรูปแบบที่เราต้องการที่ฉันทำงาน ตัวเลือกที่ใช้ได้ 2 ตัวเลือกหลักดูเหมือนจะเป็น "ผลตอบแทนหลายรายการ" และ "ตัวแปรแฟล็ก" ดูเหมือนว่าจะไม่มีความได้เปรียบที่แท้จริงใด ๆ โดยเฉลี่ย แต่ทั้งคู่ก็เหมาะสมกับสถานการณ์ที่ดีกว่าคนอื่น ต้องไปกับกรณีทั่วไปของคุณ อีกเพียง "Emacs" กับ "Vi" สงครามทางศาสนา :-)
Brian Knoblauch

11

นี่คือระดับที่สูงขึ้นของสิ่งที่เป็นนามธรรม:

if (WeCanDoSomething(file))
{
   DoSomething(contents);
}
else
{
   DefaultAction();
} 

และสิ่งนี้ก็เติมเต็มในรายละเอียด

boolean WeCanDoSomething(file)
{
    if FileExists(file)
    {
        contents = OpenFile(file);
        return (SomeTest(contents));
    }
    else
    {
        return FALSE;
    }
}

11

ฟังก์ชั่นควรทำสิ่งหนึ่ง พวกเขาควรทำมันให้ดี พวกเขาควรทำมันเท่านั้น
- Robert Martin ในClean Code

บางคนพบว่าวิธีการนี้รุนแรงน้อย แต่ก็สะอาดมาก อนุญาตให้ฉันแสดงใน Python:

def processFile(self):
    if self.fileMeetsTest():
        self.doSomething()
    else:
        self.defaultAction()

def fileMeetsTest(self):
    return os.path.exists(self.path) and self.contentsTest()

def contentsTest(self):
    with open(self.path) as file:
        line = file.readline()
        return self.firstLineTest(line)

เมื่อเขาบอกว่าฟังก์ชั่นควรทำสิ่งหนึ่งเขาหมายถึงสิ่ง หนึ่ง processFile()เลือกการดำเนินการตามผลลัพธ์ของการทดสอบและนั่นคือทั้งหมดที่ทำได้ fileMeetsTest()รวมเงื่อนไขทั้งหมดของการทดสอบเข้าด้วยกันและนั่นคือทั้งหมดที่ทำได้ contentsTest()โอนสายแรกไปที่firstLineTest()และนั่นคือทั้งหมดที่มันทำ

ดูเหมือนว่ามีฟังก์ชั่นมากมาย แต่อ่านได้เหมือนภาษาอังกฤษโดยตรง:

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

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


5
+1 เป็นคำแนะนำที่ดี แต่สิ่งที่ถือเป็น "สิ่งหนึ่ง" ขึ้นอยู่กับเลเยอร์ปัจจุบันของสิ่งที่เป็นนามธรรม โปรเซส () คือ "สิ่งเดียว" แต่มีสองสิ่ง: fileMeetsTest () และ doSomething () หรือ defaultAction () อย่างใดอย่างหนึ่ง ฉันกลัวว่า "สิ่งหนึ่งที่" มุมมองอาจสร้างความสับสนให้ผู้เริ่มต้นที่ไม่เบื้องต้นเข้าใจแนวคิด
Caleb

1
เป็นเป้าหมายที่ดี ... นั่นคือทั้งหมดที่ฉันต้องพูดเกี่ยวกับที่ ... ;-)
Brian Knoblauch

1
ฉันไม่ชอบการส่งผ่านข้อโต้แย้งแบบไม่แน่นอนเป็นตัวแปรอินสแตนซ์เช่นนั้น คุณได้รับตัวแปรอินสแตนซ์ที่ "ไร้ประโยชน์" และมีหลายวิธีในการปรับปรุงสถานะของคุณและทำลายค่าคงที่
hugomg

@Caleb, ProcessFile () กำลังทำสิ่งหนึ่ง ดังที่ Karl กล่าวไว้ในโพสต์ของเขามันกำลังใช้การทดสอบเพื่อตัดสินใจว่าจะใช้การกระทำแบบใดและชะลอการใช้งานจริงของความเป็นไปได้ในการดำเนินการกับวิธีการอื่น หากคุณต้องการเพิ่มการกระทำอื่น ๆ อีกมากมายเกณฑ์วัตถุประสงค์เดียวสำหรับวิธีการนั้นจะยังคงดำเนินต่อไปตราบใดที่ไม่มีการวางซ้อนของตรรกะในวิธีการทันที
S.Robins

6

ด้วยการไปถึงสิ่งนี้เรียกว่าก็สามารถพัฒนาเข้าสู่รูปแบบการป้องกันหัวลูกศรเป็นรหัสของคุณเติบโตขึ้นเพื่อจัดการกับความต้องการมากขึ้น (แสดงโดยคำตอบที่มีให้ ณhttps://softwareengineering.stackexchange.com/a/122625/33922 ) และ จากนั้นตกลงไปในกับดักของการมีส่วนของรหัสขนาดใหญ่ที่มีคำสั่งแบบซ้อนเงื่อนไขที่มีลักษณะลูกศร

ดูลิงค์เช่น;

http://codinghorror.com/blog/2006/01/flattening-arrow-code.html

http://lostechies.com/chrismissal/2009/05/27/anti-patterns-and-worst-practices-the-arrowhead-anti-pattern/

มีอีกมากมายเกี่ยวกับสิ่งนี้และรูปแบบการต่อต้านอื่น ๆ ที่จะพบได้ใน Google

เคล็ดลับยอดเยี่ยมที่ Jeff ให้ไว้ในบล็อกของเขาเกี่ยวกับเรื่องนี้คือ

1) แทนที่เงื่อนไขด้วยคำสั่งป้องกัน

2) ย่อยสลายบล็อกตามเงื่อนไขเป็นฟังก์ชั่นแยก

3) แปลงเช็คเชิงลบเป็นเช็คบวก

4) ฉวยโอกาสกลับมาโดยเร็วที่สุดจากฟังก์ชั่น

ดูความคิดเห็นบางส่วนในบล็อกของเจฟฟ์เกี่ยวกับคำแนะนำของ Steve McConnells เกี่ยวกับผลตอบแทนก่อน

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

...

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

ฉันสมัครสมาชิก 1 รายการ / ออกต่อทฤษฎีการทำงานเนื่องจากสิ่งที่ฉันได้รับการสอน 15 ปีหรือมากกว่านั้น ฉันรู้สึกว่านี่ทำให้การอ่านโค้ดง่ายขึ้นมากและเมื่อคุณพูดถึงการบำรุงรักษามากขึ้น


6

สิ่งนี้สอดคล้องกับกฎ DRY, no-goto และ no-multiple-return กฎสามารถปรับขนาดและอ่านได้ในความคิดของฉัน:

success = FileExists(file);
if (success)
{
    contents = OpenFile(file);
    success = SomeTest(contents);
}
if (success)
{
    DoSomething(contents);
}
else
{
    DefaultAction();
}

1
การทำตามมาตรฐานไม่จำเป็นต้องเท่ากับรหัสที่ดี ขณะนี้ฉันยังไม่แน่ใจเกี่ยวกับข้อมูลโค้ดนี้
Brian Knoblauch

เพียงแค่แทนที่ 2 defaultAction (); กับ 2 ที่เหมือนกันถ้าเงื่อนไขและเพิ่มตัวแปรธงซึ่ง imo จะยิ่งแย่
Ryathal

3
ประโยชน์ของการใช้โครงสร้างเช่นนี้ก็คือว่าเป็นตัวเลขของการทดสอบเพิ่มรหัสไม่ได้เริ่มต้นที่จะรังเพิ่มเติมifs ภายในอื่น ๆifs นอกจากนี้รหัสเพื่อจัดการกับกรณีที่ไม่ประสบความสำเร็จ ( DefaultAction()) อยู่ในที่เดียวเท่านั้นและสำหรับจุดประสงค์ในการดีบั๊กโค้ดจะไม่กระโดดไปรอบ ๆ ฟังก์ชั่นตัวช่วยและเพิ่มจุดพักในบรรทัดที่successตัวแปรนั้นเปลี่ยนสามารถแสดงการทดสอบที่ผ่าน เบรกพอยต์) และอันที่ไม่ได้รับการทดสอบ (ด้านล่าง)
Frozenkoi

1
Yeeaah ฉันชอบมันมาก แต่ฉันคิดว่าฉันจะเปลี่ยนชื่อsuccessเป็นok_so_far:)
Benjol

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

3

ฉันจะแยกมันเป็นวิธีอื่นแล้ว:

if(!FileExists(file))
{
    DefaultAction();
    return;
}

contents = OpenFile(file);
if(!SomeTest(contents))
{
    DefaultAction();
    return;
}

DoSomething(contents);

ซึ่งยังช่วยให้

if(!FileExists(file))
{
    DefaultAction();
    return Result.FileNotFound;
}

contents = OpenFile(file);
if(!SomeTest(contents))
{
    DefaultAction();
    return Result.TestFailed;
}

DoSomething(contents);
return Result.Success;            

แล้วอาจจะเป็นคุณสามารถลบการDefaultActionโทรออกและปล่อยให้การดำเนินการDefaultActionสำหรับผู้โทร:

Result OurMethod(file)
{
    if(!FileExists(file))
    {
        return Result.FileNotFound;
    }

    contents = OpenFile(file);
    if(!SomeTest(contents))
    {
        return Result.TestFailed;
    }

    DoSomething(contents);
    return Result.Success;            
}

void Caller()
{
    // something, something...

    var result = OurMethod(file);
    // if (result == Result.FileNotFound || result == Result.TestFailed), or just
    if (result != Result.Success)        
    {
        DefaultAction();
    }
}

ฉันชอบแนวทางของ Jeanne Pindarเช่นกัน


3

สำหรับกรณีนี้คำตอบนั้นง่ายพอ ...

มีสภาวะการแย่งชิงระหว่างFileExistsและOpenFile: จะเกิดอะไรขึ้นหากไฟล์ถูกลบ

วิธีเดียวที่มีสติในการจัดการกับกรณีนี้คือการข้ามFileExists:

contents = OpenFile(file);
if (!contents) // open failed
    DefaultAction();
else (SomeTest(contents))
    DoSomething(contents);

วิธีนี้จะช่วยแก้ปัญหานี้ได้อย่างเรียบร้อยและทำให้โค้ดสะอาดขึ้น

โดยทั่วไป: ลองคิดทบทวนปัญหาใหม่อีกครั้งและคิดวิธีแก้ไขปัญหาอื่นที่หลีกเลี่ยงปัญหาทั้งหมด


2

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

ดังนั้นตัวอย่างของคุณอาจกลายเป็น:

void DoABunchOfStuff()
{
    if(FileExists(file))
    {
        DoSomethingWithFileContent(file);
        return;
    }

    DefaultAction();
}

void DoSomethingWithFileContent(file)
{        
    var contents = GetFileContents(file)

    if(SomeTest(contents))
    {
        DoSomething(contents);
        return;
    }

    DefaultAction();
}

AReturnType GetFileContents(file)
{
    return OpenFile(file);
}

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


2

เคสที่แสดงในโค้ดตัวอย่างสามารถลดลงเป็นifคำสั่งเดี่ยวได้ ในหลาย ๆ ระบบฟังก์ชั่นเปิดไฟล์จะส่งคืนค่าที่ไม่ถูกต้องหากไฟล์นั้นไม่มีอยู่ บางครั้งสิ่งนี้เป็นพฤติกรรมเริ่มต้น; เวลาอื่นจะต้องระบุผ่านการโต้แย้ง ซึ่งหมายความว่าการFileExistsทดสอบสามารถถูกดร็อปซึ่งสามารถช่วยในสภาวะการแข่งขันที่เกิดจากการลบไฟล์ระหว่างการทดสอบการมีอยู่และการเปิดไฟล์

file = OpenFile(path);
if(isValidFileHandle(file) && SomeTest(file)) {
    DoSomething(file);
} else {
    DefaultAction();
}

สิ่งนี้ไม่ได้แก้ไขปัญหาการผสมในระดับที่เป็นนามธรรมโดยตรงเนื่องจากเป็นการหลีกเลี่ยงปัญหาของการทดสอบหลายรายการที่ไม่สามารถลบล้างได้ทั้งหมดแม้ว่าการทำแบบทดสอบการมีอยู่ของไฟล์จะไม่เข้ากันกับการแยกระดับนามธรรม สมมติว่าการจัดการไฟล์ที่ไม่ถูกต้องเทียบเท่ากับ "false" และการจัดการไฟล์จะปิดเมื่อไม่อยู่ในขอบเขต:

OpenFileIfSomething(path:String) : FileHandle {
    file = OpenFile(path);
    if (file && SomeTest(file)) {
        return file;
    }
    return null;
}

...

if ((file = OpenFileIfSomething(path))) {
    DoSomething(file);
} else {
    DefaultAction();
}

2

ฉันเห็นด้วยกับ Frozenkoi อย่างไรก็ตามสำหรับ C # anyways ฉันคิดว่ามันจะช่วยในการติดตามไวยากรณ์ของวิธี TryParse

if(FileExists(file) && TryOpenFile(file, out contents))
    DoSomething(contents);
else
    DefaultAction();
bool TryOpenFile(object file, out object contents)
{
    try{
        contents = OpenFile(file);
    }
    catch{
        //something bad happened, computer probably exploded
        return false;
    }
    return true;
}

1

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

if (!ProcessFile(file)) { 
  DefaultAction(); 
}

โปรแกรมเมอร์ Perl และ Ruby เขียน processFile(file) || defaultAction()

ตอนนี้ไปเขียน ProcessFile:

if (FileExists(file)) { 
  contents = OpenFile(file);
  if (SomeTest(contents)) {
    processContents(contents);
    return true;
  }
}
return false;

1

แน่นอนคุณสามารถไปได้ไกลในสถานการณ์เช่นนี้ แต่นี่เป็นวิธีไป:

interface File<T> {
    function isOK():Bool;
    function getData():T;
}

var appleFile:File<Apple> = appleStorage.get(fileURI);
if (appleFile.isOK())
    eat(file.getData());
else
    cry();

คุณอาจต้องการตัวกรองเพิ่มเติม จากนั้นทำสิ่งนี้:

var appleFile = appleStorage.get(fileURI, isEdible);
//isEdible is of type Apple->Bool and will be used internally to answer to the isOK call
if (appleFile.isOK())
    eat(file.getData());
else
    cry();

แม้ว่าสิ่งนี้อาจเข้าท่าเช่นกัน:

function eat(apple:Apple) {
     if (isEdible(apple)) 
         digest(apple);
     else
         die();
}
var appleFile = appleStorage.get(fileURI);
if (appleFile.isOK())
    eat(appleFile.getData());
else
    cry();

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


1

มีอะไรผิดปกติกับความชัดเจน

if(!FileExists(file)) {
    DefaultAction();
    return;
}
contents = OpenFile(file);
if(!SomeTest(contents))
{
    DefaultAction();
    return;
}        
DoSomething(contents);

ดูเหมือนว่าเป็นมาตรฐานที่ดีสำหรับฉัน สำหรับขั้นตอนใหญ่ ๆ ที่ต้องเกิดเรื่องเล็ก ๆ น้อย ๆ ความล้มเหลวใด ๆ ที่จะป้องกันไม่ให้สิ่งหลัง ข้อยกเว้นทำให้ทำความสะอาดได้เล็กน้อยหากเป็นตัวเลือก


0

ฉันคิดว่านี่เป็นคำถามเก่า แต่ฉันสังเกตเห็นรูปแบบที่ไม่ได้กล่าวถึง ส่วนใหญ่การตั้งค่าตัวแปรเพื่อกำหนดวิธีการ / s ที่คุณต้องการโทรหาในภายหลัง (นอก if ... else ... )

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

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

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

สิ่งนี้สามารถใช้งานได้ในตัวอย่างที่ถามในคำถามที่เรากำลังตรวจสอบว่า 'DoSomething' เกิดขึ้นหรือไม่และหากไม่ดำเนินการตามค่าเริ่มต้น หรือคุณอาจมีสถานะสำหรับแต่ละวิธีที่คุณอาจต้องการโทรตั้งค่าเมื่อใช้งานได้แล้วเรียกใช้วิธีการที่เหมาะสมนอกถ้า ... ...

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

bool ActionDone = false;

if (Method_1(object_A)) // Test 1
{
    result_A = Method_2(object_A); // Result 1

    if (Method_3(result_A)) // Test 2
    {
        Method_4(result_A); // Action 1
        ActionDone = true;
    }
}

if (!ActionDone)
{
    Method_5(); // Default Action
}

0

หากต้องการลด IF ที่ซ้อนกัน:

1 / ผลตอบแทนต้น;

2 / การแสดงออกผสม (ตระหนักถึงการลัดวงจร)

ดังนั้นตัวอย่างของคุณอาจได้รับการฟื้นฟูเช่นนี้

if( FileExists(file) && SomeTest(contents = OpenFile(file)) )
{
    DoSomething(contents);
    return;
}
DefaultAction();

0

ฉันเห็นตัวอย่างมากมายด้วย "return" ซึ่งฉันใช้ด้วย แต่บางครั้งฉันต้องการหลีกเลี่ยงการสร้างฟังก์ชั่นใหม่และใช้การวนซ้ำแทน:

while (1) {
    if (FileExists(file)) {
        contents = OpenFile(file);
        if (SomeTest(contents)) {
           DoSomething(contents);
           break;
        } 
    }
    DefaultAction();
    break;
}

หากคุณต้องการที่จะเขียนเส้นน้อยลงหรือคุณเกลียดลูปไม่มีที่สิ้นสุดเป็นฉันคุณสามารถเปลี่ยนประเภทวงเป็น "ทำ ... ในขณะที่ (0)" และหลีกเลี่ยงการ "หยุด" ล่าสุด


0

วิธีการแก้ปัญหานี้:

content = NULL; //I presume OpenFile returns a pointer 
if(FileExists(file))
    contents = OpenFile(file);
if(content != NULL && SomeTest(contents))
    DoSomething(contents);
else
    DefaultAction();

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

แน่นอนว่าฉันไม่ได้คาดหวังว่าจะมีการกระทำที่เป็นไปได้ด้วยวิธี SomeTest บนตัวชี้ NULL (แต่คุณไม่เคยรู้) ดังนั้นสิ่งนี้อาจถูกมองว่าเป็นการตรวจสอบเพิ่มเติมสำหรับตัวชี้ NULL สำหรับการโทร SomeTest (เนื้อหา)


0

เห็นได้ชัดว่าโซลูชั่นที่หรูหราและรัดกุมที่สุดคือการใช้มาโครตัวประมวลผลล่วงหน้า

#define DOUBLE_ELSE(CODE) else { CODE } } else { CODE }

ซึ่งให้คุณเขียนโค้ดที่สวยงามเช่นนี้:

if(FileExists(file))
{
    contents = OpenFile(file);
    if(SomeTest(contents))
    {
        DoSomething(contents);
    }
    DOUBLE_ELSE(DefaultAction();)

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


สำหรับบางคนและในบางภาษาแมโคร preprocessor มี :) รหัสความชั่วร้าย
Benjol

@Benjol คุณบอกว่าคุณเปิดรับข้อเสนอแนะที่ชั่วร้ายไม่? ;)
Peter Olson

ใช่อย่างที่มันเป็นเพียงแค่ wrt ของคุณ "หลีกเลี่ยงความชั่วร้าย" :)
Benjol

4
นี่มันช่างน่ากลัวมากฉันแค่ต้อง
ถอนรากถอนโคน

เชอร์ลี่ย์คุณไม่จริงจัง !!!!!!
Jim In Texas

-1

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

let
  contents = ReadFile(file)
in
  if FileExists(file) && SomeTest(contents) 
    DoSomething(contents)
  else 
    DefaultAction()
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.