Là `bắt (đào) {ném; } `một thực hành xấu?


74

Mặc dù tôi đồng ý rằng việc bắt ... mà không cần thiết lại là thực sự sai, tuy nhiên tôi tin rằng việc sử dụng các cấu trúc như thế này:

try
{
  // Stuff
}
catch (...)
{
  // Some cleanup
  throw;
}

Được chấp nhận trong trường hợp RAII không được áp dụng . (Làm ơn, đừng hỏi ... không phải mọi người trong công ty tôi đều thích lập trình hướng đối tượng và RAII thường được xem là "công cụ học tập vô dụng" ...)

Đồng nghiệp của tôi nói rằng bạn phải luôn biết những ngoại lệ nào sẽ bị ném và bạn luôn có thể sử dụng các cấu trúc như:

try
{
  // Stuff
}
catch (exception_type1&)
{
  // Some cleanup
  throw;
}
catch (exception_type2&)
{
  // Some cleanup
  throw;
}
catch (exception_type3&)
{
  // Some cleanup
  throw;
}

Có một thực hành tốt được thừa nhận tốt về các tình huống này?


3
@Pubby: Không chắc đây là câu hỏi chính xác. Câu hỏi được liên kết nhiều hơn về "Tôi có nên bắt ..." trong khi câu hỏi của tôi tập trung vào "Tôi nên bắt tốt hơn ...hay <specific exception>trước khi suy nghĩ lại"
vào

53
Rất tiếc phải nói điều đó, nhưng C ++ không có RAII không phải là C ++.
fredoverflow

46
Vì vậy, công nhân chăn bò của bạn bỏ qua các kỹ thuật đã được phát minh để đối phó với một vấn đề nhất định và sau đó ngụy biện về những lựa chọn thay thế kém hơn nên được sử dụng? Xin lỗi để nói, nhưng điều đó có vẻ ngu ngốc , bất kể tôi nhìn nó như thế nào.
sbi

11
"Bắt ... mà không nghĩ lại là thực sự sai" - bạn đã nhầm. Trong main, catch(...) { return EXIT_FAILURE; }cũng có thể đúng trong mã không chạy theo trình gỡ lỗi. Nếu bạn không bắt, thì ngăn xếp có thể không được mở ra. Chỉ khi trình gỡ lỗi của bạn phát hiện các ngoại lệ chưa được phát hiện mà bạn muốn chúng rời đi main.
Steve Jessop

3
... Vì vậy, ngay cả khi đó là "lỗi lập trình", nó không nhất thiết phải tuân theo mà bạn không muốn biết về nó. Dù sao, đồng nghiệp của bạn không phải là chuyên gia phần mềm giỏi, vì vậy như sbi nói rằng rất khó để nói về cách tốt nhất để giải quyết tình huống bắt đầu yếu đuối thường xuyên.
Steve Jessop

Câu trả lời:


196

Đồng nghiệp của tôi nói rằng bạn nên luôn biết những ngoại lệ nào sẽ bị ném [...]

Đồng nghiệp của bạn, tôi ghét nói điều đó, rõ ràng chưa bao giờ làm việc trên các thư viện đa năng.

Làm thế nào trên thế giới một lớp như std::vectorthậm chí giả vờ biết những gì các nhà xây dựng bản sao sẽ ném, trong khi vẫn đảm bảo an toàn ngoại lệ?

Nếu bạn luôn biết callee sẽ làm gì vào thời gian biên dịch, thì đa hình sẽ là vô dụng! Đôi khi, toàn bộ mục tiêu là trừu tượng hóa những gì xảy ra ở cấp độ thấp hơn, vì vậy bạn đặc biệt không muốn biết chuyện gì đang xảy ra!


32
Trên thực tế, ngay cả khi họ biết rằng các ngoại lệ sẽ được ném. Mục đích của việc sao chép mã này là gì? Trừ khi việc xử lý khác nhau, tôi không thấy có điểm nào trong việc liệt kê các ngoại lệ để thể hiện kiến ​​thức của bạn.
Michael Krelin - hacker

3
@ MichaelKrelin-hacker: Điều đó cũng vậy. Ngoài ra, thêm vào đó là thực tế rằng họ không tán thành thông số kỹ thuật ngoại lệ vì liệt kê tất cả các ngoại lệ có thể có trong mã có xu hướng gây ra lỗi sau này ... đó là ý tưởng tồi tệ nhất.
Mehrdad

