Tại sao tôi không nên bọc mọi khối trong trò chơi thử


430

Tôi luôn có niềm tin rằng nếu một phương thức có thể đưa ra một ngoại lệ thì thật thiếu thận trọng khi không bảo vệ cuộc gọi này bằng một khối thử có ý nghĩa.

Tôi vừa đăng bài ' Bạn nên LUÔN LUÔN các cuộc gọi có thể thực hiện thử, bắt các khối. 'Cho câu hỏi này và được cho biết đó là' lời khuyên tồi tệ '- tôi muốn hiểu tại sao.


4
Nếu tôi biết một phương pháp sẽ đưa ra một ngoại lệ, tôi sẽ sửa nó ngay từ đầu. Đó là bởi vì tôi không biết mã ở đâu và tại sao mã sẽ đưa ra các ngoại lệ mà tôi bắt chúng tại nguồn (mỗi phương thức - một lần thử bắt chung). Ví dụ, một nhóm đã cho tôi một cơ sở mã với cơ sở dữ liệu trước đó. Nhiều cột bị thiếu và bị bắt chỉ sau khi thêm thử bắt trong lớp SQL. Thứ hai, tôi có thể đăng nhập tên phương thức và thông báo, để gỡ lỗi ngoại tuyến - nếu không tôi không biết nó bắt nguồn từ đâu.
Luân xa

Câu trả lời:


340

Một phương thức chỉ nên bắt một ngoại lệ khi nó có thể xử lý nó theo một cách hợp lý nào đó.

Mặt khác, chuyển nó lên, với hy vọng rằng một phương thức cao hơn trong ngăn xếp cuộc gọi có thể có ý nghĩa của nó.

Như những người khác đã lưu ý, nên có một trình xử lý ngoại lệ chưa được xử lý (ghi nhật ký) ở mức cao nhất của ngăn xếp cuộc gọi để đảm bảo rằng mọi lỗi nghiêm trọng đều được ghi lại.


12
Cũng đáng lưu ý rằng có các chi phí (về mã được tạo) cho trycác khối. Có một cuộc thảo luận tốt trong "C ++ hiệu quả hơn" của Scott Meyers.
Nick Meyer

28
Trên thực tế trycác khối là miễn phí trong bất kỳ trình biên dịch C hiện đại nào, thông tin đó có niên đại là Nick. Tôi cũng không đồng ý về việc có một trình xử lý ngoại lệ cấp cao nhất vì bạn mất thông tin địa phương (địa điểm thực tế nơi hướng dẫn không thành công).
Blindy

31
@Blindly: trình xử lý ngoại lệ hàng đầu không có ở đó để xử lý ngoại lệ, nhưng trên thực tế, hãy hét to lên rằng có một ngoại lệ chưa được xử lý, đưa ra thông báo của nó và kết thúc chương trình một cách duyên dáng (trả lại 1 thay vì gọi đến terminate) . Đó là nhiều hơn một cơ chế an toàn. Ngoài ra, try/catchít nhiều miễn phí khi không có ngoại lệ. Khi có một lần nhân giống, nó sẽ tiêu tốn thời gian mỗi lần nó bị ném và bắt, do đó, một chuỗi try/catchchỉ suy nghĩ lại không tốn kém.
Matthieu M.

17
Tôi không đồng ý bạn nên luôn luôn gặp sự cố. Thiết kế phần mềm hiện đại rất ngăn nắp, vậy tại sao bạn phải trừng phạt phần còn lại của ứng dụng (và quan trọng hơn là người dùng!) Chỉ vì có một lỗi? Phá vỡ điều hoàn toàn cuối cùng bạn muốn làm, ít nhất là cố gắng cung cấp cho người dùng một số cửa sổ mã nhỏ sẽ cho phép họ lưu công việc ngay cả khi phần còn lại của ứng dụng không thể truy cập được.
Kendall Helmstetter Gelner

21
Kendall: Nếu một ngoại lệ xảy ra với trình xử lý cấp cao nhất, ứng dụng của bạn theo định nghĩa ở trạng thái không xác định. Mặc dù trong một số trường hợp cụ thể, có thể có giá trị để lưu giữ dữ liệu của người dùng (phục hồi tài liệu của Word), chương trình không nên ghi đè lên bất kỳ tệp nào hoặc cam kết vào cơ sở dữ liệu.
Hugh Brackett

