Sử dụng thành ngữ các ngoại lệ trong C ++


16

FAQ isocpp.org ngoại lệ tiểu bang

Không sử dụng throw để chỉ ra lỗi mã hóa khi sử dụng hàm. Sử dụng xác nhận hoặc cơ chế khác để gửi quy trình vào trình gỡ lỗi hoặc phá vỡ quy trình và thu thập kết xuất sự cố để nhà phát triển gỡ lỗi.

Mặt khác, thư viện tiêu chuẩn định nghĩa std :: logic_error và tất cả các dẫn xuất của nó, có vẻ như tôi phải xử lý, bên cạnh những thứ khác, lỗi lập trình. Việc chuyển một chuỗi rỗng sang std :: stof (sẽ ném không hợp lệ) không phải là lỗi lập trình? Việc chuyển một chuỗi chứa các ký tự khác nhau hơn '1' / '0' sang std :: bitset (sẽ ném không hợp lệ) không phải là lỗi lập trình? Việc gọi std :: bitset :: được đặt với một chỉ mục không hợp lệ (sẽ ném ra_of_range) không phải là lỗi lập trình? Nếu đây không phải là lỗi, thì lỗi lập trình mà người ta sẽ kiểm tra là gì? Hàm tạo dựa trên chuỗi std :: bitset chỉ tồn tại kể từ C ++ 11, do đó, nó nên được thiết kế với mục đích sử dụng ngoại lệ trong thành ngữ. Mặt khác, tôi đã có người nói với tôi logic_error về cơ bản không nên sử dụng.

Một quy tắc khác xuất hiện thường xuyên với các ngoại lệ là "chỉ sử dụng ngoại lệ trong các trường hợp đặc biệt". Nhưng làm thế nào là một chức năng thư viện được cho là biết hoàn cảnh nào là đặc biệt? Đối với một số chương trình, việc không thể mở tệp có thể là ngoại lệ. Đối với những người khác, không thể phân bổ bộ nhớ có thể không phải là ngoại lệ. Và có 100 trường hợp ở giữa. Không thể tạo ra một ổ cắm? Không thể kết nối hoặc ghi dữ liệu vào ổ cắm hoặc tệp? Không thể phân tích cú pháp đầu vào? Có thể là đặc biệt, có thể không. Bản thân hàm này chắc chắn không thể biết, nó không biết nó được gọi trong bối cảnh nào.

Vì vậy, làm thế nào tôi phải quyết định xem tôi có nên sử dụng ngoại lệ hay không cho một chức năng cụ thể? Dường như với tôi rằng cách duy nhất thực sự nhất quán là sử dụng chúng cho mọi và mọi xử lý lỗi, hoặc không có gì. Và nếu tôi đang sử dụng thư viện tiêu chuẩn, lựa chọn đó được đưa ra cho tôi.


6
Bạn phải đọc mục FAQ đó rất cẩn thận. Nó chỉ áp dụng cho các lỗi mã hóa, không phải dữ liệu không hợp lệ, hủy bỏ một đối tượng null hoặc bất cứ điều gì phải làm với tình trạng thời gian chạy chung. Nói chung, các xác nhận là về việc xác định những điều không bao giờ nên xảy ra. Đối với mọi thứ khác, có ngoại lệ, mã lỗi và vv.
Robert Harvey

1
@RobertHarvey định nghĩa đó vẫn có cùng một vấn đề - nếu một cái gì đó có thể được giải quyết mà không cần sự can thiệp của con người hay không chỉ được biết đến với các lớp trên của một chương trình.
cooky451

1
Bạn đang bị treo lên về pháp lý. Đánh giá những ưu và nhược điểm, và tạo nên tâm trí của riêng bạn. Ngoài ra, đoạn cuối cùng trong câu hỏi của bạn ... Tôi không xem xét điều đó là hiển nhiên. Suy nghĩ của bạn rất đen và trắng, khi sự thật có lẽ gần với một vài sắc thái của màu xám.
Robert Harvey

