Tại sao xử lý ngoại lệ không tốt?


89

Ngôn ngữ Go của Google không có ngoại lệ là một lựa chọn thiết kế, và Linus của Linux nổi tiếng đã gọi ngoại lệ là tào lao. Tại sao?


2
Tác giả của ZeroMQ viết về cách ông nghĩ rằng đó là một sai lầm khi viết nó trong C ++ (chủ yếu là do việc xử lý lỗi) 250bpm.com/blog:4
serbaut

Go có thể không có ngoại lệ, nhưng nó có "sự hoảng loạn" mà bạn có thể "khôi phục" (trong khi các câu lệnh hoãn vẫn được thực thi) và cung cấp luồng kiểm soát không cục bộ ...
Kerrek SB

Dưới đây là một bài viết tốt đẹp lighterra.com/papers/exceptionsharmful (Exception Handling coi là có hại)
masterxilo

Afaics, các ngoại lệ có thể được mô phỏng trong Go với bảng viết sẵn quan trọng , mặc dù điểm này có thể có ý nghĩa hơn đối với việc chuyển từ một đường cú pháp hơn là viết bảng viết sẵn theo cách thủ công.
Shelby Moore III

Câu trả lời:


81

Các ngoại lệ làm cho nó thực sự dễ dàng để viết mã trong đó một ngoại lệ được ném ra sẽ phá vỡ các bất biến và để các đối tượng ở trạng thái không nhất quán. Về cơ bản, chúng buộc bạn phải nhớ rằng hầu hết mọi tuyên bố bạn đưa ra đều có khả năng bị ném và xử lý điều đó một cách chính xác. Làm như vậy có thể phức tạp và phản trực giác.

Hãy coi một cái gì đó như thế này như một ví dụ đơn giản:

class Frobber
{
    int m_NumberOfFrobs;
    FrobManager m_FrobManager;

public:
    void Frob()
    {
        m_NumberOfFrobs++;

        m_FrobManager.HandleFrob(new FrobObject());
    }
};

Giả sử ý FrobManagerchí deleteFrobObject, điều này có vẻ ổn, phải không? Hoặc có thể không ... Hãy tưởng tượng sau đó nếu một trong hai FrobManager::HandleFrob()hoặc operator newném một ngoại lệ. Trong ví dụ này, số gia tăng của m_NumberOfFrobskhông được quay trở lại. Do đó, bất kỳ ai sử dụng phiên bản này của Frobbersẽ có một đối tượng có thể bị hỏng.

Ví dụ này có vẻ ngu ngốc (được rồi, tôi đã phải căng mình một chút để tạo ra một cái :-)), nhưng, điểm rút ra là nếu một lập trình viên không liên tục nghĩ đến các ngoại lệ và đảm bảo rằng mọi hoán vị của trạng thái đều được cuộn quay lại bất cứ khi nào có ném, bạn sẽ gặp rắc rối theo cách này.

Ví dụ, bạn có thể nghĩ về nó giống như bạn nghĩ về mutexes. Bên trong phần quan trọng, bạn dựa vào một số câu lệnh để đảm bảo rằng cấu trúc dữ liệu không bị hỏng và các luồng khác không thể nhìn thấy các giá trị trung gian của bạn. Nếu bất kỳ câu nào trong số đó ngẫu nhiên không chạy, bạn sẽ kết thúc với một thế giới đau đớn. Bây giờ loại bỏ các khóa và đồng thời, và suy nghĩ về mỗi phương pháp như vậy. Hãy coi mỗi phương thức như một giao dịch của các hoán vị trên trạng thái đối tượng, nếu bạn muốn. Khi bắt đầu cuộc gọi phương thức của bạn, đối tượng phải ở trạng thái sạch và ở cuối cũng phải có trạng thái sạch. Ở giữa, biếnfoo có thể không nhất quán vớibar, nhưng mã của bạn cuối cùng sẽ khắc phục điều đó. Ngoại lệ có nghĩa là bất kỳ câu lệnh nào của bạn có thể làm gián đoạn bạn bất cứ lúc nào. Tùy thuộc vào bạn trong từng phương pháp riêng lẻ để làm cho nó đúng và quay trở lại khi điều đó xảy ra, hoặc sắp xếp các hoạt động của bạn để việc ném không ảnh hưởng đến trạng thái đối tượng. Nếu bạn hiểu sai (và rất dễ mắc phải lỗi này), thì người gọi sẽ nhìn thấy các giá trị trung gian của bạn.

Các phương thức như RAII, mà các lập trình viên C ++ yêu thích đề cập đến như là giải pháp cuối cùng cho vấn đề này, sẽ đi một chặng đường dài để bảo vệ khỏi điều này. Nhưng chúng không phải là một viên đạn bạc. Nó sẽ đảm bảo rằng bạn giải phóng tài nguyên ngay lập tức, nhưng không giải phóng bạn khỏi việc phải suy nghĩ về sự hỏng hóc của trạng thái đối tượng và người gọi nhìn thấy các giá trị trung gian. Vì vậy, đối với nhiều người, nói dễ dàng hơn, theo định nghĩa của phong cách mã hóa, không có ngoại lệ . Nếu bạn hạn chế loại mã bạn viết, thì việc giới thiệu những lỗi này sẽ khó hơn. Nếu không, bạn rất dễ mắc sai lầm.

