Ngoại lệ - Chuyện gì đã xảy ra


19

Chúng tôi sử dụng các ngoại lệ để cho phép người tiêu dùng mã xử lý các hành vi không mong muốn theo cách hữu ích. Thông thường các trường hợp ngoại lệ được xây dựng xung quanh kịch bản "chuyện gì đã xảy ra" - như FileNotFound(chúng tôi không thể tìm thấy tệp bạn đã chỉ định) hoặc ZeroDivisionError(chúng tôi không thể thực hiện 1/0thao tác).

Điều gì xảy ra nếu có khả năng chỉ định hành vi dự kiến ​​của người tiêu dùng?

Ví dụ, hãy tưởng tượng chúng ta có fetchtài nguyên, thực hiện yêu cầu HTTP và trả về dữ liệu đã truy xuất. Và thay vì các lỗi như ServiceTemporaryUnavailablehoặc RateLimitExceededchúng tôi sẽ chỉ đưa ra một RetryableErrorgợi ý cho người tiêu dùng rằng họ chỉ nên thử lại yêu cầu và không quan tâm đến thất bại cụ thể. Vì vậy, về cơ bản, chúng tôi đang đề xuất một hành động cho người gọi - "phải làm gì".

Chúng tôi không làm điều này thường xuyên vì chúng tôi không biết tất cả các giai đoạn của người tiêu dùng. Nhưng hãy tưởng tượng đó là một số thành phần cụ thể mà chúng ta biết cách hành động tốt nhất cho người gọi - vậy chúng ta có nên sử dụng phương pháp "phải làm gì" không?


3
Không phải HTTP đã làm điều này? 503 là một lỗi tạm thời không trả lời, vì vậy người yêu cầu nên thử lại, 404 là sự vắng mặt cơ bản, vì vậy không có ý nghĩa gì để thử lại, 301 có nghĩa là "di chuyển vĩnh viễn", vì vậy bạn nên thử lại, nhưng với một địa chỉ khác, v.v.
Kilian Foth

7
Trong nhiều trường hợp, nếu chúng ta thực sự biết "phải làm gì", chúng ta có thể khiến máy tính tự động làm điều đó và người dùng thậm chí không phải biết bất cứ điều gì sai. Tôi giả sử bất cứ khi nào trình duyệt của tôi nhận được 301, nó chỉ cần đến địa chỉ mới mà không hỏi tôi.
Ixrec

@Ixrec - cũng có ý tưởng tương tự. tuy nhiên, người tiêu dùng có thể không muốn đợi yêu cầu khác và bỏ qua mặt hàng hoặc thất bại hoàn toàn.
La Mã Bodnarchuk

1
@RomanBodnarchuk: Tôi không đồng ý. Nó giống như nói một người không cần phải biết tiếng Trung Quốc để nói tiếng Trung Quốc. HTTP là một giao thức và cả máy khách và máy chủ dự kiến ​​sẽ biết và tuân theo nó. Đó là cách một giao thức hoạt động. Nếu chỉ có một bên biết và tuân theo nó, thì bạn không thể giao tiếp.
Chris Pratt

1
Điều này có vẻ như bạn đang cố gắng thay thế ngoại lệ của mình bằng một khối bắt. Đó là lý do tại sao chúng tôi chuyển sang ngoại lệ - không còn nữa if( ! function() ) handle_something();, nhưng có thể xử lý lỗi ở một nơi nào đó mà bạn thực sự biết ngữ cảnh cuộc gọi - tức là nói với khách hàng gọi quản trị viên hệ thống nếu máy chủ của bạn bị lỗi hoặc tự động tải lại nếu kết nối bị rớt, nhưng cảnh báo cho bạn trường hợp người gọi là một dịch vụ siêu nhỏ khác. Hãy để các khối bắt xử lý bắt.
năm15 lúc 23:30

Câu trả lời:


47

Nhưng hãy tưởng tượng đó là một số thành phần cụ thể mà chúng ta biết cách hành động tốt nhất cho người gọi.

Điều này hầu như luôn thất bại đối với ít nhất một trong số những người gọi của bạn, mà hành vi này gây khó chịu vô cùng. Đừng cho rằng bạn biết rõ nhất. Nói với người dùng của bạn những gì đang xảy ra, không phải những gì bạn cho rằng họ nên làm về nó. Trong nhiều trường hợp, điều đó đã rõ ràng về quá trình hành động lành mạnh (và nếu không, hãy đưa ra gợi ý trong hướng dẫn sử dụng của bạn).

