ฉันควรทำตามเส้นทางปกติหรือล้มเหลว แต่เนิ่นๆ


73

จากหนังสือCode Completeมาพร้อมคำพูดต่อไปนี้:

"ใส่เคสปกติหลังจากifดีกว่าelse"

ซึ่งหมายความว่าควรมีข้อยกเว้น / การเบี่ยงเบนจากเส้นทางมาตรฐานในelseกรณี

แต่โปรแกรมเมอร์ในทางปฏิบัติสอนให้เรา "ผิดพลาดเร็ว" (หน้า 120)

ฉันควรทำตามกฎใด


15
@gnat ไม่ซ้ำกัน
BЈовић

16
ทั้งสองอย่างนั้นไม่ได้เกิดร่วมกันโดยไม่มีความกำกวม
jwenting

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

14
หยุดทำงานเร็วผิดพลาด! หากหนึ่งในifสาขาของคุณกลับมาใช้มันก่อน และหลีกเลี่ยงelseส่วนที่เหลือของรหัสคุณจะได้รับคืนหากเงื่อนไขล่วงหน้าล้มเหลว อ่านรหัสง่ายขึ้นเยื้องน้อย ...
CodeAngry

5
ฉันแค่คนเดียวที่คิดว่าสิ่งนี้ไม่เกี่ยวกับความสามารถในการอ่าน แต่อาจเป็นเพียงความพยายามที่เข้าใจผิดในการเพิ่มประสิทธิภาพการทำนายสาขาแบบคงที่?
Mehrdad

คำตอบ:


189

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

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


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

7
@Encaitar ระดับความละเอียดนั้นเล็กกว่าระดับที่ตั้งใจไว้โดยทั่วไปเมื่อใช้วลี "ล้มเหลวเร็ว"
riwalk

2
@Encaitar นั่นคือศิลปะของการเขียนโปรแกรม เมื่อต้องเผชิญกับการตรวจสอบหลายครั้งความคิดคือการลองสิ่งที่น่าจะเป็นจริงก่อน อย่างไรก็ตามข้อมูลนั้นอาจไม่เป็นที่รู้จักในขณะออกแบบ แต่อยู่ในขั้นตอนการปรับให้เหมาะสม แต่ระวังการปรับให้เหมาะสมก่อนกำหนด
BPugh

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

ภาษาสคริปต์เช่น JavaScript, Python, perl, PHP, bash และอื่น ๆ เป็นข้อยกเว้นเนื่องจากมีการตีความเชิงเส้น ในif/elseโครงสร้างขนาดเล็กมันอาจไม่สำคัญ แต่สิ่งที่เรียกในลูปหรือมีหลายข้อความในแต่ละบล็อกสามารถทำงานได้เร็วขึ้นโดยมีเงื่อนไขที่พบบ่อยที่สุดก่อน
DocSalvager

116

หากใบelseแจ้งยอดของคุณมีรหัสความล้มเหลวเพียงอย่างเดียวส่วนใหญ่จะไม่อยู่ที่นั่น

แทนที่จะทำสิ่งนี้:

if file.exists() :
  if validate(file) :
    # do stuff with file...
  else :
    throw foodAtMummy
else :
  throw toysOutOfPram

ทำเช่นนี้

if not file.exists() :
  throw toysOutOfPram

if not validate(file) :
  throw foodAtMummy

# do stuff with file...

คุณไม่ต้องการที่จะซ้อนโค้ดของคุณอย่างลึกซึ้งเพื่อรวมการตรวจสอบข้อผิดพลาด

และอย่างที่คนอื่นได้กล่าวไปแล้วคำแนะนำทั้งสองนั้นไม่ขัดแย้งกัน หนึ่งเป็นเรื่องเกี่ยวกับการสั่งซื้อการดำเนินการอื่น ๆ ที่เป็นเรื่องเกี่ยวกับการสั่งซื้อรหัส


4
น่าจะชัดเจนว่าคำแนะนำในการทำให้การไหลปกติในบล็อกหลังจากifและการไหลพิเศษในบล็อกหลังจากelseใช้ไม่ได้ถ้าคุณไม่มีelse! ข้อความ Guard เช่นนี้เป็นรูปแบบที่ต้องการสำหรับการจัดการเงื่อนไขข้อผิดพลาดในรูปแบบการเข้ารหัสส่วนใหญ่
จูลส์

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

ชัดเจนและง่ายต่อการดูแลรักษาเป็นอย่างมาก นี่คือวิธีที่ฉันชอบที่จะทำ
จอห์น

27

คุณควรติดตามทั้งคู่

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

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


1
คุณสามารถอธิบายรายละเอียดในส่วนการทำนายสาขาได้หรือไม่? ฉันไม่คาดหวังโค้ดที่น่าจะไปที่ if case เพื่อรันเร็วกว่า code ซึ่งมีแนวโน้มที่จะไปที่ case อื่นมากกว่า ฉันหมายความว่านั่นคือจุดรวมของการทำนายสาขาใช่มั้ย
Roman Reiner

