Tại sao lại dùng thử cuối cùng mà không có mệnh đề bắt?


79

Cách cổ điển để lập trình là với try ... catch. Khi nào thì thích hợp để sử dụng trymà không cần catch?

Trong Python, phần sau đây có vẻ hợp pháp và có thể có nghĩa:

try:
  #do work
finally:
  #do something unconditional

Tuy nhiên, mã không có catchgì. Tương tự như vậy, người ta có thể nghĩ trong Java, nó sẽ như sau:

try {
    //for example try to get a database connection
}
finally {
  //closeConnection(connection)
}

Nó có vẻ tốt và đột nhiên tôi không phải lo lắng về các loại ngoại lệ, vv Nếu đây là thực hành tốt, khi nào nó là thực hành tốt? Ngoài ra, những lý do tại sao điều này là không thực hành tốt hoặc không hợp pháp? (Tôi đã không biên dịch nguồn. Tôi đang hỏi về nó vì nó có thể là một lỗi cú pháp cho Java. Tôi đã kiểm tra xem Python có chắc chắn biên dịch không.)

Một vấn đề liên quan tôi gặp phải là: Tôi tiếp tục viết hàm / phương thức, cuối cùng nó phải trả về một cái gì đó. Tuy nhiên, nó có thể ở một nơi không nên đến và phải là điểm quay lại. Vì vậy, ngay cả khi tôi xử lý các trường hợp ngoại lệ ở trên, tôi vẫn trả về NULLhoặc một chuỗi trống tại một số điểm trong mã không nên đạt được, thường là kết thúc của phương thức / hàm. Tôi đã luôn quản lý để cơ cấu lại mã để nó không phải như vậy return NULL, vì điều đó hoàn toàn có vẻ như ít hơn so với thực tiễn tốt.


4
Trong Java, tại sao không đặt câu lệnh return ở cuối khối thử?
kevin cline

2
Tôi rất buồn khi thử..finally và try..catch cả hai đều sử dụng từ khóa try, ngoài cả hai đều bắt đầu bằng thử chúng là hai cấu trúc hoàn toàn khác nhau.
Pieter B

3
try/catchkhông phải là "cách cổ điển để lập trình." Đó là cách lập trình C ++ cổ điển, bởi vì C ++ thiếu cấu trúc thử / cuối cùng thích hợp, điều đó có nghĩa là bạn phải thực hiện các thay đổi trạng thái đảo ngược được bảo đảm bằng cách sử dụng các bản hack xấu xí liên quan đến RAII. Nhưng các ngôn ngữ OO phong nha không có vấn đề đó, vì chúng cung cấp thử / cuối cùng. Nó được sử dụng cho mục đích rất khác so với thử / bắt.
Mason Wheeler

3
Tôi thấy nó rất nhiều với các tài nguyên kết nối bên ngoài. Bạn muốn có ngoại lệ nhưng cần đảm bảo rằng bạn không để lại kết nối mở, v.v ... Nếu bạn bắt được nó, bạn sẽ chỉ cần thiết lập lại nó cho lớp tiếp theo trong một số trường hợp.
Rig

4
@MasonWheeler "hack xấu xí" , làm ơn giải thích điều gì là xấu khi có một đối tượng xử lý việc dọn dẹp của chính nó?
Baldrickk

Câu trả lời:


146

Nó phụ thuộc vào việc bạn có thể đối phó với các ngoại lệ có thể được nêu ra tại thời điểm này hay không.

Nếu bạn có thể xử lý các ngoại lệ cục bộ bạn nên, và tốt hơn là xử lý lỗi càng gần nơi nó được nêu ra càng tốt.

Nếu bạn không thể xử lý chúng cục bộ thì chỉ cần có một try / finallykhối là hoàn toàn hợp lý - giả sử có một số mã bạn cần thực thi bất kể phương thức có thành công hay không. Ví dụ: (từ nhận xét của Neil ), mở một luồng và sau đó truyền luồng đó đến một phương thức bên trong sẽ được tải là một ví dụ tuyệt vời khi bạn cần try { } finally { }, sử dụng mệnh đề cuối cùng để đảm bảo rằng luồng được đóng bất kể thành công hay thất bại của việc đọc.

Tuy nhiên, bạn vẫn sẽ cần một trình xử lý ngoại lệ ở đâu đó trong mã của bạn - trừ khi bạn muốn ứng dụng của mình bị sập hoàn toàn. Nó phụ thuộc vào kiến ​​trúc của ứng dụng của bạn chính xác nơi xử lý đó.


