Thử / Bắt / Đăng nhập / Suy nghĩ lại - Có phải chống mẫu không?


18

Tôi có thể thấy một số bài đăng trong đó tầm quan trọng của việc xử lý ngoại lệ tại vị trí trung tâm hoặc tại ranh giới quy trình được nhấn mạnh là một cách thực hành tốt thay vì xả rác mọi khối mã xung quanh thử / bắt. Tôi tin tưởng mạnh mẽ rằng hầu hết chúng ta đều hiểu tầm quan trọng của nó tuy nhiên tôi thấy mọi người vẫn kết thúc với mô hình chống bắt-log-rethrow chủ yếu vì để dễ dàng khắc phục sự cố trong bất kỳ ngoại lệ nào, họ muốn đăng nhập thêm thông tin cụ thể theo ngữ cảnh (ví dụ: tham số phương thức thông qua) và cách là để bọc phương thức xung quanh try / Catch / log / rethrow.

public static bool DoOperation(int num1, int num2)
{
    try
    {
        /* do some work with num1 and num2 */
    }
    catch (Exception ex)
    {
        logger.log("error occured while number 1 = {num1} and number 2 = {num2}"); 
        throw;
    }
}

Có cách nào đúng để đạt được điều này trong khi vẫn duy trì xử lý ngoại lệ thực hành tốt? Tôi đã nghe nói về khung AOP như PostSharp cho việc này nhưng muốn biết liệu có bất kỳ nhược điểm hoặc chi phí hiệu suất lớn nào liên quan đến các khung AOP này không.

Cảm ơn!


6
Có sự khác biệt rất lớn giữa việc gói từng phương thức trong thử / bắt, ghi nhật ký ngoại lệ và để mã chug theo. Và thử / bắt và suy nghĩ lại ngoại lệ với thông tin bổ sung. Đầu tiên là thực hành khủng khiếp. Thứ hai là cách hoàn toàn tốt để cải thiện trải nghiệm gỡ lỗi của bạn.
Euphoric

Tôi đang nói thử / bắt từng phương thức và chỉ cần đăng nhập vào khối bắt và suy nghĩ lại - điều này có ổn không?
rahulaga_dev

2
Như Amon đã chỉ ra. Nếu ngôn ngữ của bạn có dấu vết ngăn xếp, đăng nhập vào mỗi lần truy cập là vô nghĩa. Nhưng gói ngoại lệ và thêm thông tin là thực hành tốt.
Euphoric

1
Xem câu trả lời của @ Liath. Bất kỳ câu trả lời nào tôi đưa ra sẽ phản ánh khá nhiều về anh ta: bắt ngoại lệ càng sớm càng tốt và nếu tất cả những gì bạn có thể làm ở giai đoạn đó là ghi lại một số thông tin hữu ích, thì hãy làm như vậy và suy nghĩ lại. Xem cái này như một antipotype là vô nghĩa theo quan điểm của tôi.
David Arno

1
Liath: thêm đoạn mã nhỏ. Tôi đang sử dụng c #
rahulaga_dev

Câu trả lời:


18

Vấn đề không phải là khối bắt cục bộ, vấn đề là nhật ký và suy nghĩ lại . Hoặc xử lý ngoại lệ hoặc bọc nó bằng một ngoại lệ mới có thêm bối cảnh bổ sung và ném nó. Nếu không, bạn sẽ chạy vào một số mục nhật ký trùng lặp cho cùng một ngoại lệ.

Ý tưởng ở đây là tăng cường khả năng gỡ lỗi ứng dụng của bạn.

Ví dụ # 1: Xử lý nó

try
{
    doSomething();
}
catch (Exception e)
{
    log.Info("Couldn't do something", e);
    doSomethingElse();
}

Nếu bạn xử lý ngoại lệ, bạn có thể dễ dàng hạ cấp tầm quan trọng của mục nhật ký ngoại lệ và không có lý do gì để loại bỏ ngoại lệ đó lên chuỗi. Nó đã được xử lý.