Toàn bộ sách đã được viết về mã hóa an toàn ngoại lệ trong C ++. Rất nhiều chuyên gia đã hiểu sai. Nếu nó thực sự phức tạp và có quá nhiều sắc thái, có lẽ đó là một dấu hiệu tốt cho thấy bạn cần bỏ qua tính năng đó. :-)


9
Câu trả lời thú vị, tuy nhiên nó không phản ánh bất cứ điều gì trong kinh nghiệm lập trình của tôi. Vì vậy, tôi đoán nó là văn hóa cụ thể (có thể nhiều vấn đề trong Java hoặc C ++ hơn là Python) hoặc miền cụ thể.
ddaa

39
Các ngoại lệ được viết bằng ngôn ngữ được quản lý sử dụng mẫu try-catch-last sẽ không bao giờ để lại trạng thái không hợp lệ nếu chúng được viết đúng cách; vì khối cuối cùng được đảm bảo sẽ được thực thi, các đối tượng có thể được định vị ở đó. Phần còn lại nên được chăm sóc bởi các biến đi ra ngoài phạm vi và thu gom rác.
Robert Harvey

7
@ddaa Vấn đề chắc chắn có thể xảy ra trong Python. Kết quả thường là một lỗi khó tái tạo. Có lẽ bạn là người đặc biệt tỉ mỉ, hoặc may mắn. Nhưng sau đó, bạn nói đúng rằng đó là một vấn đề nhiều hơn trong C ++, trong đó lỗi phổ biến nhất từ ​​EH kém là rò rỉ bộ nhớ. Tôi cố gắng nhấn mạnh rằng rò rỉ không phải là vấn đề nghiêm trọng nhất. @Robert GC sẽ giảm thiểu rò rỉ bộ nhớ, nhưng tôi không chắc mã được quản lý sẽ giải phóng bạn khỏi lỗi lập trình viên. Đặc biệt nếu ai đó không chú ý đến sự an toàn ngoại lệ vì họ không nghĩ rằng đó là một vấn đề trong ngôn ngữ của họ, đó không phải là một dấu hiệu tuyệt vời.
asveikau

4
@lzprgmr chắc chắn có: các trường hợp ngoại lệ cho phép bạn xử lý sự khác biệt. các loại lỗi khác nhau. đặt trong mã. Xử lý lỗi kết nối có thể yêu cầu kết nối lại, nhưng không phải ở giữa một hàm được lồng sâu. Bạn muốn đưa nó vào trình quản lý kết nối hoặc thứ gì đó. Sau đó, xử lý các giá trị trả về buộc bạn phải kiểm tra lỗi trên từng cuộc gọi và bong bóng đó theo cách thủ công (trong trường hợp lỗi đặt lại kết nối cho ví dụ:). Ngoài ra, các giá trị trả về xếp chồng lên nhau trong các lệnh gọi lồng nhau: func3 có thể trả về -1, func2 gọi func3, trả về -2 khi lỗi của anh ấy, -1 đối với func3, v.v.
abourget

3
Tôi đã bỏ phiếu từ chối, nhưng tôi đã đảo ngược điều đó vì đây là lý do tại sao các trường hợp ngoại lệ bị coi thường. Tuy nhiên theo ý kiến ​​của tôi, hầu như bất kỳ phương pháp hoặc đoạn mã nào cũng có thể bị lỗi. Bạn không thể xử lý từng điều kiện lỗi bằng cách đưa ra giá trị trả về cho nó. Bạn sẽ mất thông tin về lỗi. Nghĩ rằng bạn có thể giữ cho mọi thứ được đồng bộ hóa độc đáo bằng cách kiểm tra từng câu lệnh và thực hiện dọn dẹp dẫn đến mã rất phức tạp - bắt lỗi nhiều câu lệnh và dọn dẹp một hoặc hai tài nguyên không phải là GC'ed sẽ sạch hơn nhiều.
Maarten Bodewes

50

Lý do cho việc Go không có ngoại lệ được giải thích trong Câu hỏi thường gặp về thiết kế ngôn ngữ Go:

Ngoại lệ là một câu chuyện tương tự. Một số thiết kế cho các trường hợp ngoại lệ đã được đề xuất nhưng mỗi thiết kế đều tăng thêm độ phức tạp đáng kể cho ngôn ngữ và thời gian chạy. Theo bản chất của chúng, các ngoại lệ kéo dài các chức năng và thậm chí có thể là các goroutines; chúng có ý nghĩa trên phạm vi rộng. Cũng có lo ngại về ảnh hưởng của chúng đối với các thư viện. Theo định nghĩa, chúng đặc biệt nhưng có kinh nghiệm với các ngôn ngữ khác hỗ trợ chúng cho thấy chúng có ảnh hưởng sâu sắc đến đặc điểm kỹ thuật thư viện và giao diện. Sẽ thật tuyệt nếu bạn tìm được một thiết kế cho phép chúng thực sự đặc biệt mà không khuyến khích các lỗi thông thường biến thành luồng điều khiển đặc biệt đòi hỏi mọi lập trình viên phải bù đắp.