8
"và tốt hơn là xử lý lỗi càng gần nơi nó được nâng lên càng tốt." eh, nó phụ thuộc Nếu bạn có thể phục hồi và vẫn hoàn thành nhiệm vụ (có thể nói), tất nhiên là có. Nếu bạn không thể, tốt hơn là để ngoại lệ đi đến đỉnh, nơi mà (có thể) sẽ cần phải có sự can thiệp của người dùng để đối phó với những gì đã xảy ra.
Will

13
@will - đó là lý do tại sao tôi sử dụng cụm từ "càng tốt".
ChrisF

8
Câu trả lời hay, nhưng tôi sẽ thêm một ví dụ: Mở một luồng và truyền luồng đó đến một phương thức bên trong sẽ được tải là một ví dụ tuyệt vời khi bạn cần try { } finally { }, tận dụng mệnh đề cuối cùng để đảm bảo luồng cuối cùng được đóng bất kể thành công / sự thất bại.
Neil

bởi vì đôi khi tất cả các cách trên đầu là gần như người ta có thể làm
Newtopian

"Chỉ cần có một khối thử / cuối cùng là hoàn toàn hợp lý" đã tìm kiếm chính xác cho câu trả lời này. cảm ơn bạn @ChrisF
neal aise

36

Các finallykhối được sử dụng để mã mà phải luôn luôn chạy, cho dù một điều kiện lỗi (ngoại lệ) đã xảy ra hay không.

Mã trong finallykhối được chạy sau khi trykhối hoàn thành và, nếu xảy ra ngoại lệ bị bắt, sau khi catchkhối tương ứng hoàn thành. Nó luôn luôn chạy, ngay cả khi một ngoại lệ chưa được phát hiện trong khối tryhoặc catch.

Các finallykhối thường được sử dụng cho các tập tin kết thúc, kết nối mạng, vv đã được mở trong trykhối. Lý do là tập tin hoặc kết nối mạng phải được đóng lại, cho dù thao tác sử dụng tập tin hoặc kết nối mạng đó đã thành công hay không.

Cần thận trọng trong finallykhối để đảm bảo rằng bản thân nó không ném ngoại lệ. Ví dụ, hãy chắc chắn kiểm tra tất cả các biến null, v.v.


8
+1: Đó là thành ngữ cho "phải được dọn sạch". Hầu hết việc sử dụng try-finallycó thể được thay thế bằng một withtuyên bố.
S.Lott

12
Các ngôn ngữ khác nhau có các cải tiến ngôn ngữ cụ thể cực kỳ hữu ích cho try/finallycấu trúc. C # có using, Python có with, v.v.
yfeldblum

5
@yfeldblum - có một sự khác biệt tinh tế giữa usingtry-finally, vì Disposephương thức sẽ không được gọi bởi usingkhối nếu một ngoại lệ xảy ra trong hàm tạo của IDisposableđối tượng. try-finallycho phép bạn thực thi mã ngay cả khi hàm tạo của đối tượng ném ngoại lệ.
Scott Whitlock

5
@ScottWhitlock: Đó là một điều tốt ? Bạn đang cố gắng làm gì, gọi một phương thức trên một đối tượng không có cấu trúc? Đó là một tỷ loại xấu.
DeadMG


17

Một ví dụ trong đó thử ... cuối cùng không có mệnh đề bắt là phù hợp (và thậm chí nhiều hơn, thành ngữ ) trong Java là việc sử dụng Khóa trong gói khóa tiện ích đồng thời.

  • Dưới đây là cách giải thích và chứng minh trong tài liệu API (phông chữ đậm trong trích dẫn là của tôi):

    ... Việc không có khóa có cấu trúc khối sẽ loại bỏ việc giải phóng khóa tự động xảy ra với các phương thức và câu lệnh được đồng bộ hóa. Trong hầu hết các trường hợp, nên sử dụng thành ngữ sau :

     Lock l = ...;
     l.lock();
     try {
         // access the resource protected by this lock
     } finally {
         l.unlock();
     }
    

    Khi khóa và mở khóa xảy ra ở các phạm vi khác nhau, phải cẩn thận để đảm bảo rằng tất cả mã được thực thi trong khi khóa được giữ được bảo vệ bằng cách thử cuối cùng hoặc thử bắt để đảm bảo khóa được giải phóng khi cần thiết .


