Tại sao các thông số kỹ thuật ngoại lệ xấu?


50

Trở lại trường khoảng 10+ năm trước, họ đã dạy bạn sử dụng các công cụ xác định ngoại lệ. Vì nền tảng của tôi là một trong số những lập trình viên Torvaldish C, những người ngoan cố tránh C ++ trừ khi bị ép buộc, tôi chỉ kết thúc bằng C ++ một cách rời rạc, và khi tôi vẫn sử dụng các công cụ xác định ngoại lệ vì đó là những gì tôi được dạy.

Tuy nhiên, phần lớn các lập trình viên C ++ dường như cau mày với các nhà đầu cơ ngoại lệ. Tôi đã đọc các cuộc tranh luận và các lập luận từ các bậc thầy C ++ khác nhau, như thế này . Theo như tôi hiểu, nó hiểu rõ ba điều:

  1. Các nhà xác định ngoại lệ sử dụng một hệ thống loại không phù hợp với phần còn lại của ngôn ngữ ("hệ thống loại bóng").
  2. Nếu chức năng của bạn với một công cụ xác định ngoại lệ ném bất cứ thứ gì khác ngoại trừ những gì bạn đã chỉ định, chương trình sẽ bị chấm dứt theo những cách không mong muốn.
  3. Thông số ngoại lệ sẽ bị xóa trong tiêu chuẩn C ++ sắp tới.

Tôi đang thiếu một cái gì đó ở đây hay đây là tất cả những lý do?

Ý kiến ​​của riêng tôi:

Về 1): Vậy thì sao. C ++ có lẽ là ngôn ngữ lập trình không nhất quán nhất từng được tạo ra, theo cú pháp. Chúng tôi có các macro, goto / nhãn, nhóm (tích trữ?) Của hành vi không xác định- / không xác định- / xác định thực hiện, các loại số nguyên được xác định kém, tất cả các quy tắc quảng cáo kiểu ẩn, từ khóa trường hợp đặc biệt như bạn bè, tự động , đăng ký, rõ ràng ... Và như vậy. Ai đó có lẽ có thể viết một vài cuốn sách dày về sự kỳ lạ trong C / C ++. Vậy tại sao mọi người phản ứng chống lại sự không nhất quán đặc biệt này, đó là một lỗ hổng nhỏ so với nhiều tính năng nguy hiểm khác của ngôn ngữ?

Về 2): Đó không phải là trách nhiệm của riêng tôi sao? Có rất nhiều cách khác để tôi có thể viết một lỗi nghiêm trọng trong C ++, tại sao trường hợp cụ thể này lại tồi tệ hơn? Thay vì viết throw(int)và sau đó ném Crash_t, tôi cũng có thể tuyên bố rằng hàm của tôi trả về một con trỏ cho int, sau đó tạo một typecast hoang dã, rõ ràng và trả về một con trỏ cho Crash_t. Tinh thần của C / C ++ luôn là để lại phần lớn trách nhiệm cho lập trình viên.

Thế còn lợi thế thì sao? Rõ ràng nhất là nếu hàm của bạn cố gắng ném rõ ràng bất kỳ loại nào ngoài những gì bạn đã chỉ định, trình biên dịch sẽ cung cấp cho bạn một lỗi. Tôi tin rằng tiêu chuẩn là rõ ràng về điều này (?). Lỗi sẽ chỉ xảy ra khi chức năng của bạn gọi các chức năng khác mà lần lượt ném sai loại.

Đến từ một thế giới của các chương trình C nhúng, xác định, tôi chắc chắn muốn biết chính xác những gì một chức năng sẽ ném vào tôi. Nếu có một cái gì đó trong ngôn ngữ hỗ trợ điều đó, tại sao không sử dụng nó? Các lựa chọn thay thế dường như là:

void func() throw(Egg_t);

void func(); // This function throws an Egg_t

Tôi nghĩ rằng có một cơ hội lớn mà người gọi bỏ qua / quên thực hiện thử bắt trong trường hợp thứ hai, ít hơn trong trường hợp đầu tiên.

Theo tôi hiểu, nếu một trong hai hình thức này quyết định đột nhiên ném một loại ngoại lệ khác, chương trình sẽ bị sập. Trong trường hợp đầu tiên bởi vì nó không được phép đưa ra một ngoại lệ khác, trong trường hợp thứ hai bởi vì không ai mong muốn nó sẽ ném một Tây Ban NhaInquisition_t và do đó biểu thức đó không bị bắt ở nơi đáng lẽ phải có.