Xử lý một ngoại lệ có thể bao gồm thông báo cho người dùng rằng sự cố đã xảy ra, ghi nhật ký sự kiện hoặc đơn giản là bỏ qua nó.

LƯU Ý: nếu bạn cố tình bỏ qua một ngoại lệ, tôi khuyên bạn nên cung cấp nhận xét trong mệnh đề bắt trống cho biết rõ tại sao. Điều này cho phép các nhà bảo trì trong tương lai biết rằng đó không phải là một lỗi lập trình hay lười biếng. Thí dụ:

try
{
    context.DrawLine(x1,y1, x2,y2);
}
catch (OutOfMemoryException)
{
    // WinForms throws OutOfMemory if the figure you are attempting to
    // draw takes up less than one pixel (true story)
}

Ví dụ # 2: Thêm bối cảnh bổ sung và ném

try
{
    doSomething(line);
}
catch (Exception e)
{
    throw new MyApplicationException(filename, line, e);
}

Thêm ngữ cảnh bổ sung (như số dòng và tên tệp trong mã phân tích cú pháp) có thể giúp tăng cường khả năng gỡ lỗi các tệp đầu vào - giả sử có vấn đề ở đó. Đây là một trường hợp đặc biệt, do đó, bọc lại một ngoại lệ trong "ApplicationException" chỉ để đổi thương hiệu, nó không giúp bạn gỡ lỗi. Hãy chắc chắn rằng bạn thêm thông tin bổ sung.

Ví dụ # 3: Đừng làm bất cứ điều gì với ngoại lệ

try
{
    doSomething();
}
finally
{
   // cleanup resources but let the exception percolate
}

Trong trường hợp cuối cùng này, bạn chỉ cho phép ngoại lệ rời đi mà không chạm vào nó. Trình xử lý ngoại lệ ở lớp ngoài cùng có thể xử lý việc ghi nhật ký. Cácfinally khoản được sử dụng để đảm bảo mọi nguồn lực cần thiết theo phương pháp của bạn được làm sạch, nhưng điều này không phải là nơi để đăng nhập mà các ngoại lệ được ném.


Tôi thích " Vấn đề không phải là khối bắt cục bộ, vấn đề là nhật ký và suy nghĩ lại" Tôi nghĩ rằng điều này có ý nghĩa hoàn hảo để đảm bảo đăng nhập sạch hơn. Nhưng cuối cùng cũng có nghĩa là ổn khi thử / bắt được phân tán trên tất cả các phương thức, phải không? Tôi nghĩ rằng phải có một số hướng dẫn để đảm bảo thực hành này được tuân thủ một cách thận trọng thay vì có mọi phương pháp làm như vậy.
rahulaga_dev

Tôi đã cung cấp một hướng dẫn trong câu trả lời của tôi. Điều này không trả lời câu hỏi của bạn?
Berin Loritsch

@rahulaga_dev Tôi không nghĩ rằng có một hướng dẫn / viên đạn bạc nên giải quyết vấn đề này, vì nó phụ thuộc rất nhiều vào bối cảnh. Không thể có một hướng dẫn chung cho bạn biết nơi xử lý một ngoại lệ hoặc khi nào cần thiết lập lại nó. IMO, hướng dẫn duy nhất tôi thấy là trì hoãn việc ghi nhật ký / xử lý đến thời điểm gần nhất có thể và để tránh đăng nhập vào mã có thể sử dụng lại, để bạn không tạo ra các phụ thuộc không cần thiết. Người dùng mã của bạn sẽ không quá thích thú nếu bạn đăng nhập mọi thứ (nghĩa là xử lý ngoại lệ) mà không cho họ cơ hội xử lý chúng theo cách riêng của họ. Chỉ hai xu của tôi :)
andreee

7

Tôi không tin rằng sản phẩm khai thác tại địa phương là một mô hình chống, trên thực tế nếu tôi nhớ chính xác thì nó thực sự được thi hành trong Java!