136

Như Mitch những người khác đã nêu, bạn không nên bắt gặp một ngoại lệ mà bạn không có kế hoạch xử lý theo một cách nào đó. Bạn nên xem xét cách ứng dụng sẽ xử lý một cách có hệ thống các trường hợp ngoại lệ khi bạn thiết kế nó. Điều này thường dẫn đến việc có các lớp xử lý lỗi dựa trên các tóm tắt - ví dụ: bạn xử lý tất cả các lỗi liên quan đến SQL trong mã truy cập dữ liệu của mình để phần ứng dụng tương tác với các đối tượng miền không bị lộ ra thực tế là có là một DB dưới mui xe ở đâu đó.

Có một vài mùi mã liên quan mà bạn chắc chắn muốn tránh ngoài mùi "bắt mọi thứ ở mọi nơi" .

  1. "Catch, log, rethrow" : nếu bạn muốn ghi nhật ký dựa trên phạm vi, sau đó viết một lớp phát ra một câu lệnh log trong hàm hủy của nó khi ngăn xếp không được kiểm soát do một ngoại lệ (ala std::uncaught_exception()). Tất cả những gì bạn cần làm là khai báo một thể hiện đăng nhập trong phạm vi mà bạn quan tâm và, thì đấy, bạn đã đăng nhập và không cần thiết try/ catchlogic.

  2. "Bắt, ném dịch" : điều này thường chỉ ra một vấn đề trừu tượng. Trừ khi bạn đang thực hiện một giải pháp được liên kết trong đó bạn đang dịch một số ngoại lệ cụ thể sang một ngoại lệ chung hơn, bạn có thể có một lớp trừu tượng không cần thiết ... và đừng nói rằng "Tôi có thể cần nó vào ngày mai" .

  3. "bắt, dọn dẹp, nghĩ lại" : đây là một trong những thú cưng của tôi. Nếu bạn thấy nhiều thứ này, thì bạn nên áp dụng các kỹ thuật Khởi tạo tài nguyên là khởi tạo và đặt phần dọn dẹp trong hàm hủy của đối tượng janitor .

Tôi coi mã được xếp chồng try/ catchkhối là mục tiêu tốt để xem xét và tái cấu trúc mã. Nó chỉ ra rằng việc xử lý ngoại lệ không được hiểu rõ hoặc mã đã trở thành một amba và đang cần tái cấu trúc nghiêm trọng.


6
# 1 là mới đối với tôi. +1 cho điều đó. Ngoài ra, tôi muốn lưu ý một ngoại lệ phổ biến đối với # 2, đó là nếu bạn thường xuyên thiết kế thư viện, bạn sẽ muốn dịch các ngoại lệ bên trong sang một cái gì đó được chỉ định bởi giao diện thư viện của bạn để giảm sự ghép nối (đây có thể là ý bạn bởi "giải pháp liên kết", nhưng tôi không quen với thuật ngữ đó).
rmeador

3
Về cơ bản những gì bạn đã nói: parashift.com/c++-faq-lite/exceptions.html#faq-17.13
Bjorn Pollex

1
# 2, trong đó không phải là mùi mã nhưng có ý nghĩa, có thể được tăng cường bằng cách giữ ngoại lệ cũ là một ngoại lệ lồng nhau.
Ded repeatator

1
Về # 1: std :: unsaught_exception () cho bạn biết rằng có một ngoại lệ chưa được khai thác trong chuyến bay, nhưng AFAIK chỉ có một mệnh đề Catch () cho phép bạn xác định ngoại lệ đó thực sự là gì. Vì vậy, trong khi bạn có thể ghi lại sự thật rằng bạn đang thoát khỏi một phạm vi do một ngoại lệ chưa được lưu trữ, chỉ một lần thử / bắt kèm theo cho phép bạn đăng nhập bất kỳ chi tiết nào. Chính xác?
Jeremy

