คำสั่ง if - การประเมินการลัดวงจรเทียบกับความสามารถในการอ่าน


90

บางครั้งคำสั่งจะมีความซับซ้อนมากกว่าหรือยาวดังนั้นเพื่อประโยชน์ของการอ่านมันจะดีกว่าที่จะดึงสายซับซ้อนก่อนifif

เช่นนี้:

if (SomeComplicatedFunctionCall() || OtherComplicatedFunctionCall())
{
    // do stuff
}

ในสิ่งนี้

bool b1 = SomeComplicatedFunctionCall();
bool b2 = OtherComplicatedFunctionCall();

if (b1 || b2)
{
    //do stuff
}

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

แต่ด้วยการสกัดนี้ฉันสูญเสียการประเมินการลัดวงจร (SCE)

  1. ฉันแพ้ SCE ทุกครั้งหรือไม่? มีสถานการณ์บางอย่างที่คอมไพเลอร์ได้รับอนุญาตให้ "ปรับให้เหมาะสม" และยังคงให้ SCE อยู่หรือไม่
  2. มีวิธีการรักษาความสามารถในการอ่านที่ดีขึ้นของตัวอย่างข้อมูลที่สองโดยไม่สูญเสีย SCE หรือไม่

20
การปฏิบัติแสดงให้เห็นว่าคำตอบส่วนใหญ่เกี่ยวกับการแสดงที่คุณจะเห็นที่นี่หรือที่อื่นส่วนใหญ่ผิด (ผิด 4 ข้อ 1 ถูก) คำแนะนำของฉันคือการทำโปรไฟล์และตรวจสอบด้วยตัวเองเสมอคุณจะหลีกเลี่ยง "การเพิ่มประสิทธิภาพก่อนกำหนด" และเรียนรู้สิ่งใหม่ ๆ
Marek R

25
@MarekR ไม่ได้เป็นเพียงแค่ประสิทธิภาพเท่านั้น แต่ยังเกี่ยวกับผลข้างเคียงที่เป็นไปได้ใน OtherCunctionCall ...
relaxxx

3
@David เมื่อกล่าวถึงไซต์อื่น ๆ มักจะเป็นประโยชน์ในการชี้ว่าการโพสต์แบบไขว้กัน
gnat

7
หากความสามารถในการอ่านเป็นข้อกังวลหลักของคุณอย่าเรียกใช้ฟังก์ชันที่มีผลข้างเคียงภายใน if conditional
Morgen

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

คำตอบ:


119

วิธีแก้ปัญหาทางธรรมชาติวิธีหนึ่งจะมีลักษณะดังนี้:

bool b1 = SomeCondition();
bool b2 = b1 || SomeOtherCondition();
bool b3 = b2 || SomeThirdCondition();
// any other condition
bool bn = bn_1 || SomeFinalCondition();

if (bn)
{
  // do stuff
}

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


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

if (!SomeComplicatedFunctionCall())
   return; // or continue

if (!SomeOtherComplicatedFunctionCall())
   return; // or continue

// do stuff

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


4
@relaxxx: ฉันเข้าใจแล้ว แต่ "มีอีกหลายอย่างที่ต้องทำหลังจากที่if" ก็เป็นสัญญาณว่าฟังก์ชันหรือวิธีการของคุณมีขนาดใหญ่เกินไปและควรแบ่งออกเป็นฟังก์ชันเล็ก ๆ ไม่ใช่วิธีที่ดีที่สุดเสมอไป แต่บ่อยครั้ง!
mike3996

2
สิ่งนี้ละเมิดหลักการบัญชีขาว
JoulinRouge

13
@JoulinRouge: น่าสนใจฉันไม่เคยได้ยินหลักการนี้มาก่อน ตัวฉันเองชอบวิธี "ลัดวงจร" นี้เพื่อประโยชน์ในการอ่าน: มันลดการเยื้องและกำจัดความเป็นไปได้ที่จะมีบางสิ่งเกิดขึ้นหลังจากบล็อกเยื้อง
Matthieu M.

2
น่าอ่านกว่าไหม ตั้งชื่อให้b2ถูกต้องและคุณจะได้รับsomeConditionAndSomeotherConditionIsTrueไม่ได้มีความหมายมาก นอกจากนี้ฉันต้องเก็บตัวแปรมากมายไว้ในกองจิตของฉันในระหว่างการฝึกนี้ (และ tbh จนกว่าฉันจะหยุดทำงานในขอบเขตนี้) ฉันจะใช้SJuan76วิธีแก้ปัญหาหมายเลข 2 หรือใส่ทั้งสิ่งในฟังก์ชัน
Nathan Cooper