Giống như thuốc chung, ngoại lệ vẫn là một vấn đề mở.

Nói cách khác, họ vẫn chưa tìm ra cách hỗ trợ các ngoại lệ trong cờ vây theo cách mà họ cho là thỏa đáng. Họ không nói rằng ngoại lệ là xấu cho mỗi gia nhập ;

CẬP NHẬT - tháng 5 năm 2012

Các nhà thiết kế cờ vây hiện đã leo xuống khỏi hàng rào. Câu hỏi thường gặp của họ bây giờ cho biết điều này:

Chúng tôi tin rằng việc kết hợp các ngoại lệ với cấu trúc điều khiển, như trong thành ngữ try-catch-last, dẫn đến mã phức tạp. Nó cũng có xu hướng khuyến khích các lập trình viên gắn nhãn quá nhiều lỗi thông thường, chẳng hạn như không mở được tệp, là ngoại lệ.

Go có một cách tiếp cận khác. Để xử lý lỗi đơn giản, trả về nhiều giá trị của Go giúp bạn dễ dàng thông báo lỗi mà không làm quá tải giá trị trả về. Một loại lỗi chính tắc, cùng với các tính năng khác của Go, làm cho việc xử lý lỗi trở nên dễ chịu nhưng hoàn toàn khác với các ngôn ngữ khác.

Go cũng có một số chức năng tích hợp để báo hiệu và phục hồi từ các điều kiện thực sự đặc biệt. Cơ chế khôi phục chỉ được thực thi khi một phần trạng thái của chức năng bị chia nhỏ sau lỗi, đủ để xử lý thảm họa nhưng không yêu cầu cấu trúc điều khiển bổ sung và khi được sử dụng tốt, có thể dẫn đến mã xử lý lỗi sạch.

Xem bài viết Trì hoãn, Hoảng sợ và Khôi phục để biết chi tiết.

Vì vậy, câu trả lời ngắn gọn là họ có thể làm theo cách khác bằng cách sử dụng lợi tức đa giá trị. (Và dù sao thì họ cũng có một hình thức xử lý ngoại lệ.)


... và Linus của Linux nổi tiếng đã gọi các ngoại lệ là tào lao.

Nếu bạn muốn biết tại sao Linus cho rằng những trường hợp ngoại lệ là tào lao, thì điều tốt nhất là hãy tìm các bài viết của anh ấy về chủ đề này. Điều duy nhất tôi đã theo dõi cho đến nay là câu trích dẫn này được nhúng trong một vài email trên C ++ :

"Toàn bộ việc xử lý ngoại lệ C ++ về cơ bản đã bị hỏng. Nó đặc biệt bị hỏng đối với hạt nhân."

Bạn sẽ lưu ý rằng anh ấy đang nói về các ngoại lệ C ++ nói riêng và không phải ngoại lệ nói chung. (Và C ++ ngoại lệ làm rõ ràng có một số vấn đề mà làm cho họ khó khăn để sử dụng một cách chính xác.)

Kết luận của tôi là Linus đã không gọi các ngoại lệ (nói chung) là "tào lao" cả!


30
Tôi ghét khi họ giấu thông tin bằng cách đưa nó vào Câu hỏi thường gặp. :)
brian d foy

7
Lưu ý rằng Linus bắt đầu email đó bằng "C ++ là một ngôn ngữ khủng khiếp." và tiếp tục nói về việc anh ta ghét cả C ++ và những người chọn lập trình trong đó đến mức nào. Do đó, tôi không nghĩ rằng ý kiến ​​của anh ấy về các ngoại lệ của C ++ có thể được coi là đáng tin cậy với quan điểm hơi thiên vị của anh ấy đối với C ++.
Pharap

1
@ Hi-Angel - Như văn bản tôi đã trích dẫn có nói: "Để xử lý lỗi đơn giản, trả về nhiều giá trị của Go giúp bạn dễ dàng thông báo lỗi mà không làm quá tải giá trị trả về." . Đó là cách nó liên quan. Dù bằng cách nào, tôi đang trích dẫn cơ sở lý luận của các nhà thiết kế cờ vây. Nếu bạn muốn tranh luận, hãy tranh luận với >> họ <<.
Stephen C

1
@ Hi-Angel - Tôi không viết những đoạn đó. Tôi chỉ trích dẫn chúng. Nếu bạn có vấn đề với chúng, có lẽ bạn nên trao đổi với các tác giả. Về lý thuyết, tôi có thể cung cấp một hướng dẫn về ngôn ngữ cờ vây làm ngữ cảnh. Nhưng thành thật mà nói, những người không hiểu thuật ngữ cờ vây có thể truy cập trang web cờ vây để tìm hiểu ý nghĩa của nó.
Stephen C

