Sử dụng thử cuối cùng (không bắt) so với xác thực trạng thái enum


9

Tôi đã đọc lời khuyên về câu hỏi này về cách xử lý một ngoại lệ càng gần nơi nó được nêu ra càng tốt.

Tiến thoái lưỡng nan của tôi trên thực hành tốt nhất là liệu người ta nên sử dụng một try / catch / finally để trả lại một enum (hoặc một int đại diện cho một giá trị, 0 cho lỗi, 1 cho ok, 2 cho cảnh báo vv, tùy từng trường hợp) để một câu trả lời luôn luôn theo thứ tự, hay người ta nên để ngoại lệ đi qua để phần gọi sẽ giải quyết nó?

Từ những gì tôi có thể thu thập, điều này có thể khác nhau tùy theo trường hợp, vì vậy lời khuyên ban đầu có vẻ kỳ quặc.

Ví dụ, trên một dịch vụ web, bạn sẽ luôn muốn trả về trạng thái tất nhiên, vì vậy mọi trường hợp ngoại lệ phải được xử lý tại chỗ, nhưng hãy nói bên trong một chức năng đăng / nhận một số dữ liệu qua http, bạn sẽ muốn ngoại lệ (ví dụ trong trường hợp 404) để chuyển qua cái đã bắn nó. Nếu bạn không, bạn sẽ phải tạo ra một số cách để thông báo cho phần gọi về chất lượng của kết quả (lỗi: 404), cũng như chính kết quả.

Mặc dù có thể thử bắt ngoại lệ 404 bên trong chức năng của trình trợ giúp nhận / đăng dữ liệu, bạn có nên không? Có phải chỉ mình tôi sử dụng một phần nhỏ để biểu thị các trạng thái trong chương trình (và tất nhiên là ghi lại chúng một cách thích hợp), và sau đó sử dụng thông tin này cho mục đích xác thực vệ sinh (xử lý mọi thứ ok / lỗi) bên ngoài?

Cập nhật: Tôi đã mong đợi một ngoại lệ nghiêm trọng / không gây tử vong cho phân loại chính, nhưng tôi không muốn bao gồm điều này để không làm phương hại đến câu trả lời. Hãy để tôi làm rõ những gì câu hỏi là về: Xử lý các ngoại lệ được ném, không ném ngoại lệ. Hiệu ứng mong muốn là gì: Phát hiện lỗi và cố gắng khôi phục từ đó. Nếu không thể phục hồi, hãy cung cấp phản hồi có ý nghĩa nhất.

Một lần nữa, với ví dụ http get / post, câu hỏi là, bạn có nên cung cấp một đối tượng mới mô tả những gì đã xảy ra với người gọi ban đầu không? Nếu người trợ giúp này ở trong thư viện bạn đang sử dụng, bạn có mong muốn nó cung cấp cho bạn mã trạng thái cho hoạt động không, hoặc bạn sẽ đưa nó vào khối thử bắt? Nếu bạn đang thiết kế nó, bạn sẽ cung cấp một mã trạng thái hoặc ném một ngoại lệ và để cho cấp trên dịch nó thành một mã trạng thái / tin nhắn thay thế?

Tóm tắt: Làm thế nào để bạn chọn nếu một đoạn mã thay vì tạo ra một ngoại lệ, trả về mã trạng thái cùng với bất kỳ kết quả nào mà nó có thể mang lại?


1
Đó không phải là xử lý lỗi đang thay đổi hình thức xử lý lỗi đang được sử dụng. Trong trường hợp 404, bạn sẽ để nó đi qua vì bạn không thể xử lý nó.
ném đá

"một int đại diện cho một giá trị, 0 cho lỗi, 1 cho ok, 2 cho cảnh báo, v.v." Hãy nói rằng đây là một ví dụ điển hình! Sử dụng 0 để có nghĩa là OK chắc chắn là tiêu chuẩn ...
anaximander

Câu trả lời:


15

