ควรใช้ "อย่างอื่น" ในสถานการณ์ที่การควบคุมการไหลทำให้ซ้ำซ้อนหรือไม่?


18

บางครั้งฉันสะดุดกับรหัสที่คล้ายกับตัวอย่างต่อไปนี้ (สิ่งที่ฟังก์ชั่นนี้ทำไม่ตรงกับขอบเขตของคำถามนี้):

function doSomething(value) {
  if (check1(value)) {
    return -1;
  }
  else if (check2(value)) {
    return value;
  }
  else {
    return false;
  }
}

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

function doSomething(value) {
  if (check1(value)) {
    return -1;
  }
  if (check2(value)) {
    return value;
  }
  return false;
}

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

ข้อใดข้างต้นที่เหมาะกับวิธีการเข้ารหัสที่ดีมากกว่า มีข้อเสียเปรียบกับวิธีการอย่างใดอย่างหนึ่งเกี่ยวกับการอ่านรหัส?

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


14
ปัญหาelseคือน้อยกว่าปัญหาที่ใหญ่กว่าคือคุณเห็นได้ชัดว่าคืนข้อมูลสองชนิดจากฟังก์ชันเดียวซึ่งทำให้ API ของฟังก์ชันไม่แน่นอน
Andy

3
E_CLEARLY_NOTADUPE
kojiro

3
@DavidPacker: อย่างน้อยสองตัว อาจเป็นสาม -1คือตัวเลขfalseเป็นบูลีนและvalueไม่ได้ระบุไว้ที่นี่เพื่อให้เป็นวัตถุประเภทใดก็ได้
Yay295

1
@ Yay295 ฉันหวังว่าvalueจริง ๆ แล้วเป็นจำนวนเต็ม ถ้ามันสามารถเป็นอะไรก็ได้
Andy

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

คำตอบ:


23

ฉันชอบที่ไม่มีelseและนี่คือเหตุผล:

function doSomething(value) {
  //if (check1(value)) {
  //  return -1;
  //}
  if (check2(value)) {
    return value;
  }
  return false;
}

เพราะการทำเช่นนั้นไม่ได้ทำลายอะไรเลยไม่ควรทำ

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


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

@makyen คุณเพิ่มจุดดี หนึ่งฉันพยายามที่จะไม่สนใจ ฉันยังเชื่อว่าสิ่งที่ควรทำหลังจากifเพื่อให้ชัดเจนว่าส่วนของรหัสนี้เสร็จแล้วและโครงสร้างเสร็จ เมื่อต้องการทำสิ่งนี้ฉันทำสิ่งที่ฉันไม่ได้ทำที่นี่ (เพราะฉันไม่ต้องการเบี่ยงเบนความสนใจจากประเด็นหลักของ OP โดยทำการเปลี่ยนแปลงอื่น ๆ ) ฉันทำสิ่งที่ง่ายที่สุดที่ฉันสามารถทำได้เพื่อให้ได้ทุกสิ่งโดยไม่ต้องหันเหความสนใจ ฉันเพิ่มบรรทัดว่าง
candied_orange

27

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

if (x < 0) {
    return false;
} else if (x > 0) {
    return true;
} else {
    throw new IllegalArgumentException();
}

ที่นี่คุณชัดเจนขึ้นอยู่กับมูลค่าของxทั้งสามกรณี หากคุณไม่ใช้สิ่งelseนี้สิ่งนี้จะชัดเจนน้อยลง

หากกรณีของคุณไม่ได้ขึ้นอยู่กับแต่ละอื่น ๆ โดยตรงละเว้น:

if (x < 0) {
    return false;
}
if (y < 0) {
    return true;
}
return false;

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


6

ฉันชอบตัวเลือกที่สอง (แยกifsไม่มีelse ifและต้นreturn) แต่นั่นคือตราบใดที่บล็อกรหัสสั้น

หากการบล็อกโค้ดใช้เวลานานจะดีกว่าelse ifเพราะเหตุใดคุณจะไม่มีความเข้าใจที่ชัดเจนเกี่ยวกับการออกหลายจุดที่รหัสมี

ตัวอย่างเช่น:

function doSomething(value) {
  if (check1(value)) {
    // ...
    // imagine 200 lines of code
    // ...
    return -1;
  }
  if (check2(value)) {
    // ...
    // imagine 150 lines of code
    // ...
    return value;
  }
  return false;
}

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

function doSomething(value) {
  retVal=0;
  if (check1(value)) {
    // ...
    // imagine 200 lines of code
    // ...
    retVal=-1;
  } else if (check2(value)) {
    // ...
    // imagne 150 lines of code
    retVal=value;
  }
  return retVal;
}

แต่เป็นหนึ่งควรพยายามให้ฟังก์ชั่นจะสั้นฉันจะบอกว่ามันสั้นและออกก่อนในรหัสตัวอย่างแรกของคุณ


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

1
อาร์กิวเมนต์ที่อยากรู้อยากเห็น returnอยู่ภายใน 3 บรรทัดifดังนั้นจึงไม่แตกต่างกับelse ifแม้กระทั่งหลังจากโค้ด 200 บรรทัด รูปแบบผลตอบแทนที่เดียวที่คุณแนะนำที่นี่เป็นประเพณีของคและภาษาอื่น ๆ ที่ไม่ได้รายงานข้อผิดพลาดมีข้อยกเว้น จุดประสงค์คือการสร้างที่เดียวเพื่อทำความสะอาดทรัพยากร ภาษาที่เป็นข้อยกเว้นใช้finallyบล็อกสำหรับสิ่งนั้น ฉันคิดว่าสิ่งนี้จะสร้างที่สำหรับวางจุดพักถ้าดีบักเกอร์ของคุณไม่อนุญาตให้คุณใส่หนึ่งในฟังก์ชั่นปิดวงเล็บปีกกา
candied_orange