Chìa khóa cho tôi khi thực hiện xử lý lỗi là chiến lược tổng thể. Bạn có thể muốn một bộ lọc nắm bắt tất cả các ngoại lệ ở ranh giới dịch vụ, bạn có thể muốn chặn chúng theo cách thủ công - cả hai đều ổn miễn là có một chiến lược tổng thể, sẽ rơi vào các tiêu chuẩn mã hóa của nhóm của bạn.

Cá nhân tôi thích bắt lỗi bên trong một chức năng khi tôi có thể thực hiện một trong những điều sau đây:

  • Thêm thông tin theo ngữ cảnh (chẳng hạn như trạng thái của các đối tượng hoặc những gì đang diễn ra)
  • Xử lý ngoại lệ một cách an toàn (như phương pháp TryX)
  • Hệ thống của bạn đang vượt qua một ranh giới dịch vụ và gọi vào một thư viện hoặc API bên ngoài
  • Bạn muốn bắt và suy nghĩ lại một loại ngoại lệ khác (có thể với nguyên bản là ngoại lệ bên trong)
  • Ngoại lệ được đưa ra như một phần của một số chức năng nền có giá trị thấp

Nếu đó không phải là một trong những trường hợp này thì tôi không thêm thử / bắt cục bộ. Nếu đúng, tùy thuộc vào kịch bản, tôi có thể xử lý ngoại lệ (ví dụ: phương thức TryX trả về sai) hoặc suy nghĩ lại để ngoại lệ sẽ được xử lý bởi chiến lược toàn cầu.

Ví dụ:

public bool TryConnectToDatabase()
{
  try
  {
    this.ConnectToDatabase(_databaseType); // this method will throw if it fails to connect
    return true;
  }
  catch(Exception ex)
  {
     this.Logger.Error(ex, "There was an error connecting to the database, the databaseType was {0}", _databaseType);
    return false;
  }
}

Hoặc một ví dụ về suy nghĩ lại:

public IDbConnection ConnectToDatabase()
{
  try
  {
    // connect to the database and return the connection, will throw if the connection cannot be made
  }
  catch(Exception ex)
  {
     this.Logger.Error(ex, "There was an error connecting to the database, the databaseType was {0}", _databaseType);
    throw;
  }
}

Sau đó, bạn bắt lỗi ở đầu ngăn xếp và đưa ra một thông báo thân thiện với người dùng.

Bất kỳ cách tiếp cận nào bạn thực hiện luôn luôn đáng để tạo các thử nghiệm đơn vị cho các tình huống này để bạn có thể đảm bảo chức năng không thay đổi và làm gián đoạn dòng chảy của dự án vào một ngày sau đó.

Bạn đã không đề cập đến ngôn ngữ nào bạn đang làm việc nhưng là một nhà phát triển .NET và đã thấy điều này quá nhiều lần không đề cập đến nó.

ĐỪNG VIẾT:

catch(Exception ex)
{
  throw ex;
}

Sử dụng:

catch(Exception ex)
{
  throw;
}

Cái trước đặt lại dấu vết ngăn xếp và làm cho cấp cao nhất của bạn bắt hoàn toàn vô dụng!

TLD

Bắt cục bộ không phải là một mô hình chống, nó thường có thể là một phần của thiết kế và có thể giúp thêm bối cảnh bổ sung cho lỗi.


3
Điểm đăng nhập trong khai thác là gì, khi cùng một logger sẽ được sử dụng trong trình xử lý ngoại lệ cấp cao nhất?
Euphoric

Bạn có thể có thêm thông tin (chẳng hạn như các biến cục bộ) mà bạn sẽ không có quyền truy cập ở đầu ngăn xếp. Tôi sẽ cập nhật ví dụ để minh họa.
Liath

2
Trong trường hợp đó, ném ngoại lệ mới với dữ liệu bổ sung và ngoại lệ bên trong.
Euphoric