1
".. kết quả là mã phức tạp" Từ lâu rồi tôi đã viết chương trình C với nhiều phép toán chuỗi. Mọi phương thức đều được cấp phát bộ nhớ và sau đó kiểm tra xem cấp phát có thành công hay không. Có vẻ như phần lớn mã chỉ đang kiểm tra phân bổ. Nó không phức tạp? C ++ với các ngoại lệ là một cứu cánh tuyệt vời cho tôi.
robsosno

29

Các ngoại lệ không phải là xấu, nhưng nếu bạn biết chúng sẽ xảy ra rất nhiều, chúng có thể tốn kém về mặt hiệu suất.

Quy tắc chung là các ngoại lệ phải gắn cờ các điều kiện ngoại lệ và bạn không nên sử dụng chúng để kiểm soát luồng chương trình.


9
@Robert: "bạn không nên sử dụng chúng để kiểm soát luồng chương trình", tôi không nghĩ về điều đó theo cách này, quan điểm mới cho tôi: P +1
okw 15/11/09

2
Nó cũng thực sự phụ thuộc vào ngôn ngữ. Rất khó để tránh các ngoại lệ nếu bạn đang lập trình bằng Java.
Charles Salvia,

2
@Charles: Tôi nghĩ vấn đề là các ngoại lệ phù hợp trong các tình huống chỉ ra lỗi, hệ thống được định cấu hình sai hoặc các đầu vào không hợp lý. Hầu hết các ngoại lệ của thư viện Java có thể tránh được trong mã "quy trình làm việc bình thường".
Artelius

6
họ không phải tốn kém nhiều. ví dụ: bạn có thể triển khai "thử" mà không tốn thời gian thực thi và yêu cầu "ném" tìm kiếm các trình xử lý ngoại lệ trong bảng dựa trên địa chỉ người gọi mà nó nhìn thấy trên ngăn xếp ... Tôi sẽ nói rằng lý do lớn nhất không nên sử dụng ngoại lệ hoàn toàn không liên quan đến hiệu suất.
asveikau

Diễn đạt lại; câu hỏi rõ ràng gợi ý về việc sử dụng các ngoại lệ nói chung, hoặc hoàn toàn không sử dụng các ngoại lệ (chúng là tào lao hoặc thậm chí không có trong ngôn ngữ). Câu trả lời của bạn chỉ cho thấy lý do tại sao các ngoại lệ có hại cho hiệu suất khi được sử dụng cho luồng điều khiển chương trình.
Maarten Bodewes

26

Tôi không đồng ý với việc "chỉ ném các ngoại lệ trong một tình huống ngoại lệ." Trong khi nói chung là đúng, nó gây hiểu lầm. Các ngoại lệ dành cho các điều kiện lỗi (lỗi thực thi).

Bất kể ngôn ngữ bạn sử dụng là gì, hãy chọn một bản sao của Nguyên tắc thiết kế khung : Quy ước, Thành ngữ và Mẫu cho Thư viện .NET có thể tái sử dụng (Phiên bản thứ 2). Chương về ném ngoại lệ không có ngang hàng. Một số trích dẫn từ ấn bản đầu tiên (ấn bản thứ 2 tại công việc của tôi):

  • KHÔNG trả lại mã lỗi.
  • Mã lỗi có thể dễ dàng bị bỏ qua và thường là như vậy.
  • Ngoại lệ là phương tiện chính để báo cáo lỗi trong khuôn khổ.
  • Một nguyên tắc chung là nếu một phương thức không làm như tên gọi của nó, nó sẽ được coi là một thất bại ở cấp độ phương pháp, dẫn đến một ngoại lệ.
  • KHÔNG sử dụng ngoại lệ cho quy trình kiểm soát thông thường, nếu có thể.

Có các trang ghi chú về lợi ích của các ngoại lệ (tính nhất quán của API, lựa chọn vị trí mã xử lý lỗi, độ mạnh được cải thiện, v.v.) Có một phần về hiệu suất bao gồm một số mẫu (Tester-Doer, Try-Parse).

Các ngoại lệ và xử lý ngoại lệ không tồi. Giống như bất kỳ tính năng nào khác, chúng có thể bị sử dụng sai.


3
Tôi phải không đồng ý về điều đó. Tôi không chống lại các trường hợp ngoại lệ và cuốn sách đó là phải có, tuy nhiên nó thiên về phát triển .NET và C #.

3
Tôi biết điều này là cổ xưa, chỉ muốn nhận xét rằng có vẻ như có sự bất đồng về phong cách chung giữa kiểu .NET và kiểu * nix. Tất cả các thư viện mà tôi đã sử dụng với tư cách là nhà phát triển Linux đều sử dụng mã trả về và các hướng dẫn kiểu * nix mà tôi đã đọc (như của công ty tôi và của Google chẳng hạn) chỉ nói "chúng tôi không có ngoại lệ". Chỉ cần nghĩ rằng nó thú vị.
jarvisteve

