Đối số chống lại lỗi


35

Tôi đã tìm thấy một đoạn mã như thế này trong một trong các dự án của chúng tôi:

    SomeClass QueryServer(string args)
    {
        try
        {
            return SomeClass.Parse(_server.Query(args));
        }
        catch (Exception)
        {
            return null;
        }
    }

Theo tôi hiểu, việc loại bỏ các lỗi như thế này là một thực tiễn tồi, vì nó phá hủy thông tin hữu ích từ ngoại lệ của máy chủ ban đầu và làm cho mã tiếp tục khi thực sự nên chấm dứt.

Khi nào thì thích hợp để triệt tiêu hoàn toàn mọi lỗi như thế này?


13
Eric Lippert đã viết một bài đăng trên blog tốt có tên ngoại lệ , trong đó ông phân loại các ngoại lệ thành 4 loại khác nhau và gợi ý cách tiếp cận phù hợp cho từng loại. Được viết từ góc độ C # nhưng áp dụng trên các ngôn ngữ, tôi tin.
Damien_The_Unbeliever

3
Tôi tin rằng mã vi phạm nguyên tắc "Không nhanh". Có khả năng cao là lỗi thực tế được ẩn trong đó.
Euphoric


4
Bạn đang bắt nhiều. Bạn cũng sẽ bắt lỗi, chẳng hạn như NRE, chỉ số mảng ngoài giới hạn, lỗi cấu hình, ... Điều đó ngăn bạn tìm ra những lỗi đó và chúng sẽ liên tục gây ra thiệt hại.
usr

Câu trả lời:


52

Hãy tưởng tượng mã với hàng ngàn tệp bằng cách sử dụng một loạt các thư viện. Hãy tưởng tượng tất cả chúng đều được mã hóa như thế này.

Ví dụ, hãy tưởng tượng một bản cập nhật của máy chủ của bạn khiến một tệp cấu hình biến mất; và bây giờ tất cả những gì bạn có là một dấu vết ngăn xếp là một ngoại lệ con trỏ null khi bạn thử sử dụng lớp đó: bạn sẽ giải quyết nó như thế nào? Có thể mất hàng giờ, trong đó ít nhất chỉ cần ghi lại dấu vết ngăn xếp thô của tệp không tìm thấy [đường dẫn tệp] có thể cho phép bạn giải quyết trong giây lát.

Hoặc thậm chí tệ hơn: một lỗi trong một trong các thư viện bạn sử dụng sau khi cập nhật khiến mã của bạn bị sập sau này. Làm thế nào bạn có thể theo dõi điều này trở lại thư viện?

Ngay cả khi không xử lý lỗi mạnh mẽ chỉ cần làm

throw new IllegalStateException("THIS SHOULD NOT HAPPENING")

hoặc là

LOGGER.error("[method name]/[arguments] should not be there")

có thể giúp bạn tiết kiệm nhiều giờ

Nhưng có một số trường hợp bạn thực sự muốn bỏ qua ngoại lệ và trả về null như thế này (hoặc không làm gì cả). Đặc biệt là nếu bạn tích hợp với một số mã kế thừa được thiết kế xấu và ngoại lệ này được dự kiến ​​là một trường hợp bình thường.

Trong thực tế khi làm điều này, bạn chỉ nên tự hỏi nếu bạn thực sự đang bỏ qua ngoại lệ hoặc "xử lý đúng" cho nhu cầu của bạn. Nếu trả về null là "xử lý đúng" ngoại lệ của bạn trong trường hợp cụ thể đó, thì hãy làm điều đó. Và thêm một bình luận tại sao đây là điều thích hợp để làm.

Thực hành tốt nhất là những điều cần tuân thủ trong hầu hết các trường hợp, có thể 80%, có thể 99%, nhưng bạn sẽ luôn tìm thấy một trường hợp cạnh mà họ không áp dụng. Trong trường hợp đó, hãy để lại nhận xét tại sao bạn không theo dõi thông lệ cho những người khác (hoặc thậm chí chính bạn), người sẽ đọc mã của bạn vài tháng sau đó.