@Jeremy - bạn đúng rồi. Tôi thường đăng nhập các chi tiết ngoại lệ khi tôi xử lý ngoại lệ. Có một dấu vết của các khung can thiệp là rất hữu ích. Bạn thường cần phải ghi nhật ký nhận dạng luồng hoặc một số bối cảnh xác định cũng như để tương quan các dòng nhật ký. Tôi đã sử dụng một Loggerlớp tương tự log4j.Loggerbao gồm ID luồng trong mỗi dòng nhật ký và phát ra cảnh báo trong hàm hủy khi có ngoại lệ hoạt động.
D.Shawley

48

Bởi vì câu hỏi tiếp theo là "Tôi đã bắt gặp một ngoại lệ, tôi phải làm gì tiếp theo?" Bạn sẽ làm gì? Nếu bạn không làm gì - đó là lỗi ẩn và chương trình có thể "không hoạt động" mà không có cơ hội tìm thấy những gì đã xảy ra. Bạn cần hiểu chính xác những gì bạn sẽ làm một khi bạn đã bắt được ngoại lệ và chỉ bắt nếu bạn biết.


29

Bạn không cần phải che tất cả các khối bằng các lần thử vì bắt thử vẫn có thể bắt được các ngoại lệ chưa được xử lý được ném trong các chức năng xuống phía dưới ngăn xếp cuộc gọi. Vì vậy, thay vì có mọi chức năng đều có tính năng thử, bạn có thể có một chức năng ở mức logic cao nhất của ứng dụng của mình. Ví dụ, có thể có một SaveDocument()thói quen cấp cao nhất, trong đó kêu gọi nhiều phương pháp mà gọi các phương thức khác, vv Những tiểu phương pháp này không cần riêng try-sản lượng đánh bắt của họ, bởi vì nếu họ ném, nó vẫn bắt bởi SaveDocument()'s bắt.

Điều này tốt cho ba lý do: nó tiện dụng vì bạn có một nơi duy nhất để báo cáo lỗi: SaveDocument()(các) khối bắt. Không cần lặp lại điều này trong tất cả các phương thức phụ, và dù sao đó cũng là điều bạn muốn: một nơi duy nhất để cung cấp cho người dùng chẩn đoán hữu ích về sự cố.

Hai, lưu bị hủy bất cứ khi nào một ngoại lệ được ném. Với mỗi tiểu phương pháp thử bắt, nếu một ngoại lệ được ném ra, bạn nhận được trong để khối catch của phương pháp đó, lá thực hiện chức năng, và nó mang về qua SaveDocument(). Nếu một cái gì đó đã đi sai, bạn có thể muốn dừng lại ở đó.

Ba, tất cả các phương thức phụ của bạn có thể giả định mọi cuộc gọi thành công . Nếu một cuộc gọi thất bại, thực thi sẽ nhảy đến khối bắt và mã tiếp theo không bao giờ được thực thi. Điều này có thể làm cho mã của bạn sạch hơn nhiều. Ví dụ: đây là mã lỗi:

int ret = SaveFirstSection();

if (ret == FAILED)
{
    /* some diagnostic */
    return;
}

ret = SaveSecondSection();

if (ret == FAILED)
{
    /* some diagnostic */
    return;
}

ret = SaveThirdSection();

if (ret == FAILED)
{
    /* some diagnostic */
    return;
}

Đây là cách có thể được viết với các ngoại lệ:

// these throw if failed, caught in SaveDocument's catch
SaveFirstSection();
SaveSecondSection();
SaveThirdSection();

Bây giờ nó rõ ràng hơn nhiều những gì đang xảy ra.

Lưu ý mã an toàn ngoại lệ có thể khó viết hơn bằng các cách khác: bạn không muốn rò rỉ bất kỳ bộ nhớ nào nếu ném ngoại lệ. Hãy chắc chắn rằng bạn biết về RAII , STL container, con trỏ thông minh và các đối tượng khác giải phóng tài nguyên của chúng trong các hàm hủy, vì các đối tượng luôn bị hủy trước các ngoại lệ.


2
Ví dụ tuyệt vời. Yup, bắt càng cao càng tốt, trong các đơn vị logic, chẳng hạn như xung quanh một số hoạt động 'giao dịch' như tải / lưu / vv. Không có gì trông tệ hơn mã bị cắt xén với các khối lặp đi lặp lại, dư thừa try- catchcố gắng gắn cờ mọi hoán vị hơi khác nhau của một số lỗi với một thông báo hơi khác nhau, trong thực tế, tất cả đều kết thúc giống nhau: thất bại trong giao dịch hoặc chương trình! Nếu xảy ra lỗi ngoại lệ, tôi cá rằng hầu hết người dùng chỉ muốn cứu vãn những gì họ có thể hoặc, ít nhất, được để yên mà không phải xử lý 10 cấp độ thông báo về nó.
gạch dưới