Trường hợp ngoại lệ nên được sử dụng cho các điều kiện đặc biệt. Ném một ngoại lệ về cơ bản là đưa ra tuyên bố, "Tôi không thể xử lý tình trạng này ở đây; ai đó có thể cao hơn trong ngăn xếp cuộc gọi bắt tôi và xử lý nó không?"

Trả lại một giá trị có thể tốt hơn, nếu rõ ràng rằng người gọi sẽ lấy giá trị đó và làm một cái gì đó có ý nghĩa với nó. Điều này đặc biệt đúng nếu ném một ngoại lệ có ý nghĩa về hiệu suất, nghĩa là nó có thể xảy ra trong một vòng lặp chặt chẽ. Ném một ngoại lệ mất nhiều thời gian hơn trả về một giá trị (ít nhất là hai bậc độ lớn).

Các ngoại lệ không bao giờ nên được sử dụng để thực hiện logic chương trình. Nói cách khác, đừng ném ngoại lệ để hoàn thành công việc; ném một ngoại lệ để nói rằng nó không thể được thực hiện.


Cảm ơn đã trả lời, đó là thông tin hữu ích nhất nhưng trọng tâm của tôi là xử lý ngoại lệ và không ném ngoại lệ. Bạn có nên bắt ngoại lệ 404 ngay khi bạn nhận được nó hay bạn nên để nó tăng cao hơn trong ngăn xếp?
Mihalis Bagos

Tôi thấy chỉnh sửa của bạn, nhưng nó không thay đổi câu trả lời của tôi. Chuyển đổi ngoại lệ thành mã lỗi nếu điều đó có ý nghĩa với người gọi. Nếu không, xử lý ngoại lệ; để nó đi lên ngăn xếp; hoặc bắt nó, làm một cái gì đó với nó (như viết nó vào một bản ghi, hoặc một cái gì đó khác), và suy nghĩ lại.
Robert Harvey

1
+1: cho một lời giải thích hợp lý và cân bằng. Một ngoại lệ nên được sử dụng để xử lý các trường hợp ngoại lệ. Mặt khác, một hàm hoặc phương thức sẽ trả về một giá trị có ý nghĩa (enum hoặc loại tùy chọn) và người gọi nên xử lý nó đúng cách. Có lẽ người ta có thể đề cập đến một sự thay thế thứ ba phổ biến trong lập trình chức năng, tức là tiếp tục.
Giorgio

4

Một lời khuyên tốt mà tôi từng đọc là, hãy đưa ra ngoại lệ khi bạn không thể tiến triển với trạng thái dữ liệu bạn đang xử lý, tuy nhiên nếu bạn có một phương pháp có thể đưa ra một ngoại lệ, thì cũng cung cấp phương pháp để xác nhận liệu dữ liệu có thực sự là hợp lệ trước khi phương thức được gọi.

Ví dụ: System.IO.File.OpenRead () sẽ ném FileNotFoundException nếu tệp được cung cấp không tồn tại, tuy nhiên, nó cũng cung cấp phương thức .Exists () trả về giá trị boolean cho biết liệu tệp có hiện diện mà bạn nên gọi trước không gọi OpenRead () để tránh mọi trường hợp ngoại lệ không mong muốn.

Để trả lời phần "khi nào tôi nên xử lý một ngoại lệ" của câu hỏi, tôi sẽ nói bất cứ nơi nào bạn thực sự có thể làm gì đó về nó. Nếu phương thức của bạn không thể xử lý ngoại lệ được ném bởi phương thức mà nó gọi, đừng bắt nó. Hãy để nó nâng cao hơn lên chuỗi cuộc gọi đến một cái gì đó có thể đối phó với nó. Trong một số trường hợp, đây có thể chỉ là một logger lắng nghe Application.UnhandledException.


Nhiều người, đặc biệt là các lập trình viên python , thích EAFP, tức là "Yêu cầu sự tha thứ dễ dàng hơn là xin phép"
Mark Booth

1
+1 cho nhận xét về việc tránh các ngoại lệ như với .Exists ().
mã hóa