@ user136712: ในตัวประมวลผลที่ทันสมัย ​​(เร็ว) คำแนะนำถูกนำมาใช้ก่อนที่คำสั่งก่อนหน้านี้จะเสร็จสิ้นการประมวลผล การคาดคะเนสาขาใช้เพื่อเพิ่มโอกาสที่คำสั่งที่ดึงมาขณะดำเนินการสาขาที่มีเงื่อนไขเป็นคำแนะนำที่ถูกต้องในการดำเนินการ
Bart van Ingen Schenau

2
ฉันรู้ว่าการทำนายสาขาคืออะไร ถ้าฉันอ่านโพสต์ของคุณอย่างถูกต้องคุณบอกว่ามันif(cond){/*more likely code*/}else{/*less likely code*/}เร็วกว่าif(!cond){/*less likely code*/}else{/*more likely code*/}การคาดคะเนของสาขา ฉันคิดว่าการคาดคะเนสาขานั้นไม่ได้ลำเอียงกับคำแถลงifหรือelseคำแถลงและคำนึงถึงประวัติศาสตร์เท่านั้น ดังนั้นหากelseมีแนวโน้มที่จะเกิดขึ้นมากกว่านี้ก็ควรจะสามารถคาดการณ์ได้เช่นกัน สมมติฐานนี้เป็นเท็จหรือไม่?
Roman Reiner

18

ฉันคิดว่า @JackAidley พูดถึงแก่นสารของมันมาแล้วแต่ให้ฉันกำหนดเช่นนี้:

โดยไม่มีข้อยกเว้น (เช่น C)

ในโฟลว์โค้ดปกติคุณมี:

if (condition) {
    statement;
} else if (less_likely_condition) {
    less_likely_statement;
} else {
    least_likely_statement;
}
more_statements;

ในกรณี“ ผิดพลาดเร็ว” รหัสของคุณก็จะอ่าน:

/* demonstration example, do NOT code like this */
if (condition) {
    statement;
} else {
    error_handling;
    return;
}

หากคุณพบรูปแบบนี้ - returnในelse(หรือแม้กระทั่งif) บล็อกการทำงานซ้ำได้ทันทีเพื่อให้รหัสในคำถามที่ไม่ได้มีการelseบล็อก:

/* only code like this at University, to please structured programming professors */
function foo {
    if (condition) {
        lots_of_statements;
    }
    return;
}

ในโลกแห่งความจริง ...

/* code like this instead */
if (!condition) {
    error_handling;
    return;
}
lots_of_statements;

วิธีนี้จะหลีกเลี่ยงการวางซ้อนลึกเกินไปและตอบสนองกรณี“ แตกออกเร็ว” (ช่วยรักษาความคิด - และการไหลของรหัส - สะอาด) และไม่ละเมิด "ใส่สิ่งที่มีโอกาสมากขึ้นในifส่วน" เพราะไม่มีelseส่วนใด ๆ.

C และทำความสะอาด

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

struct foo *
alloc_and_init(size_t arg1, int arg2)
{
    struct foo *res;

    if (!(res = calloc(sizeof(struct foo), 1)))
        return (NULL);

    if (foo_init1(res, arg1))
        goto err;
    res.arg1_inited = true;
    if (foo_init2(&(res->blah), arg2))
        goto err;
    foo_init_complete(res);
    return (res);

 err:
    /* safe because we use calloc and false == 0 */
    if (res.arg1_inited)
        foo_dispose1(res);
    free(res);
    return (NULL);
}

คุณสามารถยุบออกเป็นจุดเดียวหากมีการล้างข้อมูลน้อยกว่า:

char *
NULL_safe_strdup(const char *arg)
{
    char *res = NULL;

    if (arg == NULL)
        goto out;

    /* imagine more lines here */
    res = strdup(arg);

 out:
    return (res);
}

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

ข้อยกเว้น

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

<igli> exceptions: a truly awful implementation of quite a nice idea.
<igli> just about the worst way you could do something like that, afaic.
<igli> it's like anti-design.
<mirabilos> that too… may I quote you on that?
<igli> sure, tho i doubt anyone will listen ;)

แต่นี่คือคำแนะนำว่าคุณทำได้ดีในภาษาที่มีข้อยกเว้นและเมื่อคุณต้องการใช้งานได้ดี:

เกิดข้อผิดพลาดในการเผชิญกับข้อยกเว้น

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

ซึ่งหมายความว่า ...

# this page is only available to logged-in users
if not isLoggedIn():
    # this is Python 2.5 style; insert your favourite raise/throw here
    raise "eh?"

…ไม่เป็นไร แต่…

/* do not code like this! */
try {
    openFile(xyz, "rw");
} catch (LockedException e) {
    return "file is locked";
}
closeFile(xyz);
return "file is not locked";

