2009-03-02 35 views
43

Tôi chỉ có một kinh nghiệm xử lý sự cố khá đau đớn trong xử lý sự cố một số mã trông như thế này:Là một khối cuối cùng mà không có một khối catch một java anti-pattern?

try { 
    doSomeStuff() 
    doMore() 
} finally { 
    doSomeOtherStuff() 
} 

Vấn đề là rất khó để khắc phục sự cố vì doSomeStuff() ném một ngoại lệ, do đó gây ra doSomeOtherStuff() cũng để ném một ngoại lệ. Ngoại lệ thứ hai (được ném bởi khối cuối cùng) đã được ném lên mã của tôi, nhưng nó không có một xử lý trên ngoại lệ đầu tiên (được ném từ doSomeStuff()), đó là nguyên nhân gốc thực sự của vấn đề.

Nếu mã đã nói điều này thay vào đó, vấn đề có thể đã được dễ dàng rõ ràng:

try { 
    doSomeStuff() 
    doMore() 
} catch (Exception e) { 
    log.error(e); 
} finally { 
    doSomeOtherStuff() 
} 

Vì vậy, câu hỏi của tôi là thế này:

là một khối finally đã qua sử dụng mà không cần bất kỳ khối catch một tốt được biết đến java anti-pattern? (Nó chắc chắn có vẻ là một lớp con không rõ ràng của mô hình chống nổi tiếng rõ ràng "Đừng gobble ngoại lệ!")

+0

Xem thêm một câu hỏi tương tự như trên [thử-cuối cùng mà không catch trong C#] (http://stackoverflow.com/questions/128818/why-is-try-finally-good-try-catch-bad) - các áp dụng cùng một đối số. – avandeursen

Trả lời

61

Nói chung, không, đây không phải là mẫu chống. Điểm cuối cùng của khối là đảm bảo mọi thứ được dọn dẹp cho dù một ngoại lệ có bị ném hay không. Toàn bộ vấn đề xử lý ngoại lệ là, nếu bạn không thể xử lý nó, bạn hãy để nó bong bóng đến một người có thể, thông qua xử lý ngoại lệ báo hiệu out-of-band tương đối sạch sẽ cung cấp. Nếu bạn cần phải chắc chắn rằng công cụ được dọn sạch nếu một ngoại lệ được ném, nhưng không thể xử lý đúng ngoại lệ trong phạm vi hiện tại, thì đây chính xác là điều đúng để làm. Bạn chỉ có thể muốn cẩn thận hơn một chút về việc đảm bảo khối cuối cùng của bạn không ném.

+0

Nhưng trong trường hợp này, nguyên nhân gốc rễ ngoại lệ không được phép để bong bóng lên, bởi vì cuối cùng dẫm lên nó (do đó làm cho nó được ăn ngấu nghiến.) – Jared

+0

Ok, nhưng đó là một trường hợp góc. Bạn đang hỏi nếu thử cuối cùng w/o bắt là một mô hình chống nói chung. – dsimcha

+1

Đồng ý, dsimcha. Tôi hy vọng rằng có sự đồng thuận rằng nói chung, đây không phải là một mô hình chống. Có lẽ một anti-pattern là, "Cuối cùng khối ném ngoại lệ"? – Jared

0

Tôi muốn nói một khối thử không có khối catch là một mẫu. Nói "Không có một cuối cùng mà không có một bắt" là một tập hợp con của "Không có một thử mà không có một bắt".

+0

Tôi đồng ý, tôi không thấy lý do tại sao điều này sẽ bị giảm giá. – BobbyShaftoe

+2

không có giá trị bỏ phiếu phủ định, nhưng nó không phải là một mẫu chống. một khối cuối cùng cho phép một đoạn mã nhất định luôn chạy, như đóng tài nguyên hoặc giải phóng khóa, điều này có thể không ném bất kỳ ngoại lệ nào. – Chii

+0

Quan điểm của tôi là Catch mất tích là mô hình chống ở trên, bất kể một Cuối cùng có hay không. Gobbling ngoại lệ là những gì gây ra vấn đề gỡ lỗi, trầm trọng bởi mã ngoại lệ có thể trong Cuối cùng. – billjamesdev

10

Hoàn toàn không có gì sai khi thử với một kết quả cuối cùng và không bị bắt. Hãy xem xét những điều sau đây:

InputStream in = null; 
try { 
    in = new FileInputStream("file.txt"); 
    // Do something that causes an IOException to be thrown 
} finally { 
    if (in != null) { 
     try { 
      in.close(); 
     } catch (IOException e) { 
      // Nothing we can do. 
     } 
    } 
} 

Nếu một ngoại lệ được ném và mã này không biết làm thế nào để đối phó với nó thì ngoại lệ nên bong bóng lên cuộc gọi ngăn xếp để người gọi. Trong trường hợp này, chúng tôi vẫn muốn dọn dẹp luồng, vì vậy tôi nghĩ rằng nó có ý nghĩa hoàn hảo để có một khối thử mà không bị bắt.

+0

Nếu bạn loại bỏ các bắt (lồng nhau cuối cùng) từ ví dụ này, sau đó nếu thử ném một IOException, và đóng() không là tốt, bạn không thể nói từ stack theo dõi rằng nguyên nhân gốc rễ của vấn đề là IOException gốc. Đây có phải chỉ là một trong số đó, "Yep, điều này sẽ luôn luôn là khó" trường hợp? – Jared

+1

@ Jared: Đúng, điều này sẽ luôn luôn khó khăn. :) –