2
@Euphoric yep, tôi cũng đã thấy điều đó, cá nhân tôi không phải là một người hâm mộ vì nó yêu cầu bạn tạo ra các loại ngoại lệ mới cho hầu hết mọi phương pháp / kịch bản mà tôi cảm thấy có quá nhiều chi phí. Thêm dòng nhật ký ở đây (và có lẽ là một dòng khác ở trên cùng) giúp minh họa dòng mã khi chẩn đoán các vấn đề
Liath

4
Java không buộc bạn phải xử lý ngoại lệ, nó buộc bạn phải nhận thức được nó. bạn có thể bắt nó và làm bất cứ điều gì hoặc chỉ tuyên bố nó là thứ mà hàm có thể ném và không làm gì với nó trong hàm .... Ít nitpicking trên một câu trả lời khá hay!
Newtopian

4

Điều này phụ thuộc rất nhiều vào ngôn ngữ. Ví dụ: C ++ không cung cấp dấu vết ngăn xếp trong các thông báo lỗi ngoại lệ, do đó, việc truy tìm ngoại lệ thông qua việc truy xuất lại log log thường xuyên có thể hữu ích. Ngược lại, Java và các ngôn ngữ tương tự cung cấp các dấu vết ngăn xếp rất tốt, mặc dù định dạng của các dấu vết ngăn xếp này có thể không được cấu hình nhiều. Việc nắm bắt và suy nghĩ lại các ngoại lệ trong các ngôn ngữ này là hoàn toàn vô nghĩa trừ khi bạn thực sự có thể thêm một số bối cảnh quan trọng (ví dụ: kết nối một ngoại lệ SQL cấp thấp với ngữ cảnh của hoạt động logic nghiệp vụ).

Bất kỳ chiến lược xử lý lỗi nào được thực hiện thông qua phản xạ hầu như không hiệu quả hơn chức năng được tích hợp trong ngôn ngữ. Ngoài ra, ghi nhật ký phổ biến có hiệu suất không thể tránh khỏi. Vì vậy, bạn cần phải cân bằng luồng thông tin bạn nhận được so với các yêu cầu khác cho phần mềm này. Điều đó nói rằng, các giải pháp như PostSharp được xây dựng trên thiết bị đo mức trình biên dịch nói chung sẽ làm tốt hơn rất nhiều so với phản xạ thời gian chạy.

Cá nhân tôi tin rằng việc ghi nhật ký mọi thứ là không hữu ích, bởi vì nó bao gồm vô số thông tin không liên quan. Do đó tôi rất nghi ngờ về các giải pháp tự động. Với một khung ghi nhật ký tốt, có thể đủ để có một hướng dẫn mã hóa đã được thống nhất để thảo luận về loại thông tin bạn muốn đăng nhập và cách định dạng thông tin này. Sau đó, bạn có thể thêm đăng nhập khi nó quan trọng.

Đăng nhập vào logic nghiệp vụ quan trọng hơn nhiều so với đăng nhập vào các chức năng tiện ích. Và thu thập dấu vết ngăn xếp của các báo cáo sự cố trong thế giới thực (chỉ yêu cầu ghi nhật ký ở cấp cao nhất của quy trình) cho phép bạn xác định vị trí các khu vực của mã nơi đăng nhập sẽ có giá trị nhất.


4

Khi tôi thấy try/catch/log trong mọi phương pháp, nó làm dấy lên mối lo ngại rằng các nhà phát triển không biết điều gì có thể xảy ra hoặc không xảy ra trong ứng dụng của họ, giả định điều tồi tệ nhất và ghi lại mọi thứ ở mọi nơi vì tất cả các lỗi họ mong đợi.

Đây là một triệu chứng cho thấy kiểm thử đơn vị và tích hợp là không đủ và các nhà phát triển đã quen với việc chuyển qua nhiều mã trong trình gỡ lỗi và hy vọng rằng bằng cách nào đó, rất nhiều bản ghi sẽ cho phép họ triển khai mã lỗi trong môi trường thử nghiệm và tìm ra các vấn đề bằng cách xem xét Các bản ghi.