Ví dụ, ngay cả các trường hợp ngoại lệ được đưa ra trong câu hỏi của bạn thể hiện giả định bị hỏng của bạn: ServiceTemporaryUnavailabletương đương với "thử lại sau" và RateLimitExceededtương đương với "woah there chill out, có thể điều chỉnh các tham số hẹn giờ của bạn và thử lại sau vài phút". Nhưng người dùng cũng có thể muốn đưa ra một số loại báo động ServiceTemporaryUnavailable(điều này cho biết sự cố máy chủ), và không phải RateLimitExceeded(không có).

Hãy cho họ sự lựa chọn .


4
Tôi đồng ý. Các máy chủ chỉ nên truyền đạt thông tin đúng. Mặt khác, tài liệu này cần phác thảo rõ ràng quá trình hành động thích hợp trong những trường hợp như vậy.
Neil

1
Một lỗ hổng nhỏ ở đây là với một số tin tặc, một số ngoại lệ nhất định có thể cho chúng biết khá nhiều về những gì mã của bạn đang làm và chúng có thể sử dụng nó để tìm ra cách khai thác nó.
Pharap

3
@Pharap Nếu tin tặc của bạn có quyền truy cập vào ngoại lệ thay vì thông báo lỗi, bạn đã bị mất.
corsiKa

2
Tôi thích câu trả lời này, nhưng nó thiếu những gì tôi coi là một yêu cầu ngoại lệ ... nếu bạn biết cách phục hồi, nó sẽ không phải là một ngoại lệ! Một ngoại lệ chỉ nên xảy ra khi bạn không thể làm gì đó với nó: đầu vào không hợp lệ, trạng thái không hợp lệ, bảo mật không hợp lệ - bạn không thể sửa những thứ đó theo chương trình.
corsiKa

1
Đã đồng ý. Nếu bạn khăng khăng cho biết liệu có thể thử lại hay không, bạn luôn có thể làm cho các ngoại lệ cụ thể có liên quan được kế thừa từ đó RetryableError.
sapi

18

Cảnh báo! Lập trình viên C ++ đến đây với những ý tưởng có thể khác nhau về cách xử lý ngoại lệ nên được thực hiện khi cố gắng trả lời một câu hỏi chắc chắn về ngôn ngữ khác!

Đưa ra ý tưởng này:

Ví dụ: hãy tưởng tượng chúng ta có tài nguyên tìm nạp, thực hiện yêu cầu HTTP và trả về dữ liệu đã truy xuất. Và thay vì các lỗi như ServiceT tạmUnav Available hoặc RateLimitExceeded, chúng tôi sẽ chỉ đưa ra một RetryableError đề xuất cho người tiêu dùng rằng họ chỉ nên thử lại yêu cầu và không quan tâm đến thất bại cụ thể.

... Một điều tôi muốn đề xuất là bạn có thể trộn lẫn các mối lo ngại về việc báo cáo lỗi với các khóa hành động để phản hồi theo cách có thể làm giảm tính tổng quát của mã của bạn hoặc yêu cầu nhiều "điểm dịch" cho các ngoại lệ .

Ví dụ: nếu tôi mô hình hóa một giao dịch liên quan đến việc tải một tệp, nó có thể thất bại vì một số lý do. Có lẽ tải tệp liên quan đến việc tải một plugin không tồn tại trên máy của người dùng. Có lẽ tập tin chỉ đơn giản là bị hỏng và chúng tôi đã gặp lỗi khi phân tích cú pháp.

Bất kể điều gì xảy ra, hãy nói rằng quá trình hành động là báo cáo những gì đã xảy ra với người dùng và nhắc anh ta về những gì anh ta muốn làm về nó ("thử lại, tải một tệp khác, hủy bỏ").

Thrower vs Catch

Quá trình hành động đó được áp dụng bất kể loại lỗi nào chúng tôi gặp phải trong trường hợp này. Nó không được nhúng vào ý tưởng chung về lỗi phân tích cú pháp, nó không được nhúng vào ý tưởng chung về việc không tải được plugin. Nó được nhúng vào ý tưởng gặp phải các lỗi như vậy trong bối cảnh chính xác của việc tải tệp (kết hợp tải tệp và không thành công). Vì vậy, thông thường tôi thấy nó, nói một cách thô thiển, là catcher'strách nhiệm xác định tiến trình hành động để đáp ứng với một ngoại lệ bị ném (ví dụ: nhắc nhở người dùng với các tùy chọn), chứ không phải thrower's.

Nói cách khác, các trang web throwngoại lệ thường thiếu loại thông tin theo ngữ cảnh này, đặc biệt là nếu các chức năng ném thường được áp dụng. Ngay cả trong bối cảnh hoàn toàn thoái hóa khi họ có thông tin này, bạn vẫn tự mình dồn vào chân tường về hành vi phục hồi bằng cách nhúng nó vào throwtrang web. Các trang web catchthường là nơi có nhiều thông tin nhất có sẵn để xác định tiến trình hành động và cung cấp cho bạn một vị trí trung tâm để sửa đổi nếu quá trình hành động đó sẽ thay đổi đối với giao dịch đã cho đó.