Chỉ muốn nói rằng đây là một trong những cách giải thích "ném sớm, bắt muộn" tốt nhất mà tôi từng đọc: súc tích và các ví dụ minh họa điểm của bạn một cách hoàn hảo. Cảm ơn bạn!
corderazo00

27

Herb Sutter đã viết về vấn đề này ở đây . Để chắc chắn đáng đọc.
Một lời trêu ghẹo:

"Viết mã an toàn ngoại lệ về cơ bản là viết 'thử' và 'bắt' ở đúng chỗ." Bàn luận.

Nói thẳng ra, tuyên bố đó phản ánh một sự hiểu lầm cơ bản về an toàn ngoại lệ. Các ngoại lệ chỉ là một hình thức báo cáo lỗi khác và chúng tôi chắc chắn biết rằng việc viết mã an toàn lỗi không chỉ là nơi kiểm tra mã trả về và xử lý các điều kiện lỗi.

Trên thực tế, hóa ra an toàn ngoại lệ hiếm khi viết về 'thử' và 'bắt' - và càng hiếm khi càng tốt. Ngoài ra, đừng bao giờ quên rằng an toàn ngoại lệ ảnh hưởng đến một phần của thiết kế mã; nó không bao giờ chỉ là một suy nghĩ có thể được trang bị thêm với một vài tuyên bố bắt thêm như thể cho gia vị.


15

Như đã nêu trong các câu trả lời khác, bạn chỉ nên bắt một ngoại lệ nếu bạn có thể thực hiện một số cách xử lý lỗi hợp lý cho nó.

Ví dụ, trong câu hỏi sinh ra câu hỏi của bạn, người hỏi hỏi liệu có an toàn không để bỏ qua các ngoại lệ cho một lexical_casttừ số nguyên đến một chuỗi. Một diễn viên như vậy không bao giờ nên thất bại. Nếu nó thất bại, một cái gì đó đã đi sai trong chương trình. Bạn có thể làm gì để phục hồi trong tình huống đó? Có lẽ tốt nhất là cứ để chương trình chết đi, vì nó ở trạng thái không thể tin tưởng được. Vì vậy, không xử lý ngoại lệ có thể là điều an toàn nhất để làm.


12

Nếu bạn luôn xử lý ngoại lệ ngay lập tức trong trình gọi phương thức có thể ném ngoại lệ, thì ngoại lệ sẽ trở nên vô dụng và tốt hơn hết bạn nên sử dụng mã lỗi.

Toàn bộ điểm ngoại lệ là chúng không cần phải được xử lý trong mọi phương thức trong chuỗi cuộc gọi.


9

Lời khuyên tốt nhất tôi từng nghe là bạn chỉ nên bắt ngoại lệ tại những điểm mà bạn có thể làm điều gì đó hợp lý về điều kiện đặc biệt và "bắt, ghi và phát hành" không phải là một chiến lược tốt (nếu đôi khi không thể tránh khỏi trong các thư viện).


2
@KeithB: Tôi sẽ coi đó là một chiến lược tốt thứ hai. Sẽ tốt hơn nếu bạn có thể viết nhật ký theo cách khác.
David Thornley

1
@KeithB: Đó là một chiến lược "tốt hơn không có gì trong thư viện". "Bắt, đăng nhập, đối phó với nó đúng cách" là tốt hơn nếu có thể. (Vâng, tôi biết không phải lúc nào cũng có thể.)
Donal Fellows

6

Tôi đồng ý với hướng cơ bản của câu hỏi của bạn để xử lý càng nhiều ngoại lệ càng tốt ở mức thấp nhất.

Một số câu trả lời hiện có như "Bạn không cần phải xử lý ngoại lệ. Một số người khác sẽ thực hiện việc đó." Theo kinh nghiệm của tôi, đó là một lý do tồi để không nghĩ về việc xử lý ngoại lệ tại đoạn mã hiện đang được phát triển, làm cho ngoại lệ xử lý vấn đề của người khác hoặc sau này.

