Tôi nên đi theo con đường bình thường hay thất bại sớm?


73

Từ cuốn sách Code Complete có đoạn trích dẫn sau:

"Đặt trường hợp bình thường sau ifthay vì sau else"

Điều đó có nghĩa là các trường hợp ngoại lệ / sai lệch so với đường dẫn chuẩn phải được đặt trong elsetrường hợp.

Nhưng Lập trình viên thực dụng dạy chúng ta "sụp đổ sớm" (trang 120).

Tôi nên tuân theo quy tắc nào?


15
@gnat không trùng lặp
BЈовић

16
cả hai không loại trừ lẫn nhau, không có sự mơ hồ.
jwenting

6
Tôi không chắc chắn rằng trích dẫn đầy đủ mã là lời khuyên tuyệt vời như vậy. Có lẽ đó là một nỗ lực để tăng cường khả năng đọc, nhưng chắc chắn có những tình huống mà bạn muốn các trường hợp không phổ biến được đưa ra trước bằng văn bản. Lưu ý rằng đây không phải là một câu trả lời; các câu trả lời hiện có đã giải thích sự nhầm lẫn câu hỏi của bạn bắt nguồn từ tốt.
Eamon Nerbonne

14
Tai nạn sớm, sụp đổ khó khăn! Nếu một trong các ifchi nhánh của bạn trở lại, sử dụng nó đầu tiên. Và tránh elsephần còn lại của mã, bạn đã quay lại nếu điều kiện trước không thành công. Mã dễ đọc hơn, ít thụt lề hơn ...
CodeAngry

5
Có phải tôi nghĩ rằng điều này không liên quan gì đến khả năng đọc, nhưng thay vào đó có lẽ chỉ là một nỗ lực sai lầm trong việc tối ưu hóa cho dự đoán nhánh tĩnh?
Mehrdad

Câu trả lời:


189

"Sụp đổ sớm" không phải là về dòng mã nào xuất hiện sớm hơn bằng văn bản. Nó cho bạn biết để phát hiện lỗi trong bước xử lý sớm nhất có thể , do đó bạn không vô tình đưa ra quyết định và tính toán dựa trên trạng thái đã bị lỗi.

Trong một if/ elsexây dựng, chỉ có một trong các khối được thực thi, do đó không thể nói là tạo thành một bước "sớm hơn" hoặc "sau". Do đó, làm thế nào để đặt hàng chúng là một câu hỏi về khả năng đọc và "thất bại sớm" không đi vào quyết định.


2
Điểm chung của bạn là tuyệt vời nhưng tôi thấy một vấn đề. Nếu bạn có (if / other if / other) thì biểu thức được ước tính trong "other if" được ước tính sau biểu thức trong câu lệnh "if". Điều quan trọng, như bạn chỉ ra, là khả năng đọc so với đơn vị xử lý khái niệm. Chỉ có một khối mã được thực thi nhưng có thể đánh giá nhiều điều kiện.
Encaitar

7
@Encaitar, mức độ chi tiết đó nhỏ hơn nhiều so với những gì thường được dự định khi cụm từ "thất bại sớm" được sử dụng.
riwalk

2
@Encaitar Đó là nghệ thuật lập trình. Khi phải đối mặt với nhiều kiểm tra, ý tưởng là thử một cái có khả năng là đúng trước tiên. Tuy nhiên, thông tin đó có thể không được biết tại thời điểm thiết kế mà ở giai đoạn tối ưu hóa, nhưng hãy cẩn thận với việc tối ưu hóa sớm.
BPugh

Nhận xét công bằng. Đây là một câu trả lời hay và tôi chỉ muốn cố gắng giúp nó tốt hơn để tham khảo trong tương lai
Encaitar

Các ngôn ngữ script như JavaScript, Python, perl, PHP, bash, v.v. là những ngoại lệ vì chúng được giải thích tuyến tính. Trong các if/elsecấu trúc nhỏ có lẽ không thành vấn đề. Nhưng những cái được gọi trong một vòng lặp hoặc có nhiều câu lệnh trong mỗi khối có thể chạy nhanh hơn với điều kiện phổ biến nhất trước tiên.
DocSalvager

116

Nếu elsecâu lệnh của bạn chỉ chứa mã lỗi, rất có thể, nó không nên ở đó.

Thay vì làm điều này:

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

làm cái này