Khi bạn bắt đầu cố gắng đưa ra các ngoại lệ không còn báo cáo những gì sai nhưng cố gắng xác định những việc cần làm, điều đó có thể làm giảm tính tổng quát và tính linh hoạt của mã của bạn. Một lỗi phân tích không phải lúc nào cũng dẫn đến loại dấu nhắc này, nó thay đổi theo bối cảnh trong đó một ngoại lệ như vậy được ném (giao dịch mà nó được ném).

Người ném mù

Nói chung, rất nhiều thiết kế xử lý ngoại lệ thường xoay quanh ý tưởng về một người ném mù. Nó không biết làm thế nào ngoại lệ sẽ bị bắt, hoặc ở đâu. Điều tương tự cũng áp dụng cho các hình thức khôi phục lỗi cũ hơn bằng cách sử dụng lan truyền lỗi thủ công. Các trang web gặp lỗi không bao gồm quá trình hành động của người dùng, họ chỉ nhúng thông tin tối thiểu để báo cáo loại lỗi nào đã gặp phải.

Đảo ngược trách nhiệm và khái quát hóa người bắt

Khi suy nghĩ về điều này cẩn thận hơn, tôi đã cố gắng tưởng tượng ra loại cơ sở mã hóa nơi điều này có thể trở thành cám dỗ. Trí tưởng tượng của tôi (có thể sai) là nhóm của bạn vẫn đang đóng vai trò là "người tiêu dùng" ở đây và thực hiện hầu hết các mã gọi điện. Có lẽ bạn có rất nhiều giao dịch khác nhau (rất nhiều trykhối) tất cả có thể gặp phải cùng một bộ lỗi và tất cả, từ quan điểm thiết kế, dẫn đến một quá trình phục hồi thống nhất.

Cân nhắc lời khuyên khôn ngoan từ Lightness Races in Orbit'scâu trả lời tốt (mà tôi nghĩ là thực sự đến từ một tư duy định hướng thư viện tiên tiến), bạn vẫn có thể bị ném ra ngoại lệ "phải làm gì", chỉ gần hơn với trang web khôi phục giao dịch.

Có thể tìm thấy một trang web xử lý giao dịch phổ biến, trung gian trong số này ở đây thực sự tập trung vào các mối quan tâm "phải làm gì" nhưng vẫn trong bối cảnh bị bắt.

nhập mô tả hình ảnh ở đây

Điều này sẽ chỉ áp dụng nếu bạn có thể thiết kế một số loại chức năng chung mà tất cả các giao dịch bên ngoài này sử dụng (ví dụ: một chức năng nhập một chức năng khác để gọi hoặc một lớp cơ sở giao dịch trừu tượng với mô hình hành vi có thể ghi đè lên trang web giao dịch trung gian này có khả năng nắm bắt tinh vi ).

Tuy nhiên, người ta có thể chịu trách nhiệm tập trung vào quá trình hành động của người dùng để đối phó với nhiều lỗi có thể xảy ra, và vẫn trong bối cảnh bắt hơn là ném. Ví dụ đơn giản (mã giả Python-ish và tôi không phải là nhà phát triển Python có kinh nghiệm một chút nào đó nên có thể có một cách thành ngữ hơn về vấn đề này):

def general_catcher(task):
    try:
       task()
    except SomeError1:
       # do some uniformly-designed recovery stuff here
    except SomeError2:
       # do some other uniformly-designed recovery stuff here
    ...

[Hy vọng với một cái tên tốt hơn general_catcher]. Trong ví dụ này, bạn có thể chuyển vào một hàm chứa tác vụ nào sẽ thực hiện nhưng vẫn được hưởng lợi từ hành vi bắt chung chung / thống nhất cho tất cả các loại ngoại lệ bạn quan tâm và tiếp tục mở rộng hoặc sửa đổi phần "việc cần làm" bạn thích từ vị trí trung tâm này và vẫn trong catchbối cảnh thường được khuyến khích. Trên hết, chúng ta có thể giữ cho các trang web ném không liên quan đến mình với "phải làm gì" (bảo tồn khái niệm "người ném mù").

Nếu bạn thấy không có gợi ý nào ở đây hữu ích và dù sao cũng có một sự cám dỗ mạnh mẽ để đưa ra những ngoại lệ "phải làm gì", chủ yếu hãy lưu ý rằng điều này rất ít phản đối, cũng như có khả năng làm nản lòng một tư duy khái quát.