Vấn đề đó phát triển mạnh mẽ trong phát triển phân tán, nơi bạn có thể cần gọi một phương thức được thực hiện bởi đồng nghiệp. Và sau đó bạn phải kiểm tra một chuỗi các lệnh gọi phương thức lồng nhau để tìm hiểu lý do tại sao anh ấy / cô ấy ném một số ngoại lệ vào bạn, có thể được xử lý dễ dàng hơn nhiều ở phương thức lồng nhau sâu nhất.


5

Lời khuyên mà giáo sư khoa học máy tính của tôi đã đưa ra cho tôi một lần là: "Chỉ sử dụng các khối Thử và Bắt khi không thể xử lý lỗi bằng các phương tiện tiêu chuẩn".

Ví dụ, anh ấy nói với chúng tôi rằng nếu một chương trình gặp phải một số vấn đề nghiêm trọng ở một nơi không thể làm điều gì đó như:

int f()
{
    // Do stuff

    if (condition == false)
        return -1;
    return 0;
}

int condition = f();

if (f != 0)
{
    // handle error
}

Sau đó, bạn nên sử dụng thử, bắt khối. Mặc dù bạn có thể sử dụng các ngoại lệ để xử lý việc này, nhưng điều đó thường không được khuyến khích vì các ngoại lệ là hiệu suất đắt tiền khôn ngoan.


7
Đó là một chiến lược, nhưng nhiều người khuyên không bao giờ trả lại mã lỗi hoặc trạng thái thất bại / thành công từ các chức năng, thay vào đó sử dụng ngoại lệ. Xử lý lỗi dựa trên ngoại lệ thường dễ đọc hơn mã dựa trên mã lỗi. (Xem ví dụ về câu trả lời của AshleyBrain cho câu hỏi này.) Ngoài ra, hãy luôn nhớ rằng nhiều giáo sư khoa học máy tính có rất ít kinh nghiệm viết mã thực.
Kristopher Johnson

1
-1 @Sagelika Câu trả lời của bạn bao gồm tránh ngoại lệ, do đó không cần thử bắt.
Vicente Botet Escribea

3
@Kristopher: Nhược điểm lớn khác của mã trả lại là rất dễ quên kiểm tra mã trả lại và ngay sau cuộc gọi không nhất thiết là nơi tốt nhất để xử lý sự cố.
David Thornley

ehh, điều đó phụ thuộc, nhưng trong nhiều trường hợp (đặt sang một bên những người ném khi họ thực sự không nên), ngoại lệ là tốt hơn để trả lại mã vì rất nhiều lý do. trong hầu hết các trường hợp, ý tưởng cho rằng các ngoại lệ gây bất lợi cho hiệu suất là một ol 'lớn [cần dẫn nguồn]
underscore_d

3

Nếu bạn muốn kiểm tra kết quả của mọi chức năng, hãy sử dụng mã trả về.

Mục đích của Ngoại lệ là để bạn có thể kiểm tra kết quả LESS thường xuyên. Ý tưởng là tách các điều kiện đặc biệt (bất thường, hiếm hơn) ra khỏi mã thông thường hơn của bạn. Điều này giữ cho mã thông thường sạch hơn và đơn giản hơn - nhưng vẫn có thể xử lý các điều kiện đặc biệt đó.

Trong mã được thiết kế tốt, các hàm sâu hơn có thể ném và các hàm cao hơn có thể bắt được. Nhưng điều quan trọng là nhiều chức năng "ở giữa" sẽ không còn gánh nặng xử lý các điều kiện đặc biệt. Họ chỉ phải "ngoại lệ an toàn", điều đó không có nghĩa là họ phải bắt.


2

Tôi muốn thêm vào cuộc thảo luận này rằng, kể từ C ++ 11 , nó có rất nhiều ý nghĩa, miễn là mọi catchkhối đều rethrowngoại lệ cho đến khi nó có thể / nên được xử lý. Bằng cách này, một backtrace có thể được tạo ra . Do đó tôi tin rằng những ý kiến ​​trước đây đã phần nào lỗi thời.

Sử dụng std::nested_exceptionstd::throw_with_nested

Nó được mô tả trên StackOverflow tại đâyđây là cách để đạt được điều này.