if not file.exists() :
  throw toysOutOfPram

if not validate(file) :
  throw foodAtMummy

# do stuff with file...

Bạn không muốn lồng sâu mã của mình chỉ đơn giản là bao gồm kiểm tra lỗi.

Và, như mọi người khác đã nêu, hai lời khuyên không mâu thuẫn. Một là về thứ tự thực hiện , hai là về thứ tự mã .


4
Đáng để nói rõ rằng lời khuyên nên đặt luồng bình thường trong khối sau ifvà luồng đặc biệt trong khối sau elsekhông áp dụng nếu bạn không có else! Các câu lệnh bảo vệ như thế này là hình thức ưa thích để xử lý các điều kiện lỗi trong hầu hết các kiểu mã hóa.
Jules

+1 đây là một điểm tốt và thực sự đưa ra một câu trả lời cho câu hỏi thực sự về cách đặt hàng công cụ với các điều kiện lỗi.
tro999

Chắc chắn rất nhiều rõ ràng và dễ dàng hơn để duy trì. Đây là cách tôi thích làm điều đó.
Giăng

27

Bạn nên theo dõi cả hai.

Lời khuyên "Sụp đổ sớm" / thất bại sớm có nghĩa là bạn nên kiểm tra các đầu vào của mình để biết các lỗi có thể xảy ra càng sớm càng tốt.
Ví dụ: nếu phương thức của bạn chấp nhận kích thước hoặc số được cho là dương (> 0), thì lời khuyên sớm không có nghĩa là bạn kiểm tra điều kiện đó ngay khi bắt đầu phương thức thay vì chờ thuật toán tạo ra vô nghĩa các kết quả.

Lời khuyên cho việc đặt trường hợp bình thường lên đầu tiên có nghĩa là nếu bạn kiểm tra một tình trạng, thì con đường rất có thể sẽ đến trước. Điều này giúp hiệu năng (vì dự đoán nhánh của bộ xử lý sẽ đúng hơn) và dễ đọc, vì bạn không phải bỏ qua các khối mã khi cố gắng tìm hiểu chức năng đang làm gì trong trường hợp bình thường.
Lời khuyên này không thực sự áp dụng khi bạn kiểm tra điều kiện tiên quyết và ngay lập tức bảo lãnh (bằng cách sử dụng các xác nhận hoặc if (!precondition) throwcấu trúc), vì không có xử lý lỗi nào để bỏ qua trong khi đọc mã.


1
Bạn có thể giải thích về phần dự đoán chi nhánh? Tôi sẽ không mong đợi mã có nhiều khả năng đi đến trường hợp if để chạy nhanh hơn mã có nhiều khả năng đi đến trường hợp khác. Ý tôi là, đó là toàn bộ dự đoán của chi nhánh, phải không?
Roman Reiner

@ user136712: Trong các bộ xử lý hiện đại (nhanh), các hướng dẫn được tìm nạp trước khi lệnh trước đó xử lý xong. Dự đoán nhánh được sử dụng để tăng khả năng các lệnh được tìm nạp trong khi thực hiện nhánh có điều kiện cũng là hướng dẫn đúng để thực thi.
Bart van Ingen Schenau

2
Tôi biết dự đoán chi nhánh là gì. Nếu tôi đọc bài viết của bạn một cách chính xác, bạn nói rằng if(cond){/*more likely code*/}else{/*less likely code*/}chạy nhanh hơn if(!cond){/*less likely code*/}else{/*more likely code*/}vì dự đoán chi nhánh. Tôi nghĩ rằng dự đoán chi nhánh không thiên về tuyên bố ifhoặc elsetuyên bố và chỉ tính đến lịch sử. Vì vậy, nếu elsecó nhiều khả năng xảy ra, nó sẽ có thể dự đoán điều đó cũng tốt. Là giả định này sai?
Roman Reiner

18

Tôi nghĩ rằng @JackAidley đã nói ý chính của nó rồi , nhưng hãy để tôi xây dựng nó như thế này:

không có ngoại lệ (ví dụ C)

Trong dòng mã thông thường, bạn có:

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

Trong trường hợp lỗi ra khỏi trường hợp sớm, mã của bạn đột nhiên đọc:

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

Nếu bạn phát hiện ra mẫu này - một returnkhối else(hoặc thậm chí if), hãy xử lý lại ngay lập tức để mã được đề cập khôngelsekhối:

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

