ประสิทธิภาพของการกลับมาก่อนกำหนดในฟังก์ชัน


97

นี่เป็นสถานการณ์ที่ฉันพบบ่อยในฐานะโปรแกรมเมอร์ที่ไม่มีประสบการณ์และฉันสงสัยว่าโดยเฉพาะอย่างยิ่งสำหรับโครงการที่มีความทะเยอทะยานและใช้ความเร็วสูงของฉันฉันกำลังพยายามเพิ่มประสิทธิภาพ สำหรับภาษาหลัก ๆ เช่น C (C, objC, C ++, Java, C # ฯลฯ ) และคอมไพเลอร์ตามปกติฟังก์ชันทั้งสองนี้จะทำงานได้อย่างมีประสิทธิภาพหรือไม่ มีความแตกต่างในโค้ดที่คอมไพล์หรือไม่?

void foo1(bool flag)
{
    if (flag)
    {
        //Do stuff
        return;
    }

    //Do different stuff
}

void foo2(bool flag)
{
    if (flag)
    {
        //Do stuff
    }
    else
    {
        //Do different stuff
    }
}

โดยทั่วไปแล้วจะมีโบนัส / บทลงโทษที่มีประสิทธิภาพโดยตรงbreakหรือreturnไม่เมื่อเริ่มต้นหรือไม่? Stackframe เกี่ยวข้องอย่างไร? มีกรณีพิเศษที่เหมาะสมหรือไม่? มีปัจจัยใดบ้าง (เช่นการซับในหรือขนาดของ "Do stuff") ที่อาจส่งผลกระทบอย่างมาก

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

และฉันตระหนักถึงข้อผิดพลาดของการเพิ่มประสิทธิภาพก่อนเวลาอันควร ... เอ่อนั่นคือความทรงจำที่เจ็บปวด

แก้ไข: ฉันยอมรับคำตอบ แต่คำตอบของ EJP อธิบายได้อย่างชัดเจนว่าทำไมการใช้ a returnจึงไม่สำคัญในทางปฏิบัติ (ในการประกอบการreturnสร้าง 'สาขา' ไปยังจุดสิ้นสุดของฟังก์ชันซึ่งเร็วมากสาขาเปลี่ยนแปลงการลงทะเบียนพีซีและ อาจส่งผลต่อแคชและไปป์ไลน์ซึ่งค่อนข้างเล็ก) สำหรับกรณีนี้โดยเฉพาะอย่างยิ่งมันไม่ได้สร้างความแตกต่างอย่างแท้จริงเพราะทั้งสองif/elseและreturnสร้างสาขาเดียวกันไปยังจุดสิ้นสุดของฟังก์ชัน


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

10
@SirVaulterScott เว้นแต่ว่าทั้งสองกรณีจะสมมาตรไม่ทางใดก็ทางหนึ่งซึ่งในกรณีนี้คุณต้องการดึงความสมมาตรออกมาโดยวางไว้ที่ระดับการเยื้องเดียวกัน
luqui

3
SirVaulterScoff: +1 เพื่อลดการทำรังที่ไม่จำเป็น
fjdumont

11
ความสามารถในการอ่าน >>> การเพิ่มประสิทธิภาพแบบไมโคร ทำวิธีใดก็ได้ที่เหมาะสมกว่าสำหรับ wetware ที่จะดูแลรักษาสิ่งนี้ ในระดับรหัสเครื่องจักรโครงสร้างทั้งสองนี้จะเหมือนกันเมื่อป้อนเข้าไปในคอมไพเลอร์ที่ค่อนข้างโง่ คอมไพเลอร์ที่ปรับให้เหมาะสมจะลบล้างความได้เปรียบด้านความเร็วระหว่างทั้งสอง
SplinterReality

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

คำตอบ:


92

ไม่มีความแตกต่างเลย:

=====> cat test_return.cpp
extern void something();
extern void something2();

void test(bool b)
{
    if(b)
    {
        something();
    }
    else
        something2();
}
=====> cat test_return2.cpp
extern void something();
extern void something2();

void test(bool b)
{
    if(b)
    {
        something();
        return;
    }
    something2();
}
=====> rm -f test_return.s test_return2.s
=====> g++ -S test_return.cpp 
=====> g++ -S test_return2.cpp 
=====> diff test_return.s test_return2.s
=====> rm -f test_return.s test_return2.s
=====> clang++ -S test_return.cpp 
=====> clang++ -S test_return2.cpp 
=====> diff test_return.s test_return2.s
=====> 

ความหมายไม่มีความแตกต่างในโค้ดที่สร้างขึ้นแม้ว่าจะไม่มีการเพิ่มประสิทธิภาพในสองคอมไพเลอร์


59
หรือดีกว่า: มีอย่างน้อยเวอร์ชันของคอมไพเลอร์ที่สร้างรหัสเดียวกันสำหรับทั้งสองเวอร์ชัน
UncleZeiv

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

6
@ Steve314 แน่นอนว่าฉันแค่
คิดสั้น

@UncleZeiv: ทดสอบเสียงดังเกินไปและผลลัพธ์เดียวกัน
Dani

ฉันไม่เข้าใจ. ดูเหมือนชัดเจนว่าsomething()จะถูกประหารชีวิตเสมอ ในคำถามเดิม OP มีDo stuffและDo diffferent stuffขึ้นอยู่กับธง ฉันไม่มั่นใจว่ารหัสที่สร้างขึ้นจะเหมือนกัน
Luc M

65

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

เน้นความสามารถในการอ่านและการบำรุงรักษา

หากคุณต้องการดูว่าเกิดอะไรขึ้นให้สร้างสิ่งเหล่านี้ด้วยการเพิ่มประสิทธิภาพและดูที่เอาต์พุตแอสเซมเบลอร์


8
@ ฟิลิป: และทุกคนก็ให้ความช่วยเหลือเช่นกันและเลิกกังวลเรื่องนี้ รหัสที่คุณเขียนจะถูกอ่านและดูแลโดยผู้อื่นเช่นกัน (และถึงแม้คุณจะเขียนโดยที่คนอื่นจะไม่อ่านคุณก็ยังพัฒนานิสัยที่จะส่งผลต่อโค้ดอื่น ๆ ที่คุณเขียนซึ่งคนอื่นจะอ่าน) ควรเขียนโค้ดให้เข้าใจง่ายที่สุด
hlovdal

8
นักเพิ่มประสิทธิภาพไม่ได้ฉลาดไปกว่าคุณ !!! พวกเขาเร็วขึ้นเท่านั้นในการตัดสินใจว่าผลกระทบไม่สำคัญมาก ในกรณีที่สำคัญจริงๆคุณจะต้องมีประสบการณ์บางอย่างที่ดีกว่าคอมไพเลอร์
johannes

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

3
@johannes - สำหรับคำถามนี้คุณสามารถสรุปได้ นอกจากนี้โดยทั่วไปบางครั้งคุณอาจปรับให้เหมาะสมได้ดีกว่าคอมไพเลอร์ในบางกรณีพิเศษ แต่ต้องใช้ความรู้จากผู้เชี่ยวชาญพอสมควรในปัจจุบัน - กรณีปกติคือเครื่องมือเพิ่มประสิทธิภาพจะใช้การเพิ่มประสิทธิภาพส่วนใหญ่ที่คุณนึกออกและทำได้ อย่างเป็นระบบไม่ใช่เฉพาะในบางกรณีพิเศษ WRT คำถามนี้คอมไพเลอร์อาจจะสร้างโฟลว์กราฟการดำเนินการเดียวกันอย่างแม่นยำสำหรับทั้งสองรูปแบบ การเลือกอัลกอริทึมที่ดีกว่าเป็นงานของมนุษย์ แต่การเพิ่มประสิทธิภาพระดับโค้ดมักจะเสียเวลาไปเปล่า ๆ
Steve314

4
ฉันเห็นด้วยและไม่เห็นด้วยกับเรื่องนี้ มีหลายกรณีที่คอมไพลเลอร์ไม่สามารถรู้ได้ว่ามีบางอย่างเทียบเท่ากับสิ่งอื่น คุณรู้ไหมว่ามันมักจะทำเร็วx = <some number>กว่าที่if(<would've changed>) x = <some number>กิ่งไม้ที่ไม่ได้ปลูกจะทำร้ายได้จริงๆ ในทางกลับกันเว้นแต่สิ่งนี้จะอยู่ในลูปหลักของการดำเนินการที่เข้มข้นมากฉันก็ไม่ต้องกังวลเช่นกัน
user606723

28

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

หากตัวอย่างง่ายๆด้านบนถูกขยายด้วยการจัดสรรทรัพยากรแล้วเกิดข้อผิดพลาดในการตรวจสอบว่าอาจทำให้ทรัพยากรว่างรูปภาพอาจเปลี่ยนไป

ลองพิจารณาแนวทางที่ไร้เดียงสาของผู้เริ่มต้นอาจใช้:

int func(..some parameters...) {
  res_a a = allocate_resource_a();
  if (!a) {
    return 1;
  }
  res_b b = allocate_resource_b();
  if (!b) {
    free_resource_a(a);
    return 2;
  }
  res_c c = allocate_resource_c();
  if (!c) {
    free_resource_b(b);
    free_resource_a(a);
    return 3;
  }

  do_work();

  free_resource_c(c);
  free_resource_b(b);
  free_resource_a(a);

  return 0;
}

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

int func(..some parameters...) {
  res_a a;
  res_b b;
  res_c c;

  try {
    a = allocate_resource_a(); # throws ExceptionResA
    b = allocate_resource_b(); # throws ExceptionResB
    c = allocate_resource_c(); # throws ExceptionResC
    do_work();
  }  
  catch (ExceptionBase e) {
    # Could use type of e here to distinguish and
    # use different catch phrases here
    # class ExceptionBase must be base class of ExceptionResA/B/C
    if (c) free_resource_c(c);
    if (b) free_resource_b(b);
    if (a) free_resource_a(a);
    throw e
  }
  return 0;
}

ฟิลิปแนะนำหลังจากดูตัวอย่าง goto ด้านล่างแล้วเพื่อใช้สวิตช์ / เคสแบบไม่ต้องหยุดพักภายในบล็อกจับด้านบน หนึ่งสามารถสลับ (typeof (จ)) และแล้วตกผ่านfree_resourcex()สาย แต่นี้ไม่น่ารำคาญและความต้องการการพิจารณาการออกแบบ และจำไว้ว่าสวิตช์ / เคสที่ไม่มีการหยุดพักนั้นเหมือนกับ goto ที่มีป้ายกำกับเดซี่ด้านล่าง ...

ในฐานะที่เป็นมาร์ค B ชี้ให้เห็นใน C ++ ก็ถือว่าเป็นรูปแบบที่ดีที่จะปฏิบัติตามทรัพยากรได้มาคือการเริ่มต้นหลักการRAIIในระยะสั้น สาระสำคัญของแนวคิดคือการใช้การสร้างอินสแตนซ์อ็อบเจ็กต์เพื่อรับทรัพยากร จากนั้นทรัพยากรจะถูกปลดปล่อยโดยอัตโนมัติทันทีที่วัตถุอยู่นอกขอบเขตและมีการเรียกผู้ทำลายล้าง สำหรับทรัพยากรที่อยู่ระหว่างกันจะต้องได้รับการดูแลเป็นพิเศษเพื่อให้แน่ใจว่าลำดับการจัดสรรที่ถูกต้องและการออกแบบประเภทของออบเจ็กต์ที่มีข้อมูลที่จำเป็นสำหรับผู้ทำลายทั้งหมด

หรือในช่วงก่อนวันยกเว้นอาจทำ:

int func(..some parameters...) {
  res_a a = allocate_resource_a();
  res_b b = allocate_resource_b();
  res_c c = allocate_resource_c();
  if (a && b && c) {   
    do_work();
  }  
  if (c) free_resource_c(c);
  if (b) free_resource_b(b);
  if (a) free_resource_a(a);

  return 0;
}

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

เพื่อให้โค้ดเร็ว (!) มีขนาดกะทัดรัดและอ่านง่ายและขยายได้Linus Torvalds บังคับใช้รูปแบบที่แตกต่างกันสำหรับโค้ดเคอร์เนลที่เกี่ยวข้องกับทรัพยากรแม้จะใช้goto ที่น่าอับอายในลักษณะที่สมเหตุสมผล :

int func(..some parameters...) {
  res_a a;
  res_b b;
  res_c c;

  a = allocate_resource_a() || goto error_a;
  b = allocate_resource_b() || goto error_b;
  c = allocate_resource_c() || goto error_c;

  do_work();

error_c:
  free_resource_c(c);
error_b:
  free_resource_b(b);
error_a:
  free_resource_a(a);

  return 0;
}

สาระสำคัญของการสนทนาในรายการส่งเมลของเคอร์เนลคือคุณลักษณะภาษาส่วนใหญ่ที่ "ต้องการ" เหนือคำสั่ง goto นั้นเป็น gotos โดยปริยายเช่น if / else ขนาดใหญ่ตัวจัดการข้อยกเว้นคำสั่ง loop / break / continue ฯลฯ และ goto ในตัวอย่างข้างต้นถือว่าโอเคเนื่องจากพวกมันกระโดดได้เพียงระยะทางเล็ก ๆ มีป้ายกำกับที่ชัดเจนและไม่มีรหัสของความยุ่งเหยิงอื่น ๆ เพื่อติดตามเงื่อนไขข้อผิดพลาด คำถามนี้ยังได้รับการกล่าวถึงที่นี่ใน StackOverflow

อย่างไรก็ตามสิ่งที่ขาดหายไปในตัวอย่างสุดท้ายเป็นวิธีที่ดีในการส่งคืนรหัสข้อผิดพลาด ฉันกำลังคิดที่จะเพิ่มresult_code++หลังการfree_resource_x()โทรแต่ละครั้งและส่งคืนรหัสนั้น แต่สิ่งนี้จะชดเชยความเร็วที่เพิ่มขึ้นของรูปแบบการเข้ารหัสข้างต้น และยากที่จะคืนค่า 0 ในกรณีที่สำเร็จ บางทีฉันอาจจะไม่ได้จินตนาการ ;-)

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


1
ว้าวน่าสนใจจริงๆ แน่นอนฉันสามารถชื่นชมความไม่สามารถเข้าถึงได้ของวิธีการที่ไร้เดียงสา การจัดการข้อยกเว้นจะปรับปรุงในกรณีนั้น ๆ อย่างไร เช่นเดียวกับcatchที่มีswitchคำสั่งbreak-less เกี่ยวกับรหัสข้อผิดพลาด?
Philip Guin

@Philip เพิ่มตัวอย่างการจัดการข้อยกเว้นพื้นฐาน โปรดทราบว่ามีเพียง goto เท่านั้นที่มีโอกาสล้มเหลว สวิทช์เสนอของคุณ (typeof (จ)) จะช่วย แต่ไม่น่ารำคาญและความต้องการการพิจารณาการออกแบบ และโปรดจำไว้ว่าสวิตช์ / เคสที่ไม่มีการหยุดพักนั้นเหมือนกับ goto ที่มีป้ายกำกับแบบเดซี่ ;-)
cfi

