Chuyển đổi câu lệnh với trả về - độ đúng của mã


91

Giả sử tôi có mã trong C với cấu trúc gần như sau:

switch (something)
{
    case 0:
      return "blah";
      break;

    case 1:
    case 4:
      return "foo";
      break;

    case 2:
    case 3:
      return "bar";
      break;

    default:
      return "foobar";
      break;
}

Bây giờ rõ ràng, các breaks là không cần thiết để mã chạy chính xác, nhưng nó có vẻ giống như thực hành xấu nếu tôi không đặt chúng ở đó cho tôi.

Bạn nghĩ sao? Có tốt không để loại bỏ chúng? Hay bạn sẽ giữ chúng để tăng độ "đúng"?

Câu trả lời:


137

Loại bỏ các breakcâu lệnh. Chúng không cần thiết và có lẽ một số trình biên dịch sẽ đưa ra cảnh báo "Mã không thể truy cập" .


2
Điều tương tự cũng áp dụng với các câu lệnh nhảy điều khiển vô điều kiện khác, như continuehoặc goto- thật thành ngữ khi sử dụng chúng thay cho break.
caf

31

Tôi sẽ thực hiện một cách hoàn toàn khác. Đừng QUAY LẠI ở giữa phương thức / hàm. Thay vào đó, chỉ cần đặt giá trị trả về trong một biến cục bộ và gửi nó ở cuối.

Cá nhân tôi thấy những điều sau đây dễ đọc hơn:

String result = "";

switch (something) {
case 0:
  result = "blah";
  break;
case 1:
  result = "foo";
  break;
}

return result;

24
Triết lý "Một lối thoát" có ý nghĩa trong C, nơi bạn cần đảm bảo mọi thứ được dọn dẹp một cách chính xác. Xét trong C ++, bạn có thể bị loại khỏi hàm bất kỳ lúc nào bởi một ngoại lệ, triết lý một lối thoát không thực sự hữu ích trong C ++.
Billy ONeal

29
Về lý thuyết, đây là một ý tưởng tuyệt vời, nhưng nó thường yêu cầu thêm các khối điều khiển có thể cản trở khả năng đọc.
mikerobi

8
Lý do chính khiến tôi làm theo cách này là trong các chức năng dài hơn, rất dễ bỏ sót véc tơ thoát khi quét qua mã. Tuy nhiên, khi lối ra luôn ở cuối thì bạn sẽ dễ dàng nắm bắt nhanh những gì đang diễn ra hơn.
NotMe

7
Chà, điều đó và việc trả về các khối ở giữa mã chỉ nhắc nhở tôi về việc lạm dụng GOTO.
NotMe

5
@Chris: Trong các chức năng dài hơn, tôi hoàn toàn đồng ý - nhưng điều đó thường nói lên những vấn đề lớn hơn, vì vậy hãy cấu trúc lại nó. Trong nhiều trường hợp, đó là một chức năng tầm thường trả về trên một công tắc, và nó rất rõ ràng điều gì sẽ xảy ra, vì vậy đừng lãng phí sức lực của bộ não để theo dõi một biến phụ.
Stephen

8

Cá nhân tôi sẽ loại bỏ các khoản trả lại và giữ các khoản nghỉ. Tôi sẽ sử dụng câu lệnh switch để gán giá trị cho một biến. Sau đó trả về biến đó sau câu lệnh switch.

Mặc dù đây là một điểm đáng tranh luận, tôi luôn cảm thấy rằng thiết kế và đóng gói tốt có nghĩa là một lối vào và một lối ra. Sẽ dễ dàng hơn nhiều để đảm bảo tính logic và bạn không vô tình bỏ lỡ mã dọn dẹp dựa trên độ phức tạp theo chu kỳ của hàm của bạn.

Một ngoại lệ: Bạn có thể quay lại sớm nếu một tham số xấu được phát hiện ở đầu một hàm - trước khi có được bất kỳ tài nguyên nào.


