Đồng thuận chung, không sử dụng ngoại lệ! Phần lớn đến từ các ngôn ngữ khác và thậm chí đôi khi còn lỗi thời.
Trong C ++, việc ném một ngoại lệ rất tốn kém do stack stack Uninding của. Mỗi khai báo biến cục bộ giống như một with
câu lệnh trong Python và đối tượng trong biến đó có thể chạy các hàm hủy. Các hàm hủy này được thực thi khi ném ngoại lệ, nhưng cũng có khi trở về từ hàm. Thành ngữ RAII này là một tính năng ngôn ngữ không thể thiếu và cực kỳ quan trọng để viết mã chính xác, mạnh mẽ - vì vậy RAII so với các ngoại lệ rẻ tiền là một sự đánh đổi mà C ++ đã quyết định đối với RAII.
Vào đầu C ++, rất nhiều mã không được viết theo cách an toàn ngoại lệ: trừ khi bạn thực sự sử dụng RAII, rất dễ bị rò rỉ bộ nhớ và các tài nguyên khác. Vì vậy, ném ngoại lệ sẽ khiến mã đó không chính xác. Điều này không còn hợp lý vì ngay cả thư viện chuẩn C ++ cũng sử dụng ngoại lệ: bạn không thể giả vờ ngoại lệ không tồn tại. Tuy nhiên, ngoại lệ vẫn là một vấn đề khi kết hợp mã C với C ++.
Trong Java, mọi ngoại lệ đều có dấu vết ngăn xếp liên quan. Theo dõi ngăn xếp rất có giá trị khi gỡ lỗi, nhưng lãng phí công sức khi ngoại lệ không bao giờ được in, ví dụ vì nó chỉ được sử dụng cho luồng điều khiển.
Vì vậy, trong các ngôn ngữ đó, ngoại lệ là quá đắt để sử dụng làm dòng điều khiển. Trong Python đây không phải là một vấn đề và ngoại lệ rẻ hơn rất nhiều. Ngoài ra, ngôn ngữ Python đã phải chịu một số chi phí khiến cho chi phí ngoại lệ không đáng chú ý so với các cấu trúc luồng điều khiển khác: ví dụ: kiểm tra xem một mục nhập có tồn tại với kiểm tra thành viên rõ ràng if key in the_dict: ...
thường nhanh như truy cập vào mục the_dict[key]; ...
và kiểm tra nếu bạn lấy KeyError. Một số tính năng ngôn ngữ tách rời (ví dụ: máy phát điện) được thiết kế theo các trường hợp ngoại lệ.
Vì vậy, trong khi không có lý do kỹ thuật để đặc biệt tránh các ngoại lệ trong Python, vẫn còn câu hỏi liệu bạn có nên sử dụng chúng thay vì trả về giá trị hay không. Các vấn đề ở cấp thiết kế với các ngoại lệ là:
chúng không rõ ràng Bạn không thể dễ dàng nhìn vào một chức năng và xem nó có thể ngoại lệ, vì vậy bạn không biết phải bắt cái gì. Giá trị trả về có xu hướng được xác định rõ hơn.
ngoại lệ là luồng kiểm soát không cục bộ làm phức tạp mã của bạn. Khi bạn ném một ngoại lệ, bạn không biết luồng điều khiển sẽ tiếp tục ở đâu. Đối với các lỗi không thể xử lý ngay thì đây có lẽ là một ý kiến hay, khi thông báo cho người gọi của bạn về một điều kiện thì điều này là hoàn toàn không cần thiết.
Văn hóa Python nói chung nghiêng về các trường hợp ngoại lệ, nhưng thật dễ dàng để vượt qua. Hãy tưởng tượng một list_contains(the_list, item)
hàm kiểm tra xem danh sách có chứa một mục bằng với mục đó không. Nếu kết quả được truyền đạt thông qua các ngoại lệ hoàn toàn gây khó chịu, bởi vì chúng ta phải gọi nó như thế này:
try:
list_contains(invited_guests, person_at_door)
except Found:
print("Oh, hello {}!".format(person_at_door))
except NotFound:
print("Who are you?")
Trả lại một bool sẽ rõ ràng hơn nhiều:
if list_contains(invited_guests, person_at_door):
print("Oh, hello {}!".format(person_at_door))
else:
print("Who are you?")
Nếu hàm được cho là trả về một giá trị, thì việc trả về một giá trị đặc biệt cho các điều kiện đặc biệt khá dễ bị lỗi, bởi vì mọi người sẽ quên kiểm tra giá trị này (đó có thể là nguyên nhân của 1/3 vấn đề trong C). Một ngoại lệ thường đúng hơn.
Một ví dụ điển hình là một pos = find_string(haystack, needle)
hàm tìm kiếm sự xuất hiện đầu tiên của needle
chuỗi trong chuỗi `haystack và trả về vị trí bắt đầu. Nhưng nếu chúng haystack-string không chứa chuỗi kim thì sao?
Giải pháp của C và bắt chước bởi Python là trả về một giá trị đặc biệt. Trong C đây là một con trỏ null, trong Python đây là -1
. Điều này sẽ dẫn đến kết quả đáng ngạc nhiên khi vị trí được sử dụng làm chỉ mục chuỗi mà không cần kiểm tra, đặc biệt -1
là chỉ mục hợp lệ trong Python. Trong C, con trỏ NULL của bạn ít nhất sẽ cung cấp cho bạn một segfault.
Trong PHP, một giá trị đặc biệt của một loại khác được trả về: boolean FALSE
thay vì một số nguyên. Vì hóa ra điều này thực sự không tốt hơn do các quy tắc chuyển đổi ngầm định của ngôn ngữ (nhưng lưu ý rằng trong Python cũng như các booleans có thể được sử dụng như ints!). Các hàm không trả về một kiểu nhất quán thường được coi là rất khó hiểu.
Một biến thể mạnh hơn sẽ có một ngoại lệ khi không thể tìm thấy chuỗi, điều này đảm bảo rằng trong luồng điều khiển thông thường, không thể vô tình sử dụng giá trị đặc biệt thay cho giá trị thông thường:
try:
pos = find_string(haystack, needle)
do_something_with(pos)
except NotFound:
...
Ngoài ra, luôn luôn trả về một loại không thể được sử dụng trực tiếp nhưng trước tiên phải được mở khóa có thể được sử dụng, ví dụ: bộ dữ liệu kết quả trong đó boolean cho biết liệu có ngoại lệ xảy ra hay không nếu kết quả có thể sử dụng được. Sau đó:
pos, ok = find_string(haystack, needle)
if not ok:
...
do_something_with(pos)
Điều này buộc bạn phải xử lý vấn đề ngay lập tức, nhưng nó gây khó chịu rất nhanh. Nó cũng ngăn bạn khỏi chức năng xích dễ dàng. Mỗi cuộc gọi chức năng bây giờ cần ba dòng mã. Golang là một ngôn ngữ cho rằng phiền toái này là giá trị an toàn.
Vì vậy, để tóm tắt, các trường hợp ngoại lệ không hoàn toàn không có vấn đề và chắc chắn có thể bị lạm dụng quá mức, đặc biệt là khi chúng thay thế một giá trị trả về bình thường. Nhưng khi được sử dụng để báo hiệu các điều kiện đặc biệt (không nhất thiết chỉ là lỗi), thì các ngoại lệ có thể giúp bạn phát triển các API sạch, trực quan, dễ sử dụng và khó sử dụng.