+1 Đây vẫn là lời khuyên tốt. Trong mã tôi viết / quản lý, một Ngoại lệ là "Đặc biệt", 9/10 lần Ngoại lệ được dành cho nhà phát triển để xem, nó nói hey, bạn nên lập trình defensivley! Lần khác, đó là điều chúng tôi không thể giải quyết, và chúng tôi đăng nhập và thoát ra tốt nhất có thể. Sự đồng thuận là rất quan trọng, ví dụ, theo quy ước, thông thường chúng ta sẽ có một phản hồi sai sự thật và thông điệp nội bộ cho giá vé / xử lý tiêu chuẩn. Api của bên thứ 3 dường như đưa ra ngoại lệ cho mọi thứ có thể được xử lý tại cuộc gọi và được trả lại bằng quy trình đã được thống nhất.
Gavin Howden

3

sử dụng thử / bắt / cuối cùng để trả về enum (hoặc int đại diện cho một giá trị, 0 cho lỗi, 1 cho ok, 2 cho cảnh báo, v.v., tùy theo trường hợp) để câu trả lời luôn theo thứ tự,

Đó là một thiết kế khủng khiếp. Đừng "che dấu" một ngoại lệ bằng cách dịch sang mã số. Để nó như một ngoại lệ thích hợp, rõ ràng.

hoặc nên để ngoại lệ đi qua để phần gọi sẽ giải quyết?

Đó là những gì ngoại lệ dành cho.

xử lý càng gần nơi nó được nâng lên càng tốt

Không phải là một sự thật phổ quát ở tất cả. Đó là một ý tưởng tốt một số lần. Những lần khác, nó không hữu ích.


Nó không phải là một thiết kế khủng khiếp. Microsoft triển khai nó ở nhiều nơi, cụ thể là trên nhà cung cấp Thành viên asp.NET mặc định. Ngoài ra, tôi không thể thấy câu trả lời này đóng góp bất cứ điều gì cho cuộc trò chuyện
Mihalis Bagos

@MihalisBagos: Tất cả những gì tôi có thể làm là đề xuất rằng cách tiếp cận của Microsoft không được áp dụng bởi mọi ngôn ngữ lập trình. Trong các ngôn ngữ không có ngoại lệ, trả về một giá trị là điều cần thiết. C là ví dụ đáng chú ý nhất. Trong các ngôn ngữ có ngoại lệ, trả về "giá trị mã" để chỉ ra lỗi là một thiết kế tồi tệ. Nó dẫn đến (đôi khi) các ifcâu lệnh rườm rà thay vì (đôi khi) xử lý ngoại lệ đơn giản hơn. Câu trả lời ("Cách bạn chọn ...") rất đơn giản. Đừng . Tôi nghĩ rằng nói điều này thêm vào cuộc trò chuyện. Bạn có thể bỏ qua câu trả lời này, tuy nhiên.
S.Lott

Tôi không nói ý kiến ​​của bạn không được tính nhưng tôi đang nói ý kiến ​​của bạn không được phát triển. Với nhận xét đó, tôi lấy lý do là nơi chúng ta có thể sử dụng ngoại lệ, chúng ta nên, chỉ vì chúng ta có thể? Tôi không thấy mã trạng thái là mặt nạ, thay vì phân loại / tổ chức các trường hợp dòng mã
Mihalis Bagos

1
Ngoại lệ đã là một phân loại / tổ chức. Tôi không thấy giá trị trong việc thay thế một ngoại lệ rõ ràng bằng giá trị trả về có thể dễ bị nhầm lẫn với các giá trị trả về "bình thường" hoặc "không đặc biệt". Giá trị trả về phải luôn luôn không đặc biệt. Ý kiến ​​của tôi không được phát triển lắm: nó rất đơn giản. Đừng chuyển đổi một ngoại lệ thành mã số. Nó đã là một đối tượng dữ liệu hoàn toàn tốt, phục vụ tất cả các trường hợp sử dụng hoàn toàn tốt mà chúng ta có thể tưởng tượng.
S.Lott