4
Bạn đã cố gắng thực hiện bất kỳ nghiên cứu trước khi đặt câu hỏi này? Thành ngữ xử lý lỗi C ++ hầu như chắc chắn được thảo luận chi tiết buồn nôn trên web. Một tài liệu tham khảo cho một mục FAQ không làm nghiên cứu tốt. Sau khi bạn thực hiện nghiên cứu của mình, bạn vẫn sẽ phải tự quyết định. Đừng để tôi bắt đầu về việc các trường lập trình của chúng ta rõ ràng đang tạo ra những robot mã hóa mô hình phần mềm không suy nghĩ mà không biết tự suy nghĩ.
Robert Harvey

2
Điều này cho thấy sự tin tưởng vào lý thuyết của tôi rằng một quy tắc như vậy có thể không thực sự tồn tại. Tôi đã mời một số người từ Phòng chờ C ++ để xem họ có thể trả lời câu hỏi của bạn không, mặc dù mỗi lần tôi đến đó, lời khuyên của họ là "Ngừng sử dụng C ++, nó sẽ rán não bạn". Vì vậy, có lời khuyên của họ có nguy cơ của riêng bạn.
Robert Harvey

Câu trả lời:


15

Đầu tiên, tôi cảm thấy bắt buộc phải chỉ ra rằng std::exceptionvà những đứa trẻ của nó đã được thiết kế từ lâu. Có một số phần có thể (gần như chắc chắn) sẽ khác nếu chúng được thiết kế ngày nay.

Đừng hiểu sai ý tôi: có những phần của thiết kế đã hoạt động khá tốt và là những ví dụ khá hay về cách thiết kế hệ thống phân cấp ngoại lệ cho C ++ (ví dụ, thực tế là, không giống như hầu hết các lớp khác, tất cả đều có chung một gốc chung).

Nhìn cụ thể logic_error, chúng ta có một câu hỏi hóc búa. Một mặt, nếu bạn có bất kỳ sự lựa chọn hợp lý nào trong vấn đề này, lời khuyên bạn đã trích dẫn là đúng: nói chung tốt nhất là thất bại nhanh và ồn ào nhất có thể để có thể gỡ lỗi và sửa chữa.

Tuy nhiên, dù tốt hay xấu, thật khó để xác định thư viện chuẩn xung quanh những gì bạn thường làm. Nếu nó xác định những điều này để thoát khỏi chương trình (ví dụ như gọi điện abort()) khi được đưa vào đầu vào không chính xác, đó sẽ là điều luôn xảy ra cho tình huống đó - và thực sự có khá nhiều trường hợp mà điều này có lẽ không thực sự đúng. , ít nhất là trong mã được triển khai.

Điều đó sẽ áp dụng trong mã với các yêu cầu thời gian thực (ít nhất là mềm) và hình phạt tối thiểu cho một đầu ra không chính xác. Ví dụ, hãy xem xét một chương trình trò chuyện. Nếu nó giải mã một số dữ liệu giọng nói và nhận được một số đầu vào không chính xác, rất có thể người dùng sẽ hạnh phúc hơn rất nhiều khi sống với một phần nghìn giây tĩnh trong đầu ra so với một chương trình vừa tắt hoàn toàn. Tương tự như vậy khi thực hiện phát lại video, việc sống với việc tạo ra các giá trị sai cho một số pixel cho một hoặc hai khung hình sẽ dễ chấp nhận hơn so với việc thoát khỏi chương trình vì luồng đầu vào bị hỏng.

Về việc có nên sử dụng ngoại lệ để báo cáo một số loại lỗi nhất định hay không: bạn có đúng - hoạt động tương tự có thể đủ điều kiện là ngoại lệ hay không, tùy thuộc vào cách sử dụng.