+0

FYI - khi bạn loại bỏ lượng từ khối cuối cùng, về cơ bản bạn nói rằng bạn không quan tâm đến nơi ngoại lệ đến từ ... –

26

Tôi nghĩ rằng "mô hình chống" thực sự ở đây đang làm một cái gì đó trong một khối finally có thể ném, không bị bắt.

1

tôi sử dụng try/cuối cùng trong các hình thức sau đây:

try{ 
    Connection connection = ConnectionManager.openConnection(); 
    try{ 
     //work with the connection; 
    }finally{ 
     if(connection != null){ 
      connection.close();   
     } 
    } 
}catch(ConnectionException connectionException){ 
    //handle connection exception; 
} 

Tôi thích này để try/catch/finally (+ lồng nhau try/catch trong cuối cùng). Tôi nghĩ rằng nó ngắn gọn hơn và tôi không lặp lại bắt (Ngoại lệ).

+0

Không mà bị cùng một vấn đề của một ngoại lệ trong các cuối cùng ngấu nghiến một ngoại lệ trong thử? – Jared

1
try { 
    doSomeStuff() 
    doMore() 
} catch (Exception e) { 
    log.error(e); 
} finally { 
    doSomeOtherStuff() 
} 

Đừng làm điều đó ... bạn chỉ cần ẩn nhiều lỗi hơn (không giấu chính xác chúng ... nhưng làm cho việc xử lý chúng trở nên khó khăn hơn). RuntimeException (như NullPointer và ArrayIndexOutOfBounds)

Nói chung, nắm bắt các ngoại lệ bạn phải nắm bắt (kiểm tra ngoại lệ) và đối phó với những người khác tại thời điểm thử nghiệm. điều đó không nên xảy ra trong một chương trình được gỡ rối đúng cách.

+0

Yep ... đã đồng ý. Điều này tiếp tục trỏ đến mô hình chống thực sự là một cuối cùng mà ném một ngoại lệ. – Jared

-1

Tôi nghĩ rằng cố gắng không bắt là chống mẫu. Sử dụng try/catch để xử lý các điều kiện đặc biệt (lỗi tệp IO, thời gian chờ của ổ cắm, v.v.) không phải là mẫu chống.

Nếu bạn đang sử dụng thử/cuối cùng để dọn dẹp, hãy cân nhắc sử dụng khối thay thế.

16

Không hề.

Có gì sai trong mã cuối cùng.

Hãy nhớ rằng cuối cùng sẽ luôn bị xử tử và chỉ mạo hiểm (như bạn vừa chứng kiến) để đặt một thứ có thể ném một ngoại lệ ở đó.

5

Tôi nghĩ rằng nó không phải là chống mẫu và là điều tôi thường xuyên làm khi các tài nguyên deallocate thu được trong quá trình thực thi phương pháp.

Một điều tôi làm khi giao dịch với xử lý tập tin (đối với văn bản) được xả nước suối trước khi đóng nó bằng cách sử dụng phương pháp IOUtils.closeQuietly, mà không ném ngoại lệ:

 