2
ฉันไม่ได้อ่านความคิดเห็นทั้งหมด แต่หลังจากการค้นหาอย่างรวดเร็วฉันไม่พบข้อได้เปรียบที่ยิ่งใหญ่ของข้อมูลโค้ดแรกคือการดีบัก การวางสิ่งต่างๆลงใน if-statement โดยตรงแทนที่จะกำหนดให้ตัวแปรล่วงหน้าจากนั้นใช้ตัวแปรแทนทำให้การดีบักทำได้ยากกว่าที่ควรจะเป็น การใช้ตัวแปรยังช่วยให้สามารถจัดกลุ่มค่าความหมายเข้าด้วยกันซึ่งจะช่วยเพิ่มความสามารถในการอ่าน
rbaleksandar

30

ฉันมักจะแบ่งเงื่อนไขออกเป็นหลายบรรทัดกล่าวคือ:

if( SomeComplicatedFunctionCall()
 || OtherComplicatedFunctionCall()
  ) {

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

if( one()
 ||( two()> 1337
  &&( three()== 'foo'
   || four()
    )
   )
 || five()!= 3.1415
  ) {

28

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

bool b = SomeComplicatedFunctionCall() || OtherComplicatedFunctionCall();
if (b && some_other_expression) { ... }

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

auto e = []()
{
    return SomeComplicatedFunctionCall() || OtherComplicatedFunctionCall();
};

if (e() && some_other_expression) { ... }

21

1) ใช่คุณไม่มี SCE อีกต่อไป มิฉะนั้นคุณจะมีสิ่งนั้น

bool b1 = SomeComplicatedFunctionCall();
bool b2 = OtherComplicatedFunctionCall();

ทำงานไม่ทางใดก็ทางหนึ่งขึ้นอยู่กับว่ามีifคำสั่งในภายหลัง ซับซ้อนเกินไป

2) นี่เป็นไปตามความคิดเห็น แต่สำหรับนิพจน์ที่ซับซ้อนพอสมควรคุณสามารถทำได้:

if (SomeComplicatedFunctionCall()
    || OtherComplicatedFunctionCall()) {

หากวิธีนี้ซับซ้อนเกินไปวิธีแก้ปัญหาที่ชัดเจนคือสร้างฟังก์ชันที่ประเมินนิพจน์และเรียกมัน


21

คุณยังสามารถใช้:

bool b = someComplicatedStuff();
b = b || otherComplicatedStuff(); // it has to be: b = b || ...;  b |= ...; is bitwise OR and SCE is not working then 

และ SCE จะทำงาน

แต่ก็ไม่สามารถอ่านได้มากไปกว่าตัวอย่าง:

if (
    someComplicatedStuff()
    ||
    otherComplicatedStuff()
   )

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

3
ฉันใช้เฉพาะเจาะจง b = b || otherComplicatedStuff();และ @SargeBorsch ทำการแก้ไขเพื่อลบ SCE ขอบคุณที่สังเกตเห็นฉันเกี่ยวกับการเปลี่ยนแปลงนั้น @Ant
KIIV

14

1) ฉันแพ้ SCE ทุกครั้งหรือไม่? คอมไพเลอร์ได้รับอนุญาตให้ "ปรับให้เหมาะสม" และยังคงให้ SCE อยู่หรือไม่

ฉันไม่คิดว่าการเพิ่มประสิทธิภาพดังกล่าวจะได้รับอนุญาต โดยเฉพาะอย่างยิ่งOtherComplicatedFunctionCall()อาจมีผลข้างเคียงบางอย่าง

2) แนวทางปฏิบัติที่ดีที่สุดในสถานการณ์เช่นนี้คืออะไร? เป็นไปได้หรือไม่ (เมื่อฉันต้องการให้ SCE) มีทั้งหมดที่ฉันต้องการโดยตรงหากและ "จัดรูปแบบให้อ่านได้มากที่สุด"

ฉันชอบที่จะ refactor ให้เป็นฟังก์ชันเดียวหรือตัวแปรเดียวด้วยชื่อที่สื่อความหมาย ซึ่งจะรักษาทั้งการประเมินการลัดวงจรและการอ่านค่า:

bool getSomeResult() {
    return SomeComplicatedFunctionCall() || OtherComplicatedFunctionCall();
}

...

if (getSomeResult())
{
    //do stuff
}

และเมื่อเราใช้งานgetSomeResult()ตามSomeComplicatedFunctionCall()และOtherComplicatedFunctionCall()เราสามารถแยกย่อยซ้ำได้หากยังคงมีความซับซ้อน


2
ฉันชอบสิ่งนี้เพราะคุณสามารถอ่านได้โดยตั้งชื่อที่สื่อความหมายให้กับฟังก์ชัน wrapper (แม้ว่าอาจจะไม่ใช่ getSomeResult) คำตอบอื่น ๆ มากเกินไปก็ไม่ได้เพิ่มมูลค่าอะไรเลย
aw04

9