Trong trường hợp sau này, để có một số biện pháp cuối cùng (...) ở mức cao nhất của chương trình, có vẻ không tốt hơn một sự cố chương trình: "Này, ở đâu đó trong chương trình của bạn có một ngoại lệ kỳ lạ, chưa được xử lý . ". Bạn không thể khôi phục chương trình một khi bạn ở xa nơi bị ném ngoại lệ, điều duy nhất bạn có thể làm là thoát khỏi chương trình.

Và theo quan điểm của người dùng, họ không thể quan tâm hơn nếu họ nhận được một hộp thông báo xấu từ HĐH có nội dung "Chương trình bị chấm dứt. Blablabla tại địa chỉ 0x12345" hoặc hộp thông báo xấu từ chương trình của bạn có nội dung "Ngoại lệ chưa được xử lý: lớp của tôi. func.s Something ". Lỗi vẫn còn đó.


Với tiêu chuẩn C ++ sắp tới, tôi không còn lựa chọn nào khác ngoài việc từ bỏ các nhà đầu cơ ngoại lệ. Nhưng tôi thà nghe một số lập luận chắc chắn tại sao họ xấu, thay vì "Đức Pháp vương đã tuyên bố điều đó và vì thế nó là như vậy". Có lẽ có nhiều tranh luận chống lại họ hơn những gì tôi liệt kê, hoặc có lẽ có nhiều điều với họ hơn tôi nhận ra?


30
Tôi cố gắng hạ thấp điều này thành "rant, ngụy trang thành câu hỏi". Bạn hỏi ba điểm hợp lệ về E.specs, nhưng tại sao bạn lại làm phiền chúng tôi với C ++ của bạn - rất khó chịu và tôi rất thích C-ram-ram?
Martin Ba

10
@Martin: Bởi vì tôi muốn chỉ ra rằng tôi vừa thiên vị vừa không cập nhật tất cả các chi tiết, nhưng tôi cũng coi ngôn ngữ này với đôi mắt ngây thơ và / hoặc chưa bị hủy hoại. Nhưng C C ++ cũng là những ngôn ngữ không hoàn hảo, do đó, một lỗ hổng ít nhiều không thực sự quan trọng. Bài đăng thực sự tồi tệ hơn nhiều trước khi tôi chỉnh sửa nó :) Các lập luận chống lại các nhà đầu cơ ngoại lệ cũng khá chủ quan và do đó thật khó để thảo luận về chúng mà không tự mình chủ quan.

Tiếng Tây Ban Nha_t! Vui nhộn! Cá nhân tôi đã bị ấn tượng bởi việc sử dụng con trỏ khung stack để đưa ra các ngoại lệ và có vẻ như nó có thể làm cho mã sạch hơn nhiều. Tuy nhiên, tôi chưa bao giờ thực sự viết mã với các ngoại lệ. Gọi tôi là lỗi thời, nhưng giá trị trả về chỉ hoạt động tốt với tôi.
Shahbaz

@Shahbaz Như người ta có thể đọc giữa các dòng, tôi cũng khá cổ hủ, nhưng tôi vẫn chưa bao giờ thực sự đặt câu hỏi liệu ngoại lệ của họ là tốt hay xấu.

2
Thì quá khứ némném , không ném . Đó là một động từ mạnh mẽ.
TRiG

Câu trả lời:


48

Thông số ngoại lệ là xấu vì chúng được thực thi yếu và do đó không thực sự đạt được nhiều và chúng cũng xấu vì chúng buộc thời gian chạy để kiểm tra các ngoại lệ không mong muốn để chúng có thể chấm dứt (), thay vì gọi UB , điều này có thể lãng phí một lượng đáng kể hiệu suất.

Vì vậy, tóm lại, thông số kỹ thuật ngoại lệ không được thực thi đủ mạnh trong ngôn ngữ để thực sự tạo mã bất kỳ an toàn hơn và thực hiện chúng theo quy định là một sự tiêu tốn hiệu suất lớn.


2
Nhưng kiểm tra thời gian chạy không phải là lỗi của thông số ngoại lệ như vậy, mà là bất kỳ phần nào của tiêu chuẩn quy định rằng cần phải chấm dứt nếu ném loại không chính xác. Sẽ không thông minh hơn khi chỉ cần loại bỏ yêu cầu dẫn đến kiểm tra động này và để mọi thứ được trình biên dịch đánh giá tĩnh? Nghe có vẻ như RTTI là thủ phạm thực sự ở đây, hay ...?