7
+1 để giải thích lý do tại sao bạn nghĩ rằng bạn cần phải làm điều này trong một bình luận. Không có lời giải thích, nuốt ngoại lệ sẽ luôn gióng lên hồi chuông báo động trong đầu tôi.
jpmc26

Vấn đề của tôi với điều đó là bình luận bị kẹt trong mã đó. Là một người tiêu dùng của chức năng, tôi có thể không bao giờ thấy nhận xét đó.
corsiKa

@ jpmc26 Nếu ngoại lệ được xử lý (ví dụ: bằng cách trả về một giá trị đặc biệt), đó không phải là ngoại lệ - nuốt tất cả.
Ded repeatator

2
@corsiKa một người tiêu dùng không cần biết chi tiết triển khai nếu chức năng thực hiện đúng công việc của mình. có thể chúng là một số thứ này trong các thư viện apache, hoặc mùa xuân và bạn không biết điều đó.
Walfrat

@Dedsplator có nhưng sử dụng một phần khai thác như một phần của thuật toán của bạn cũng không phải là một cách tốt để làm như vậy :)
Walfrat

14

Có những trường hợp mẫu này hữu ích - nhưng chúng thường được sử dụng khi ngoại lệ được tạo không bao giờ nên có (nghĩa là khi ngoại lệ được sử dụng cho hành vi bình thường).

Ví dụ, hãy tưởng tượng bạn có một lớp mở tệp, lưu trữ nó trong đối tượng được trả về. Nếu tệp không tồn tại, bạn có thể coi đó không phải là trường hợp lỗi, bạn trả về null và để người dùng tạo tệp mới thay thế. Phương pháp mở tệp có thể đưa ra một ngoại lệ ở đây để cho biết không có tệp nào xuất hiện, do đó, việc âm thầm bắt nó có thể là một tình huống hợp lệ.

Tuy nhiên, không thường xuyên bạn muốn làm điều này và nếu mã bị vấy bẩn với một mẫu như vậy bạn muốn xử lý ngay bây giờ. Ít nhất tôi mong đợi một cái bẫy im lặng như vậy để viết một dòng nhật ký nói rằng đây là những gì đã xảy ra (bạn có dấu vết, phải) và một bình luận để giải thích hành vi này.


2
"Được sử dụng khi ngoại lệ được tạo ra không bao giờ nên là" không có điều đó. ĐIỂM của ngoại lệ là để báo hiệu một cái gì đó đã đi sai lầm khủng khiếp.
Euphoric

3
@Euphoric Nhưng đôi khi người viết thư viện không biết ý định của người dùng cuối của thư viện đó. "Sai lầm khủng khiếp" của một người có thể là tình trạng dễ phục hồi của người khác.
Simon B

2
@Euphoric, nó phụ thuộc vào những gì ngôn ngữ của bạn cho là "sai lầm khủng khiếp": Người Python thích ngoại lệ cho những thứ rất gần với kiểm soát dòng chảy trong (ví dụ:) C ++. Trả về null có thể được bảo vệ trong một số trường hợp - ví dụ: đọc từ bộ đệm trong đó các mục có thể bị loại bỏ đột ngột và không thể đoán trước.
Matt Krause

10
@Euphoric, "Không tìm thấy tệp không phải là ngoại lệ bình thường. Trước tiên, bạn nên kiểm tra xem tệp có tồn tại không nếu có khả năng nó không tồn tại." Không! Không! Không! Không! Đó là suy nghĩ sai lầm cổ điển xung quanh các hoạt động nguyên tử. Các tập tin có thể bị xóa giữa bạn kiểm tra nếu nó tồn tại và truy cập nó. Cách chính xác duy nhất để xử lý các tình huống như vậy là truy cập vào nó và xử lý các trường hợp ngoại lệ nếu chúng xảy ra, ví dụ bằng cách quay lại nullnếu đó là ngôn ngữ tốt nhất mà bạn chọn có thể cung cấp.
David Arno

3
@Euphoric: Vì vậy, hãy viết mã để xử lý việc không thể truy cập tệp ít nhất hai lần? Điều đó vi phạm DRY, và mã không được khám phá là mã bị hỏng.
Ded repeatator

7