1
Các khung công tác phải xử lý các ngoại lệ khác với các ứng dụng của người dùng cuối. Các khung công tác không có cách nào để xử lý lỗi ngoài việc đưa ra một ngoại lệ, các ứng dụng dành cho người tiêu dùng thì có.
0x6C38

11

Từ quan điểm của golang, tôi đoán không có xử lý ngoại lệ giữ cho quá trình biên dịch đơn giản và an toàn.

Từ quan điểm của Linus, tôi hiểu rằng mã nhân là TẤT CẢ về các trường hợp góc. Vì vậy, nó là hợp lý để từ chối các trường hợp ngoại lệ.

Các ngoại lệ có ý nghĩa trong mã là không sao để bỏ nhiệm vụ hiện tại xuống sàn và trong đó mã trường hợp phổ biến có tầm quan trọng hơn việc xử lý lỗi. Nhưng chúng yêu cầu tạo mã từ trình biên dịch.

Ví dụ: chúng tốt trong hầu hết các mã cấp cao, hướng tới người dùng, chẳng hạn như mã ứng dụng web và máy tính để bàn.


Nhưng những gì đúng với mã hạt nhân cũng đúng với các quy trình máy chủ gốc chạy lâu.
Lothar

11

Bản thân các ngoại lệ không phải là "xấu", đó là cách mà các ngoại lệ được xử lý đôi khi có xu hướng xấu. Có một số nguyên tắc có thể được áp dụng khi xử lý các trường hợp ngoại lệ để giúp giảm bớt một số vấn đề này. Một số trong số này bao gồm (nhưng chắc chắn không giới hạn):

  1. Không sử dụng ngoại lệ để điều khiển luồng chương trình - tức là không dựa vào các câu lệnh "bắt" để thay đổi luồng logic. Điều này không chỉ có xu hướng che giấu các chi tiết khác nhau xung quanh logic mà nó có thể dẫn đến hiệu suất kém.
  2. Không ném ngoại lệ từ bên trong một hàm khi "trạng thái" trả về sẽ có ý nghĩa hơn - chỉ ném ngoại lệ trong một tình huống ngoại lệ. Tạo ngoại lệ là một hoạt động tốn kém, đòi hỏi nhiều hiệu suất. Ví dụ: nếu bạn gọi một phương thức để mở một tệp và tệp đó không tồn tại, hãy ném một ngoại lệ "FileNotFound". Nếu bạn gọi một phương thức xác định xem tài khoản khách hàng có tồn tại hay không, hãy trả về giá trị boolean, không trả về ngoại lệ "CustomerNotFound".
  3. Khi xác định có xử lý ngoại lệ hay không, không sử dụng mệnh đề "thử ... bắt" trừ khi bạn có thể làm điều gì đó hữu ích với ngoại lệ. Nếu bạn không thể xử lý ngoại lệ, bạn chỉ nên để nó bong bóng lên ngăn xếp cuộc gọi. Nếu không, các ngoại lệ có thể bị trình xử lý "nuốt" và các chi tiết sẽ bị mất (trừ khi bạn đặt lại ngoại lệ).

2
Trả lại trạng thái là một điều khó khăn. Tôi đã thấy quá nhiều mã có phương thức GetCustomer trả về thực thể Khách hàng khi thành công hoặc vô hiệu khi thất bại. Trong nhiều trường hợp, mã gọi điện không bao giờ kiểm tra kết quả mà ngay lập tức truy cập vào Khách hàng. Điều này hầu hết thời gian hoạt động ...
TrueWill

3
Nhưng nếu GetCustomer ném một ngoại lệ thay vì trả về null, thì mã máy khách vẫn cần xử lý ngoại lệ. Cho dù đó là bằng cách kiểm tra null hay bằng cách xử lý các ngoại lệ, trách nhiệm nằm ở mã máy khách - nếu nó không thực hiện đúng những gì, thì sớm hay muộn cái gì đó sẽ phát nổ.
Chris

1
@TrueWill Các ngôn ngữ hỗ trợ mẫu / chung giải quyết vấn đề này bằng cách trả về Option<T>thay vì trả về nullhiện nay. Chẳng hạn, vừa được giới thiệu trong Java 8, lấy một gợi ý từ Guava (và những người khác).
Maarten Bodewes

@owlstead Yep. Yêu đơn nguyên có thể. Đó là một cách tốt nếu ngôn ngữ của bạn hỗ trợ nó và cung cấp tính năng khớp mẫu.
TrueWill

@owlstead Một lần nữa, những gì Chris đã nói vẫn có giá trị cho điều đó - người dùng phải nhớ gọi hàm .orElse (defaultObject) hoặc bất kỳ thành ngữ nào mà ngôn ngữ được đề cập đã quyết định. Cuối cùng thì vấn đề chính là các lập trình viên chứ không phải là phương pháp xử lý lỗi.
Pharap

9

