2009-06-25 19 views
17

"Sự khác biệt lớn giữa một điều mà có thể đi sai và một điều rằng không thể nào đi sai là khi một điều mà không thể nào đi sai đi sai nó thường hóa ra là không thể có được tại hoặc sửa chữa. " -Douglas AdamsLàm thế nào tôi có thể xử lý một IOException mà tôi biết không bao giờ có thể được ném, trong một cách an toàn và có thể đọc được?

Tôi có một lớp FileItems. FileItems constructor có một tập tin, và ném một ngoại lệ (FileNotFoundException) nếu tập tin không tồn tại. Các phương thức khác của lớp đó cũng liên quan đến các hoạt động của tệp và do đó có khả năng ném FileNotFoundException. Tôi muốn tìm một giải pháp tốt hơn. Một giải pháp không yêu cầu các lập trình viên khác phải xử lý tất cả các FileNotFoundExceptions vô cùng khó lường này.

Các dữ kiện của vấn đề:

  1. Các tập tin đã được kiểm tra để tồn tại nhưng khả năng rất khó xảy ra tồn tại mà thông qua một số lỗi nghiêm trọng của thực tế các tập tin có thể bị xóa trước khi phương pháp này được gọi.
  2. Vì xác suất xảy ra 1 là vô cùng không giống và không thể phục hồi, tôi muốn xác định ngoại lệ không được kiểm soát.
  3. Tệp đã được tìm thấy tồn tại, buộc các lập trình viên khác viết mã và bắt được FileNotFoundException được kiểm tra có vẻ tẻ nhạt và vô dụng. Chương trình sẽ hoàn toàn thất bại tại thời điểm đó. Ví dụ: luôn có khả năng máy tính có thể catch fire, but no one is insane enough to force other programmers to handle that as a checked exception.
  4. Đôi khi tôi gặp phải vấn đề ngoại lệ này và xác định các ngoại lệ tùy chỉnh không được kiểm tra mỗi khi tôi gặp sự cố này (giải pháp cũ của tôi) là mệt mỏi và thêm vào mã-bloat.

Mã này hiện trông như thế này

