Sử dụng tốt các khối bắt thử?


14

Tôi luôn thấy mình phải vật lộn với điều này ... cố gắng tìm sự cân bằng phù hợp giữa thử / bắt và mã không trở thành mớ hỗn độn của các tab, dấu ngoặc và ngoại lệ bị ném ngược lên ngăn xếp cuộc gọi như một củ khoai tây nóng. Ví dụ: tôi có một ứng dụng tôi đang phát triển sử dụng SQLite. Tôi có giao diện Cơ sở dữ liệu trừu tượng hóa các lệnh gọi SQLite và Mô hình chấp nhận mọi thứ đi vào / ra khỏi Cơ sở dữ liệu ... Vì vậy, nếu / khi xảy ra ngoại lệ SQLite, nó phải được đưa lên Mô hình (người gọi nó là Mô hình ), người phải chuyển nó cho bất cứ ai được gọi là AddRecord / DeleteRecord / bất cứ điều gì ...  

Tôi là người hâm mộ các trường hợp ngoại lệ trái ngược với việc trả lại mã lỗi vì mã lỗi có thể bị bỏ qua, bị lãng quên, v.v., trong khi đó Ngoại lệ về cơ bản phải được xử lý (được cấp, tôi có thể bắt và di chuyển ngay lập tức ...) chắc chắn phải có một cách tốt hơn những gì tôi đã diễn ra ngay bây giờ.

Chỉnh sửa:  Tôi nên có cụm từ này một chút khác nhau. Tôi hiểu để ném lại như các loại khác nhau và như vậy, tôi đã nói từ đó kém và đó là lỗi của riêng tôi. Câu hỏi của tôi là ... làm thế nào tốt nhất để giữ mã sạch khi làm như vậy? Nó chỉ bắt đầu cảm thấy vô cùng bừa bộn với tôi sau một thời gian.


Ngôn ngữ lập trình nào?
Apalala

2
C # tại thời điểm này, nhưng tôi đang cố gắng suy nghĩ nói chung.
trycatch

C # không buộc phải khai báo các ngoại lệ, điều này giúp xử lý các ngoại lệ dễ dàng hơn khi hợp lý và tránh các lập trình viên bị cám dỗ bắt chúng mà không thực sự xử lý chúng. Anders Hejlsberg, nhà thiết kế của C #, đưa ra trường hợp chống lại các ngoại lệ được kiểm tra trong bài viết này artima.com/intv/handcuffs.html
Apalala

Câu trả lời:


13

Hãy nghĩ về nó theo kiểu gõ mạnh, ngay cả khi bạn không sử dụng ngôn ngữ được gõ mạnh - nếu phương thức của bạn không thể trả về loại bạn mong đợi, thì nó sẽ bị ném ngoại lệ.

Ngoài ra, thay vì ném toàn bộ nhận thức cho mô hình (hoặc tệ hơn là UI), mỗi lớp nên nắm bắt các ngoại lệ đã biết và bọc / biến đổi / thay thế chúng bằng các ngoại lệ phù hợp với lớp đó:

Layer      Handles Exception
----------------------------
UI         DataNotFoundException
Model      DatabaseRetrievalException
DAO        SQLException

Điều này sẽ giúp hạn chế số lượng ngoại lệ bạn đang tìm kiếm trong mỗi lớp và giúp bạn duy trì một hệ thống ngoại lệ có tổ chức.


Tôi đã thực hiện một vài chỉnh sửa, từ ngữ Q ban đầu kém .. câu hỏi của tôi là về cách giữ mã sạch trong khi xử lý tất cả các thử và bắt và không có gì. Nó bắt đầu cảm thấy rất lộn xộn khi thử các khối bắt ở khắp mọi nơi ...
trycatch

1
@Ed không làm phiền bạn rằng UI hoặc Model của bạn đang bắt "SQLException"? Đối với tôi, điều đó không liên quan lắm. Để mỗi người của họ, tôi đoán.
Nicole