1) ฉันแพ้ SCE ทุกครั้งหรือไม่? คอมไพเลอร์ได้รับอนุญาตให้ "ปรับให้เหมาะสม" และยังคงให้ SCE อยู่หรือไม่

ไม่คุณไม่ทำ แต่มันใช้แตกต่างกัน:

if (SomeComplicatedFunctionCall() || OtherComplicatedFunctionCall())
{
    // do stuff
}

ที่นี่คอมไพเลอร์จะไม่ทำงานOtherComplicatedFunctionCall()หากSomeComplicatedFunctionCall()ส่งคืนจริง

bool b1 = SomeComplicatedFunctionCall();
bool b2 = OtherComplicatedFunctionCall();

if (b1 || b2)
{
    //do stuff
}

นี่คือฟังก์ชั่นทั้งสองจะทำงานได้เนื่องจากพวกเขาจะต้องเก็บไว้ในและb1 b2ff b1 == trueแล้วb2จะไม่ได้รับการประเมิน (SCE) แต่OtherComplicatedFunctionCall()ได้รับการเรียกใช้แล้ว.

หากb2ไม่มีการใช้งานที่อื่นคอมไพเลอร์อาจฉลาดพอที่จะอินไลน์การเรียกฟังก์ชันภายใน if if the function ไม่มีผลข้างเคียงที่สังเกตได้

2) แนวทางปฏิบัติที่ดีที่สุดในสถานการณ์เช่นนี้คืออะไร? เป็นไปได้หรือไม่ (เมื่อฉันต้องการให้ SCE) มีทั้งหมดที่ฉันต้องการโดยตรงหากและ "จัดรูปแบบให้อ่านได้มากที่สุด"

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


8

ความเป็นไปได้อีกอย่างที่ลัดวงจรและมีเงื่อนไขในที่เดียว:

bool (* conditions [])()= {&a, &b, ...}; // list of conditions
bool conditionsHold = true;
for(int i= 0; i < sizeOf(conditions); i ++){
     if (!conditions[i]()){;
         conditionsHold = false;
         break;
     }
}
//conditionsHold is true if all conditions were met, otherwise false

คุณสามารถใส่ลูปลงในฟังก์ชันและปล่อยให้ฟังก์ชันยอมรับรายการเงื่อนไขและส่งออกค่าบูลีน


1
@ Erbureth ไม่มีพวกเขาไม่ได้ องค์ประกอบของอาร์เรย์เป็นตัวชี้ฟังก์ชันซึ่งจะไม่ถูกเรียกใช้จนกว่าฟังก์ชันจะถูกเรียกใช้ในลูป
Barmar

ขอบคุณ Barmar แต่ฉันได้ทำการแก้ไข Erbureth ถูกต้องก่อนที่จะแก้ไข (ฉันคิดว่าการแก้ไขของฉันจะนำเสนอภาพโดยตรงมากกว่า)
levilime

4

แปลกมาก: คุณกำลังพูดถึงความสามารถในการอ่านเมื่อไม่มีใครพูดถึงการใช้ความคิดเห็นภายในรหัส:

if (somecomplicated_function() || // let me explain what this function does
    someother_function())         // this function does something else
...

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

/*---------------------------*/
/*! interpolates between values
* @param[in] X_axis : contains X-values
* @param[in] Y_axis : contains Y-values
* @param[in] value  : X-value, input to the interpolation process
* @return[out]      : the interpolated value
* @example          : interpolate([2,0],[3,2],2.4) -> 0.8
*/
int interpolate(std::vector<int>& X_axis, std::vector<int>& Y_axis, int value)

เห็นได้ชัดว่าการจัดรูปแบบที่จะใช้สำหรับความคิดเห็นของคุณอาจขึ้นอยู่กับสภาพแวดล้อมการพัฒนาของคุณ (Visual studio, JavaDoc ภายใต้ Eclipse, ... )

เท่าที่เกี่ยวข้องกับ SCE ฉันคิดว่าคุณหมายถึงสิ่งต่อไปนี้:

bool b1;
b1 = somecomplicated_function(); // let me explain what this function does
bool b2 = false;
if (!b1) {                       // SCE : if first function call is already true,
                                 // no need to spend resources executing second function.
  b2 = someother_function();     // this function does something else
}

if (b1 || b2) {
...
}

-7

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


24
จำไว้ว่า "คุณในหกเดือนเวลา" คือ"คนอื่น" อย่างแน่นอนและบางครั้ง "คุณในวันพรุ่งนี้" ก็เป็นได้ ฉันจะไม่ยอมเสียสละความสามารถในการอ่านเพื่อประสิทธิภาพจนกว่าฉันจะมีหลักฐานที่ชัดเจนว่ามีปัญหาด้านประสิทธิภาพ
Martin Bonner สนับสนุน Monica
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.