Các lập luận điển hình là không có cách nào để biết những ngoại lệ nào sẽ xuất hiện từ một đoạn mã cụ thể (tùy thuộc vào ngôn ngữ) và chúng quá giống gotos, gây khó khăn cho việc theo dõi thực thi.

http://www.joelonsoftware.com/items/2003/10/13.html

Chắc chắn không có sự đồng thuận về vấn đề này. Tôi có thể nói rằng từ quan điểm của một lập trình viên C cứng như Linus, các ngoại lệ chắc chắn là một ý tưởng tồi. Tuy nhiên, một lập trình viên Java điển hình lại ở trong một tình huống rất khác.


1
Mã C có các loại ngoại lệ, chỉ theo một cách khác. Bạn cần kết hợp mọi cuộc gọi đến một hàm không tầm thường trong ifs khiến việc sử dụng ngôn ngữ đó trở nên đau đầu!
RCIX

1
Ngoài ra còn có setjmp/ longjmpthứ, khá tệ.
Tim Sylvester

9
Bạn có thực sự muốn nghe lời khuyên của một người coi trọng các lập trình viên Duct Tape và người không tin rằng các bài kiểm tra đơn vị là cần thiết? joelonsoftware.com/items/2009/09/23.html
TrueWill

4
Đây là sai lầm cổ điển (hoặc gian lận) trong một cuộc thảo luận nơi các lập luận về chủ đề được thay thế bằng các tham chiếu về tính cách. Đó thường là một dấu hiệu của cuộc thảo luận thoái hóa.
Petr Gladkikh

1
@PetrGladkikh Cuộc thảo luận bắt đầu với việc OP đề cập đến ý kiến ​​của Linus ... rằng gian lận được gọi là ngụy biện của việc kháng cáo lên chính quyền. Cuộc thảo luận chỉ có thể đi lên từ đó, và việc trả lời câu hỏi tại sao Linus không thích các trường hợp ngoại lệ bằng cách đề cập đến tính cách của anh ta là điều không nên.
Jim Balter

7

Các trường hợp ngoại lệ không tệ. Chúng rất phù hợp với mô hình RAII của C ++, đây là điều tao nhã nhất về C ++. Nếu bạn đã có một loạt mã không phải là ngoại lệ an toàn, thì chúng không tốt trong bối cảnh đó. Nếu bạn đang viết phần mềm thực sự cấp thấp, chẳng hạn như hệ điều hành Linux, thì chúng rất tệ. Nếu bạn thích rải mã của mình với một loạt các kiểm tra trả về lỗi, thì chúng không hữu ích. Nếu bạn không có kế hoạch kiểm soát tài nguyên khi một ngoại lệ được ném ra (mà các trình hủy C ++ cung cấp) thì chúng rất tệ.


7
RAII hữu ích ngay cả khi không có ngoại lệ.
Đánh dấu tiền chuộc

4
tuy nhiên, các ngoại lệ không hữu ích nếu không có RAII (hoặc một số quản lý tài nguyên tự động khác).
Greg Rogers

+1 để chỉ ra các tình huống trong đó các ngoại lệ không phù hợp và các ngoại lệ đó không phải là xấu.
Pharap

4

Một trường hợp sử dụng tuyệt vời cho các trường hợp ngoại lệ là do đó ....

Giả sử bạn đang tham gia một dự án và mọi bộ điều khiển (khoảng 20 bộ điều khiển chính khác nhau) mở rộng một bộ điều khiển siêu lớp duy nhất với một phương thức hành động. Sau đó, mỗi bộ điều khiển thực hiện một loạt các công cụ khác nhau gọi các đối tượng B, C, D trong một trường hợp và F, G, D trong một trường hợp khác. Các trường hợp ngoại lệ đến giải cứu ở đây trong nhiều trường hợp có hàng tấn mã trả lại và MỌI bộ điều khiển đang xử lý nó theo cách khác nhau. Tôi đã đánh tất cả mã đó, ném ngoại lệ thích hợp từ "D", bắt nó trong phương thức hành động của bộ điều khiển siêu lớp và bây giờ tất cả các bộ điều khiển của chúng tôi đều nhất quán. Trước đây D trả về null cho MULTIPLE trường hợp lỗi khác nhau mà chúng tôi muốn thông báo cho người dùng cuối nhưng không thể và tôi đã không làm như vậy '

vâng, chúng tôi phải lo lắng về từng cấp độ và bất kỳ việc dọn dẹp / rò rỉ tài nguyên nào nhưng nói chung không bộ điều khiển nào của chúng tôi có bất kỳ tài nguyên nào để dọn dẹp sau đó.

cảm ơn chúa, chúng tôi đã có những ngoại lệ nếu không tôi đã tham gia vào một công ty tái cấu trúc khổng lồ và lãng phí quá nhiều thời gian cho một thứ mà lẽ ra là một vấn đề lập trình đơn giản.