2
+1. Tôi chưa bao giờ nghe về ý tưởng "Người ném mù" như vậy trước đây, nhưng nó phù hợp với cách tôi nghĩ về việc xử lý ngoại lệ: nêu lỗi xảy ra, đừng nghĩ về cách xử lý. Khi bạn chịu trách nhiệm cho toàn bộ ngăn xếp, thật khó (nhưng quan trọng!) Để phân tách các trách nhiệm một cách sạch sẽ và callee chịu trách nhiệm thông báo về các vấn đề và người gọi xử lý các vấn đề. Các callee chỉ biết những gì nó được yêu cầu làm, không phải tại sao. Xử lý lỗi nên được thực hiện trong ngữ cảnh của 'tại sao', do đó: trong trình gọi.
Sjoerd Công việc Postmus

1
(Ngoài ra: Tôi không nghĩ câu trả lời của bạn là cụ thể về C ++, nhưng áp dụng cho việc xử lý ngoại lệ nói chung)
Sjoerd Job Postmus

1
@SjoerdJobPostmus Yay cảm ơn! "Người ném mù" chỉ là một sự tương tự ngớ ngẩn mà tôi đã nghĩ ra ở đây - Tôi không thông minh hay nhanh nhạy trong việc tiêu hóa các khái niệm kỹ thuật phức tạp, vì vậy tôi thường muốn tìm ra những hình ảnh và sự tương tự nhỏ để cố gắng giải thích và cải thiện sự hiểu biết của riêng tôi của sự vật. Có lẽ một ngày nào đó tôi có thể cố gắng làm việc theo cách của mình để viết một cuốn sách lập trình nhỏ chứa rất nhiều hình vẽ hoạt hình. :-D

1
Heh, đó là một hình ảnh nhỏ đẹp. Một nhân vật hoạt hình nhỏ đeo băng bịt mắt và ném ngoại lệ hình bóng chày ra ngoài, không chắc ai sẽ bắt được chúng (hoặc thậm chí liệu chúng có bị bắt không), nhưng hoàn thành nhiệm vụ của mình như một kẻ ném bóng mù.
Blacklight Shining

1
@DrunkCoder: Xin đừng phá hoại bài viết của bạn. Chúng tôi có đủ các công cụ borken trên Internet rồi. Nếu bạn có lý do chính đáng để xóa, hãy gắn cờ bài đăng của bạn để người điều hành chú ý và đưa ra trường hợp của bạn.
Robert Harvey

2

Tôi nghĩ rằng hầu hết thời gian sẽ tốt hơn để truyền các đối số cho hàm cho nó biết cách xử lý các tình huống đó.

Ví dụ, hãy xem xét một chức năng:

Response fetchUrl(URL url, RetryPolicy retryPolicy);

Tôi có thể vượt qua RetryPolicy.noRetries () hoặc RetryPolicy.retries (3) hoặc bất cứ điều gì. Trong trường hợp thất bại có thể thử lại, nó sẽ tham khảo chính sách để quyết định có nên thử lại hay không.


Tuy nhiên, điều đó thực sự không liên quan nhiều đến việc ném ngoại lệ vào trang web cuộc gọi. Bạn đang nói về một cái gì đó hơi khác một chút, điều đó tốt nhưng không thực sự là một phần của câu hỏi ..
Cuộc đua Lightness với Monica

@LightnessRacesinOrbit, ngược lại. Tôi đang trình bày nó như là một thay thế cho ý tưởng ném ngoại lệ trở lại trang web cuộc gọi. Trong ví dụ của OP, fetchUrl sẽ đưa ra RetryableException và tôi đang nói thay vào đó bạn nên nói với fetchUrl khi nào nên thử lại.
Winston Ewert

2
@WinstonEwert: Mặc dù tôi đồng ý với LightnessRacesinOrbit, tôi cũng thấy quan điểm của bạn, nhưng nghĩ về nó như một cách khác để thể hiện cùng một điều khiển. Nhưng, hãy xem xét rằng bạn có thể muốn vượt qua new RetryPolicy().onRateLimitExceeded(STOP).onServiceTemporaryUnavailable(RETRY, 3)hoặc một cái gì đó, bởi vì RateLimitExceededcó thể cần phải được xử lý khác nhau ServiceTemporaryUnavailable. Sau khi viết nó ra, suy nghĩ của tôi là: tốt hơn nên ném một ngoại lệ, bởi vì nó cho phép kiểm soát linh hoạt hơn.
Sjoerd Công việc Postmus

@SjoerdJobPostmus, tôi nghĩ nó phụ thuộc. Cũng có thể là việc giới hạn tốc độ và thử lại logic được thực hiện từ trong thư viện của bạn, trong trường hợp đó tôi nghĩ cách tiếp cận của tôi có ý nghĩa. Nếu nó có ý nghĩa hơn chỉ để lại điều đó cho người gọi của bạn, bằng mọi cách ném.
Winston Ewert
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.