Mặt khác, bạn cũng sai - sử dụng thư viện tiêu chuẩn không (nhất thiết) buộc quyết định đó đối với bạn. Trong trường hợp mở tệp, thông thường bạn sẽ sử dụng iostream. Iostreams cũng không chính xác là thiết kế mới nhất và tốt nhất, nhưng trong trường hợp này họ hiểu đúng: họ cho phép bạn đặt chế độ lỗi, do đó bạn có thể kiểm soát việc không mở tệp có dẫn đến ngoại lệ bị ném hay không. Vì vậy, nếu bạn có một tệp thực sự cần thiết cho ứng dụng của mình và không mở được thì có nghĩa là bạn phải thực hiện một số biện pháp khắc phục nghiêm trọng, sau đó bạn có thể yêu cầu nó ném ngoại lệ nếu không mở được tệp đó. Đối với hầu hết các tệp, bạn sẽ cố mở, nếu chúng không tồn tại hoặc không truy cập được, chúng sẽ thất bại (đây là mặc định).

Về cách bạn quyết định: Tôi không nghĩ có một câu trả lời dễ dàng. Dù tốt hay xấu, "hoàn cảnh đặc biệt" không phải lúc nào cũng dễ đo lường. Mặc dù chắc chắn có những trường hợp dễ quyết định phải là [un] ngoại lệ, nhưng có những trường hợp (và có lẽ sẽ luôn luôn như vậy) khi nó mở để đặt câu hỏi hoặc yêu cầu kiến ​​thức về bối cảnh nằm ngoài phạm vi của chức năng. Đối với những trường hợp như vậy, ít nhất có thể đáng để xem xét một thiết kế gần giống với phần này của iostreams, nơi người dùng có thể quyết định liệu thất bại có dẫn đến ngoại lệ bị ném hay không. Ngoài ra, hoàn toàn có thể có hai bộ hàm (hoặc lớp, v.v.) riêng biệt, một trong số đó sẽ đưa ra các ngoại lệ để chỉ ra sự thất bại, còn lại sử dụng các phương tiện khác. Nếu bạn đi theo con đường đó,


9

Hàm tạo dựa trên chuỗi std :: bitset chỉ tồn tại kể từ C ++ 11, do đó, nó nên được thiết kế với mục đích sử dụng ngoại lệ trong thành ngữ. Mặt khác, tôi đã có người nói với tôi logic_error về cơ bản không nên sử dụng.

Bạn có thể không tin điều này, nhưng, tốt, các lập trình viên C ++ khác nhau không đồng ý. Đó là lý do tại sao Câu hỏi thường gặp nói một điều nhưng thư viện tiêu chuẩn không đồng ý.

Câu hỏi thường gặp ủng hộ sự cố vì điều đó sẽ dễ gỡ lỗi hơn. Nếu bạn gặp sự cố và nhận được kết xuất cốt lõi, bạn sẽ có trạng thái chính xác của ứng dụng của mình. Nếu bạn ném một ngoại lệ, bạn sẽ mất rất nhiều trạng thái đó.

Thư viện chuẩn lấy lý thuyết cho phép người viết mã có khả năng bắt và có thể xử lý lỗi là quan trọng hơn sau đó là khả năng sửa lỗi.

Có thể là đặc biệt, có thể không. Bản thân hàm này chắc chắn không thể biết, nó không biết nó được gọi trong bối cảnh nào.

Ý tưởng ở đây là nếu chức năng của bạn không biết liệu tình huống có đặc biệt hay không, thì nó cũng không nên ném ngoại lệ. Nó sẽ trả về một trạng thái lỗi thông qua một số cơ chế khác. Một khi nó đạt đến một điểm trong chương trình nơi nó biết trạng thái là ngoại lệ, thì nó sẽ ném ngoại lệ.

Nhưng điều này có vấn đề riêng của nó. Nếu một trạng thái lỗi được trả về từ một hàm, bạn có thể không nhớ kiểm tra nó và lỗi sẽ tự động chuyển qua. Điều này dẫn đến một số người từ bỏ các ngoại lệ là quy tắc đặc biệt có lợi cho việc ném ngoại lệ cho bất kỳ loại trạng thái lỗi nào.

Nhìn chung, điểm mấu chốt là những người khác nhau có những ý tưởng khác nhau về thời điểm đưa ra ngoại lệ. Bạn sẽ không tìm thấy một ý tưởng gắn kết duy nhất. Mặc dù một số người sẽ khẳng định một cách giáo điều rằng điều này hoặc đó là cách đúng đắn để xử lý các trường hợp ngoại lệ, không có lý thuyết nào được thống nhất.