Vì bạn có thể làm điều này với bất kỳ lớp ngoại lệ dẫn xuất nào, bạn có thể thêm rất nhiều thông tin vào một backtrace như vậy! Bạn cũng có thể xem MWE của tôi trên GitHub , nơi một backtrace sẽ trông giống như thế này:

Library API: Exception caught in function 'api_function'
Backtrace:
~/Git/mwe-cpp-exception/src/detail/Library.cpp:17 : library_function failed
~/Git/mwe-cpp-exception/src/detail/Library.cpp:13 : could not open file "nonexistent.txt"

2

Tôi đã được trao "cơ hội" để cứu vãn một số dự án và giám đốc điều hành đã thay thế toàn bộ nhóm phát triển vì ứng dụng có quá nhiều lỗi và người dùng đã mệt mỏi với các vấn đề và chạy xung quanh. Các cơ sở mã này đều có xử lý lỗi tập trung ở cấp ứng dụng như câu trả lời được bình chọn hàng đầu mô tả. Nếu câu trả lời đó là cách thực hành tốt nhất tại sao nó không hoạt động và cho phép nhóm nhà phát triển trước giải quyết vấn đề? Có lẽ đôi khi nó không hoạt động? Các câu trả lời ở trên không đề cập đến việc các nhà phát triển dành bao lâu để khắc phục các sự cố đơn lẻ. Nếu thời gian để giải quyết các vấn đề là số liệu chính, mã thiết bị với các khối try..catch là cách thực hành tốt hơn.

Làm thế nào để nhóm của tôi sửa chữa các vấn đề mà không thay đổi đáng kể giao diện người dùng? Đơn giản, mọi phương thức đều được gắn với try..catch bị chặn và mọi thứ được ghi lại tại điểm không thành công với tên phương thức, các giá trị tham số phương thức được nối vào một chuỗi được gửi cùng với thông báo lỗi, thông báo lỗi, tên ứng dụng, ngày, và phiên bản. Với thông tin này, các nhà phát triển có thể chạy các phân tích về các lỗi để xác định ngoại lệ xảy ra nhiều nhất! Hoặc không gian tên có số lỗi cao nhất. Nó cũng có thể xác nhận rằng một lỗi xảy ra trong một mô-đun được xử lý đúng cách và không phải do nhiều nguyên nhân.

Một lợi ích chuyên nghiệp khác của việc này là các nhà phát triển có thể đặt một điểm dừng trong phương pháp ghi nhật ký lỗi và với một điểm dừng và một lần bấm nút gỡ lỗi "bước ra", họ đang ở phương thức không thể truy cập đầy đủ vào thực tế đối tượng tại điểm thất bại, thuận tiện có sẵn trong cửa sổ ngay lập tức. Nó làm cho nó rất dễ dàng để gỡ lỗi và cho phép kéo thực thi trở lại bắt đầu phương thức để nhân đôi vấn đề để tìm dòng chính xác. Có xử lý ngoại lệ tập trung cho phép nhà phát triển sao chép ngoại lệ trong 30 giây không? Không.

Câu lệnh "Một phương thức chỉ nên bắt một ngoại lệ khi nó có thể xử lý nó theo một cách hợp lý nào đó." Điều này ngụ ý rằng các nhà phát triển có thể dự đoán hoặc sẽ gặp phải mọi lỗi có thể xảy ra trước khi phát hành. Nếu đây là mức cao nhất, trình xử lý ngoại lệ ứng dụng sẽ không cần thiết và sẽ không có thị trường cho Tìm kiếm đàn hồi và đăng nhập.

Cách tiếp cận này cũng cho phép các nhà phát triển tìm và khắc phục các vấn đề không liên tục trong sản xuất! Bạn có muốn gỡ lỗi mà không có trình sửa lỗi trong sản xuất không? Hay bạn muốn nhận cuộc gọi và nhận email từ những người dùng khó chịu? Điều này cho phép bạn khắc phục sự cố trước khi bất kỳ ai khác biết và không phải gửi email, IM hoặc Slack với sự hỗ trợ vì mọi thứ cần thiết để khắc phục sự cố đều ở ngay đó. 95% các vấn đề không bao giờ cần phải sao chép.