+1 นี่คือคำตอบที่ถูกต้องสำหรับ C / C ++ (หรือภาษาใด ๆ ที่ต้องการการเพิ่มหน่วยความจำด้วยตนเอง) โดยส่วนตัวแล้วฉันไม่ชอบเวอร์ชันหลายป้าย ที่ บริษัท เดิมของฉันมักจะเป็น "goto fin" เสมอ (เป็น บริษัท ฝรั่งเศส) ในขั้นสุดท้ายเราจะยกเลิกการจัดสรรหน่วยความจำใด ๆ และนั่นเป็นการใช้ goto เพียงอย่างเดียวที่จะผ่านการตรวจสอบโค้ด
กีบ

1
โปรดทราบว่าใน C ++ คุณจะไม่ทำตามแนวทางเหล่านี้ แต่จะใช้ RAII เพื่อให้แน่ใจว่าทรัพยากรได้รับการทำความสะอาดอย่างเหมาะสม
Mark B

12

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


9

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


Gotcha ที่จริงฉันคิดว่านี่ตอบคำถามของฉันได้ค่อนข้างรวบรัด ฉันเดาว่ามันเป็นเพียงการเพิ่มการลงทะเบียนซึ่งค่อนข้างน้อยมาก (เว้นแต่ว่าคุณกำลังเขียนโปรแกรมระบบและถึงอย่างนั้น ... ) ฉันจะให้รางวัลชมเชยนี้
Philip Guin