3

Tôi đồng ý với S.Lott . Nắm bắt ngoại lệ càng gần càng tốt với nguồn có thể là một ý tưởng tốt hoặc một ý tưởng tồi tùy thuộc vào tình huống. Chìa khóa để xử lý các trường hợp ngoại lệ là chỉ bắt chúng khi bạn có thể làm gì đó với nó. Bắt chúng và trả về một giá trị số cho chức năng gọi nói chung là một thiết kế tồi. Bạn chỉ muốn để chúng nổi lên cho đến khi bạn có thể phục hồi.

Tôi luôn coi việc xử lý ngoại lệ là một bước đi khỏi logic ứng dụng của mình. Tôi tự hỏi mình, nếu ngoại lệ này bị ném thì sao cho ngăn xếp cuộc gọi tôi phải thu thập dữ liệu trước khi ứng dụng của tôi ở trạng thái có thể phục hồi? Trong rất nhiều trường hợp, nếu không có bất cứ điều gì tôi có thể làm trong ứng dụng để khôi phục, điều đó có nghĩa là tôi không nắm bắt được nó cho đến cấp cao nhất và chỉ đăng nhập ngoại lệ, thất bại công việc và cố gắng tắt sạch.

Thực sự không có quy tắc cứng và nhanh nào khi nào và làm thế nào để thiết lập xử lý ngoại lệ ngoài việc để chúng một mình cho đến khi bạn biết phải làm gì với chúng.


1

Ngoại lệ là những điều đẹp. Chúng cho phép bạn đưa ra một mô tả rõ ràng về một vấn đề thời gian chạy mà không cần dùng đến sự mơ hồ không cần thiết. Các ngoại lệ có thể được gõ, gõ phụ và có thể được xử lý theo loại. Chúng có thể được chuyển xung quanh để xử lý ở nơi khác và nếu không thể xử lý chúng, chúng có thể được nâng lên để xử lý ở lớp cao hơn trong ứng dụng của bạn. Họ cũng sẽ tự động quay trở lại từ phương thức của bạn mà không cần phải gọi nhiều logic điên để xử lý các mã lỗi bị che giấu.

Bạn nên ném một ngoại lệ ngay lập tức sau khi gặp dữ liệu không hợp lệ trong mã của bạn. Bạn nên thực hiện các cuộc gọi đến các phương thức khác trong một thử..catch..tất cả để xử lý bất kỳ trường hợp ngoại lệ nào có thể bị ném và nếu bạn không biết cách phản hồi với bất kỳ ngoại lệ cụ thể nào, bạn lại ném nó để chỉ ra các lớp cao hơn có một cái gì đó sai nên được xử lý ở nơi khác.

Quản lý mã lỗi có thể rất khó khăn. Bạn thường kết thúc với rất nhiều sự trùng lặp không cần thiết trong mã của mình và / hoặc rất nhiều logic lộn xộn để xử lý các trạng thái lỗi. Trở thành người dùng và gặp phải mã lỗi thậm chí còn tệ hơn, vì bản thân mã sẽ không có ý nghĩa và sẽ không cung cấp cho người dùng bối cảnh cho lỗi. Mặt khác, một ngoại lệ có thể cho người dùng biết điều gì đó hữu ích, như "Bạn quên nhập giá trị" hoặc "bạn đã nhập giá trị không hợp lệ, đây là phạm vi hợp lệ bạn có thể sử dụng ..." hoặc "Tôi không biết chuyện gì đã xảy ra, liên hệ với bộ phận hỗ trợ kỹ thuật và nói với họ rằng tôi vừa gặp sự cố và đưa cho họ dấu vết ngăn xếp sau đây ... ".

Vì vậy, câu hỏi của tôi với OP là tại sao trên Trái đất bạn KHÔNG muốn sử dụng ngoại lệ đối với việc trả lại mã lỗi?


0