7
@Lundin: không thể xác định tĩnh tất cả các ngoại lệ có thể được ném ra từ một hàm trong C ++, vì ngôn ngữ cho phép thay đổi tùy ý và không xác định đối với luồng điều khiển khi chạy (ví dụ: thông qua con trỏ hàm, phương thức ảo và như thế). Việc thực hiện kiểm tra ngoại lệ thời gian biên dịch tĩnh như Java sẽ yêu cầu các thay đổi cơ bản đối với ngôn ngữ và rất nhiều khả năng tương thích C bị vô hiệu hóa.

3
@OrbWeaver: Tôi nghĩ rằng kiểm tra tĩnh là hoàn toàn có thể - thông số ngoại lệ sẽ là một phần của đặc tả của hàm (như giá trị trả về) và con trỏ hàm, ghi đè ảo, v.v. sẽ phải có thông số kỹ thuật tương thích. Sự phản đối chính là đó là một tính năng hoàn toàn hoặc không có gì - các chức năng với thông số ngoại lệ tĩnh không thể gọi các chức năng cũ. (Gọi các hàm C sẽ ổn, vì chúng không thể ném bất cứ thứ gì).
Mike Seymour

2
@Lundin: Thông số ngoại lệ chưa được xóa, chỉ không dùng nữa. Mã kế thừa sử dụng chúng vẫn sẽ hợp lệ.
Mike Seymour

1
@Lundin: Các phiên bản cũ của C ++ với thông số ngoại lệ hoàn toàn tương thích. Họ sẽ không biên dịch hay chạy bất ngờ nếu mã hợp lệ trước đó.
DeadMG

18

Một lý do không ai sử dụng chúng là vì giả định chính của bạn là sai:

"[Ưu điểm] rõ ràng nhất là nếu hàm của bạn cố gắng ném một cách rõ ràng bất kỳ loại nào khác với những gì bạn đã chỉ định, trình biên dịch sẽ báo lỗi cho bạn."

struct foo {};
struct bar {};

struct test
{
    void baz() throw(foo)
    {
        throw bar();
    }
};

int main()
{
    test x;
    try { x.baz(); } catch(bar &b) {}
}

Chương trình này biên dịch không có lỗi hoặc cảnh báo .

Hơn nữa, mặc dù ngoại lệ đã bị bắt , chương trình vẫn chấm dứt.


Chỉnh sửa: để trả lời một điểm trong câu hỏi của bạn, bắt (...) (hoặc tốt hơn, bắt (std :: exellect &) hoặc lớp cơ sở khác, sau đó bắt (...)) vẫn hữu ích ngay cả khi bạn không biết chính xác những gì đã sai.

Hãy xem xét rằng người dùng của bạn đã nhấn nút menu để "lưu". Trình xử lý cho nút menu mời ứng dụng lưu. Điều này có thể thất bại vì vô số lý do: tệp nằm trên tài nguyên mạng đã biến mất, có tệp chỉ đọc và không thể lưu lại, v.v. Nhưng người xử lý không thực sự quan tâm tại sao lại có lỗi; nó chỉ quan tâm rằng nó thành công hay thất bại. Nếu nó thành công, tất cả đều tốt. Nếu thất bại, nó có thể cho người dùng biết. Bản chất chính xác của ngoại lệ là không liên quan. Hơn nữa, nếu bạn viết mã phù hợp, an toàn ngoại lệ, điều đó có nghĩa là bất kỳ lỗi nào như vậy có thể lan truyền qua hệ thống của bạn mà không đưa nó xuống - ngay cả đối với mã không quan tâm đến lỗi. Điều này làm cho nó dễ dàng hơn để mở rộng hệ thống. Ví dụ: bây giờ bạn lưu thông qua kết nối cơ sở dữ liệu.


4
@Lundin nó được biên dịch mà không có cảnh báo. Và không, bạn không thể. Không đáng tin cậy. Bạn có thể gọi thông qua các con trỏ hàm hoặc nó có thể là một hàm thành viên ảo và lớp dẫn xuất ném dẫn xuất_exception (mà lớp cơ sở không thể biết về).
Kaz Dragon

May mắn lớn. Embarcadero / Borland đưa ra lời cảnh báo cho điều này: "ném ngoại lệ vi phạm công cụ xác định ngoại lệ". Rõ ràng GCC không. Không có lý do gì họ không thể thực hiện một cảnh báo mặc dù. Và tương tự, một công cụ phân tích tĩnh có thể dễ dàng tìm thấy lỗi này.

14
@Lundin: Vui lòng không bắt đầu tranh luận và lặp lại thông tin sai lệch. Câu hỏi của bạn đã là một câu nói hay, và tuyên bố những điều sai trái không có ích. Một công cụ phân tích tĩnh KHÔNG THỂ tìm thấy nếu điều này xảy ra; để làm như vậy sẽ liên quan đến việc giải quyết vấn đề dừng. Hơn nữa, nó sẽ cần phân tích toàn bộ chương trình và thường thì toàn bộ nguồn không có sẵn.
David Thornley