Có thể tốt cho điều này nói riêng switch, nhưng không nhất thiết phải tốt nhất nói chung. Vâng, giả sử câu lệnh switch bên trong một hàm đứng trước 500 dòng mã khác chỉ có hiệu lực khi một số trường hợp nhất định true. Mục đích của việc thực thi tất cả mã bổ sung đó đối với các trường hợp không cần thực thi nó là gì; không phải là tốt hơn để chỉ returnbên trong switchcho những cases?
Fr0zenFyr

6

Giữ các ngắt - bạn sẽ ít gặp rắc rối hơn nếu / khi bạn chỉnh sửa mã sau đó nếu các ngắt đã có sẵn.

Phải nói rằng, nhiều người (bao gồm cả tôi) coi việc quay trở lại từ giữa một hàm là một hành động xấu. Lý tưởng nhất là một hàm nên có một điểm vào và một điểm ra.


5

Loại bỏ chúng. Việc trả về từ các casecâu lệnh là thành ngữ và ngược lại đó là tiếng ồn "mã không thể truy cập".


5

Tôi sẽ loại bỏ chúng. Trong cuốn sách của tôi, mã chết như vậy nên được coi là lỗi vì nó khiến bạn thực hiện hai lần và tự hỏi bản thân "Làm thế nào tôi có thể thực hiện dòng đó?"


4

Tôi thường viết mã mà không có chúng. IMO, mã chết có xu hướng chỉ ra sự cẩu thả và / hoặc thiếu hiểu biết.

Tất nhiên, tôi cũng sẽ xem xét một số thứ như:

char const *rets[] = {"blah", "foo", "bar"};

return rets[something];

Chỉnh sửa: ngay cả với bài đăng đã chỉnh sửa, ý tưởng chung này có thể hoạt động tốt:

char const *rets[] = { "blah", "foo", "bar", "bar", "foo"};

if ((unsigned)something < 5)
    return rets[something]
return "foobar";

Tại một số thời điểm, đặc biệt nếu các giá trị đầu vào thưa thớt (ví dụ: 1, 100, 1000 và 10000), bạn muốn một mảng thưa thớt thay thế. Bạn có thể triển khai điều đó dưới dạng cây hoặc bản đồ một cách hợp lý (tuy nhiên, tất nhiên, công tắc vẫn hoạt động trong trường hợp này).


Đã chỉnh sửa bài đăng để phản ánh lý do tại sao giải pháp này không hoạt động tốt.
houbysoft

@your chỉnh sửa: Có, nhưng sẽ tốn nhiều bộ nhớ hơn, v.v. IMHO công tắc là cách đơn giản nhất, đó là những gì tôi muốn trong một chức năng nhỏ như vậy (nó chỉ thực hiện công tắc).
houbysoft

3
@houbysoft: hãy xem xét kỹ trước khi bạn kết luận rằng nó sẽ tốn thêm bộ nhớ. Với một trình biên dịch điển hình, việc sử dụng "foo" hai lần (ví dụ) sẽ sử dụng hai con trỏ tới một khối dữ liệu duy nhất, không sao chép dữ liệu. Bạn cũng có thể mong đợi mã ngắn hơn. Trừ khi bạn có nhiều giá trị lặp lại, nó thường sẽ tiết kiệm bộ nhớ tổng thể.
Jerry Coffin

thật. Mặc dù vậy, tôi sẽ vẫn giữ nguyên giải pháp của mình vì danh sách các giá trị rất dài và một công tắc có vẻ đẹp hơn.
houbysoft

1
Thật vậy, câu trả lời thanh lịch và hiệu quả hơn cho một vấn đề như thế này, khi các giá trị của switch liền kề và kiểu dữ liệu đồng nhất, là sử dụng một mảng, tránh một switch hoàn toàn.
Dwayne Robinson,

2

Tôi sẽ nói rằng loại bỏ chúng và xác định một nhánh: mặc định.


Nếu không có mặc định hợp lý thì bạn không nên xác định một mặc định.
Billy ONeal