@ ฟิลิปทะเบียนอะไรเพิ่ม? ไม่มีการสอนพิเศษในเส้นทางเลย
Marquis of Lorne

ทั้งคู่จะมีการเพิ่มการลงทะเบียน นั่นคือสาขาการประกอบทั้งหมดใช่หรือไม่? นอกเหนือจากตัวนับโปรแกรม? ฉันอาจจะผิดที่นี่
Philip Guin

1
@ ฟิลิปไม่ใช่สาขาแอสเซมบลีคือสาขาแอสเซมบลี มันส่งผลกระทบต่อพีซีแน่นอน แต่อาจเป็นได้โดยการโหลดซ้ำทั้งหมดและยังมีผลข้างเคียงในโปรเซสเซอร์บีบท่อส่งแคช ฯลฯ
Marquis of Lorne

4

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

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

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


4

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

void foo1(bool flag)
{
    if (flag)
    {
        //Do stuff
        return;
    }

    //Do different stuff
}

void foo2(bool flag)
{
    if (flag)
    {
        //Do stuff
    }
    else
    {
        //Do different stuff
    }
}

แน่นอนว่าฟังก์ชันไม่ควรมีความยาวเกินหนึ่ง (หรือสอง) หน้า แต่ด้านการดีบักยังไม่ครอบคลุมในคำตอบอื่น ๆ ชี้!
cfi