Tôi có thể đặt l.lock()bên trong thử không? try{ l.lock(); }finally{l.unlock();}
RMachnik

1
về mặt kỹ thuật, bạn có thể. Tôi đã không đặt nó ở đó bởi vì về mặt ngữ nghĩa, nó có ý nghĩa ít hơn. trytrong đoạn trích này nhằm mục đích bao bọc quyền truy cập tài nguyên, tại sao lại gây ô nhiễm nó với thứ gì đó không liên quan đến điều đó
gnat

4
Bạn có thể, nhưng nếu l.lock()thất bại, finallykhối vẫn sẽ chạy nếu l.lock()nằm trong trykhối. Nếu bạn làm như gnat gợi ý, finallykhối sẽ chỉ chạy khi chúng tôi biết rằng khóa đã được mua.
Wtrmute

12

Ở cấp độ cơ bản catchfinallygiải quyết hai vấn đề liên quan nhưng khác nhau:

  • catch được sử dụng để xử lý sự cố được báo cáo bởi mã bạn đã gọi
  • finally được sử dụng để dọn sạch dữ liệu / tài nguyên mà mã hiện tại được tạo / sửa đổi, bất kể có sự cố xảy ra hay không

Vì vậy, cả hai đều liên quan đến một số vấn đề (ngoại lệ), nhưng đó là khá nhiều tất cả những gì họ có chung.

Một sự khác biệt quan trọng là finallykhối phải ở cùng một phương thức nơi tài nguyên được tạo (để tránh rò rỉ tài nguyên) và không thể được đặt ở một cấp độ khác trong ngăn xếp cuộc gọi.

Các catchtuy nhiên là một vấn đề khác nhau: địa điểm chính xác cho nó phụ thuộc vào nơi bạn thực sự có thể xử lý các ngoại lệ. Không có ích gì trong việc bắt ngoại lệ tại một nơi mà bạn không thể làm gì về nó, do đó, đôi khi chỉ đơn giản là để nó rơi qua.


2
Nitpick: "... khối cuối cùng phải ở cùng phương thức mà tài nguyên đã được tạo ..." . Đó chắc chắn là một ý tưởng tốt để làm theo cách đó, bởi vì dễ thấy hơn là không có rò rỉ tài nguyên. Tuy nhiên, nó không phải là điều kiện tiên quyết cần thiết; tức là bạn không phải làm theo cách đó. Bạn có thể giải phóng tài nguyên trong finallycâu lệnh thử (kèm theo tĩnh hoặc động) ... và vẫn không bị rò rỉ 100%.
Stephen C

6

@yfeldblum có câu trả lời đúng: cuối cùng không có câu lệnh bắt thường nên được thay thế bằng cấu trúc ngôn ngữ phù hợp.

Trong C ++, nó sử dụng RAII và các hàm tạo / hàm hủy; trong Python đó là một withtuyên bố; và trong C #, đó là một usingtuyên bố.

Chúng hầu như luôn thanh lịch hơn vì mã khởi tạo và mã hoàn thiện nằm ở một nơi (đối tượng được trừu tượng hóa) chứ không phải ở hai nơi.


2
Và trong Java, các khối ARM
MarkJ