Đây là phụ thuộc 100% bối cảnh. Nếu người gọi của chức năng đó có yêu cầu hiển thị hoặc ghi nhật ký lỗi ở đâu đó, thì rõ ràng là vô nghĩa. Nếu người gọi bỏ qua tất cả các lỗi hoặc thông báo ngoại lệ, sẽ không có ý nghĩa gì khi trả lại ngoại lệ hoặc tin nhắn của nó. Khi yêu cầu là làm cho mã chỉ chấm dứt mà không hiển thị bất kỳ lý do nào, thì một cuộc gọi

   if(QueryServer(args)==null)
      TerminateProgram();

có lẽ sẽ là đủ Bạn phải xem xét nếu điều này sẽ làm cho người dùng khó tìm ra nguyên nhân gây ra lỗi - nếu đó là trường hợp, đây là hình thức xử lý lỗi sai. Tuy nhiên, nếu mã cuộc gọi trông như thế này

  result = QueryServer(args);
  if(result!=null)
      DoSomethingMeaningful(result);
  // else ??? Mask the error and let the user run into trouble later

sau đó bạn nên có một đối số với nhà phát triển trong quá trình xem xét mã. Nếu ai đó thực hiện một hình thức xử lý không lỗi như vậy chỉ vì anh ta quá lười biếng để tìm hiểu về các yêu cầu chính xác, thì mã này sẽ không được đưa vào sản xuất và tác giả của mã nên được dạy về hậu quả có thể xảy ra của sự lười biếng đó .


5

Trong những năm tôi đã dành lập trình và phát triển hệ thống, chỉ có hai tình huống tôi thấy mô hình được đề cập là hữu ích (trong cả hai trường hợp, áp lực cũng bao gồm ghi nhật ký ngoại lệ bị ném, tôi không coi việc bắt và nulltrả lại đơn giản là một cách thực hành tốt ).

Hai tình huống như sau:

1. Khi ngoại lệ không được coi là một trạng thái ngoại lệ

Đây là khi bạn thực hiện một thao tác trên một số dữ liệu, có thể ném, bạn biết nó có thể ném nhưng bạn vẫn muốn ứng dụng của mình tiếp tục chạy, vì bạn không cần dữ liệu đã xử lý. Nếu bạn nhận được chúng, thật tốt, nếu bạn không, nó cũng tốt.

Một số thuộc tính tùy chọn của một lớp có thể xuất hiện trong tâm trí.

2. Khi bạn đang cung cấp triển khai thư viện mới (tốt hơn, nhanh hơn?) Bằng giao diện đã được sử dụng trong ứng dụng

Hãy tưởng tượng bạn có một ứng dụng sử dụng một loại thư viện cũ, không ném ngoại lệ nhưng bị nulllỗi. Vì vậy, bạn đã tạo một bộ điều hợp cho thư viện này, sao chép khá nhiều API gốc của thư viện và đang sử dụng giao diện mới (vẫn không ném) này trong ứng dụng của bạn và tự xử lý nullkiểm tra.

Một phiên bản mới của thư viện xuất hiện, hoặc có lẽ là một thư viện hoàn toàn khác cung cấp cùng chức năng, thay vì trả về nulls, sẽ ném ngoại lệ và bạn muốn sử dụng nó.

Bạn không muốn rò rỉ các ngoại lệ cho ứng dụng chính của mình, vì vậy bạn thay thế và đăng nhập chúng trong bộ điều hợp bạn tạo để bọc phụ thuộc mới này.


Trường hợp đầu tiên không phải là một vấn đề, đó là hành vi mong muốn của mã. Tuy nhiên, trong tình huống thứ hai, nếu ở mọi nơi nullgiá trị trả về của bộ điều hợp thư viện thực sự có nghĩa là có lỗi, thì việc tái cấu trúc API để đưa ra một ngoại lệ và bắt nó thay vì kiểm tra nullcó thể là (và thông thường là mã) là một ý tưởng tốt.

Cá nhân tôi chỉ sử dụng siêu áp lực cho trường hợp đầu tiên. Tôi chỉ sử dụng nó cho trường hợp thứ hai, khi chúng tôi không có ngân sách để làm cho phần còn lại của ứng dụng hoạt động với các ngoại lệ thay vì nulls.