3

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

ซึ่งจะหมายถึงการใช้ decompiler หรือดูที่เอาต์พุตคอมไพเลอร์ระดับต่ำ (เช่น lanuage แอสเซมบลี) ในภาษา C # หรือภาษา. Net เครื่องมือในเอกสารนี้จะให้สิ่งที่คุณต้องการ

แต่อย่างที่คุณสังเกตเห็นว่านี่อาจเป็นการเพิ่มประสิทธิภาพก่อนกำหนด


1

จากClean Code: A Handbook of Agile Software Craftsmanship

ข้อโต้แย้งของธงเป็นสิ่งที่น่าเกลียด การส่งผ่านบูลีนไปยังฟังก์ชันเป็นการปฏิบัติที่แย่มาก มันทำให้ลายเซ็นของวิธีซับซ้อนขึ้นทันทีโดยประกาศเสียงดังว่าฟังก์ชันนี้ทำมากกว่าหนึ่ง มันจะทำสิ่งหนึ่งถ้าแฟล็กเป็นจริงและอีกอย่างหากแฟล็กเป็นเท็จ!

foo(true);

ในโค้ดจะทำให้ผู้อ่านนำทางไปยังฟังก์ชันและเสียเวลาอ่าน foo (แฟล็กบูลีน)

ฐานรหัสที่มีโครงสร้างที่ดีขึ้นจะทำให้คุณมีโอกาสเพิ่มประสิทธิภาพโค้ดได้ดีขึ้น