1
@Ed, đó có thể là loại OK trong các ngôn ngữ không kiểm tra ngoại lệ, nhưng trong các ngôn ngữ có ngoại lệ được kiểm tra, thực sự xấu khi áp dụng throws SQLExceptionmột phương thức không ngụ ý rằng SQL thậm chí còn liên quan. Và điều gì xảy ra nếu bạn quyết định rằng một số thao tác nên đến một cửa hàng tệp? Bây giờ bạn phải khai báo throws SQLException, IOException, vv Nó sẽ ra khỏi tầm tay.
Mike Daniels

1
@Ed cho người dùng cuối SQLException không có nghĩa gì nhiều, đặc biệt là nếu hệ thống của bạn có thể hỗ trợ nhiều loại kiên trì ngoài cơ sở dữ liệu quan hệ. Là một lập trình viên gui, tôi muốn đối phó với DataNotFoundException hơn là toàn bộ các trường hợp ngoại lệ cấp thấp. Rất thường là một ngoại lệ đối với thư viện cấp thấp chỉ là một phần của cuộc sống bình thường ở trên, trong trường hợp này chúng dễ xử lý hơn khi mức độ trừu tượng của chúng khớp với ứng dụng.
Newtopian

1
@Newtopian - Tôi chưa bao giờ nói trình bày Ngoại lệ thô cho người dùng cuối. Đối với người dùng cuối, một thông báo 'Chương trình đơn giản đã ngừng hoạt động' là đủ trong khi đăng nhập Ngoại lệ ở đâu đó hữu ích để nhận hỗ trợ. Trong chế độ Gỡ lỗi, rất hữu ích để hiển thị Ngoại lệ. Bạn không nên quan tâm đến mọi Ngoại lệ có thể có, API có thể ném trừ khi bạn có một trình xử lý cụ thể cho các Ngoại lệ đó; chỉ cần để người bắt Exception không xử lý của bạn xử lý tất cả.
Ed James

9

Các ngoại lệ cho phép viết mã sạch hơn vì phần lớn nó xử lý trường hợp thông thường và các trường hợp ngoại lệ có thể được xử lý sau này, ngay cả trong một bối cảnh khác.

Quy tắc xử lý (bắt) ngoại lệ là nó phải được thực hiện bởi bối cảnh thực sự có thể làm gì đó về nó. Nhưng điều đó có một ngoại lệ:

Các ngoại lệ phải được bắt tại các ranh giới mô-đun (ranh giới lớp đặc biệt) và ngay cả khi chỉ kèm theo chúng, một ngoại lệ cấp cao hơn có ý nghĩa đối với người gọi. Mỗi mô-đun và lớp phải ẩn các chi tiết triển khai của nó ngay cả liên quan đến các ngoại lệ (một mô-đun heap có thể ném HeapFull nhưng không bao giờ ArrayIndexOutOfBound).

Trong ví dụ của bạn, không chắc là các lớp trên có thể làm bất cứ điều gì về ngoại lệ SQLite (nếu chúng xảy ra, thì tất cả đều được ghép với SQLite đến mức bạn sẽ không thể chuyển lớp dữ liệu sang thứ khác). Có một số lý do có thể thấy trước cho những thứ như Thêm / Xóa / Cập nhật không thành công và một số trong số chúng (thay đổi không tương thích trong giao dịch đồng thời) không thể phục hồi ngay cả trong lớp dữ liệu / lưu trữ (vi phạm quy tắc toàn vẹn, fi). Lớp kiên trì sẽ dịch các ngoại lệ thành một cái gì đó có ý nghĩa trong các thuật ngữ của lớp mô hình để các lớp trên có thể quyết định thử lại hoặc thất bại một cách duyên dáng.


Tôi đồng ý và chỉnh sửa câu hỏi của mình để phản ánh rằng tôi đã nói sai từ đâu. Tôi có nghĩa là đây là một câu hỏi làm thế nào để giữ cho sự cố gắng và nắm bắt từ những thứ lộn xộn lên.
trycatch