4
ฉันไม่ชอบมอบหมายให้retValทุก หากคุณสามารถออกจากฟังก์ชั่นอย่างปลอดภัยได้ทันทีโดยไม่ต้องกำหนดค่า safe ที่จะกลับสู่ตัวแปรของการออกครั้งเดียวจากฟังก์ชั่น เมื่อคุณใช้จุดส่งคืนเดียวในฟังก์ชั่นที่ยาวมาก ๆ ฉันไม่เชื่อใจโปรแกรมเมอร์คนอื่นและจบลงด้วยการมองหาส่วนที่เหลือของฟังก์ชั่นสำหรับการแก้ไขค่าตอบแทนอื่น ๆ เช่นกัน ล้มเหลวเร็วและกลับเร็ว แต่ก็มีกฎสองข้อที่ฉันใช้อยู่
Andy

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

2
ฉันอยู่กับ DavidPacker ที่นี่ สไตล์การส่งคืนเดียวบังคับให้เราศึกษาคะแนนที่ได้รับมอบหมายทั้งหมดรวมถึงโค้ดหลังจากคะแนนที่ได้รับมอบหมาย การศึกษาจุดออกของรูปแบบการคืนต้นจะดีกว่าสำหรับฉันไม่ว่าจะยาวหรือสั้น ตราบใดที่ภาษาอนุญาตให้บล็อกในที่สุด
candied_orange

4

ฉันใช้ทั้งในสถานการณ์ที่แตกต่างกัน ในการตรวจสอบความถูกต้องฉันไม่ต้องทำอย่างอื่น ในโฟลว์การควบคุมฉันใช้อย่างอื่น

bool doWork(string name) {
  if(name.empty()) {
    return false;
  }

  //...do work
  return true;
}

VS

bool doWork(int value) {
  if(value % 2 == 0) {
    doWorkWithEvenNumber(value);
  }
  else {
    doWorkWithOddNumber(value);
  }
}

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


0

สถานการณ์เดียวที่ฉันเคยเห็นมันมีความสำคัญคือรหัสเช่นนี้:

List<?> toList() {
    if (left != null && right != null) {
        Arrays.asList(merge(left, right));
    }
    if (left != null) {
        return Arrays.asList(left);
    }
    if (right != null) {
        return Arrays.asList(right);
    }
    return Arrays.asList();
}

เห็นได้ชัดว่ามีบางอย่างผิดปกติ ปัญหาคือมันไม่ชัดเจนว่าreturnควรจะเพิ่มหรือArrays.asListลบออก คุณไม่สามารถแก้ไขได้หากไม่ตรวจสอบวิธีการที่เกี่ยวข้องให้ลึกซึ้งยิ่งขึ้น คุณสามารถหลีกเลี่ยงความคลุมเครือนี้ได้โดยการใช้บล็อก if-else แบบละเอียดถี่ถ้วน นี้:

List<?> toList() {
    if (left != null && right != null) {
        Arrays.asList(merge(left, right));
    } else if (left != null) {
        return Arrays.asList(left);
    } else if (right != null) {
        return Arrays.asList(right);
    } else {
        return Arrays.asList();
    }
}

จะไม่รวบรวม (ในภาษาการตรวจสอบแบบคงที่) ifจนกว่าคุณจะเพิ่มผลตอบแทนที่ชัดเจนในครั้งแรก

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

List<?> toList() {
    if (left == null || right == null) {
        throw new IllegalStateException()
    }
    // ...
    // long method
    // ...
}

-2

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

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

var result;
if(check1(value)
{
    result = -1;
}
else if(check2(value))
{
    result = value;
}
else 
{
    result = 0;
}
return result;

ในความเป็นจริงให้ไปไกลยิ่งขึ้น

var result = 0;
var c1 = check1(value);
var c2 = check2(value);

if(c1)
{
    result = -1;
}
else if(c2)
{
    result = value;
}

return result;

ยุติธรรมมากพอที่คุณจะโต้กลับสำหรับการตรวจสอบอินพุตล่วงหน้า เพียงหลีกเลี่ยงการห่อทั้งกลุ่มถ้าตรรกะของคุณอยู่ใน if


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

a: ไม่มีการระบุภาษาในคำถาม b: ฉันคิดว่าคุณผิดในเรื่องนี้ มีเหตุผลที่ดีมากมายสำหรับการกลับมาครั้งเดียว มันลดความซับซ้อนและเพิ่มความสามารถในการอ่าน
Ewan

1
บางครั้งมันลดความซับซ้อนบางครั้งก็เพิ่ม ดูwiki.c2.com/?ArrowAntiPattern
Robert Johnson

ไม่ฉันคิดว่ามันช่วยลดความซับซ้อนของวัฏจักรตลอดเวลาดูตัวอย่างที่สองของฉัน check2 ทำงานเสมอ
Ewan

มีผลตอบแทนเร็วคือการเพิ่มประสิทธิภาพที่ซับซ้อนรหัสโดยการย้ายรหัสบางอย่างเข้าเงื่อนไข
Ewan

-3

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

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


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