4
Và điều làm phiền tôi là những gì có thể là nguồn gốc của một thái độ như vậy khi kết hợp với việc xem kỹ thuật hữu ích và tiện lợi là "công cụ học tập vô dụng". Nhưng cũng ...
Michael Krelin - hacker

1
+1, việc liệt kê tất cả các tùy chọn có thể là một công thức tuyệt vời cho một thất bại trong tương lai, tại sao một người nào đó lại chọn làm điều đó hơn ...?
littleadv

2
Câu trả lời tốt đẹp. Có thể có lợi khi đề cập rằng nếu trình biên dịch mà người ta phải hỗ trợ có lỗi trong khu vực X, thì việc sử dụng chức năng từ khu vực X là không thông minh, ít nhất là không sử dụng trực tiếp. Ví dụ, được cung cấp thông tin về công ty, tôi sẽ không ngạc nhiên nếu họ sử dụng Visual C ++ 6.0, có một số lỗi trong khu vực này (như kẻ hủy diệt đối tượng ngoại lệ được gọi hai lần) - một số hậu duệ nhỏ hơn của những con bọ ban đầu đó đã sống sót ngày này, nhưng yêu cầu sắp xếp cẩn thận để biểu hiện.
Alf P. Steinbach

44

Những gì bạn dường như bị bắt gặp là địa ngục cụ thể của một người nào đó đang cố gắng để có bánh của họ và ăn nó.

RAII và các trường hợp ngoại lệ được thiết kế để đi đôi với nhau. RAII là phương tiện mà bạn không phải viết nhiều catch(...)tuyên bố để dọn dẹp. Nó sẽ xảy ra tự động, như một vấn đề tất nhiên. Và các ngoại lệ là cách duy nhất để làm việc với các đối tượng RAII, bởi vì các hàm tạo chỉ có thể thành công hoặc ném (hoặc đặt đối tượng ở trạng thái lỗi, nhưng ai muốn điều đó?).

Một catchtuyên bố có thể làm một trong hai điều: xử lý lỗi hoặc hoàn cảnh đặc biệt hoặc thực hiện công việc dọn dẹp. Đôi khi nó làm cả hai, nhưng mọi catchtuyên bố tồn tại để làm ít nhất một trong số này.

catch(...)không có khả năng thực hiện xử lý ngoại lệ thích hợp. Bạn không biết ngoại lệ là gì; bạn không thể có được thông tin về ngoại lệ. Bạn hoàn toàn không có thông tin nào ngoài thực tế là một ngoại lệ đã bị ném bởi thứ gì đó trong một khối mã nhất định. Điều hợp pháp duy nhất bạn có thể làm trong một khối như vậy là dọn dẹp. Và điều đó có nghĩa là ném lại ngoại lệ vào cuối đợt dọn dẹp.

Những gì RAII mang lại cho bạn liên quan đến việc xử lý ngoại lệ là dọn dẹp miễn phí. Nếu mọi thứ được RAII đóng gói đúng cách, thì mọi thứ sẽ được dọn sạch. Bạn không còn cần phải có catchtuyên bố làm sạch. Trong trường hợp đó, không có lý do để viết một catch(...)tuyên bố.

Vì vậy, tôi đồng ý rằng catch(...)hầu hết là xấu xa ... tạm thời .

Điều khoản đó được sử dụng đúng RAII. Bởi vì không có nó, bạn cần có khả năng dọn dẹp nhất định. Không có xung quanh nó; bạn phải có khả năng làm công việc dọn dẹp. Bạn cần có khả năng đảm bảo rằng việc ném một ngoại lệ sẽ khiến mã ở trạng thái hợp lý. Và catch(...)là một công cụ quan trọng để làm như vậy.

Bạn không thể có cái này mà không có cái kia. Bạn không thể nói rằng cả RAII catch(...) đều xấu. Bạn cần ít nhất một trong số này; mặt khác, bạn không phải là ngoại lệ an toàn.

Tất nhiên, có một cách sử dụng hợp lệ mặc dù hiếm khi catch(...)mà RAII không thể loại bỏ: nhận được một exception_ptrchuyển tiếp cho người khác. Thông thường thông qua một promise/futuregiao diện hoặc tương tự.

Đồng nghiệp của tôi nói rằng bạn phải luôn biết những ngoại lệ nào sẽ bị ném và bạn luôn có thể sử dụng các cấu trúc như:

Đồng nghiệp của bạn là một thằng ngốc (hoặc chỉ là không biết gì khủng khiếp). Điều này cần phải rõ ràng ngay lập tức do có bao nhiêu mã sao chép và dán mà anh ấy đề nghị bạn viết. Việc dọn dẹp cho mỗi câu lệnh khai thác sẽ hoàn toàn giống nhau . Đó là một cơn ác mộng bảo trì, chưa kể đến khả năng đọc.