Bạn có thể ném ngoại lệ:

  1. Không bao giờ
  2. Mọi nơi
  3. Chỉ có lỗi về lập trình
  4. Không bao giờ có lỗi lập trình viên
  5. Chỉ trong những thất bại không thường xuyên (ngoại lệ)

và tìm ai đó trên internet đồng ý với bạn. Bạn sẽ phải áp dụng phong cách phù hợp với bạn.


Có thể đáng lưu ý rằng đề xuất chỉ sử dụng ngoại lệ khi hoàn cảnh thực sự đặc biệt đã được quảng bá rộng rãi bởi những người dạy về ngôn ngữ nơi ngoại lệ có hiệu suất kém. C ++ không phải là một trong những ngôn ngữ đó.
Jules

1
@Jules - bây giờ (hiệu suất) chắc chắn xứng đáng có câu trả lời của riêng bạn, nơi bạn sao lưu yêu cầu của mình. Hiệu suất ngoại lệ C ++ chắc chắn là một vấn đề, có thể nhiều hơn, có lẽ ít hơn những nơi khác, nhưng nêu rõ "C ++ không phải là một trong những ngôn ngữ [nơi ngoại lệ có hiệu suất kém]" chắc chắn là gây tranh cãi.
Martin Ba

1
@MartinBa - so với, hiệu suất ngoại lệ của Java, C ++ là các đơn đặt hàng có cường độ nhanh hơn. Điểm chuẩn cho thấy hiệu suất của việc ném ngoại lệ lên 1 cấp độ chậm hơn khoảng 50 lần so với xử lý giá trị trả về trong C ++, so với chậm hơn 1000 lần trong Java. Lời khuyên được viết cho Java trong trường hợp này không nên được áp dụng cho C ++ mà không cần suy nghĩ thêm vì có nhiều hơn một mức độ khác biệt về hiệu suất giữa hai bên. Có lẽ tôi nên viết "hiệu suất cực kỳ kém" thay vì "hiệu suất kém".
Jules