Trong thế giới thực…

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

Điều này tránh việc làm tổ quá sâu hoàn thành trường hợp đột phá sớm (giúp giữ cho tâm trí - và dòng mã - sạch sẽ) không vi phạm các điều có thể xảy ra với ifphần nhiều vì phần đơn giản là không có elsephần nào .

C và dọn dẹp

Lấy cảm hứng từ một câu trả lời cho một câu hỏi tương tự (đã hiểu sai), đây là cách bạn dọn dẹp với C. Bạn có thể sử dụng một hoặc hai điểm thoát ở đó, đây là một cho hai điểm thoát:

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);
}

Bạn có thể thu gọn chúng vào một điểm thoát nếu có ít việc dọn dẹp hơn:

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);
}

Việc sử dụng gotonày là hoàn toàn tốt, nếu bạn có thể đối phó với nó; Lời khuyên để không sử dụng gotolà hướng đến những người chưa thể tự quyết định xem việc sử dụng là tốt, chấp nhận được, xấu, mã spaghetti, hoặc một cái gì khác.

Ngoại lệ

Ở trên nói về các ngôn ngữ không có ngoại lệ, điều mà tôi rất thích bản thân mình (tôi có thể sử dụng xử lý lỗi rõ ràng tốt hơn nhiều và ít gây ngạc nhiên hơn). Để trích dẫn 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 ;)

Nhưng đây là một gợi ý về cách bạn làm tốt nó trong một ngôn ngữ có ngoại lệ và khi bạn muốn sử dụng chúng tốt:

trả lại lỗi khi đối mặt với ngoại lệ

Bạn có thể thay thế hầu hết những người đầu tiên returnbằng cách ném một ngoại lệ. Tuy nhiên , luồng chương trình bình thường của bạn , tức là bất kỳ luồng mã nào trong đó chương trình không gặp phải, ngoại trừ một điều kiện lỗi hoặc somesuch, sẽ không đưa ra bất kỳ ngoại lệ nào.

Điều này có nghĩa rằng…

# 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?"

Giáo dục thì không sao, nhưng

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

… không phải. Về cơ bản, một ngoại lệ không phải là một yếu tố dòng điều khiển . Điều này cũng làm cho các hoạt động trở nên kỳ lạ đối với bạn (các lập trình viên Java ™ luôn nói với chúng tôi rằng những ngoại lệ này là bình thường) và có thể cản trở việc gỡ lỗi (ví dụ: bảo IDE chỉ phá vỡ mọi ngoại lệ). Các trường hợp ngoại lệ thường yêu cầu môi trường thời gian chạy để giải phóng ngăn xếp để tạo ra các dấu vết, v.v. Có lẽ có nhiều lý do hơn để chống lại nó.

Điều này nắm rõ: trong một ngôn ngữ hỗ trợ các ngoại lệ, sử dụng bất cứ điều gì phù hợp với logic và phong cách hiện có và cảm thấy tự nhiên. Nếu viết một cái gì đó từ đầu, hãy đồng ý sớm. Nếu viết một thư viện từ đầu, hãy nghĩ về người tiêu dùng của bạn. (Đừng, bao giờ, sử dụng abort()trong thư viện hoặc)) Nhưng bất cứ điều gì bạn làm, đừng, như một quy tắc, sẽ có một ngoại lệ được ném nếu hoạt động tiếp tục (ít nhiều) bình thường sau nó.

tư vấn chung wrt. Ngoại lệ

Trước tiên, hãy thử sử dụng tất cả các ngoại lệ trong chương trình được toàn bộ nhóm nhà phát triển đồng ý. Về cơ bản, lập kế hoạch cho họ. Không sử dụng chúng trong phong phú. Đôi khi, ngay cả trong C ++, Java ™, Python, trả về lỗi vẫn tốt hơn. Đôi khi không phải vậy; sử dụng chúng với suy nghĩ.


Nói chung, tôi xem lợi nhuận sớm như mùi mã. Thay vào đó, tôi sẽ đưa ra một ngoại lệ, nếu đoạn mã sau sẽ thất bại vì điều kiện tiên quyết không được đáp ứng. Chỉ cần nói
Danman