2
Giả sử bạn có một đối tượng được thiết kế tồi (Ví dụ: một đối tượng không triển khai IDis Dùng một cách thích hợp trong C #) không phải lúc nào cũng là một lựa chọn khả thi.
mootinator

1
@mootinator: bạn không thể thừa hưởng từ đối tượng được thiết kế xấu và sửa nó sao?
Neil G

2
Hay đóng gói? Bah Ý tôi là có, tất nhiên.
mootinator

4

Trong nhiều ngôn ngữ, một finallycâu lệnh cũng chạy sau câu lệnh return. Điều này có nghĩa là bạn có thể làm một cái gì đó như:

try {
  // Do processing
  return result;
} finally {
  // Release resources
}

Việc giải phóng các tài nguyên bất kể phương thức đã kết thúc như thế nào với một ngoại lệ hoặc câu lệnh trả về thông thường.

Cho dù điều này tốt hay xấu là tùy tranh luận, nhưng try {} finally {}không phải lúc nào cũng bị giới hạn trong việc xử lý ngoại lệ.


0

Tôi có thể viện dẫn cơn thịnh nộ của Pythonistas (không biết vì tôi không sử dụng Python nhiều) hoặc lập trình viên từ các ngôn ngữ khác có câu trả lời này, nhưng theo tôi, hầu hết các chức năng không nên có một catchkhối, nói một cách lý tưởng. Để cho thấy lý do tại sao, hãy để tôi đối chiếu điều này với việc truyền mã lỗi thủ công thuộc loại tôi phải làm khi làm việc với Turbo C vào cuối những năm 80 và đầu thập niên 90.

Vì vậy, giả sử chúng ta có một chức năng để tải một hình ảnh hoặc một cái gì đó tương tự để đáp ứng với người dùng chọn một tệp hình ảnh để tải và điều này được viết bằng C và lắp ráp:

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

Tôi đã bỏ qua một số chức năng cấp thấp nhưng chúng ta có thể thấy rằng tôi đã xác định các loại chức năng khác nhau, được mã hóa màu, dựa trên trách nhiệm của chúng đối với việc xử lý lỗi.

Điểm thất bại và phục hồi

Bây giờ không bao giờ khó để viết các loại chức năng mà tôi gọi là "điểm có thể xảy ra lỗi" (các chức năng throw, nghĩa là) và các chức năng "phục hồi lỗi và báo cáo" ( catchnghĩa là).

Những chức năng luôn tầm thường để viết một cách chính xác trước khi xử lý ngoại lệ là có sẵn từ một chức năng mà có thể chạy vào một thất bại bên ngoài, giống như thất bại trong việc cấp phát bộ nhớ, chỉ có thể trả về một NULLhoặc 0hoặc -1hoặc thiết lập một mã lỗi toàn cầu hoặc một cái gì đó để tác động này. Và phục hồi / báo cáo lỗi luôn dễ dàng vì một khi bạn đã tìm cách ngăn xếp cuộc gọi đến điểm có ý nghĩa để khôi phục và báo cáo lỗi, bạn chỉ cần lấy mã lỗi và / hoặc thông báo và báo cáo cho người dùng. Và tự nhiên, một chức năng trong lá của hệ thống phân cấp này không bao giờ có thể thất bại cho dù nó có thay đổi như thế nào trong tương lai ( Convert Pixel) rất đơn giản để viết chính xác (ít nhất là đối với việc xử lý lỗi).

Sự truyền lỗi

Tuy nhiên, các chức năng tẻ nhạt dễ bị lỗi của con người là các bộ truyền lỗi , những chức năng không trực tiếp gặp sự cố nhưng được gọi là các chức năng có thể thất bại ở đâu đó sâu hơn trong hệ thống phân cấp. Vào thời điểm đó, Allocate Scanlinecó thể phải xử lý một sự thất bại từ mallocvà sau đó trả về một lỗi xuống Convert Scanlines, sau đó Convert Scanlinessẽ phải kiểm tra xem có lỗi đó và vượt qua nó xuống Decompress Image, sau đó Decompress Image->Parse Image, và Parse Image->Load Image, và Load Imageđể lệnh do người dùng cuối mà lỗi cuối cùng được báo cáo .

Đây là nơi rất nhiều người mắc lỗi vì chỉ cần một người truyền lỗi không kiểm tra và chuyển lỗi cho toàn bộ hệ thống phân cấp các chức năng để lật đổ khi xử lý lỗi đúng.

Hơn nữa, nếu các mã lỗi được trả về bởi các hàm, chúng ta sẽ mất rất nhiều khả năng, giả sử, 90% cơ sở mã của chúng ta, để trả về các giá trị quan tâm khi thành công vì rất nhiều hàm sẽ phải dự trữ giá trị trả về của chúng để trả về mã lỗi thất bại .

Giảm lỗi của con người: Mã lỗi toàn cầu

Vậy làm thế nào chúng ta có thể giảm khả năng lỗi của con người? Ở đây tôi thậm chí có thể viện dẫn cơn thịnh nộ của một số lập trình viên C, nhưng theo tôi, một cải tiến ngay lập tức là sử dụng mã lỗi toàn cầu , như OpenGL với glGetError. Điều này ít nhất giải phóng các chức năng để trả về các giá trị quan tâm có ý nghĩa về thành công. Có nhiều cách để làm cho luồng này an toàn và hiệu quả trong đó mã lỗi được bản địa hóa thành một luồng.

Cũng có một số trường hợp hàm có thể gặp lỗi nhưng tương đối vô hại để nó tiếp tục tồn tại lâu hơn một chút trước khi nó trả về sớm do phát hiện ra lỗi trước đó. Điều này cho phép điều đó xảy ra mà không phải kiểm tra lỗi đối với 90% các lệnh gọi chức năng được thực hiện trong mỗi chức năng, do đó nó vẫn có thể cho phép xử lý lỗi thích hợp mà không quá tỉ mỉ.

Giảm lỗi của con người: Xử lý ngoại lệ

Tuy nhiên, giải pháp trên vẫn đòi hỏi rất nhiều chức năng để xử lý khía cạnh luồng điều khiển của việc truyền lỗi thủ công, ngay cả khi nó có thể làm giảm số lượng dòng if error happened, return errormã loại thủ công. Nó sẽ không loại bỏ nó hoàn toàn vì thường vẫn cần phải có ít nhất một nơi kiểm tra lỗi và trả lại cho hầu hết mọi chức năng lan truyền lỗi. Vì vậy, đây là khi xử lý ngoại lệ đi vào hình ảnh để lưu ngày (sorta).

Nhưng giá trị của xử lý ngoại lệ ở đây là giải phóng nhu cầu xử lý khía cạnh luồng điều khiển của việc truyền lỗi thủ công. Điều đó có nghĩa là giá trị của nó được gắn với khả năng tránh phải viết một catchkhối thuyền trong toàn bộ cơ sở mã của bạn. Trong sơ đồ trên, nơi duy nhất cần phải có một catchkhối là Load Image User Commandnơi báo cáo lỗi. Không có gì khác lý tưởng phải có catchbất cứ điều gì vì nếu không, nó bắt đầu trở nên tẻ nhạt và dễ bị lỗi như xử lý mã lỗi.

Vì vậy, nếu bạn hỏi tôi, nếu bạn có một codebase rằng thực sự hưởng lợi từ ngoại lệ xử lý một cách tao nhã, nó cần phải có tối thiểu số lượng catchcác khối (bằng cách tối thiểu tôi không có nghĩa là không, nhưng nhiều hơn như một cho mỗi cao độc đáo hoạt động của người dùng cuối có thể thất bại và thậm chí có thể ít hơn nếu tất cả các hoạt động của người dùng cao cấp được gọi thông qua hệ thống lệnh trung tâm).

Dọn dẹp tài nguyên

Tuy nhiên, xử lý ngoại lệ chỉ giải quyết được yêu cầu tránh xử lý thủ công các khía cạnh luồng điều khiển của lan truyền lỗi trong các đường dẫn đặc biệt tách biệt với các luồng thực thi thông thường. Thường thì một chức năng đóng vai trò là công cụ truyền lỗi, ngay cả khi nó tự động thực hiện điều này với EH, vẫn có thể có được một số tài nguyên cần thiết để phá hủy. Ví dụ, một chức năng như vậy có thể mở một tệp tạm thời mà nó cần phải đóng trước khi quay trở lại từ chức năng đó, hoặc khóa một mutex mà nó cần để mở khóa bất kể điều gì.

Đối với điều này, tôi có thể viện dẫn cơn thịnh nộ của rất nhiều lập trình viên từ tất cả các loại ngôn ngữ, nhưng tôi nghĩ cách tiếp cận C ++ cho việc này là lý tưởng. Ngôn ngữ giới thiệu các hàm hủy được gọi theo kiểu xác định ngay lập tức một đối tượng đi ra khỏi phạm vi. Do đó, mã C ++, giả sử, khóa một mutex thông qua một đối tượng mutex có phạm vi với một hàm hủy không cần phải mở khóa bằng tay, vì nó sẽ được mở khóa tự động khi đối tượng ra khỏi phạm vi bất kể điều gì xảy ra (ngay cả khi có ngoại lệ đã gặp). Vì vậy, thực sự không cần mã C ++ được viết tốt bao giờ phải xử lý việc dọn dẹp tài nguyên cục bộ.

Trong các ngôn ngữ thiếu công cụ hủy, họ có thể cần sử dụng một finallykhối để dọn dẹp thủ công các tài nguyên cục bộ. Điều đó nói rằng, nó vẫn đánh bại việc phải xả mã của bạn bằng cách truyền lỗi thủ công với điều kiện bạn không phải catchngoại lệ ở khắp nơi.

Đảo ngược tác dụng phụ bên ngoài

Đây là những vấn đề khái niệm khó khăn nhất để giải quyết. Nếu bất kỳ chức năng nào, cho dù đó là bộ truyền lỗi hay điểm hỏng gây ra tác dụng phụ bên ngoài, thì nó cần phải khôi phục hoặc "hoàn tác" các tác dụng phụ đó để đưa hệ thống trở lại trạng thái như thể hoạt động không bao giờ xảy ra, thay vì " trạng thái nửa hợp lệ "trong đó hoạt động nửa chừng đã thành công. Tôi biết không có ngôn ngữ nào làm cho vấn đề khái niệm này dễ dàng hơn nhiều ngoại trừ các ngôn ngữ đơn giản là giảm nhu cầu về hầu hết các chức năng để gây ra tác dụng phụ bên ngoài, như các ngôn ngữ chức năng xoay quanh cấu trúc dữ liệu không thay đổi và liên tục.

finallythể nói đây là một trong những giải pháp tao nhã nhất cho vấn đề ngôn ngữ xoay quanh tính biến đổi và tác dụng phụ, bởi vì loại logic này rất đặc trưng cho một chức năng cụ thể và không phù hợp với khái niệm "dọn dẹp tài nguyên ". Và tôi khuyên bạn nên sử dụng finallytự do trong những trường hợp này để đảm bảo chức năng của bạn đảo ngược tác dụng phụ trong các ngôn ngữ hỗ trợ nó, bất kể bạn có cần một catchkhối hay không (và một lần nữa, nếu bạn hỏi tôi, mã được viết tốt nên có số lượng tối thiểu catchcác khối và tất cả catchcác khối phải ở những nơi có ý nghĩa nhất như với sơ đồ ở trên Load Image User Command).

Ngôn ngữ mơ ước

Tuy nhiên, IMO finallygần với lý tưởng cho việc đảo ngược hiệu ứng phụ nhưng không hoàn toàn. Chúng ta cần giới thiệu một booleanbiến để đẩy lùi hiệu quả các tác dụng phụ trong trường hợp thoát sớm (từ một ngoại lệ bị ném hoặc nếu không), như vậy:

bool finished = false;
try
{
    // Cause external side effects.
    ...

    // Indicate that all the external side effects were
    // made successfully.
    finished = true; 
}
finally
{
    // If the function prematurely exited before finishing
    // causing all of its side effects, whether as a result of
    // an early 'return' statement or an exception, undo the
    // side effects.
    if (!finished)
    {
        // Undo side effects.
        ...
    }
}

Nếu tôi có thể thiết kế một ngôn ngữ, cách giải quyết vấn đề mơ ước của tôi sẽ như thế này để tự động hóa đoạn mã trên:

transaction
{
    // Cause external side effects.
    ...
}
rollback
{
    // This block is only executed if the above 'transaction'
    // block didn't reach its end, either as a result of a premature
    // 'return' or an exception.

    // Undo side effects.
    ...
}

... Với các công cụ hủy diệt để tự động dọn dẹp các tài nguyên cục bộ, khiến chúng ta chỉ cần transaction, rollbackcatch(mặc dù tôi vẫn có thể muốn thêm finallyvào, giả sử, làm việc với các tài nguyên C không tự dọn sạch). Tuy nhiên, finallyvới một booleanbiến là điều gần nhất để thực hiện điều này một cách đơn giản mà tôi thấy cho đến nay vẫn thiếu ngôn ngữ mơ ước của mình. Giải pháp đơn giản thứ hai mà tôi tìm thấy cho vấn đề này là bộ bảo vệ phạm vi trong các ngôn ngữ như C ++ và D, nhưng tôi luôn thấy bộ bảo vệ phạm vi hơi lúng túng về mặt khái niệm vì nó làm mờ ý tưởng "dọn sạch tài nguyên" và "đảo ngược hiệu ứng phụ". Theo tôi đó là những ý tưởng rất khác biệt cần được giải quyết theo một cách khác.

Ước mơ về ngôn ngữ nhỏ bé của tôi cũng sẽ xoay quanh các cấu trúc dữ liệu bất biến và bền bỉ để giúp dễ dàng hơn, mặc dù không cần thiết, để viết các hàm hiệu quả mà không phải sao chép toàn bộ cấu trúc dữ liệu lớn mặc dù chức năng này gây ra không có tác dụng phụ.

Phần kết luận

Vì vậy, dù sao đi nữa, với những lùm xùm của tôi sang một bên, tôi nghĩ rằng try/finallymã của bạn để đóng ổ cắm là tốt và tuyệt vời khi xem xét rằng Python không có công cụ hủy diệt tương đương C ++ và cá nhân tôi nghĩ rằng bạn nên sử dụng nó một cách tự do cho những nơi cần đảo ngược tác dụng phụ và giảm thiểu số lượng nơi bạn phải catchđến những nơi có ý nghĩa nhất.


-66

Bắt lỗi / ngoại lệ và xử lý chúng một cách gọn gàng rất được khuyến khích ngay cả khi không bắt buộc.

Lý do tôi nói điều này là bởi vì tôi tin rằng mọi nhà phát triển nên biết và giải quyết hành vi của ứng dụng của mình nếu không anh ta đã hoàn thành công việc của mình một cách hợp lý. Không có tình huống nào một khối thử cuối cùng thay thế khối thử bắt cuối cùng.

Tôi sẽ cho bạn một ví dụ đơn giản: Giả sử rằng bạn đã viết mã để tải tệp lên máy chủ mà không bắt ngoại lệ. Bây giờ, nếu vì một lý do nào đó, việc tải lên thất bại, khách hàng sẽ không bao giờ biết được điều gì đã xảy ra. Nhưng, nếu bạn đã bắt gặp ngoại lệ, bạn có thể hiển thị một thông báo lỗi gọn gàng giải thích những gì đã sai và làm thế nào người dùng có thể khắc phục nó.

Nguyên tắc vàng: Luôn luôn bắt ngoại lệ, vì đoán cần có thời gian


22
-1: Trong Java, có thể cần một mệnh đề cuối cùng để giải phóng tài nguyên (ví dụ: đóng tệp hoặc giải phóng kết nối DB). Đó là độc lập với khả năng xử lý một ngoại lệ.
kevin cline

@kevincline, Anh ấy không hỏi có nên sử dụng cuối cùng hay không ... Tất cả những gì anh ấy hỏi là có bắt được ngoại lệ hay không .... Anh ấy biết thử, bắt và cuối cùng là gì ..... Cuối cùng là phần quan trọng nhất, tất cả chúng ta đều biết điều đó và tại sao nó được sử dụng ....
Pankaj Upadhyay

63
@Pankaj: câu trả lời của bạn cho thấy rằng một catchmệnh đề phải luôn luôn có mặt bất cứ khi nào có a try. Những người đóng góp có kinh nghiệm hơn, bao gồm cả tôi, tin rằng đây là lời khuyên tồi. Lý luận của bạn là thiếu sót. Phương thức chứa trykhông phải là nơi duy nhất có thể bắt được ngoại lệ. Nó thường đơn giản và tốt nhất để cho phép bắt và báo cáo các trường hợp ngoại lệ từ cấp cao nhất, thay vì sao chép các mệnh đề bắt trong suốt mã.
kevin cline

@kevincline, tôi tin rằng nhận thức về câu hỏi về người khác có chút khác biệt. Câu hỏi chính xác là, chúng ta có nên bắt ngoại lệ hay không .... Và tôi đã trả lời về vấn đề đó. Xử lý ngoại lệ có thể được thực hiện theo nhiều cách và không chỉ thử - cuối cùng. Nhưng, đó không phải là mối quan tâm của OP. Nếu nêu ra một ngoại lệ là đủ tốt cho bạn và những người khác, thì tôi phải nói là may mắn nhất.
Pankaj Upadhyay

Với các ngoại lệ, bạn muốn việc thực thi các câu lệnh bình thường bị gián đoạn (và không kiểm tra thành công theo cách thủ công ở mỗi bước). Điều này đặc biệt là trường hợp với các lệnh gọi phương thức lồng nhau sâu - một phương thức 4 lớp trong một số thư viện không thể "nuốt" một ngoại lệ; nó cần phải được ném trở lại qua tất cả các lớp. Tất nhiên, mỗi lớp có thể bao bọc ngoại lệ và thêm thông tin bổ sung; điều này thường không được thực hiện vì nhiều lý do, thêm thời gian để phát triển và tính dài dòng không cần thiết là hai lý do lớn nhất.
Daniel B
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.