Nói tóm lại: đây là vấn đề mà RAII được tạo ra để giải quyết (không phải là nó không giải quyết được các vấn đề khác).

Điều khiến tôi bối rối về khái niệm này là nó thường ngược với cách mà hầu hết mọi người cho rằng RAII là xấu. Nói chung, lập luận "RAII rất tệ vì bạn phải sử dụng ngoại lệ để báo hiệu lỗi nhà xây dựng. Nhưng bạn không thể ném ngoại lệ, vì nó không an toàn và bạn sẽ phải có nhiều catchtuyên bố để dọn dẹp mọi thứ." Đó là một đối số bị phá vỡ vì RAII giải quyết vấn đề mà sự thiếu RAII tạo ra.

Nhiều khả năng, anh ta chống lại RAII vì nó che giấu chi tiết. Các cuộc gọi hủy không hiển thị ngay lập tức trên các biến tự động. Vì vậy, bạn nhận được mã được gọi ngầm. Một số lập trình viên thực sự ghét điều đó. Rõ ràng, đến mức họ nghĩ rằng có 3 catchcâu lệnh, tất cả đều làm điều tương tự với mã sao chép và dán là một ý tưởng tốt hơn.


2
Có vẻ như bạn không viết mã cung cấp đảm bảo an toàn ngoại lệ mạnh mẽ . RAII giúp cung cấp đảm bảo cơ bản . Nhưng để cung cấp sự bảo đảm mạnh mẽ, bạn phải hoàn tác một số hành động để hoàn nguyên hệ thống về trạng thái mà nó có trước khi chức năng được gọi. Bảo đảm cơ bản là dọn dẹp , bảo đảm mạnh mẽ là rollback . Làm rollback là chức năng cụ thể. Vì vậy, bạn không thể đặt nó vào "RAII". Và đó là khi khối bắt tất cả trở nên tiện dụng. Nếu bạn viết mã với sự bảo đảm mạnh mẽ, bạn sẽ sử dụng tất cả nhiều.
anton_rh

@anton_rh: Có lẽ, nhưng ngay cả trong những trường hợp đó, các câu lệnh bắt tất cả là công cụ của phương sách cuối cùng . Công cụ ưa thích là làm mọi thứ ném trước khi bạn thay đổi bất kỳ trạng thái nào mà bạn sẽ phải hoàn nguyên ngoại lệ. Rõ ràng bạn không thể thực hiện mọi thứ theo cách đó trong mọi trường hợp, nhưng đó là cách lý tưởng để có được sự đảm bảo ngoại lệ mạnh mẽ.
Nicol Bolas

14

Hai ý kiến, thực sự. Đầu tiên là trong khi ở một thế giới lý tưởng, bạn phải luôn biết những ngoại lệ nào có thể bị ném, trong thực tế, nếu bạn đang làm việc với các thư viện của bên thứ ba hoặc biên dịch với trình biên dịch Microsoft, bạn không nên. Tuy nhiên, nhiều hơn đến điểm; ngay cả khi bạn biết chính xác tất cả các ngoại lệ có thể xảy ra, điều đó có liên quan ở đây không? catch (...)thể hiện ý định tốt hơn nhiều catch ( std::exception const& ), thậm chí cho rằng tất cả các ngoại lệ có thể xuất phát từ std::exception(đó sẽ là trường hợp trong một thế giới lý tưởng). Đối với việc sử dụng một số khối bắt, nếu không có cơ sở chung cho tất cả các ngoại lệ: đó là sự xáo trộn hoàn toàn và một cơn ác mộng bảo trì. Làm thế nào để bạn nhận ra rằng tất cả các hành vi là giống hệt nhau? Và đó là ý định? Và điều gì xảy ra nếu bạn phải thay đổi hành vi (ví dụ sửa lỗi)? Tất cả quá dễ dàng để bỏ lỡ một.


3
Trên thực tế, đồng nghiệp của tôi đã thiết kế lớp ngoại lệ của riêng anh ta không xuất phát std::exceptionvà cố gắng hàng ngày để thực thi việc sử dụng nó trong cơ sở mã của chúng tôi. Tôi đoán là anh ta cố gắng trừng phạt tôi vì đã sử dụng mã và các thư viện bên ngoài mà anh ta không tự viết.
vào