Để hoạt động chính xác, nó cần được kết hợp với ghi nhật ký tập trung để có thể nắm bắt không gian tên / mô-đun, tên lớp, phương thức, đầu vào và thông báo lỗi và lưu trữ trong cơ sở dữ liệu để có thể tổng hợp để làm nổi bật phương thức nào thất bại nhất để có thể cố định trước.

Đôi khi, các nhà phát triển chọn cách ném ngoại lệ lên ngăn xếp từ khối bắt nhưng cách tiếp cận này chậm hơn 100 lần so với mã thông thường không ném. Bắt và phát hành với đăng nhập được ưa thích.

Kỹ thuật này đã được sử dụng để nhanh chóng ổn định một ứng dụng thất bại mỗi giờ đối với hầu hết người dùng trong một công ty Fortune 500 được phát triển bởi 12 Dev trong hơn 2 năm. Sử dụng 3000 ngoại lệ khác nhau này đã được xác định, sửa chữa, thử nghiệm và triển khai trong 4 tháng. Điều này tính trung bình cho một sửa chữa trung bình cứ sau 15 phút trong 4 tháng.

Tôi đồng ý rằng không có gì thú vị khi nhập mọi thứ cần thiết vào công cụ mã và tôi không muốn nhìn vào mã lặp đi lặp lại, nhưng thêm 4 dòng mã vào mỗi phương thức là điều đáng giá trong thời gian dài.


1
Bao bọc mọi khối có vẻ như quá mức cần thiết. Nó nhanh chóng làm cho mã của bạn đầy hơi và đau đớn để đọc. Ghi nhật ký ngăn xếp từ một ngoại lệ ở các cấp cao hơn cho bạn biết vấn đề đã xảy ra ở đâu và kết hợp với lỗi đó là đủ thông tin để tiếp tục. Tôi sẽ tò mò về nơi bạn tìm thấy mà không đủ. Chỉ để tôi có thể có được kinh nghiệm của người khác.
dùng441521

1
"Các ngoại lệ chậm hơn 100 đến 1000 lần so với mã thông thường và không bao giờ được rút lại" - tuyên bố đó không đúng trên hầu hết các trình biên dịch và phần cứng hiện đại.
Mitch Wheat

Nó có vẻ như quá mức cần thiết và cần một chút gõ nhưng là cách duy nhất để thực hiện phân tích về các ngoại lệ để tìm và sửa các lỗi lớn nhất trước tiên bao gồm các lỗi không liên tục trong sản xuất. Khối bắt xử lý các lỗi cụ thể nếu được yêu cầu và có một dòng mã duy nhất ghi nhật ký.
dùng2502917

Không, ngoại lệ rất chậm. Thay thế là trả về mã, đối tượng hoặc biến. Xem bài đăng tràn ngăn xếp này ... "ngoại lệ chậm hơn ít nhất 30.000 lần so với mã trả về" stackoverflow.com/questions/891217/iêu
user2502917

1

Bên cạnh những lời khuyên trên, cá nhân tôi sử dụng một số thử + bắt + ném; vì lý do sau:

  1. Ở ranh giới của các lập trình viên khác nhau, tôi sử dụng thử + bắt + ném mã do chính tôi viết, trước khi ngoại lệ được ném cho người gọi do người khác viết, điều này cho tôi cơ hội biết một số tình trạng lỗi xảy ra trong mã của mình và nơi này gần với mã mà ban đầu ném ngoại lệ, càng gần, càng dễ tìm lý do.
  2. Tại ranh giới của các mô-đun, mặc dù các mô-đun khác nhau có thể được viết cùng một người.
  3. Mục đích học tập + Gỡ lỗi, trong trường hợp này tôi sử dụng Catch (...) trong C ++ và Catch (Exception ex) trong C #, đối với C ++, thư viện chuẩn không ném quá nhiều ngoại lệ, vì vậy trường hợp này rất hiếm trong C ++. Nhưng điểm chung trong C #, C # có một thư viện khổng lồ và hệ thống phân cấp ngoại lệ trưởng thành, mã thư viện C # ném vô số ngoại lệ, theo lý thuyết tôi (và bạn) nên biết mọi trường hợp ngoại lệ từ hàm bạn đã gọi và biết lý do / trường hợp tại sao ngoại lệ này bị ném và biết cách xử lý chúng (đi ngang qua hoặc bắt và xử lý tại chỗ) một cách duyên dáng. Thật không may trong thực tế, rất khó để biết mọi thứ về các ngoại lệ tiềm năng trước khi tôi viết một dòng mã. Vì vậy, tôi bắt tất cả và để mã của mình nói to bằng cách đăng nhập (trong môi trường sản phẩm) / hộp thoại khẳng định (trong môi trường phát triển) khi bất kỳ ngoại lệ nào thực sự xảy ra. Bằng cách này, tôi thêm mã xử lý ngoại lệ dần dần. Tôi biết nó không rõ ràng với lời khuyên tốt nhưng thực tế nó hiệu quả với tôi và tôi không biết cách nào tốt hơn cho vấn đề này.