-1

Mặc dù có vẻ hợp lý khi nói rằng các chương trình chỉ nên nắm bắt các ngoại lệ mà chúng biết cách xử lý và không thể biết cách xử lý các ngoại lệ mà lập trình viên không lường trước được, một tuyên bố như vậy bỏ qua thực tế là nhiều hoạt động có thể thất bại trong một gần như vô hạn các cách không có tác dụng phụ, và trong nhiều trường hợp, việc xử lý thích hợp cho phần lớn các lỗi như vậy sẽ giống hệt nhau; các chi tiết chính xác của sự thất bại sẽ không liên quan, và do đó, không quan trọng việc lập trình viên có lường trước được chúng hay không.

Ví dụ, nếu mục đích của chức năng là đọc tệp tài liệu vào một đối tượng phù hợp và tạo một cửa sổ tài liệu mới để hiển thị đối tượng đó hoặc báo cáo cho người dùng rằng tệp không thể đọc được, hãy thử tải một tệp Tệp tài liệu không hợp lệ không nên làm sập ứng dụng - thay vào đó, nó sẽ hiển thị một thông báo cho biết sự cố nhưng để phần còn lại của ứng dụng tiếp tục chạy bình thường trừ khi vì lý do nào đó, việc tải tài liệu đã làm hỏng trạng thái của một thứ khác trong hệ thống .

Về cơ bản, việc xử lý thích hợp một ngoại lệ thường sẽ phụ thuộc ít hơn vào loại ngoại lệ so với vị trí mà nó được ném; nếu một tài nguyên được bảo vệ bởi khóa đọc ghi và một ngoại lệ được ném vào một phương thức đã thu được khóa để đọc, thì hành vi thích hợp thường là giải phóng khóa vì phương thức đó không thể làm gì đối với tài nguyên . Nếu một ngoại lệ được ném trong khi khóa được lấy để ghi, khóa thường sẽ bị vô hiệu, vì tài nguyên được bảo vệ có thể ở trạng thái không hợp lệ; nếu nguyên thủy khóa không có trạng thái "vô hiệu", người ta nên thêm một cờ để theo dõi sự vô hiệu đó. Phát hành khóa mà không làm mất hiệu lực nó là xấu vì mã khác có thể thấy đối tượng được bảo vệ ở trạng thái không hợp lệ. Rời khỏi ổ khóa, tuy nhiên, không phải là ' Giải pháp thích hợp hoặc. Giải pháp phù hợp là vô hiệu hóa khóa để mọi nỗ lực mua lại trong tương lai hoặc đang chờ xử lý sẽ ngay lập tức thất bại.

Nếu nó chỉ ra rằng một tài nguyên không hợp lệ bị bỏ rơi trước khi có bất kỳ nỗ lực sử dụng nào, thì không có lý do gì để đưa ứng dụng xuống. Nếu tài nguyên không hợp lệ là rất quan trọng đối với hoạt động liên tục của ứng dụng, ứng dụng sẽ cần phải được đưa xuống nhưng việc vô hiệu hóa tài nguyên có thể sẽ khiến điều đó xảy ra. Mã nhận được ngoại lệ ban đầu thường sẽ không có cách nào để biết tình huống nào được áp dụng, nhưng nếu nó làm mất hiệu lực tài nguyên, nó có thể đảm bảo rằng hành động chính xác cuối cùng sẽ được thực hiện trong cả hai trường hợp.


2
Điều này không trả lời được câu hỏi vì xử lý một ngoại lệ mà không biết chi tiết ngoại lệ! = Âm thầm tiêu thụ một ngoại lệ (chung).
Taemyr

@Taemyr: Nếu mục đích của hàm là "làm X nếu có thể, nếu không thì chỉ ra là không" và X là không thể, thường sẽ không thực tế khi liệt kê tất cả các loại ngoại lệ mà cả hàm không phải là hàm người gọi cũng không quan tâm đến vấn đề "Không thể làm X". Xử lý ngoại lệ Pokemon thường là cách thực tế duy nhất để khiến mọi thứ hoạt động trong những trường hợp như vậy và không cần phải xấu xa như một số người phát hiện ra nếu mã bị kỷ luật về việc vô hiệu hóa những thứ bị hỏng do ngoại lệ không mong muốn.
supercat