Bạn có thể áp dụng các nguyên tắc @Ed James và tôi đã đề cập trong một lớp hoặc mô-đun. Thay vì gọi SQLite trực tiếp từ mọi nơi, hãy có một vài phương thức / hàm nói chuyện với SQLite và phục hồi từ các ngoại lệ hoặc dịch chúng sang các phương thức chung hơn. Nếu một giao dịch liên quan đến một số truy vấn và cập nhật, bạn không cần phải xử lý mọi ngoại lệ có thể xảy ra trong mỗi giao dịch: một lần thử bên ngoài duy nhất có thể dịch các ngoại lệ và một giao dịch bên trong có thể xử lý các cập nhật một phần với một rollback. Bạn cũng có thể di chuyển các bản cập nhật sang chức năng của riêng họ để xử lý ngoại lệ đơn giản hơn.
Apalala

1
công cụ này sẽ dễ hiểu hơn với các ví dụ mã ..
Nhấp vào Upvote

Đây có lẽ là một câu hỏi phù hợp hơn với stackoverflow ngay từ đầu.
Apalala

5

Theo nguyên tắc chung, bạn chỉ nên bắt Ngoại lệ cụ thể (ví dụ IOException) và chỉ khi bạn có việc cụ thể cần thực hiện khi bạn bắt gặp Ngoại lệ.

Mặt khác, tốt nhất là để Ngoại lệ nổi lên trên bề mặt để chúng có thể được phơi bày và xử lý. Một số người gọi đây là thất bại nhanh chóng.

Bạn nên có một số loại trình xử lý ở gốc ứng dụng của bạn để bắt các Ngoại lệ chưa được xử lý đã nổi lên từ bên dưới. Điều này cho bạn cơ hội để trình bày, báo cáo hoặc quản lý Ngoại lệ theo cách phù hợp.

Gói ngoại lệ rất hữu ích khi bạn cần ném Ngoại lệ qua hệ thống phân tán và máy khách không có định nghĩa về lỗi phía máy chủ.


Bắt và im lặng ngoại lệ là một điều khủng khiếp để làm. Nếu chương trình bị hỏng, nó sẽ bị sập với một ngoại lệ và một dấu vết. Nó nên nhầm lẫn với việc âm thầm ghi lại mọi thứ nhưng làm cho dữ liệu bị rối tung.
S.Lott

1
@ S.Lott Tôi đồng ý rằng người ta không nên im lặng ngoại lệ vì họ thấy chúng gây phiền nhiễu nhưng chỉ làm sập một ứng dụng thì hơi cực. Có nhiều trường hợp có thể bắt ngoại lệ và thiết lập lại hệ thống ở trạng thái đã biết, trong những trường hợp như vậy, trình xử lý toàn cầu của mọi thứ khá hữu ích. Tuy nhiên, nếu quá trình thiết lập lại thất bại lần lượt theo cách không thể xử lý một cách an toàn thì có, hãy để nó sụp đổ tốt hơn nhiều so với việc dính đầu vào cát.
Newtopian

2
một lần nữa, tất cả phụ thuộc vào những gì bạn đang xây dựng nhưng tôi thường không đồng ý với việc chỉ để chúng nổi bong bóng vì nó tạo ra sự rò rỉ trừu tượng. Khi tôi sử dụng API, tôi rất muốn thấy nó phơi bày các ngoại lệ phù hợp với mô hình của API. Tôi cũng ghét những điều bất ngờ vì vậy tôi ghét khi một API chỉ để ngoại lệ liên quan đến việc triển khai nó không được thông báo. Nếu tôi không biết điều gì đang đến với tôi thì làm sao tôi có thể phản ứng! Tôi thấy mình quá thường xuyên sử dụng lưới bắt rộng hơn nhiều để ngăn chặn những điều bất ngờ làm sập ứng dụng của tôi không báo trước.
Newtopian