17
@ereOn Nghe có vẻ như đồng nghiệp của bạn đang rất cần được đào tạo. Trong mọi trường hợp, tôi có thể tránh sử dụng các thư viện được viết bởi anh ấy.

2
Các mẫu và biết những ngoại lệ nào sẽ được ném đi cùng nhau như bơ đậu phộng và tắc kè chết. Một cái gì đó đơn giản như std::vector<>có thể ném bất kỳ loại ngoại lệ nào cho khá nhiều lý do.
David Thornley

3
Xin vui lòng cho chúng tôi biết, chính xác làm thế nào để bạn biết ngoại lệ nào sẽ bị ném bởi lỗi sửa lỗi ngày mai xuống cây gọi?
mattnz

11

Tôi nghĩ rằng đồng nghiệp của bạn đã trộn lẫn một số lời khuyên tốt - bạn chỉ nên xử lý các trường hợp ngoại lệ đã biết trong một catchkhối khi bạn không ném lại chúng.

Điều này có nghĩa là:

try
{
  // Stuff
}
catch (...)
{
  // General stuff
}

Là xấu vì nó sẽ âm thầm che giấu bất kỳ lỗi.

Tuy nhiên:

try
{
  // Stuff
}
catch (exception_type_we_can_handle&)
{
  // Deal with the known exception
}

Sẽ ổn thôi - chúng tôi biết những gì chúng tôi đang giải quyết và không cần phải tiếp xúc với mã cuộc gọi.

Tương tự như vậy:

try
{
  // Stuff
}
catch (...)
{
  // Rollback transactions, log errors, etc
  throw;
}

Tốt, thực hành tốt nhất, mã để xử lý các lỗi chung phải là với mã gây ra chúng. Tốt hơn là dựa vào callee để biết rằng một giao dịch cần quay trở lại hoặc bất cứ điều gì.


9

Bất kỳ câu trả lời hoặc không nên được kèm theo một lý do tại sao điều này là như vậy.

Nói rằng đó là sai chỉ đơn giản vì tôi đã được dạy theo cách đó chỉ là sự cuồng tín mù quáng.

Viết giống nhau //Some cleanup; thrownhiều lần, như trong ví dụ của bạn là sai vì sao chép mã và đó là một gánh nặng bảo trì. Viết nó chỉ một lần là tốt hơn.

Viết một catch(...)để im lặng tất cả các ngoại lệ là sai bởi vì bạn chỉ nên xử lý các ngoại lệ mà bạn biết cách xử lý và với ký tự đại diện đó, bạn có thể xử lý nhiều hơn bạn mong đợi và làm như vậy có thể làm im lặng các lỗi quan trọng.

Nhưng nếu bạn suy nghĩ lại sau một catch(...), thì lý do sau không còn được áp dụng nữa, vì bạn không thực sự xử lý ngoại lệ, vì vậy không có lý do gì để điều này được khuyến khích.

Trên thực tế tôi đã thực hiện điều này để đăng nhập vào các chức năng nhạy cảm mà không gặp vấn đề gì:

void DoSomethingImportant()
{
    try
    {
        Log("Going to do something important");
        DoIt();
    }
    catch (std::exception &e)
    {
        Log("Error doing something important: %s", e.what());
        throw;
    }
    catch (...)
    {
        Log("Unexpected error doing something important");
        throw;
    }
    Log("Success doing something important");
}

2
Hãy hy vọng Log(...)không thể ném.
Ded repeatator

2

Tôi thường đồng ý với tâm trạng của các bài đăng ở đây, tôi thực sự không thích mô hình bắt ngoại lệ cụ thể - Tôi nghĩ rằng cú pháp của điều này vẫn còn trong giai đoạn trứng nước và chưa thể đối phó với mã dự phòng.

Nhưng vì mọi người đang nói vậy, tôi sẽ nói với thực tế rằng mặc dù tôi sử dụng chúng một cách tiết kiệm, tôi vẫn thường nhìn vào một trong những câu "bắt (ngoại lệ e)" của mình và nói "Chết tiệt, tôi ước tôi đã gọi đưa ra các ngoại lệ cụ thể vào thời điểm đó "bởi vì khi bạn đến sau, thường rất hay để biết ý định là gì và khách hàng có khả năng sẽ đưa ra cái gì trong nháy mắt.

Tôi không biện minh cho thái độ "Luôn sử dụng x", chỉ nói rằng thỉnh thoảng thật tuyệt khi thấy chúng được liệt kê ra và tôi chắc chắn đó là lý do tại sao một số người nghĩ rằng đó là cách "Đúng".

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.