1
+1 Dễ dàng là một trong những đối số tốt nhất để sử dụng các ngoại lệ mà tôi đã đọc. Có thể đã sử dụng các ví dụ chi tiết hơn (tức là một sơ đồ UML, một số mã giả hoặc một số mã thực tế) nhưng điểm về việc làm cho các lớp con hoạt động nhất quán là một điều tốt. Ngoài ra, thực tế bằng chứng của bạn chỉ là giai thoại cho thấy rằng các ngoại lệ rất hữu ích trong các tình huống thực tế và tính hữu ích là mục đích chính của bất kỳ tính năng ngôn ngữ nào.
Pharap

chỉ là một phép bổ sung, nếu bạn đang ở trong phạm vi tỷ lệ, thay vào đó bạn có thể trả về Thử [Kiểu phản hồi] đại diện cho một ngoại lệ hoặc phản hồi thực tế. Sau đó, bạn có thể làm theo cùng một mô hình mà tôi đã loại trừ ở trên mà không có ngoại lệ thực tế nào khác ngoài việc chúng được đưa vào thử nghiệm. Sau đó, nó giống như mọi phương thức bạn trả về 1 + n kiểu phản hồi mà tôi nghĩ là cần thiết. Tuy nhiên, chúng tôi trả về tương lai [response] trong tương lai hoạt động giống như Try nhưng có thể giúp lập trình không đồng bộ hơn.
Dean Hiller

2

Về mặt lý thuyết thì chúng thực sự rất tệ. Trong thế giới toán học hoàn hảo, bạn không thể có những tình huống ngoại lệ. Hãy nhìn vào các ngôn ngữ chức năng, chúng không có tác dụng phụ, vì vậy chúng hầu như không có nguồn cho các tình huống bất thường.

Nhưng, thực tế lại là một câu chuyện khác. Chúng ta luôn có những tình huống “bất ngờ”. Đây là lý do tại sao chúng ta cần ngoại lệ.

Tôi nghĩ rằng chúng ta có thể nghĩ về các ngoại lệ đối với đường cú pháp cho ExceptionSituationObserver. Bạn chỉ nhận được thông báo về các trường hợp ngoại lệ. Chỉ có bấy nhiêu thôi.

Với cờ vây, tôi nghĩ họ sẽ giới thiệu một thứ gì đó sẽ giải quyết những tình huống “bất ngờ”. Tôi có thể đoán rằng họ sẽ cố gắng làm cho nó nghe ít phá hoại hơn như các ngoại lệ và nhiều hơn như logic ứng dụng. Nhưng đây chỉ là suy đoán của tôi.


2
"Hãy nhìn vào các ngôn ngữ chức năng, chúng không có tác dụng phụ, vì vậy chúng hầu như không có nguồn cho các tình huống bất thường." Đó là một lời nói quá thô thiển.
Stephen C

3
5/0 trong toán học là gì? Arcsin (200)? Sqrt (-1)? Toán học có vô số tình huống đặc biệt.
Robert Fraser

3
Đây không phải là tình huống thực thi ... chúng không có ý nghĩa gì ... và vì điều đó có thể được thực hiện như những trường hợp ngoại lệ ... nhưng cũng có thể được thực hiện dưới dạng các điều kiện tiên quyết .. vì vậy nó phụ thuộc vào việc thực hiện kỹ thuật.
Mike Chaliy

3
@MikeChaliy - Tôi xin lỗi, nhưng đó chỉ là ngụy biện. Bạn có thể áp dụng kiểu lý luận đó để nói rằng KHÔNG CÓ tình huống ngoại lệ nào trong bất cứ điều gì, BAO GIỜ. Trong thực tế, các biểu thức toán học không có nghĩa (hoặc không có giá trị xác định) là ngoại lệ. Điều này không có nghĩa là chúng cần được xử lý bằng cách ném và bắt các ngoại lệ ... nhưng nếu bạn không làm vậy, bạn cần các giá trị đặc biệt (như Inf và Nan) hoặc các phép toán trả về nhiều giá trị. Nói tóm lại, những trường hợp này cần một loại điều trị đặc biệt.
Stephen C

1
Máy tính là máy trạng thái. Không phải là một thế giới toán học hoàn hảo.
Arunav Sanyal

1

Mô hình xử lý ngoại lệ của C ++, tạo thành cơ sở một phần cho Java và đến lượt nó .net, giới thiệu một số khái niệm tốt, nhưng cũng có một số hạn chế nghiêm trọng. Một trong những mục đích thiết kế chính của việc xử lý ngoại lệ là cho phép các phương thức đảm bảo rằng chúng sẽ thỏa mãn các điều kiện hậu kỳ của chúng hoặc tạo ra một ngoại lệ, đồng thời cũng đảm bảo rằng bất kỳ quá trình dọn dẹp nào cần xảy ra trước khi một phương thức có thể thoát ra, sẽ xảy ra. Thật không may, các mô hình xử lý ngoại lệ của C ++, Java và .net đều không cung cấp bất kỳ phương tiện tốt nào để xử lý tình huống trong đó các yếu tố bất ngờ ngăn cản quá trình dọn dẹp dự kiến ​​được thực hiện. Đến lượt nó, điều này có nghĩa là người ta phải có nguy cơ khiến mọi thứ dừng lại nếu có điều gì đó không mong muốn xảy ra (phương pháp C ++ để xử lý một ngoại lệ xảy ra trong quá trình giải nén ngăn xếp),