public Iterator getFileItemsIterator() { 
    try{ 
     Scanner sc = new Scanner(this.fileWhichIsKnowToExist); 
     return new specialFileItemsIterator(sc);   
     } catch (FileNotFoundException e){ //can never happen} 

    return null; 
} 

Làm thế nào tôi có thể làm tốt hơn này, mà không cần xác định một FileNotFoundException tùy chỉnh không được kiểm soát? Có cách nào để bỏ một checkedException đến một uncheckException không?

+2

Các lỗi nghiêm trọng của thực tế xảy ra một cách đáng ngạc nhiên thường xuyên. –

+0

Các lỗi của thực tế đã phá vỡ phần mềm hoàn hảo của tôi cả tuần. Tôi đã cố gắng để nộp một vài báo cáo lỗi nhưng nó xuất hiện máy chủ jira của họ là xuống cũng http://jira.reality.net/ –

Trả lời

46

Mẫu thông thường để giải quyết vấn đề này là exception chaining. Bạn chỉ cần quấn FileNotFoundException trong một RuntimeException:

catch(FileNotFoundException e) { 
    throw new RuntimeException(e); 
} 

mô hình này không chỉ được áp dụng khi một ngoại lệ không thể xảy ra trong các tình huống cụ thể (như bạn), nhưng cũng có khi bạn không có phương tiện hoặc ý định thực sự xử lý ngoại lệ (chẳng hạn như lỗi liên kết cơ sở dữ liệu).

Sửa: Cẩn thận với chống mô hình này tương tự như trai, mà tôi đã thấy trong tự nhiên xa quá thường xuyên:

catch(FileNotFoundException e) { 
    throw new RuntimeException(e.getMessage()); 
} 

Bằng cách này, bạn vứt bỏ tất cả các thông tin quan trọng trong bản gốc stacktrace, thường sẽ gây khó khăn cho việc theo dõi.

chỉnh sửa khác: Như Thorbjørn Ravn Andersen một cách chính xác chỉ ra trong phản ứng của mình, nó không làm tổn thương để nêu lý do tại sao bạn đang chaining ngoại lệ, hoặc trong một bình luận hoặc, thậm chí tốt hơn, như thông điệp ngoại lệ:

catch(FileNotFoundException e) { 
    throw new RuntimeException(
     "This should never happen, I know this file exists", e); 
} 
+3

+1 trên chống mẫu. điều đó khiến tôi phát điên. – akf

+0

+1, tôi đã không nghĩ rằng constructor RuntimeException mất một ngoại lệ do tất cả các mã tôi kể từ đó chỉ cần vượt qua tin nhắn. Cám ơn rất nhiều. –

+1

Bây giờ hãy truy cập dailywtf.com cho ảnh chụp màn hình của hộp thoại có nội dung "Điều này sẽ không bao giờ xảy ra, tôi biết tệp này tồn tại" –

11

Bạn có thể chuyển đổi một ngoại lệ đã chọn thành không được đánh dấu bằng cách lồng ẩn nó bên trong RuntimException. Nếu ngoại lệ bị bắt cao hơn trong ngăn xếp và xuất ra bằng cách sử dụng printStackTrace(), ngăn xếp cho ngoại lệ ban đầu cũng sẽ được hiển thị.

try { 
    // code... 
} 
catch (IOException e) { 
    throw new RuntimeException(e); 
} 

Đây là giải pháp tốt, bạn không nên ngần ngại sử dụng trong những trường hợp này.

7

Nếu vị trí của bạn là điều này rất khó xảy ra và chỉ nên kết thúc chương trình, hãy sử dụng ngoại lệ thời gian chạy hiện tại, ngay cả chính RuntimeException (nếu không phải là IllegalStateException).

1

Có lẽ bạn nên bỏ lớp cha và ngoại lệ chung chung hơn IOException tại bất kỳ thời điểm nào trong mã của bạn bao gồm việc đọc hoặc ghi từ tệp.

Các tập tin có thể tồn tại khi constructor của lớp học của bạn chạy, nhưng điều đó không đảm bảo rằng:

  1. Nó tồn tại khi phương pháp này được gọi là
  2. Nó có thể ghi/đọc
  3. chủ đề khác không truy cập và bằng cách nào đó, hãy vặn luồng của bạn
  4. Tài nguyên không biến mất ở giữa chế biến

vv

Thay vì phát minh lại bánh xe, tôi sẽ nói chỉ cần ném lại IOException ở bất cứ đâu lớp JDK/java.io bạn đang sử dụng vũ lực để làm như vậy.

Ngoài ra tôi cho một lớp học ghét mà ném ngoại lệ từ nhà xây dựng của họ - Tôi muốn loại bỏ những điều này nếu tôi là bạn.

1

Tôi đã làm một chút googling và tìm thấy này glob của mã. Đó là một chút linh hoạt hơn của một cách tiếp cận tôi nghĩ

Khen ngợi những điều này article

class SomeOtherException extends Exception {} 

public class TurnOffChecking { 
    private static Test monitor = new Test(); 
    public static void main(String[] args) { 
    WrapCheckedException wce = new WrapCheckedException(); 
    // You can call f() without a try block, and let 
    // RuntimeExceptions go out of the method: 
    wce.throwRuntimeException(3); 
    // Or you can choose to catch exceptions: 
    for(int i = 0; i < 4; i++) 
     try { 
     if(i < 3) 
      wce.throwRuntimeException(i); 
     else 
      throw new SomeOtherException(); 
     } catch(SomeOtherException e) { 
      System.out.println("SomeOtherException: " + e); 
     } catch(RuntimeException re) { 
     try { 
      throw re.getCause(); 
     } catch(FileNotFoundException e) { 
      System.out.println(
      "FileNotFoundException: " + e); 
     } catch(IOException e) { 
      System.out.println("IOException: " + e); 
     } catch(Throwable e) { 
      System.out.println("Throwable: " + e); 
     } 
     } 
    monitor.expect(new String[] { 
     "FileNotFoundException: " + 
     "java.io.FileNotFoundException", 
     "IOException: java.io.IOException", 
     "Throwable: java.lang.RuntimeException: Where am I?", 
     "SomeOtherException: SomeOtherException" 
    }); 
    } 
} ///:~ 
10

Xem xét sử dụng các hình thức

throw new RuntimeException("This should never happen", e); 

để thay thế. Điều này cho phép bạn truyền đạt ý nghĩa cho người duy trì để theo dõi bạn, cả khi đọc mã nhưng cũng NÊN ngoại lệ xảy ra để được ném vào một số trường hợp lạ.

EDIT: Đây cũng là một cách hay để truyền ngoại lệ thông qua cơ chế không mong đợi những ngoại lệ đó. Ví dụ. nếu bạn có trình lặp "có nhiều hàng hơn từ cơ sở dữ liệu" thì giao diện Iterator không cho phép v.v. một FileNotFoundException để bạn có thể bọc nó như thế này. Trong mã SỬ DỤNG iterator, bạn có thể bắt được runtimeexception và kiểm tra excpetion ban đầu với getCause(). Rất hữu ích khi đi qua các đường dẫn mã kế thừa.

1

Tôi đã gặp phải sự cố tương tự.

Trong trường hợp đơn giản nhất, bạn thông báo cho người dùng về lỗi và đề xuất lặp lại hoặc hủy thao tác.

Trong trường hợp chung, quy trình làm việc là một chuỗi các hành động (I/O bao gồm), trong đó mỗi hành động "giả định" rằng hành động trước đó đã thành công.

Cách tiếp cận mà tôi đã chọn là tạo danh sách các hành động 'khôi phục'. Nếu quy trình làm việc thành công, chúng sẽ bị bỏ qua. Nếu một ngoại lệ xảy ra, tôi thực thi rollback và trình bày một ngoại lệ cho người dùng.

này:

  • giữ toàn vẹn của dữ liệu
  • cho phép mã hóa dễ dàng hơn nhiều

chức năng tiêu biểu như:

returntype func(blah-blah-blah, Transaction tr) 
{ 
    // IO example 
    Stream s = null; 
    try 
    { 
     s = new FileStream(filename); 
     tr.AddRollback(File.Delete(filename)); 
    } 
    finally 
    { 
     if (s != null) 
      s.close(); 
    } 
} 

sử dụng điển hình là:

Transaction tr = new Transaction(); 
try 
{ 
    DoAction1(blah1, tr); 
    DoAction2(blah2, tr); 
    //... 
} 
catch (Exception ex) 
{ 
    tr.ExecuteRollbacks(); 
    // queue the exception message to the user along with a command to repeat all the actions above 
} 

Đây là một chút chút phức tạp hơn trong thực tế, bởi vì

  • đôi khi nó là cần thiết để có được một loạt các khóa trước khi hành động thực hiện
  • đang rollback nên ngoại lệ im lặng tự (ví dụ)

Nhưng tôi đã quen với cách tiếp cận này, giờ đây các ứng dụng của tôi ổn định hơn.

0

Không chỉ tank toàn bộ ứng dụng của bạn với RuntimeException khi bạn gặp phải tình trạng lỗi không chắc chắn. RuntimeException nên được dành riêng cho lỗi lập trình viên và IOException rất có thể không do lỗi lập trình viên gây ra.

Thay vào đó, hãy đóng gói ngoại lệ cấp thấp hơn trong trường hợp ngoại lệ cấp cao hơn và truy cập lại. Sau đó xử lý ngoại lệ cấp cao hơn lên chuỗi cuộc gọi.

Ví dụ:

class SomeClass { 

    public void doActions() { 
    try { 
     someAction(); 
    } catch (HigherLevelException e) { 
     notifyUser(); 
    } 

    someOtherUnrelatedAction(); 
    } 

    public void someAction() throws HigherLevelException { 
    try { 
     // user lower-level abstraction to do our bidding 
    } catch(LowerLevelException e) { 
     throw new HigherLevelException(e); 
    } 
    } 

    public void someOtherUnrelatedAction() { 
    // does stuff 
    } 
} 

Nhiều khả năng các cuộc gọi stack mà ném ngoại lệ được thực hiện một số nhiệm vụ trong ứng dụng của bạn. Thay vì lực đâm toàn bộ ứng dụng của bạn với một số RuntimeException, hãy tìm hiểu xem phải làm gì khi sự cố xảy ra trong tác vụ đó. Ví dụ: bạn có đang cố lưu một tệp không? Đừng gặp sự cố, thay vào đó hãy thông báo cho người dùng đã xảy ra sự cố.

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