có một cái, và tôi đã xác định một cái, tôi chỉ không đặt nó vào câu hỏi, đã chỉnh sửa nó ngay bây giờ.
houbysoft

2

Sẽ tốt hơn nếu có một mảng với

arr[0] = "blah"
arr[1] = "foo"
arr[2] = "bar"

và làm gì return arr[something];?

Nếu đó là về thực hành nói chung, bạn nên giữ các breaktuyên bố trong công tắc. Trong trường hợp bạn không cần returntuyên bố trong tương lai, nó sẽ làm giảm cơ hội chuyển sang lần tiếp theo case.


Đã chỉnh sửa bài đăng để phản ánh lý do tại sao giải pháp này không hoạt động tốt.
houbysoft

2

Đối với "tính đúng đắn", một mục nhập duy nhất, các khối thoát đơn lẻ là một ý kiến ​​hay. Ít nhất là khi tôi lấy bằng khoa học máy tính. Vì vậy, tôi có thể sẽ khai báo một biến, gán cho nó trong công tắc và trả về một lần ở cuối hàm


2

Bạn nghĩ sao? Có tốt không để loại bỏ chúng? Hay bạn sẽ giữ chúng để tăng độ "đúng"?

Nó là tốt để loại bỏ chúng. Sử dụng returnchính xác những kịch bản mà breakkhông nên được sử dụng.


1

Hấp dẫn. Sự đồng thuận từ hầu hết các câu trả lời này dường như là breaktuyên bố thừa là sự lộn xộn không cần thiết. Mặt khác, tôi đọc breaktuyên bố trong một công tắc như là 'kết thúc' của một trường hợp. casecác khối không kết thúc breakcó xu hướng nhảy ra khỏi tôi như một sự cố tiềm ẩn mặc dù có lỗi.

Tôi biết rằng đó không phải là cách khi có một returnthay vì a break, nhưng đó là cách mắt tôi 'đọc' các khối trường hợp trong một công tắc, vì vậy cá nhân tôi muốn rằng mỗi khối caseđược ghép nối với một break. Nhưng nhiều trình biên dịch phàn nàn về việc breaksau đó returnlà không cần thiết / không thể truy cập được, và dường như dù sao thì tôi cũng thuộc nhóm thiểu số.

Vì vậy, hãy loại bỏ những breakđiều sau đây a return.

NB: tất cả những điều này là bỏ qua việc vi phạm quy tắc vào / ra một lần có phải là một ý kiến ​​hay hay không. Về khía cạnh đó, tôi có một ý kiến ​​rằng rất tiếc sẽ thay đổi tùy theo hoàn cảnh ...


0

Tôi nói loại bỏ chúng. Nếu mã của bạn khó đọc đến mức bạn cần phải dán các dấu ngắt vào đó 'để ở bên an toàn', bạn nên xem xét lại phong cách viết mã của mình :)

Ngoài ra, tôi luôn thích không kết hợp ngắt và trả về trong câu lệnh switch, mà nên gắn bó với một trong số chúng.


0

Cá nhân tôi có xu hướng đánh mất break s. Có thể một nguồn gốc của thói quen này là từ các thủ tục cửa sổ lập trình cho các ứng dụng Windows:

LRESULT WindowProc (HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
{
    switch (uMsg)
    {
        case WM_SIZE:
            return sizeHandler (...);
        case WM_DESTROY:
            return destroyHandler (...);
        ...
    }

    return DefWindowProc(hwnd, uMsg, wParam, lParam);
}

Cá nhân tôi thấy cách tiếp cận này đơn giản, ngắn gọn và linh hoạt hơn rất nhiều so với việc khai báo một biến trả về được đặt bởi mỗi trình xử lý, sau đó trả lại nó ở cuối. Với cách tiếp cận này, các breaks là thừa và do đó nên sử dụng - chúng không phục vụ mục đích hữu ích (về mặt cú pháp hoặc IMO về mặt trực quan) và chỉ làm cồng kềnh mã.


0

Tôi nghĩ rằng * break * có mục đích. Đó là giữ cho 'ý thức hệ' của lập trình tồn tại. Nếu chúng ta chỉ 'lập trình' mã của mình mà không có sự liên kết logic thì có lẽ bạn có thể đọc được nó ngay bây giờ, nhưng hãy thử vào ngày mai. Hãy thử giải thích điều đó với sếp của bạn. Hãy thử chạy nó trên Windows 3030.

Bleah, ý tưởng rất đơn giản:


Switch ( Algorithm )
{

 case 1:
 {
   Call_911;
   Jump;
 }**break**;
 case 2:
 {
   Call Samantha_28;
   Forget;
 }**break**;
 case 3:
 {
   Call it_a_day;
 }**break**;

Return thinkAboutIt?1:return 0;

void Samantha_28(int oBed)
{
   LONG way_from_right;
   SHORT Forget_is_my_job;
   LONG JMP_is_for_assembly;
   LONG assembly_I_work_for_cops;

   BOOL allOfTheAbove;

   int Elligence_says_anyways_thinkAboutIt_**break**_if_we_code_like_this_we_d_be_monkeys;

}
// Sometimes Programming is supposed to convey the meaning and the essence of the task at hand. It is // there to serve a purpose and to keep it alive. While you are not looking, your program is doing  // its thing. Do you trust it?
// This is how you can...
// ----------
// **Break**; Please, take a **Break**;

/ * Chỉ là một câu hỏi nhỏ. Bạn đã uống bao nhiêu cà phê khi đọc phần trên? IT Đôi khi phá vỡ hệ thống * /


0

Thoát mã tại một điểm. Điều đó cung cấp khả năng đọc mã tốt hơn. Thêm các câu lệnh trả về (Nhiều lần thoát) ở giữa sẽ gây khó khăn cho việc gỡ lỗi.


0

Nếu bạn có loại mã "tra cứu", bạn có thể tự đóng gói mệnh đề switch-case trong một phương thức.

Tôi có một vài trong số này trong hệ thống "sở thích" mà tôi đang phát triển cho vui:

private int basePerCapitaIncomeRaw(int tl) {
    switch (tl) {
        case 0:     return 7500;
        case 1:     return 7800;
        case 2:     return 8100;
        case 3:     return 8400;
        case 4:     return 9600;
        case 5:     return 13000;
        case 6:     return 19000;
        case 7:     return 25000;
        case 8:     return 31000;
        case 9:     return 43000;
        case 10:    return 67000;
        case 11:    return 97000;
        default:    return 130000;
    }
}

(Đúng. Đó là không gian GURPS ...)

Tôi đồng ý với những người khác rằng trong hầu hết các trường hợp, bạn nên tránh nhiều hơn một phương thức trả về trong một phương thức và tôi nhận ra rằng phương thức này có thể đã được triển khai tốt hơn dưới dạng một mảng hoặc một cái gì đó khác. Tôi chỉ thấy switch-case-return là một kết hợp khá dễ dàng với một bảng tra cứu với mối tương quan 1-1 giữa đầu vào và đầu ra, giống như điều ở trên (các trò chơi nhập vai có đầy đủ chúng, tôi chắc chắn rằng chúng tồn tại trong các trò chơi khác "doanh nghiệp" cũng vậy): D

Mặt khác, nếu mệnh đề trường hợp phức tạp hơn hoặc có điều gì đó xảy ra sau câu lệnh switch, tôi sẽ không khuyên bạn nên sử dụng return trong đó, mà nên đặt một biến trong switch, kết thúc nó bằng dấu ngắt và trả về giá trị của biến cuối cùng.

(Mặt ... thứ ba? ... bạn luôn có thể cấu trúc lại một công tắc thành phương thức của riêng nó ... Tôi nghi ngờ nó sẽ có ảnh hưởng đến hiệu suất và tôi sẽ không ngạc nhiên nếu các trình biên dịch hiện đại thậm chí có thể nhận ra nó là một cái gì đó có thể được nội tuyến ...)

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.