1
@DanMan: bài viết của tôi được viết bằng C trong tâm trí Tôi thường không sử dụng Ngoại lệ. Nhưng tôi đã mở rộng bài viết với một đề nghị (rất tiếc, nó khá dài). Ngoại lệ; tình cờ, chúng tôi đã có cùng một câu hỏi trong danh sách gửi thư dành cho nhà phát triển nội bộ của công ty ngày hôm qua
mirabilos

Ngoài ra, sử dụng niềng răng ngay cả trên ifs và fors một dòng. Bạn không muốn goto fail;ẩn khác trong nhận dạng.
Bruno Kim

1
@BrunoKim: điều này hoàn toàn phụ thuộc vào phong cách / quy ước mã hóa của dự án bạn làm việc cùng. Tôi làm việc với BSD, nơi điều này được tán thành (sự lộn xộn quang học nhiều hơn và mất không gian theo chiều dọc); tại $ dayjob tuy nhiên tôi đặt chúng như chúng ta đã thỏa thuận (ít khó khăn hơn cho người mới, ít cơ hội sai sót, v.v. như bạn đã nói).
mirabilos

3

Theo tôi, 'Điều kiện bảo vệ' là một trong những cách tốt nhất và dễ nhất để làm cho mã có thể đọc được. Tôi thực sự ghét khi tôi nhìn thấy ifở đầu phương thức và không thấy elsemã vì nó ở ngoài màn hình. Tôi phải cuộn xuống chỉ để xem throw new Exception.

Đặt các kiểm tra ở đầu để người đọc mã không phải nhảy qua phương thức để đọc mà thay vào đó luôn quét nó từ trên xuống dưới.


2

( Câu trả lời của @mirabilos là tuyệt vời, nhưng đây là cách tôi nghĩ về câu hỏi để đi đến kết luận tương tự :)

Tôi đang nghĩ về bản thân mình (hoặc người khác) đọc mã chức năng của tôi sau này. Khi tôi đọc dòng đầu tiên, tôi không thể đưa ra bất kỳ giả định nào về đầu vào của mình (ngoại trừ những điều tôi sẽ không kiểm tra bằng mọi cách). Vì vậy, suy nghĩ của tôi là "Ok, tôi biết tôi sẽ làm mọi thứ với lý lẽ của mình. Nhưng trước tiên hãy 'dọn sạch chúng" - tức là tiêu diệt những con đường kiểm soát mà chúng không theo ý thích của tôi. "Nhưng đồng thời , Tôi không thấy trường hợp bình thường là điều gì đó có điều kiện, tôi muốn nhấn mạnh nó là bình thường.

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

Loại thứ tự điều kiện này phụ thuộc vào sự phê phán của phần mã theo câu hỏi và liệu có mặc định nào có thể được sử dụng hay không.

Nói cách khác:

A. phần quan trọng và không có mặc định => Thất bại sớm

B. phần không quan trọng và mặc định => Sử dụng mặc định trong phần khác

C. giữa các trường hợp => quyết định mỗi trường hợp khi cần thiết


đây chỉ là ý kiến ​​của bạn hay bạn có thể giải thích / sao lưu nó bằng cách nào đó?
gnat

Làm thế nào chính xác là điều này không được sao lưu, vì mỗi tùy chọn giải thích (không có nhiều từ thực sự) tại sao nó được sử dụng?
Nikos M.

tôi không muốn nói điều này, nhưng những câu trả lời trong (của tôi) câu trả lời này nằm ngoài ngữ cảnh :). Đây là câu hỏi mà OP đã hỏi, liệu bạn có câu trả lời thay thế hay không là một vấn đề khác
Nikos M.

Tôi thực sự không thấy lời giải thích ở đây. Nói, nếu ai đó viết một ý kiến ​​khác, như "phần quan trọng và không mặc định => đừng thất bại sớm" , câu trả lời này sẽ giúp người đọc chọn ra hai ý kiến ​​trái ngược nhau như thế nào? Xem xét chỉnh sửa ing nó thành một hình dạng tốt hơn, để phù hợp với Hướng dẫn cách trả lời .
gnat

ok tôi hiểu rồi, đây thực sự có thể là một lời giải thích khác, nhưng ít nhất bạn cũng hiểu từ "phần quan trọng" và "không mặc định" có thể ám chỉ một chiến lược để thất bại sớm và đây thực sự là một sự mở rộng mặc dù là một điều tối giản
Nikos M.
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.