OutputStream os = null; 
OutputStreamWriter wos = null; 
try { 
    os = new FileOutputStream(...); 
    wos = new OutputStreamWriter(os); 
    // Lots of code 

    wos.flush(); 
    os.flush(); 
finally { 
    IOUtils.closeQuietly(wos); 
    IOUtils.closeQuietly(os); 
} 
 

Tôi thích làm việc đó mà vì những lý do sau:

  • Không hoàn toàn an toàn để bỏ qua ngoại lệ khi đóng tệp - nếu có các byte chưa được ghi vào tệp, thì tệp có thể không ở trạng thái người gọi sẽ chờ đợi;
  • Vì vậy, nếu ngoại lệ được nêu ra trong phương thức flush(), nó sẽ được truyền cho người gọi nhưng tôi vẫn sẽ đảm bảo tất cả các tệp được đóng. Phương thức IOUtils.closeQuietly (...) ít chi tiết hơn thì thử tương ứng ... bắt ... bỏ qua khối tôi;
  • Nếu sử dụng nhiều luồng đầu ra, thứ tự cho phương thức flush() là quan trọng. Các luồng được tạo bằng cách truyền các luồng khác như các hàm tạo nên được xóa trước. Điều tương tự cũng hợp lệ đối với phương thức close(), nhưng phương thức flush() rõ ràng hơn trong quan điểm của tôi.
1

Theo ý kiến ​​của tôi, có nhiều trường hợp là finally với số catch cho biết một số loại sự cố. Thành ngữ tài nguyên rất đơn giản:

acquire 
try { 
    use 
} finally { 
    release 
} 

Trong Java, bạn có thể có ngoại lệ từ khá nhiều ở mọi nơi. Thường thì việc mua được ném một ngoại lệ kiểm tra, cách hợp lý để xử lý đó là đặt một vòng quanh bao nhiêu. Đừng thử một số kiểm tra null ghê tởm.

Nếu bạn định thực sự hậu môn, bạn nên lưu ý rằng có những ưu tiên ngụ ý trong số các trường hợp ngoại lệ. Ví dụ ThreadDeath nên clobber tất cả, cho dù nó đến từ mua/sử dụng/phát hành. Xử lý các ưu tiên này một cách chính xác là khó coi.

Do đó, hãy trừu tượng hóa tài nguyên của bạn để xử lý đi với thành ngữ Thực thi xung quanh.

0

Hãy thử/Cuối cùng là cách để giải phóng tài nguyên miễn phí. Mã trong khối cuối cùng sẽ KHÔNG BAO GIỜ ném vì nó chỉ nên hoạt động trên các tài nguyên hoặc trạng thái đã được PRIOR mua lại để nhập vào khối thử.

Là một sang một bên, tôi nghĩ log4J là gần như chống mẫu.

NẾU BẠN MUỐN KIỂM TRA CHƯƠNG TRÌNH CHẠY SỬ DỤNG CÔNG CỤ KIỂM SOÁT TRƯỚC (ví dụ: trình gỡ rối, IDE, hoặc theo ý nghĩa cực đoan một bộ mã byte nhưng KHÔNG PUT ĐĂNG NHẬP TẤT CẢ CÁC LOẠI FEW!).

Trong hai ví dụ bạn giới thiệu là người đầu tiên có vẻ đúng. Phần thứ hai bao gồm mã nhật ký và giới thiệu một lỗi. Trong ví dụ thứ hai, bạn sẽ loại bỏ một ngoại lệ nếu một câu lệnh được ném bởi hai câu lệnh đầu tiên (nghĩa là bạn nắm bắt nó và ghi lại nó nhưng không rethrow. Đây là điều tôi thấy rất phổ biến trong việc sử dụng log4j và là một vấn đề thực sự của thiết kế ứng dụng. với thay đổi của bạn, bạn làm cho chương trình thất bại theo cách sẽ rất khó cho hệ thống xử lý vì bạn về cơ bản diễu hành trở đi như thể bạn chưa bao giờ có ngoại lệ (sorta như VB cơ bản về lỗi tiếp tục xây dựng tiếp theo)

0

try-finally có thể giúp bạn giảm mã sao chép trong trường hợp phương thức có nhiều câu lệnh return. Hãy xem xét ví dụ sau (Android Java):

boolean doSomethingIfTableNotEmpty(SQLiteDatabase db) { 
    Cursor cursor = db.rawQuery("SELECT * FROM table", null); 
    if (cursor != null) { 
     try { 
      if (cursor.getCount() == 0) { 
       return false; 
      } 
     } finally { 
      // this will get executed even if return was executed above 
      cursor.close(); 
     } 
    } 
    // database had rows, so do something... 
    return true; 
} 

Nếu có không finally khoản, bạn có thể phải viết cursor.close() hai lần: ngay trước khi return false và cũng sau khi if khoản xung quanh.

Các vấn đề liên quan