… ไม่ใช่. โดยทั่วไปมีข้อยกเว้นไม่ได้เป็นองค์ประกอบการควบคุมการไหล สิ่งนี้ยังทำให้การดำเนินการดูแปลก ๆ ที่คุณ (“ โปรแกรมเมอร์ Java ™เหล่านั้นมักจะบอกเราว่าข้อยกเว้นเหล่านี้เป็นเรื่องปกติ”) และสามารถขัดขวางการแก้ไขข้อบกพร่อง (เช่นบอก IDE ว่าเพียงแค่ผิดพลาด) ข้อยกเว้นมักต้องการสภาพแวดล้อมรันไทม์เพื่อคลายสแต็กเพื่อสร้างการติดตามย้อนกลับ ฯลฯ อาจมีเหตุผลมากกว่านั้น

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

คำแนะนำทั่วไป ข้อยกเว้น

พยายามใช้การยกเว้นทั้งหมดที่อยู่ในโปรแกรมที่ตกลงกันโดยทั้งทีม dev ก่อน โดยทั่วไปวางแผนพวกเขา อย่าใช้พวกเขาในความอุดมสมบูรณ์ บางครั้งแม้ใน C ++, Java ™, Python การส่งคืนข้อผิดพลาดจะดีกว่า บางครั้งมันไม่ได้; ใช้พวกเขาด้วยความคิด


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

1
@DanMan: บทความของฉันเขียนด้วย C ในใจ…ฉันปกติไม่ได้ใช้ข้อยกเว้น แต่ฉันได้ขยายบทความด้วยข้อเสนอแนะ (อุ๊ปส์มันค่อนข้างยาว) คำแนะนำ ข้อยกเว้น; บังเอิญเรามีคำถามเดียวกันมากในรายการทาง บริษัท ฯ dev ภายในเมื่อวานนี้ ...
mirabilos

และใช้เครื่องหมายปีกกาแม้ในบรรทัดเดียว ifs and fors คุณไม่ต้องการให้คนอื่นgoto fail;ซ่อนอยู่ในการระบุตัวตน
บรูโน่คิม

1
@BrunoKim: สิ่งนี้ขึ้นอยู่กับระเบียบการสไตล์ / การเข้ารหัสของโครงการที่คุณทำงานด้วย ฉันทำงานกับ BSD ซึ่งเป็นสิ่งที่ขมวดคิ้ว (ความยุ่งเหยิงแก้วนำแสงมากขึ้นและการสูญเสียพื้นที่แนวตั้ง); ที่ $ dayjob แต่ฉันวางไว้ตามที่เราตกลงกันไว้ (ยากสำหรับมือใหม่, โอกาสที่จะเกิดข้อผิดพลาดน้อยลง, อย่างที่คุณพูด)
mirabilos

3

ในความเห็นของฉัน 'Guard Condition' เป็นหนึ่งในวิธีที่ดีที่สุดและง่ายที่สุดในการอ่านโค้ด ฉันเกลียดเมื่อฉันเห็นifที่จุดเริ่มต้นของวิธีการและไม่เห็นelseรหัสเพราะมันอยู่นอกหน้าจอ throw new Exceptionฉันต้องเลื่อนลงไปเพียงเพื่อดู

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


2

(@mirabilos ' คำตอบนั้นยอดเยี่ยม แต่นี่คือวิธีที่ฉันคิดเกี่ยวกับคำถามเพื่อให้ได้ข้อสรุปเดียวกัน :)

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

int foo(int* bar, int baz) {

   if (bar == NULL) /* I don't like you, leave me alone */;
   if (baz < 0) /* go away */;

   /* there, now I can do the work I came into this function to do,
      and I can safely forget about those if's above and make all 
      the assumptions I like. */

   /* etc. */
}

-3

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

ในคำอื่น ๆ :

A. ส่วนที่สำคัญและไม่มีค่าเริ่มต้น => Fail Early

B. ส่วนที่ไม่สำคัญและค่าเริ่มต้น => ใช้ค่าเริ่มต้นในส่วนอื่น

C. ในระหว่างกรณี => ตัดสินใจต่อกรณีตามความจำเป็น


นี่เป็นเพียงความคิดเห็นของคุณหรือคุณสามารถอธิบาย / สำรองอย่างใด?
ริ้น

สิ่งนี้ไม่ได้ถูกสำรองไว้อย่างแน่นอนเนื่องจากตัวเลือกแต่ละตัวอธิบาย (ไม่มีคำหลายคำ) ทำไมจึงใช้
Nikos M.

ฉันไม่อยากจะพูดแบบนี้ แต่ downvotes ใน (ของฉัน) คำตอบนี้อยู่นอกบริบท :) นี่เป็นคำถามที่ OP ถามว่าคุณมีคำตอบอื่นหรือไม่เป็นเรื่องอื่นโดยสิ้นเชิง
Nikos M.

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

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