Chính xác. Mọi phương pháp nên có một mục đích, nếu nó có thể hoàn thành mục đích của nó bất kể phương thức nào nó gọi là ném ngoại lệ hay không, sau đó nuốt ngoại lệ, bất kể đó là gì và thực hiện công việc đó là điều nên làm. Xử lý lỗi về cơ bản là hoàn thành nhiệm vụ sau khi xảy ra lỗi. Đó là những gì quan trọng, không phải những gì xảy ra lỗi.
jmoreno

@jmoreno: Điều gây khó khăn là trong nhiều tình huống sẽ có một số lượng lớn các trường hợp ngoại lệ, trong đó nhiều lập trình viên sẽ không lường trước được, điều này sẽ không chỉ ra vấn đề và một vài trường hợp ngoại lệ, một số trường hợp lập trình viên sẽ không đặc biệt dự đoán, điều này chỉ ra một vấn đề và không có cách hệ thống xác định nào để phân biệt chúng. Cách duy nhất tôi biết để giải quyết vấn đề đó là ngoại lệ làm mất hiệu lực những thứ bị hỏng, vì vậy nếu chúng quan trọng thì chúng sẽ được chú ý và nếu chúng không quan trọng thì chúng sẽ bị bỏ qua.
supercat

-2

Nuốt loại lỗi này không đặc biệt hữu ích với bất kỳ ai. Có, người gọi ban đầu có thể không quan tâm đến ngoại lệ nhưng người khác có thể .

Vậy thì họ làm gì? Họ sẽ thêm mã để xử lý ngoại lệ để xem hương vị của ngoại lệ nào họ sẽ quay lại. Tuyệt quá. Nhưng nếu trình xử lý ngoại lệ được để lại, người gọi ban đầu sẽ không còn trở lại null và một cái gì đó bị phá vỡ ở nơi khác.

Ngay cả khi bạn biết rằng một số mã ngược dòng có thể gây ra lỗi và do đó trả về null, điều đó sẽ khiến bạn không cố gắng ít nhất là cố gắng loại bỏ ngoại lệ trong mã gọi IMHO.


"Trơ nghiêm trọng" - có lẽ bạn có nghĩa là "thiếu nghiêm túc"?
Tên màn hình bí truyền

@EsotericScreenName Chọn một ... ;-)
Robbie Dee

-3

Tôi đã thấy các ví dụ trong đó các thư viện của bên thứ ba có các phương thức hữu ích, ngoại trừ việc họ đưa ra các ngoại lệ trong một số trường hợp phù hợp với tôi. Tôi có thể làm gì?

  • Thực hiện chính xác những gì tôi cần bản thân mình. Có thể khó khăn về thời hạn.
  • Sửa đổi mã của bên thứ ba. Nhưng sau đó tôi phải tìm kiếm xung đột hợp nhất với mỗi lần nâng cấp.
  • Viết phương thức trình bao bọc để biến ngoại lệ thành một con trỏ null thông thường, sai boolean hoặc bất cứ điều gì.

Ví dụ: phương thức thư viện

public foo findFoo() {...} 

trả về foo đầu tiên, nhưng nếu không có nó sẽ ném ngoại lệ. Nhưng cái tôi cần là một phương thức để trả về foo đầu tiên hoặc null. Vì vậy, tôi viết cái này:

public foo myFindFoo() {
    try {
        return findFoo() 
    } 
    catch (NoFooException ex) {
        return null;
    }
}

Không gọn gàng. Nhưng đôi khi là giải pháp thực dụng.


1
Bạn có ý nghĩa gì "ném ngoại lệ nơi họ nên làm việc cho bạn"?
corsiKa

@corsiKa, ví dụ xuất hiện trong đầu tôi là các phương pháp tìm kiếm thông qua một danh sách hoặc cấu trúc dữ liệu tương tự. Họ có thất bại khi họ không tìm thấy gì hay họ trả về giá trị null? Một ví dụ khác là các phương thức WaitUntil không thành công khi hết thời gian chờ thay vì trả về giá trị boolean false.
om
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.