Tất cả các câu trả lời tốt. Tôi cũng muốn thêm rằng việc trả lại mã lỗi thay vì ném ngoại lệ có thể làm cho mã của người gọi trở nên phức tạp hơn. Nói phương thức A gọi phương thức B gọi phương thức C và C gặp lỗi. Nếu C trả về mã lỗi, bây giờ B cần phải có logic để xác định xem nó có thể xử lý mã lỗi đó không. Nếu không thể thì nó cần phải trả lại cho A. Nếu A không thể xử lý lỗi thì bạn phải làm gì? Ném một ngoại lệ? Trong ví dụ này, mã sạch hơn nhiều nếu C chỉ cần ném ngoại lệ, B không bắt ngoại lệ để nó tự động hủy mà không cần thêm mã nào để làm như vậy và A có thể bắt được một số loại ngoại lệ nhất định trong khi để người khác tiếp tục cuộc gọi cây rơm.

Điều này mang đến cho một quy tắc tốt để mã bằng cách:

Các dòng mã giống như những viên đạn vàng. Bạn muốn sử dụng càng ít càng tốt để hoàn thành công việc.


0

Tôi đã sử dụng kết hợp cả hai giải pháp: đối với mỗi chức năng xác thực, tôi chuyển một bản ghi mà tôi điền vào trạng thái xác thực (mã lỗi). Ở cuối hàm, nếu tồn tại lỗi xác thực, tôi đưa ra một ngoại lệ, theo cách này tôi không ném ngoại lệ cho mỗi trường, mà chỉ một lần.

Tôi cũng đã lợi dụng việc ném một ngoại lệ sẽ dừng thực thi vì tôi không muốn việc thực thi tiếp tục khi dữ liệu không hợp lệ.

Ví dụ

procedure Validate(var R:TValidationRecord);
begin
  if Field1 is not valid then
  begin
    R.Field1ErrorCode=SomeErrorCode;
    ErrorFlag := True; 
  end; 
  if Field2 is not valid then
  begin
    R.Field2ErrorCode=SomeErrorCode;
    ErrorFlag := True; 
  end;
  if Field3 is not valid then
  begin
    R.Field3ErrorCode=SomeErrorCode;
    ErrorFlag := True; 
  end;

  if ErrorFlag then
    ThrowException
end;

Nếu chỉ dựa vào boolean, nhà phát triển sử dụng chức năng của tôi sẽ đưa vấn đề này vào tài khoản:

if not Validate() then
  DoNotContinue();

nhưng anh ta có thể quên và chỉ gọi Validate()(tôi biết rằng anh ta không nên, nhưng có lẽ anh ta có thể).

Vì vậy, trong đoạn mã trên tôi đã đạt được hai lợi thế:

  1. Chỉ có một ngoại lệ trong chức năng xác nhận.
  2. Ngoại lệ, thậm chí chưa được xử lý, sẽ dừng thực thi và xuất hiện vào thời gian thử nghiệm

0

Không có một câu trả lời nào ở đây - giống như không có một loại HTTPException.

Điều này có ý nghĩa rất lớn khi các thư viện HTTP cơ bản đưa ra một ngoại lệ khi họ nhận được phản hồi 4xx hoặc 5xx; lần trước tôi đã xem xét các thông số kỹ thuật HTTP là những lỗi.

Đối với việc ném ngoại lệ đó - hoặc gói lại và suy nghĩ lại - tôi nghĩ đó thực sự là một câu hỏi về trường hợp sử dụng. Ví dụ: nếu bạn đang viết một trình bao bọc để lấy một số dữ liệu từ API và hiển thị nó cho các ứng dụng, bạn có thể quyết định rằng về mặt ngữ nghĩa, yêu cầu tài nguyên không tồn tại trả về HTTP 404 sẽ có ý nghĩa hơn để nắm bắt điều đó và trả về null. Mặt khác, một lỗi 406 (không chấp nhận được) có thể đáng để ném lỗi vì điều đó có nghĩa là có gì đó đã thay đổi và ứng dụng sẽ bị sập và cháy và la hét để được giúp đỡ.

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.