1
@Lundin cùng một công cụ phân tích tĩnh có thể cho bạn biết ngoại lệ nào đã rời khỏi ngăn xếp của bạn, vì vậy trong trường hợp này, sử dụng thông số ngoại lệ vẫn không mua gì cho bạn ngoại trừ trường hợp âm tính giả sẽ làm giảm chương trình của bạn, nơi bạn có thể xử lý trường hợp lỗi trong một cách đẹp hơn nhiều (chẳng hạn như thông báo về sự thất bại và tiếp tục chạy: xem nửa sau câu trả lời của tôi để biết ví dụ).
Kaz Dragon

1
@Lundin Một câu hỏi hay. Câu trả lời là, nói chung, bạn không biết liệu các chức năng bạn đang gọi có đưa ra ngoại lệ hay không, vì vậy, giả định an toàn là tất cả đều thực hiện. Bắt những câu này ở đâu là một câu hỏi mà tôi đã trả lời trong stackoverflow.com/questions / 4025667 / . Trình xử lý sự kiện đặc biệt ở dạng ranh giới mô đun tự nhiên. Cho phép các ngoại lệ thoát vào một hệ thống cửa sổ / sự kiện chung (ví dụ), sẽ bị trừng phạt. Người xử lý nên nắm bắt tất cả nếu chương trình tồn tại ở đó.
Kaz Dragon

17

Cuộc phỏng vấn này với Anders Hejlsberg khá nổi tiếng. Trong đó, ông giải thích lý do tại sao nhóm thiết kế C # loại bỏ các ngoại lệ được kiểm tra ngay từ đầu. Tóm lại, có hai lý do chính: khả năng phiên bản và khả năng mở rộng.

Tôi biết OP đang tập trung vào C ++ trong khi Hejlsberg đang thảo luận về C #, nhưng những điểm mà Hejlsberg đưa ra cũng hoàn toàn có thể áp dụng cho C ++.


5
"Những điểm mà Hejlsberg đưa ra cũng hoàn toàn có thể áp dụng cho C ++" .. Sai! Đã kiểm tra các ngoại lệ như được triển khai trong Java buộc người gọi phải xử lý chúng (và / hoặc người gọi của người gọi, v.v.). Thông số ngoại lệ trong C ++ (mặc dù khá yếu và vô dụng) chỉ áp dụng cho một "ranh giới cuộc gọi chức năng" duy nhất và không "truyền bá ngăn xếp cuộc gọi" như các ngoại lệ được kiểm tra.
Martin Ba

3
@Martin: Tôi đồng ý với bạn về hành vi của thông số kỹ thuật ngoại lệ trong C ++. Nhưng cụm từ của tôi mà bạn trích dẫn "những điểm mà Hejlsberg đưa ra cũng hoàn toàn có thể áp dụng cho C ++", nói đến, những điểm mà Hejlsberg đưa ra, thay vì thông số kỹ thuật của C ++. Nói cách khác, tôi đang nói ở đây về khả năng phiên bản và khả năng mở rộng của chương trình, chứ không phải là tuyên truyền ngoại lệ.
CesarGon

3
Tôi không đồng ý với Hejlsberg từ khá sớm: "Mối quan tâm của tôi về các ngoại lệ được kiểm tra là cái còng tay họ đặt cho các lập trình viên. Bạn thấy các lập trình viên chọn các API mới có tất cả các mệnh đề này, và sau đó bạn thấy mã của họ bị phá hủy như thế nào, và bạn nhận ra các trường hợp ngoại lệ được kiểm tra không giúp ích gì cho họ. Đó là loại các nhà thiết kế API độc tài này cho bạn biết cách xử lý ngoại lệ của mình. " --- Ngoại lệ có được kiểm tra hay không, bạn vẫn phải xử lý các ngoại lệ do API bạn gọi. Kiểm tra ngoại lệ làm cho nó đơn giản làm cho nó rõ ràng.
quant_dev

5
@quant_dev: Không, bạn không phải xử lý các trường hợp ngoại lệ do API bạn gọi. Một tùy chọn hoàn toàn hợp lệ là không xử lý các trường hợp ngoại lệ và để chúng nổi lên ngăn xếp cuộc gọi để người gọi của bạn xử lý.
CesarGon

4
@CesarGon Không xử lý ngoại lệ cũng là lựa chọn cách xử lý chúng.
quant_dev
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.