1

Tôi cảm thấy bắt buộc phải thêm một câu trả lời khác mặc dù câu trả lời của Mike Wheat tổng hợp những điểm chính khá tốt. Tôi nghĩ về nó như thế này. Khi bạn có các phương thức làm nhiều việc, bạn sẽ nhân lên độ phức tạp, không thêm nó.

Nói cách khác, một phương pháp được gói trong một lần thử bắt có hai kết quả có thể xảy ra. Bạn có kết quả không ngoại lệ và kết quả ngoại lệ. Khi bạn đang đối phó với rất nhiều phương pháp, điều này sẽ tăng lên theo cấp số nhân.

Theo cấp số nhân bởi vì nếu mỗi phương thức phân nhánh theo hai cách khác nhau thì mỗi lần bạn gọi một phương thức khác, bạn sẽ bình phương số lượng kết quả tiềm năng trước đó. Vào thời điểm bạn đã gọi năm phương pháp, bạn sẽ có tối thiểu 256 kết quả có thể xảy ra. So sánh điều này với việc không thực hiện thử / bắt trong mọi phương thức và bạn chỉ có một đường dẫn để theo.

Về cơ bản là cách tôi nhìn vào nó. Bạn có thể muốn tranh luận rằng bất kỳ loại phân nhánh nào cũng làm điều tương tự nhưng thử / bắt là một trường hợp đặc biệt vì trạng thái của ứng dụng về cơ bản trở nên không xác định.

Vì vậy, trong ngắn hạn, thử / bắt làm cho mã khó hiểu hơn rất nhiều.


-2

Bạn không cần phải che đậy mọi phần của mã bên trong try-catch. Việc sử dụng chính của try-catchkhối là xử lý lỗi và có lỗi / ngoại lệ trong chương trình của bạn. Một số cách sử dụng try-catch-

  1. Bạn có thể sử dụng khối này nơi bạn muốn xử lý một ngoại lệ hoặc đơn giản là bạn có thể nói rằng khối mã được viết có thể ném ngoại lệ.
  2. Nếu bạn muốn loại bỏ các đối tượng của mình ngay sau khi sử dụng chúng, Bạn có thể sử dụng try-catchkhối.

1
"Nếu bạn muốn loại bỏ các đối tượng của mình ngay sau khi sử dụng chúng, Bạn có thể sử dụng khối thử bắt." Bạn có ý định này để thúc đẩy RAII / tối thiểu đối tượng trọn đời? Nếu vậy, tốt, try/ catchlà hoàn thành riêng biệt / trực giao từ đó. Nếu bạn muốn loại bỏ các đối tượng trong một phạm vi nhỏ hơn, bạn chỉ có thể mở một cái mới { Block likeThis; /* <- that object is destroyed here -> */ }- tất nhiên không cần phải bọc cái này vào try/ catchtrừ khi bạn thực sự cần catchbất cứ thứ gì, tất nhiên.
gạch dưới

# 2 - Loại bỏ các đối tượng (được tạo thủ công) trong trường hợp ngoại lệ có vẻ lạ đối với tôi, điều này có thể hữu ích trong một số ngôn ngữ, nhưng nói chung bạn làm điều đó trong một thử / cuối cùng "trong khối thử / ngoại trừ", và không cụ thể trong chính khối ngoại trừ - vì chính đối tượng có thể là nguyên nhân của ngoại lệ ở nơi đầu tiên, và do đó gây ra một ngoại lệ khác và có khả năng xảy ra sự cố.
TS
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.