ฉันแค่ใช้สิ่งนี้เป็นตัวอย่าง สิ่งที่ส่งผ่านเข้าสู่ฟังก์ชันอาจเป็น int, double, class, คุณตั้งชื่อมัน, มันไม่ได้เป็นหัวใจของปัญหาจริงๆ
Philip Guin

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

0

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

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


เอ่อฉันคิดว่ามันเกี่ยวข้องกับการล้างข้อมูล (โดยเฉพาะเมื่อเข้ารหัสใน C)
Thomas Eding

ไม่ว่าคุณจะทิ้งเมธอดไว้ที่ใดตราบเท่าที่คุณคืนสแต็กกลับลงมา (นั่นคือทั้งหมดที่ "ล้างข้อมูล")
MartyTPS

-4

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


3
ฉันบอกได้เลยว่าคุณล้อเล่น แต่สิ่งที่น่ากลัวคือบางคนอาจให้คำแนะนำของคุณอย่างจริงจัง!
Daniel Pryden

เห็นด้วยกับแดเนียล ฉันชอบการดูถูกเหยียดหยามมากพอ ๆ กัน - ไม่ควรใช้ในเอกสารทางเทคนิคเอกสารไวท์เปเปอร์และไซต์ถาม - ตอบเช่น SO
cfi

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