1
@Jules - cảm ơn vì những con số này. (bất kỳ nguồn nào?) Tôi có thể tin chúng, bởi vì Java (và C #) cần phải nắm bắt dấu vết ngăn xếp, điều này chắc chắn có vẻ như nó có thể thực sự tốn kém. Tôi vẫn nghĩ rằng phản ứng ban đầu của bạn là sai lệch, bởi vì ngay cả sự chậm lại 50 lần cũng khá nặng, tôi nghĩ vậy. trong một ngôn ngữ định hướng hiệu suất như C ++.
Martin Ba

2

Nhiều câu trả lời hay khác đã được viết, tôi chỉ muốn thêm một điểm ngắn.

Câu trả lời truyền thống, đặc biệt là khi Câu hỏi thường gặp về ISO C ++ được viết, chủ yếu so sánh "ngoại lệ C ++" với "mã trả về kiểu C". Một lựa chọn thứ ba, "trở lại một số loại giá trị composite, ví dụ một structhoặc union, hoặc ngày nay, boost::varianthoặc (đề xuất) std::expected, không được xem xét.

Trước C ++ 11, tùy chọn "trả về loại tổng hợp" thường rất yếu. Bởi vì, không có ngữ nghĩa di chuyển nên việc sao chép mọi thứ trong và ngoài cấu trúc có khả năng rất tốn kém. Điều đó cực kỳ quan trọng tại thời điểm đó trong ngôn ngữ để định kiểu mã của bạn đối với RVO để có được hiệu suất tốt nhất. Các ngoại lệ giống như một cách dễ dàng để trả về một kiểu hỗn hợp một cách hiệu quả, khi không thì điều đó sẽ khá khó khăn.

IMO, sau C ++ 11, tùy chọn này "trả lại một liên minh bị phân biệt đối xử", tương tự như thành ngữ Result<T, E>được sử dụng trong Rust ngày nay, nên được sử dụng thường xuyên hơn trong mã C ++. Đôi khi nó thực sự là một phong cách đơn giản và thuận tiện hơn để chỉ ra lỗi. Với các trường hợp ngoại lệ, luôn có khả năng này là các hàm không ném trước đó có thể đột nhiên bắt đầu ném sau một bộ tái cấu trúc và các lập trình viên không luôn luôn ghi chép tốt những thứ đó. Khi lỗi được biểu thị như một phần giá trị trả về trong một liên kết bị phân biệt đối xử, nó sẽ giảm đáng kể khả năng lập trình viên sẽ bỏ qua mã lỗi, đó là lời chỉ trích thông thường về xử lý lỗi kiểu C.

Thường Result<T, E>hoạt động loại như tăng tùy chọn. Bạn có thể kiểm tra, sử dụng operator bool, nếu đó là một giá trị hoặc lỗi. Và sau đó sử dụng say operator *để truy cập giá trị hoặc một số chức năng "get" khác. Thông thường truy cập đó không được kiểm tra, cho tốc độ. Nhưng bạn có thể làm cho nó để trong bản dựng gỡ lỗi, quyền truy cập được kiểm tra và xác nhận đảm bảo rằng thực sự có một giá trị và không có lỗi. Bằng cách này, bất cứ ai không kiểm tra lỗi chính xác sẽ nhận được một khẳng định cứng rắn hơn là một số vấn đề ngấm ngầm hơn.

Một lợi thế nữa là, không giống như các trường hợp ngoại lệ nếu không bắt được, nó chỉ bay lên ngăn xếp một khoảng cách tùy ý, với kiểu này, khi một hàm bắt đầu báo hiệu một lỗi mà trước đó không có, bạn không thể biên dịch trừ khi mã được thay đổi để xử lý nó. Điều này làm cho các vấn đề trở nên to hơn - vấn đề truyền thống về "ngoại lệ chưa được xử lý" trở nên giống như lỗi thời gian biên dịch hơn là lỗi thời gian chạy.

Tôi đã trở thành một fan hâm mộ lớn của phong cách này. Thông thường, ngày nay tôi sử dụng hoặc ngoại lệ này. Nhưng tôi cố gắng hạn chế các ngoại lệ cho các vấn đề lớn. Đối với một cái gì đó như một lỗi phân tích cú pháp, tôi cố gắng trở lại expected<T>ví dụ. Những thứ như std::stoiboost::lexical_castném ngoại lệ C ++ trong trường hợp xảy ra một số vấn đề tương đối nhỏ "chuỗi không thể chuyển đổi thành số" dường như rất tệ đối với tôi hiện nay.


1
std::expectedvẫn là một đề xuất không được chấp nhận, phải không?
Martin Ba

Bạn nói đúng, tôi đoán nó chưa được chấp nhận. Nhưng có một số triển khai nguồn mở trôi nổi xung quanh và tôi đã tự mình thực hiện một vài lần. Nó ít phức tạp hơn so với làm một loại biến thể vì chỉ có hai trạng thái có thể. Các cân nhắc thiết kế chính là như thế nào, bạn muốn giao diện chính xác nào và bạn có muốn nó giống như mong đợi của <t> trong đó đối tượng lỗi thực sự là một exception_ptrhoặc bạn chỉ muốn sử dụng một số loại cấu trúc hoặc một cái gì đó như thế
Chris Beck

Buổi nói chuyện của Andrei Alexandrescu có tại đây: channel9.msdn.com/Shows/Going+Deep/iêu Ông trình bày chi tiết cách xây dựng một lớp như thế này và những điều bạn có thể cân nhắc.
Chris Beck

Đề xuất [[nodiscard]] attributenày sẽ hữu ích cho phương pháp xử lý lỗi này vì nó đảm bảo rằng bạn không bỏ qua kết quả lỗi do tai nạn.
CodeInChaos

- vâng tôi biết cuộc nói chuyện của AA. Tôi thấy thiết kế khá kỳ lạ vì để giải nén nó ( except_ptr) bạn phải ném một ngoại lệ trong nội bộ. Cá nhân tôi nghĩ rằng một công cụ như vậy nên hoạt động hoàn toàn độc lập với thực thi. Chỉ là một nhận xét.
Martin Ba

1

Đây là một vấn đề rất chủ quan, vì nó là một phần của thiết kế. Và bởi vì thiết kế về cơ bản là nghệ thuật, tôi thích thảo luận về những điều này thay vì tranh luận (tôi không nói rằng bạn đang tranh luận).

Đối với tôi, các trường hợp đặc biệt có hai loại - những trường hợp liên quan đến tài nguyên và những trường hợp liên quan đến các hoạt động quan trọng. Những gì có thể được coi là quan trọng phụ thuộc vào vấn đề trong tay, và, trong nhiều trường hợp, theo quan điểm của lập trình viên.

Thất bại trong việc thu thập tài nguyên là một ứng cử viên hàng đầu để đưa ra các ngoại lệ. Tài nguyên có thể là bộ nhớ, tệp, kết nối mạng hoặc bất cứ thứ gì khác dựa trên vấn đề và nền tảng của bạn. Bây giờ, không phát hành một tài nguyên đảm bảo một ngoại lệ? Vâng, điều đó một lần nữa phụ thuộc. Tôi chưa làm gì khi giải phóng bộ nhớ thất bại, vì vậy tôi không chắc về kịch bản đó. Tuy nhiên, việc xóa các tệp như một phần của việc phát hành tài nguyên có thể thất bại và đã thất bại đối với tôi và thất bại đó thường được liên kết với quy trình khác đã giữ cho nó được mở trong một ứng dụng đa quy trình. Tôi đoán các tài nguyên khác có thể thất bại trong quá trình phát hành như một tệp có thể, và nó thường là lỗi thiết kế mang đến vấn đề này, vì vậy sửa nó sẽ tốt hơn ném ngoại lệ.

Sau đó đến cập nhật tài nguyên. Điểm này, đối với tôi ít nhất, liên quan chặt chẽ đến khía cạnh hoạt động quan trọng của ứng dụng. Tưởng tượng một Employeelớp có chức năng UpdateDetails(std::string&)sửa đổi các chi tiết dựa trên chuỗi được phân tách bằng dấu phẩy đã cho. Tương tự như việc giải phóng bộ nhớ bị lỗi, tôi cảm thấy khó tưởng tượng việc gán các giá trị biến thành viên không thành công do thiếu kinh nghiệm của tôi trong các miền như vậy nơi những điều này có thể xảy ra. Tuy nhiên, một chức năng giống UpdateDetailsAndUpdateFile(std::string&)như tên cho thấy sẽ bị lỗi. Đây là những gì tôi gọi là hoạt động quan trọng.

Bây giờ, bạn phải xem liệu cái gọi là hoạt động quan trọng có đảm bảo một ngoại lệ được ném ra không. Ý tôi là, việc cập nhật tệp xảy ra ở cuối, như trong hàm hủy, hay nó chỉ đơn giản là một cuộc gọi hoang tưởng được thực hiện sau mỗi lần cập nhật? Có tồn tại một cơ chế dự phòng viết các đối tượng không được ghi lại thường xuyên không? Những gì tôi đang nói là, bạn phải đánh giá mức độ quan trọng của hoạt động.

Rõ ràng, có nhiều hoạt động quan trọng không gắn liền với tài nguyên. Nếu UpdateDetails()dữ liệu được cung cấp sai, nó sẽ không cập nhật thông tin chi tiết và lỗi phải được biết, vì vậy bạn sẽ ném ngoại lệ vào đây. Nhưng hãy tưởng tượng một chức năng như thế nào GiveRaise(). Bây giờ, nếu nhân viên nói trên may mắn có được một ông chủ tóc nhọn và sẽ không được tăng lương (về mặt lập trình, một số giá trị của biến sẽ ngăn điều này xảy ra), về cơ bản chức năng đã thất bại. Bạn sẽ ném một ngoại lệ ở đây? Điều tôi đang nói là, bạn phải đánh giá sự cần thiết của một ngoại lệ.

Đối với tôi, tính nhất quán là về phương pháp thiết kế của tôi hơn là khả năng sử dụng các lớp học của tôi. Ý tôi là, tôi không nghĩ về 'tất cả các chức năng Nhận phải làm điều này và tất cả các chức năng Cập nhật phải làm điều này' nhưng hãy xem liệu một chức năng cụ thể có hấp dẫn một ý tưởng nào đó trong phương pháp của tôi không. Nhìn bề ngoài, các lớp có thể trông giống như 'sự ngớ ngẩn' nhưng bất cứ khi nào người dùng (chủ yếu là đồng nghiệp từ các đội khác) giận dữ hoặc hỏi về điều đó, tôi sẽ giải thích và họ có vẻ hài lòng.

Tôi thấy nhiều người về cơ bản thay thế các giá trị trả về bằng các ngoại lệ vì họ đang sử dụng C ++ chứ không phải C, và điều đó mang lại một 'sự phân tách xử lý lỗi' tốt, v.v. và thúc giục tôi ngừng 'trộn' các ngôn ngữ, v.v. Tôi thường tránh xa Những người như vậy.


1

Thứ nhất, như những người khác đã nói, mọi thứ không rõ ràng trong C ++, IMHO chủ yếu là vì các yêu cầu và hạn chế được phần nào nhiều thay đổi trong C ++ so với các ngôn ngữ khác, đặc biệt. C # và Java, có các vấn đề ngoại lệ "tương tự".

Tôi sẽ phơi bày trên ví dụ std :: stof:

chuyển một chuỗi rỗng đến std :: stof (sẽ ném không hợp lệ) không phải là lỗi lập trình

Hợp đồng cơ bản , như tôi thấy, của chức năng này là nó cố gắng chuyển đổi đối số của nó thành một số float và bất kỳ lỗi nào không được thực hiện đều được báo cáo bởi một ngoại lệ. Cả hai trường hợp ngoại lệ có thể được bắt nguồn từ logic_errornhưng không phải theo nghĩa của lỗi lập trình viên mà theo nghĩa "đầu vào không thể, bao giờ, được chuyển đổi thành dấu phẩy".

Ở đây, người ta có thể nói a logic_errorđược sử dụng để chỉ ra rằng, với đầu vào (thời gian chạy), luôn cố gắng chuyển đổi nó - nhưng đó là công việc của hàm để xác định điều đó và cho bạn biết (thông qua ngoại lệ).

Lưu ý bên lề: Theo quan điểm đó, một cái runtime_error có thể được xem như một cái gì đó, được đưa ra cùng một đầu vào cho một chức năng, về mặt lý thuyết có thể thành công cho các lần chạy khác nhau. (ví dụ: thao tác tệp, truy cập DB, v.v.)

Lưu ý bên lề: Thư viện regex C ++ đã chọn xuất phát lỗi từ runtime_errormặc dù có những trường hợp có thể được phân loại giống như ở đây (mẫu regex không hợp lệ).

Điều này chỉ cho thấy, IMHO, việc nhóm logic_hoặc runtime_lỗi khá mờ trong C ++ và không thực sự giúp ích nhiều trong trường hợp chung (*) - nếu bạn cần xử lý các lỗi cụ thể, có lẽ bạn cần phải bắt thấp hơn hai.

(*): Đó không phải là để nói rằng một mảnh duy nhất của mã không phải nhất quán, nhưng cho dù bạn ném runtime_hoặc logic_hoặc custom_somethings thực sự không phải là quan trọng, tôi nghĩ.


Để bình luận về cả hai stofbitset:

Cả hai hàm đều lấy các chuỗi làm đối số và trong cả hai trường hợp, đó là:

  • không tầm thường để kiểm tra người gọi xem một chuỗi đã cho có hợp lệ không (ví dụ trường hợp xấu nhất bạn phải sao chép logic hàm; trong trường hợp bitet, không rõ ngay lập tức chuỗi rỗng có hợp lệ hay không, vì vậy hãy để ctor quyết định)
  • Hàm đã có trách nhiệm "phân tích" chuỗi, do đó, nó đã phải xác thực chuỗi, do đó, có nghĩa là nó báo cáo lỗi để "sử dụng" chuỗi một cách thống nhất (và trong cả hai trường hợp, đây là một ngoại lệ) .

quy tắc xuất hiện thường xuyên với các ngoại lệ là "chỉ sử dụng ngoại lệ trong trường hợp đặc biệt". Nhưng làm thế nào là một chức năng thư viện được cho là biết hoàn cảnh nào là đặc biệt?

Tuyên bố này có, IMHO, hai gốc:

Hiệu suất : Nếu một chức năng được gọi trong một đường dẫn quan trọng và trường hợp "ngoại lệ" không phải là ngoại lệ, tức là một lượng đáng kể các đường chuyền sẽ liên quan đến việc ném một ngoại lệ, sau đó trả tiền mỗi lần cho máy móc ngoại lệ không có ý nghĩa , và thể quá chậm.

Địa phương xử lý lỗi : Nếu một hàm được gọi và ngoại lệ được ngay lập tức bị bắt và xử lý, sau đó có rất ít điểm trong ném một ngoại lệ, như việc xử lý lỗi sẽ tiết hơn với các catchso với một if.

Thí dụ:

float readOrDefault;
try {
  readOrDefault = stof(...);
} catch(std::exception&) {
  // discard execption, just use default value
  readOrDefault = 3.14f; // 3.14 is the default value if cannot be read
}

Đây là nơi các chức năng như TryParseso với Parsehoạt động: Một phiên bản khi mã cục bộ dự kiến chuỗi được phân tích cú pháp là hợp lệ, một phiên bản khi mã cục bộ giả định rằng nó thực sự được mong đợi (nghĩa là không đặc biệt) rằng phân tích cú pháp sẽ thất bại.

Thật vậy, stofchỉ là (được định nghĩa là) một trình bao bọc xung quanh strtof, vì vậy nếu bạn không muốn có ngoại lệ, hãy sử dụng gói đó.


Vì vậy, làm thế nào tôi phải quyết định xem tôi có nên sử dụng ngoại lệ hay không cho một chức năng cụ thể?

IMHO, bạn có hai trường hợp:

  • Chức năng như "Thư viện" (thường được sử dụng lại trong các ngữ cảnh khác nhau): Về cơ bản, bạn không thể quyết định. Có thể cung cấp cả hai phiên bản, có thể một phiên bản báo cáo lỗi và một trình bao bọc chuyển đổi lỗi được trả về thành một ngoại lệ.

  • Chức năng "Ứng dụng" (cụ thể cho một đốm mã ứng dụng, có thể được sử dụng lại một số, nhưng bị hạn chế bởi kiểu xử lý lỗi ứng dụng, v.v.): Ở đây, nó thường phải được cắt khá rõ ràng. Nếu (các) đường dẫn mã gọi các hàm xử lý các ngoại lệ theo cách hữu ích và hữu ích, hãy sử dụng các ngoại lệ để báo cáo bất kỳ lỗi nào (nhưng xem bên dưới) . Nếu mã ứng dụng dễ đọc và viết hơn cho kiểu trả về lỗi, bằng mọi cách, hãy sử dụng mã đó.

Tất nhiên, sẽ có những nơi ở giữa - chỉ cần sử dụng những gì cần và ghi nhớ YAGNI.


Cuối cùng, tôi nghĩ rằng tôi nên quay lại câu hỏi FAQ,

Không sử dụng throw để chỉ ra lỗi mã hóa khi sử dụng hàm. Sử dụng xác nhận hoặc cơ chế khác để gửi quy trình vào trình gỡ lỗi hoặc để phá vỡ quy trình ...

Tôi đăng ký này cho tất cả các lỗi rõ ràng cho thấy có gì đó bị rối loạn nghiêm trọng hoặc mã cuộc gọi rõ ràng không biết nó đang làm gì.

Nhưng khi điều này là thích hợp thường có tính ứng dụng cao, do đó hãy xem miền thư viện so với miền ứng dụng.

Điều này rơi vào câu hỏi về việc và làm thế nào để xác nhận các điều kiện tiên quyết trong cuộc gọi , nhưng tôi sẽ không đi sâu vào vấn đề đó, câu trả lời đã quá lâu :-)

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.