@Newtopian: Ngoại lệ "Đóng gói" và "Viết lại" giảm rò rỉ trừu tượng. Họ vẫn bong bóng thích hợp. "Nếu tôi không biết điều gì đang đến với mình thì làm sao tôi có thể phản ứng! Tôi thấy mình quá thường xuyên sử dụng lưới bắt rộng hơn nhiều" Có phải là điều chúng tôi đề nghị bạn ngừng làm. Bạn không cần phải nắm bắt mọi thứ. 80% thời gian, điều đúng đắn là không bắt được gì. 20% thời gian có một phản ứng có ý nghĩa.
S.Lott

1
@Newtopian, tôi nghĩ rằng chúng ta cần phân biệt giữa các ngoại lệ thường được ném bởi một đối tượng và do đó nên được bọc và các ngoại lệ phát sinh do lỗi trong mã của đối tượng và không nên được bọc.
Winston Ewert

3

Hãy tưởng tượng rằng bạn đang viết một lớp stack. Bạn không đặt bất kỳ mã xử lý ngoại lệ nào trong lớp, do đó, nó có thể tạo ra các ngoại lệ sau.

  1. ArrayIndexError - được nâng lên khi người dùng cố gắng bật từ ngăn xếp trống
  2. NullPtrException - được nêu ra do lỗi trong quá trình triển khai gây ra nỗ lực tham chiếu tham chiếu null

Một cách tiếp cận đơn giản để bọc các ngoại lệ có thể quyết định bọc cả hai ngoại lệ này trong một lớp ngoại lệ StackError. Tuy nhiên, điều này thực sự bỏ lỡ các điểm ngoại lệ gói. Nếu một đối tượng ném một ngoại lệ cấp thấp có nghĩa là đối tượng bị hỏng. Tuy nhiên, có một trường hợp điều này được chấp nhận: khi thực tế đối tượng bị hỏng.

Điểm của các ngoại lệ gói là đối tượng nên đưa ra các ngoại lệ thích hợp cho các lỗi thông thường. Ngăn xếp sẽ tăng StackEmpty chứ không phải ArrayIndexError khi xuất hiện từ ngăn xếp trống. Mục đích là không tránh ném các ngoại lệ khác nếu đối tượng hoặc mã bị hỏng.

Điều chúng tôi thực sự muốn tránh là nắm bắt các ngoại lệ cấp thấp đã được truyền qua các đối tượng cấp cao. Một lớp ngăn xếp ném ra một Array IndexError khi xuất hiện từ một ngăn xếp trống là một vấn đề nhỏ. Nếu bạn thực sự bắt được ArrayIndexError đó thì chúng tôi có một vấn đề nghiêm trọng. Tuyên truyền các lỗi cấp thấp là một tội lỗi ít nghiêm trọng hơn sau đó bắt chúng.

Để đưa điều này trở lại ví dụ của bạn về một nhận thức SQLException: tại sao bạn lại nhận được các tham số SQLEx? Một lý do là bởi vì bạn đang chuyển các truy vấn không hợp lệ. Tuy nhiên, nếu lớp truy cập dữ liệu của bạn đang tạo ra các truy vấn xấu, thì nó bị hỏng. Nó không nên cố gắng viết lại sự hỏng hóc của nó trong một ngoại lệ DataAccessFailure.

Tuy nhiên, một nhận thức SQLEx cũng có thể phát sinh do mất kết nối với cơ sở dữ liệu. Chiến lược của tôi về điểm đó là bắt ngoại lệ ở tuyến phòng thủ cuối cùng, báo cáo với người dùng rằng kết nối cơ sở dữ liệu bị mất và tắt máy. Vì ứng dụng bị mất quyền truy cập vào cơ sở dữ liệu, nên thực sự không thể làm gì nhiều hơn nữa.

Tôi không biết mã của bạn trông như thế nào. Nhưng có vẻ như bạn có thể đang mù quáng chuyển tất cả các ngoại lệ sang các ngoại lệ cấp cao hơn. Bạn chỉ nên làm điều đó trong một số ít trường hợp. Hầu hết các trường hợp ngoại lệ cấp thấp hơn chỉ ra lỗi trong mã của bạn. Bắt và quấn lại chúng là phản tác dụng.

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.