Ngay cả khi việc xử lý ngoại lệ nói chung là tốt, không có gì vô lý khi coi là không thể chấp nhận được một mô hình xử lý ngoại lệ không cung cấp phương tiện tốt để xử lý các vấn đề xảy ra khi dọn dẹp sau các vấn đề khác. Điều đó không có nghĩa là một khuôn khổ không thể được thiết kế với một mô hình xử lý ngoại lệ có thể đảm bảo hành vi hợp lý ngay cả trong các tình huống nhiều lỗi, nhưng không có ngôn ngữ hoặc khung công tác hàng đầu nào có thể làm như vậy.


1

Tôi đã không đọc tất cả các câu trả lời khác, vì vậy câu hỏi này đã được đề cập đến, nhưng một lời chỉ trích là chúng khiến các chương trình bị hỏng trong chuỗi dài, gây khó khăn cho việc tìm kiếm lỗi khi gỡ lỗi mã. Ví dụ, nếu Foo () gọi Bar () gọi Wah () gọi ToString () thì việc vô tình đẩy dữ liệu sai vào ToString () sẽ giống như một lỗi trong Foo (), một hàm gần như hoàn toàn không liên quan.


0
  • Ngoại lệ không được xử lý nói chung là xấu.
  • Ngoại lệ được xử lý không tốt là không tốt (tất nhiên).
  • 'Tính tốt / xấu' của việc xử lý ngoại lệ phụ thuộc vào ngữ cảnh / phạm vi và sự phù hợp, chứ không phải vì lợi ích của việc đó.

0

Được rồi, câu trả lời nhàm chán ở đây. Tôi đoán nó phụ thuộc vào ngôn ngữ thực sự. Trường hợp một ngoại lệ có thể để lại các tài nguyên được phân bổ, chúng nên được tránh. Trong các ngôn ngữ kịch bản, chúng chỉ loại bỏ hoặc chèn ép các phần của luồng ứng dụng. Bản thân điều đó là không đáng tin cậy, nhưng việc thoát khỏi các lỗi gần như nghiêm trọng với các ngoại lệ là một ý tưởng có thể chấp nhận được.

Đối với báo hiệu lỗi, tôi thường thích tín hiệu lỗi hơn. Tất cả phụ thuộc vào API, trường hợp sử dụng và mức độ nghiêm trọng hoặc nếu việc ghi nhật ký là đủ. Ngoài ra, tôi đang cố gắng xác định lại hành vi và throw Phonebooks()thay vào đó. Ý tưởng cho rằng "Ngoại lệ" thường là kết thúc, nhưng "Danh bạ" chứa thông tin hữu ích về khôi phục lỗi hoặc các lộ trình thực thi thay thế. (Chưa tìm thấy một trường hợp sử dụng tốt nào, nhưng hãy tiếp tục thử.)


0

Đối với tôi vấn đề rất đơn giản. Nhiều lập trình viên sử dụng trình xử lý ngoại lệ một cách không thích hợp. Nhiều nguồn ngôn ngữ hơn là tốt hơn. Có khả năng xử lý các trường hợp ngoại lệ là tốt. Một ví dụ về việc sử dụng sai là một giá trị phải là số nguyên không được xác minh, hoặc một đầu vào khác có thể chia và không được kiểm tra để chia cho 0 ... xử lý ngoại lệ có thể là một cách dễ dàng để tránh làm việc nhiều hơn và suy nghĩ nhiều hơn, lập trình viên có thể muốn thực hiện một phím tắt bẩn và áp dụng một xử lý ngoại lệ ... Tuyên bố: "một mã chuyên nghiệp KHÔNG BAO GIỜ thất bại" có thể là viển vông, nếu một số vấn đề được xử lý bởi thuật toán là không chắc chắn về bản chất của chính nó. Có lẽ trong những tình huống không xác định theo bản chất thì tốt nhất là xử lý ngoại lệ. Thực hành lập trình tốt là một vấn đề tranh luận.


Vấn đề không phải là (hoặc không nên) liệu mã có thể bị lỗi hay không - một vấn đề lớn hơn là mức độ mà nếu mã không thành công, người ta quan tâm đến các chi tiết cụ thể. Nếu một người cố gắng tải một tài liệu và một trong các phương pháp "đọc dữ liệu" không thành công, thì người ta thường không thực sự quan tâm đến phương thức nào, vì hiệu quả là như nhau bất kể: không thể tải tài liệu. Về mặt khái niệm, xử lý ngoại lệ sẽ tốt cho điều đó. Vấn đề là các mô hình xử lý ngoại lệ của .NET và Java không cung cấp bất kỳ cách tốt nào để phân biệt các ngoại lệ "nhàm chán" nên được gộp lại với nhau, với những ngoại lệ không nên.
supercat
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.