ném ngoại lệ có thể hữu ích hơn mã dự phòng bắt và ghi nhật ký ngoại lệ. Nếu bạn đưa ra một ngoại lệ với một thông điệp có ý nghĩa khi một phương thức nhận được một đối số không mong muốn (và ghi lại nó ở ranh giới dịch vụ) thì sẽ hữu ích hơn nhiều so với việc ghi lại ngoại lệ ngay lập tức như một tác dụng phụ của đối số không hợp lệ và phải đoán điều gì đã gây ra nó .

Nulls là một ví dụ. Nếu bạn nhận được một giá trị làm đối số hoặc kết quả của một cuộc gọi phương thức và nó không nên là null, thì hãy ném ngoại lệ. Đừng chỉ đăng nhập kết quả NullReferenceExceptionném năm dòng sau vì giá trị null. Dù bằng cách nào bạn cũng có được một ngoại lệ, nhưng người ta nói với bạn điều gì đó trong khi người kia khiến bạn tìm kiếm thứ gì đó.

Như những người khác đã nói, tốt nhất là ghi nhật ký ngoại lệ tại ranh giới dịch vụ hoặc bất cứ khi nào một ngoại lệ không được xử lý lại vì nó được xử lý một cách duyên dáng. Sự khác biệt quan trọng nhất là giữa một cái gì đó và không có gì. Nếu trường hợp ngoại lệ của bạn được đăng nhập ở một nơi dễ dàng truy cập, bạn sẽ tìm thấy thông tin bạn cần khi bạn cần.


Cảm ơn Scott. Điểm bạn đưa ra " Nếu bạn đưa ra một ngoại lệ với một thông điệp có ý nghĩa khi một phương thức nhận được một đối số không mong muốn (và ghi lại nó ở ranh giới dịch vụ) " thực sự tấn công và giúp tôi hình dung ra tình huống lơ lửng xung quanh tôi trong bối cảnh của các đối số phương thức. Tôi nghĩ sẽ hợp lý khi có điều khoản bảo vệ an toàn và ném ArgumentException trong trường hợp đó thay vì dựa vào các chi tiết đối số bắt và ghi nhật ký
rahulaga_dev

Scott, tôi cũng có cảm giác tương tự. Bất cứ khi nào tôi thấy nhật ký và cách làm lại chỉ để ghi nhật ký ngữ cảnh, tôi cảm thấy như các nhà phát triển không thể kiểm soát các bất biến của lớp hoặc không thể bảo vệ cuộc gọi phương thức. Thay vào đó, mọi phương thức đều được gói gọn trong một lần thử / bắt / ghi / ném tương tự. Và nó thật kinh khủng.
Tối đa

2

Nếu bạn cần ghi lại thông tin ngữ cảnh chưa có ngoại lệ, bạn bọc nó trong một ngoại lệ mới và cung cấp ngoại lệ ban đầu là InnerException. Bằng cách đó bạn vẫn có dấu vết ngăn xếp ban đầu được bảo tồn. Vì thế:

public static bool DoOperation(int num1, int num2)
{
    try
    {
        /* do some work with num1 and num2 */
    }
    catch (Exception ex)
    {
        throw new Exception("error occured while number 1 = {num1} and number 2 = {num2}", ex);
    }
}

Tham số thứ hai cho hàm Exceptiontạo cung cấp một ngoại lệ bên trong. Sau đó, bạn có thể ghi nhật ký tất cả các ngoại lệ ở một nơi duy nhất và bạn vẫn nhận được dấu vết ngăn xếp đầy đủ thông tin theo ngữ cảnh trong cùng một mục nhật ký.

Bạn có thể muốn sử dụng một lớp ngoại lệ tùy chỉnh, nhưng điểm này là như nhau.

thử / bắt / đăng nhập / suy nghĩ lại là một mớ hỗn độn vì nó sẽ dẫn đến các nhật ký khó hiểu - ví dụ: nếu một ngoại lệ khác xảy ra trong một luồng khác giữa việc ghi thông tin theo ngữ cảnh và ghi nhật ký ngoại lệ thực tế trong trình xử lý cấp cao nhất thì sao? mặc dù thử / bắt / ném là tốt, nếu ngoại lệ mới thêm thông tin vào bản gốc.


Còn loại ngoại lệ ban đầu thì sao? Nếu chúng ta bọc, nó đã biến mất. Đó có phải là vấn đề? Chẳng hạn, ai đó đã dựa vào SqlTimeoutException.
Tối đa

@Max: Loại ngoại lệ ban đầu vẫn có sẵn dưới dạng ngoại lệ bên trong.
JacquesB

Ý tôi là thế! Bây giờ tất cả mọi người trong ngăn xếp cuộc gọi, được bắt cho SqlException, sẽ không bao giờ có được nó.
Max

1

Bản thân ngoại lệ sẽ cung cấp tất cả các thông tin cần thiết để ghi nhật ký thích hợp, bao gồm thông báo, mã lỗi và những gì không. Do đó, không cần phải bắt một ngoại lệ chỉ để suy nghĩ lại hoặc ném một ngoại lệ khác.

Thông thường, bạn sẽ thấy mô hình của một số trường hợp ngoại lệ được bắt gặp và truy xuất lại như một ngoại lệ phổ biến, chẳng hạn như bắt một DatabaseConnectionException, UnlimitedQueryException và UnlimitedQueryParameterException và lấy lại một cơ sở dữ liệu ngoại lệ. Mặc dù vậy, tôi cho rằng tất cả các ngoại lệ cụ thể này phải xuất phát từ DatabaseException ngay từ đầu, do đó, việc thiết lập lại là không cần thiết.

Bạn sẽ thấy rằng việc loại bỏ các mệnh đề thử không cần thiết (ngay cả những mệnh đề cho mục đích ghi nhật ký thuần túy) sẽ thực sự giúp công việc dễ dàng hơn, không khó hơn. Chỉ những nơi trong chương trình của bạn xử lý ngoại lệ mới được ghi nhật ký ngoại lệ và, nếu tất cả những thứ khác không thành công, một trình xử lý ngoại lệ trên toàn chương trình cho một lần thử cuối cùng để ghi lại ngoại lệ trước khi thoát khỏi chương trình một cách duyên dáng. Ngoại lệ phải có dấu vết ngăn xếp đầy đủ cho biết điểm chính xác trong đó ngoại lệ được ném, do đó thường không cần thiết phải cung cấp ghi nhật ký "ngữ cảnh".

Điều đó nói rằng AOP có thể là một giải pháp khắc phục nhanh cho bạn, mặc dù nó thường kéo theo sự chậm lại một chút về tổng thể. Tôi sẽ khuyến khích bạn thay vào đó loại bỏ các mệnh đề thử không cần thiết hoàn toàn khi không có giá trị nào được thêm vào.


1
" Bản thân ngoại lệ sẽ cung cấp tất cả thông tin cần thiết để ghi nhật ký thích hợp, bao gồm thông báo, mã lỗi và những gì không " Họ nên, nhưng trong thực tế, họ không Null ngoại lệ tham chiếu là trường hợp cổ điển. Tôi không biết bất kỳ ngôn ngữ nào sẽ cho bạn biết biến đã gây ra nó trong một biểu thức phức tạp, chẳng hạn.
David Arno

1
@DavidArno Đúng, nhưng bất kỳ bối cảnh nào bạn có thể cung cấp cũng không thể cụ thể. Nếu không bạn sẽ có try { tester.test(); } catch (NullPointerException e) { logger.error("Variable tester was null!"); }. Theo dõi ngăn xếp là đủ trong hầu hết các trường hợp, nhưng thiếu điều đó, loại lỗi thường là đủ.
Neil
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.