Làm thế nào để điều trị ngoại lệ chưa được xử lý? (Chấm dứt ứng dụng so với Giữ cho nó sống)


30

Thực hành tốt nhất là gì khi một ngoại lệ chưa được xử lý xảy ra trong ứng dụng máy tính để bàn?

Tôi đã suy nghĩ về việc hiển thị một tin nhắn cho người dùng, để anh ta có thể liên hệ với bộ phận hỗ trợ. Tôi muốn giới thiệu cho người dùng khởi động lại ứng dụng, nhưng không ép buộc. Tương tự như những gì được thảo luận ở đây: ux.stackexchange.com - Cách tốt nhất để xử lý các lỗi ứng dụng không mong muốn là gì?

Dự án là một ứng dụng .NET WPF, vì vậy đề xuất được mô tả có thể giống như thế này (Lưu ý rằng đây là một ví dụ đơn giản. Có lẽ sẽ có ý nghĩa để ẩn các chi tiết ngoại lệ cho đến khi người dùng nhấp vào "Hiển thị chi tiết" và cung cấp một số chức năng cho dễ dàng báo cáo lỗi):

public partial class App : Application
{
    public App()
    {
        DispatcherUnhandledException += OnDispatcherUnhandledException;
    }

    private void OnDispatcherUnhandledException(object sender, DispatcherUnhandledExceptionEventArgs e)
    {
        LogError(e.Exception);
        MessageBoxResult result = MessageBox.Show(
             $"Please help us fix it and contact support@example.com. Exception details: {e.Exception}" +
                        "We recommend to restart the application. " +
                        "Do you want to stop the application now? (Warning: Unsaved data gets lost).", 
            "Unexpected error occured.", MessageBoxButton.YesNo);

        // Setting 'Handled' to 'true' will prevent the application from terminating.
        e.Handled = result == MessageBoxResult.No;
    }

    private void LogError(Exception ex)
    {
        // Log to a log file...
    }
}

Trong quá trình triển khai (Lệnh của ViewModels hoặc trình xử lý sự kiện của các sự kiện bên ngoài) sau đó tôi sẽ chỉ bắt ngoại lệ ngoại sinh cụ thể và để tất cả các ngoại lệ khác (ngoại lệ xương và không xác định) nổi lên "Trình xử lý cuối cùng" được mô tả ở trên. Để có định nghĩa về các trường hợp ngoại lệ có xương và ngoại sinh, hãy xem: Eric Lippert - Ngoại lệ làm phiền

Liệu nó có ý nghĩa để cho người dùng quyết định nếu ứng dụng nên được chấm dứt? Khi ứng dụng bị chấm dứt, thì bạn chắc chắn không có trạng thái không nhất quán ... Mặt khác, người dùng có thể mất dữ liệu chưa được lưu hoặc không thể dừng bất kỳ quy trình bên ngoài đã bắt đầu nào nữa cho đến khi ứng dụng được khởi động lại.

Hoặc là quyết định nếu bạn nên chấm dứt ứng dụng trên các ngoại lệ chưa được xử lý tùy thuộc vào loại ứng dụng bạn đang viết? Có phải đó chỉ là sự đánh đổi giữa "sự mạnh mẽ" so với "tính chính xác" như được mô tả trong Code Complete, Second Edition

Để cung cấp cho bạn một số bối cảnh chúng ta đang nói về loại ứng dụng nào: Ứng dụng chủ yếu được sử dụng để kiểm soát các dụng cụ phòng thí nghiệm hóa học và hiển thị kết quả đo cho người dùng. Để làm như vậy, các ứng dụng WPF giao tiếp với một số dịch vụ (dịch vụ địa phương và từ xa). Ứng dụng WPF không giao tiếp trực tiếp với các công cụ.


27
Nếu bạn không mong đợi một ngoại lệ, làm thế nào bạn có thể chắc chắn rằng ứng dụng có thể tiếp tục hoạt động một cách an toàn?
Ded repeatator

2
@Ded repeatator: Tất nhiên, bạn không thể chắc chắn. Giống như đã được viết dưới dạng nhận xét cho câu trả lời của Matthew : "Vâng, tất nhiên ứng dụng có thể ở trạng thái không hợp lệ. Có thể một số ViewModel chỉ được cập nhật một phần. Nhưng điều này có gây hại gì không? Người dùng có thể tải lại dữ liệu và nếu có gì đó không hợp lệ gửi đến dịch vụ, sau đó dịch vụ sẽ không chấp nhận dịch vụ này. Người dùng có thể lưu lại trước khi anh ta khởi động lại ứng dụng không?
Jonas Benz

2
@Voo Vì vậy, bạn có chắc chắn rằng ứng dụng có thể tiếp tục hoạt động một cách an toàn bằng cách luôn mong đợi một ngoại lệ? Có vẻ như bạn đang từ chối tiền đề nhận được một ngoại lệ bất ngờ.
Ded repeatator

2
Trong mọi trường hợp, xin vui lòng sao chép thông báo lỗi. Ngoài ra, hãy nói trong đó tệp nhật ký đã được viết.
ComFalet

2
Xử lý không nhất thiết ngụ ý hành động rõ ràng. Nếu bạn có thể chắc chắn ứng dụng có thể tiếp tục an toàn, bạn đã xử lý ngoại lệ.
chepner

Câu trả lời:


47

Bạn phải hy vọng chương trình của mình chấm dứt vì nhiều lý do hơn là chỉ là một ngoại lệ chưa được xử lý, như mất điện hoặc một quá trình nền khác làm hỏng toàn bộ hệ thống. Do đó, tôi khuyên bạn nên chấm dứt và khởi động lại ứng dụng, nhưng với một số biện pháp để giảm thiểu hậu quả của việc khởi động lại như vậygiảm thiểu việc mất dữ liệu có thể .

Bắt đầu với việc phân tích các điểm sau:

  • Bao nhiêu dữ liệu thực sự có thể bị mất trong trường hợp chấm dứt chương trình?

  • Làm thế nào nghiêm trọng là một mất mát thực sự cho người dùng? Dữ liệu bị mất có thể được xây dựng lại trong vòng chưa đầy 5 phút không, hay chúng ta đang nói về việc mất một ngày làm việc?

  • Cần bao nhiêu nỗ lực để thực hiện một số chiến lược "sao lưu trung gian"? Đừng loại trừ điều này vì "người dùng sẽ phải nhập lý do thay đổi" trong thao tác lưu thông thường, như bạn đã viết trong một bình luận. Tốt hơn nên nghĩ về một cái gì đó như một tập tin hoặc trạng thái tạm thời, có thể được tải lại sau khi chương trình bị sập tự động. Nhiều loại phần mềm năng suất thực hiện điều này (ví dụ: MS Office và LibreOffice đều có tính năng "tự động lưu" và khôi phục sự cố).

  • Trong trường hợp dữ liệu bị sai hoặc bị hỏng, người dùng có thể thấy điều này dễ dàng không (có thể sau khi khởi động lại chương trình)? Nếu có, bạn có thể cung cấp tùy chọn cho phép người dùng lưu dữ liệu (với một số khả năng nhỏ là nó bị hỏng), sau đó buộc khởi động lại, tải lại và để người dùng kiểm tra xem dữ liệu có ổn không. Đảm bảo không ghi đè lên phiên bản cuối cùng được lưu thường xuyên (thay vào đó ghi vào vị trí / tệp tạm thời) để tránh làm hỏng phiên bản cũ.

Nếu chiến lược "sao lưu trung gian" như vậy là một lựa chọn hợp lý cuối cùng phụ thuộc vào ứng dụng và kiến ​​trúc của nó, và vào bản chất và cấu trúc của dữ liệu liên quan. Nhưng nếu người dùng sẽ mất ít hơn 10 phút làm việc và một sự cố như vậy xảy ra mỗi tuần một lần hoặc thậm chí hiếm khi hơn, tôi có lẽ sẽ không đầu tư quá nhiều vào việc này.


10
vi.wikipedia.org/wiki/Crash-only_software và đây là cách các ứng dụng Android hoạt động theo sự cần thiết.
Vịt Mooing

3
Câu trả lời tuyệt vời - và một ví dụ hay về việc xem xét mọi thứ trong bối cảnh rộng hơn (trong trường hợp này "làm thế nào chúng ta có thể ngăn ngừa mất dữ liệu trong mọi trường hợp sự cố?") Dẫn đến một giải pháp tốt hơn.
sleske

1
Tôi đã thực hiện một chỉnh sửa nhỏ để lưu ý rằng bạn không nên ghi đè dữ liệu cũ - hy vọng bạn không phiền.
sleske

1
@MooingDuck Rất nhiều ứng dụng Android (như trò chơi) bị mất trạng thái khi gặp sự cố.
dùng253751

1
@immibis: Vâng, Android thực sự có một số lượng lớn các ứng dụng chất lượng thực sự thấp.
Vịt Mooing

30

Nó phụ thuộc vào một mức độ nào đó vào ứng dụng bạn đang phát triển nhưng nói chung, tôi nói rằng nếu ứng dụng của bạn gặp phải một ngoại lệ chưa được xử lý, bạn cần phải chấm dứt nó.

Tại sao?

Bởi vì bạn không còn có thể tin tưởng vào trạng thái của ứng dụng.

Chắc chắn, cung cấp một thông điệp hữu ích cho người dùng, nhưng cuối cùng bạn nên chấm dứt ứng dụng.

Với bối cảnh của bạn, tôi chắc chắn sẽ muốn ứng dụng chấm dứt. Bạn không muốn phần mềm chạy trong phòng thí nghiệm tạo ra đầu ra bị hỏng và vì bạn không nghĩ sẽ xử lý ngoại lệ, bạn không biết tại sao nó bị ném và những gì đang xảy ra.


Tôi đã cố gắng thêm một số thông tin ngữ cảnh về ứng dụng trong phần cuối.
Jonas Benz

10
@JonasBenz Không tốt hơn cho người dùng nếu anh ta có thể lưu trước khi khởi động lại ứng dụng? Có, nhưng làm thế nào để bạn biết liệu dữ liệu người dùng sẽ lưu có hợp lệ và không bị hỏng không? Tại thời điểm này, bạn đã có một ngoại lệ không mong muốn và bạn thực sự không biết tại sao. Con đường an toàn nhất của bạn, mặc dù gây khó chịu cho người dùng, là chấm dứt ứng dụng. Nếu bạn lo lắng về công việc tiết kiệm người dùng, bạn sẽ sử dụng chiến lược tiết kiệm liên tục. Một lần nữa, tất cả phụ thuộc vào ứng dụng bạn đang viết.
Matthew

4
Vâng, tôi có thể tranh luận theo cách tương tự ở đây: Tôi không đồng ý với sự hiện diện của nút Tiếp tục. Vấn đề chỉ đơn giản là nếu bạn, nhà phát triển ứng dụng không biết liệu bạn có thể tiếp tục an toàn không, làm sao người dùng có thể biết? Nếu bạn nhận được một ngoại lệ chưa được xử lý, điều đó có nghĩa là bạn có một lỗi mà bạn không mong đợi và bạn không thể nói chắc chắn điều gì đang xảy ra vào thời điểm này. Người dùng sẽ muốn tiếp tục vì họ sẽ không muốn mất công việc của mình, tôi hiểu điều đó, nhưng bạn có muốn để họ tiếp tục ngay cả khi ứng dụng của bạn có thể tạo ra kết quả xấu vì lỗi này không?
Matthew

3
@Matthew "nếu bạn, nhà phát triển ứng dụng không biết liệu bạn có thể tiếp tục an toàn không, làm sao người dùng có thể biết" , nhà phát triển không biết khi họ viết mã. Khi người dùng gặp một lỗi cụ thể như thế này, nó có thể được biết đến. Và người dùng có thể tìm hiểu từ bất kỳ diễn đàn người dùng, kênh hỗ trợ nào và không có gì, hoặc đơn giản bằng cách kiểm tra và xem điều gì xảy ra với dữ liệu của họ ... Tôi đồng ý rằng nó vẫn hơi mơ hồ và nguy hiểm như tính năng của người dùng, chỉ ra rằng thời gian làm cho nó người dùng có thể thực sự biết liệu "tiếp tục" có hợp lý hay không.
hyde

1
@JonasBenz, trong Windows 3.1, hộp thoại xuất hiện khi chương trình thực hiện truy cập bộ nhớ bất hợp pháp có nút "bỏ qua" cho phép chương trình tiếp tục chạy. Bạn sẽ lưu ý rằng mọi phiên bản Windows tiếp theo không có nút đó.
Đánh dấu

12

Xem xét rằng điều này có nghĩa là cho một phòng thí nghiệm hóa học và ứng dụng của bạn không kiểm soát trực tiếp các công cụ mà thông qua các dịch vụ khác:

Buộc chấm dứt sau khi hiển thị thông báo. Sau một ngoại lệ chưa được xử lý, ứng dụng của bạn ở trạng thái không xác định. Nó có thể gửi các lệnh sai. Nó thậm chí có thể gọi quỷ mũi . Một lệnh sai lầm có thể có khả năng lãng phí thuốc thử đắt tiền hoặc gây nguy hiểm cho thiết bị hoặc con người .

Nhưng bạn có thể làm một cái gì đó khác: phục hồi duyên dáng sau khi khởi động lại . Tôi giả định rằng ứng dụng của bạn không tự hủy các dịch vụ nền đó khi nó gặp sự cố. Trong trường hợp đó bạn có thể dễ dàng phục hồi trạng thái từ chúng. Hoặc, nếu bạn có nhiều trạng thái hơn, hãy xem xét việc lưu nó. Trong một bộ lưu trữ có các quy định về tính nguyên vẹn và tính toàn vẹn của dữ liệu (có thể là SQLite?).

Chỉnh sửa:

Như đã nêu trong các nhận xét, quy trình bạn kiểm soát có thể yêu cầu thay đổi đủ nhanh để người dùng không có thời gian phản ứng. Trong trường hợp đó, bạn nên cân nhắc âm thầm khởi động lại ứng dụng bên cạnh việc khôi phục trạng thái duyên dáng.


Chấm dứt ở trạng thái yêu cầu theo dõi các lệnh NGAY BÂY GIỜ có thể nguy hiểm như trong phòng thí nghiệm hóa học.
Oleg V. Volkov

@ OlegV.Volkov vì vậy có thể tự khởi động lại khi chấm dứt? Trên một máy tính phong nha, việc khởi động GUI sẽ có thứ tự hàng trăm mili giây hàng đầu. Nếu quy trình yêu cầu kiểm soát thời gian khó hơn sẽ không được thực hiện trên hệ điều hành không theo thời gian thực. Mặc dù đó là OP, người nên thực hiện đánh giá rủi ro cuối cùng.
Jan Dorniak

@ OlegV.Volkov đó là một điểm tốt, vì vậy tôi đã thêm ý kiến ​​của tôi về nó trong câu trả lời.
Jan Dorniak

8

Cố gắng trả lời chung câu hỏi này ở cấp cao nhất của chương trình không phải là một trò chơi thông minh.

Nếu có thứ gì đó nổi lên trên mọi nẻo đường, và không có điểm nào trong kiến ​​trúc của ứng dụng đã có ai xem xét trường hợp này, bạn không có khái quát nào bạn có thể thực hiện về những hành động, hoặc không an toàn để thực hiện.

Vì vậy, không, chắc chắn đây không phải là một thiết kế có thể chấp nhận được để cho phép người dùng chọn xem ứng dụng có cố gắng khôi phục hay không, bởi vì ứng dụng và nhà phát triển rõ ràng không thực hiện sự cần mẫn cần thiết để tìm hiểu xem điều đó có thể hay thậm chí là khôn ngoan .

Tuy nhiên, nếu ứng dụng có các phần có giá trị cao về logic hoặc hành vi đã được thiết kế với loại phục hồi thất bại này và có thể tận dụng chúng trong trường hợp này, thì bằng mọi cách, hãy làm như vậy - Trong trường hợp đó, hãy làm như vậy - , có thể chấp nhận nhắc nhở người dùng xem họ có muốn thử phục hồi hay không, nếu họ muốn gọi nó là thoát và bắt đầu lại.

Loại phục hồi này thường không cần thiết hoặc được khuyến nghị cho tất cả (hoặc thậm chí là hầu hết) các chương trình, nhưng, nếu bạn đang làm việc trên một chương trình mà mức độ toàn vẹn hoạt động này được yêu cầu, đó có thể là một tình huống trong đó trình bày loại này nhắc nhở người dùng sẽ là một việc lành mạnh để làm.

Trong bất kỳ logic phục hồi thất bại đặc biệt nào - Không, đừng làm điều này. Bạn thực sự không biết điều gì sẽ xảy ra, nếu bạn đã làm, bạn sẽ bắt gặp ngoại lệ tiếp tục và xử lý nó.


Thật không may, nhiều phương pháp cho những thứ như "Xây dựng một đối tượng với dữ liệu nhận được từ một vị trí nào đó" không phân biệt giữa các ngoại lệ chỉ ra rằng hành động không thể hoàn thành, nhưng nỗ lực không có tác dụng phụ, so với những phương pháp chỉ ra rằng điều gì đó nghiêm trọng hơn đã đi sai Thực tế là một nỗ lực tải tài nguyên đã thất bại vì một lý do nào đó mà người ta không lường trước được nên không gây ra lỗi nghiêm trọng nếu người ta thường chuẩn bị cho việc không thể xây dựng đối tượng. Vấn đề là tác dụng phụ, điều không may là một khung ngoại lệ bỏ qua.
supercat

@supercat - Nếu bạn có thể xác định một lỗi, bạn có thể xử lý nó. Nếu bạn không thể xác định nó, bạn không thể xử lý nó, trừ khi bạn viết một thói quen để chạy kiểm tra tính toàn vẹn về trạng thái của ứng dụng để cố gắng tìm ra những gì có thể đã sai. Không có vấn đề gì về lỗi 'có thể là do', chúng tôi đã thiết lập rõ ràng rằng chúng tôi không biết rằng thực tế là chúng tôi đang cố gắng xử lý các trường hợp ngoại lệ chưa được xử lý.
Iron Gremlin

3

Vấn đề với "trường hợp ngoại lệ đặc biệt", tức là trường hợp ngoại lệ mà bạn không lường trước được, là bạn không biết chương trình đang ở trạng thái nào. Ví dụ: cố gắng lưu dữ liệu của người dùng thực sự có thể phá hủy nhiều dữ liệu hơn .

Vì lý do đó, bạn nên chấm dứt ứng dụng.

Có một ý tưởng rất thú vị được gọi là Phần mềm chỉ dành cho Crash của George Candea và Armando Fox . Ý tưởng là nếu bạn thiết kế phần mềm của mình theo cách mà cách duy nhất để đóng nó là sập nó và cách duy nhất để khởi động nó là phục hồi sau sự cố, thì phần mềm của bạn sẽ linh hoạt hơn và phục hồi lỗi đường dẫn mã sẽ được kiểm tra và thực hiện kỹ lưỡng hơn nhiều.

Họ đã nảy ra ý tưởng này sau khi nhận thấy rằng một số hệ thống bắt đầu nhanh hơn sau một sự cố so với sau khi tắt máy có trật tự.

Một ví dụ tốt, mặc dù không còn phù hợp, là một số phiên bản Firefox cũ hơn, không chỉ bắt đầu nhanh hơn khi phục hồi sau sự cố, mà còn có trải nghiệm khởi động tốt hơn theo cách đó ! Trong các phiên bản đó, nếu bạn tắt Firefox bình thường, nó sẽ đóng tất cả các tab đang mở và khởi động với một tab trống duy nhất. Trong khi phục hồi sau sự cố, nó sẽ khôi phục các tab đang mở tại thời điểm xảy ra sự cố. (Và đó là cách duy nhất để đóng Firefox mà không làm mất bối cảnh duyệt web hiện tại của bạn.) Vậy, mọi người đã làm gì? Họ chỉ đơn giản là không bao giờ đóng Firefox và thay vào đó luôn luôn chỉnh sửa pkill -KILL firefoxnó.

một bài viết hay về phần mềm chỉ có sự cố của Valerie Aurora trên Linux Weekly News . Các ý kiến ​​cũng đáng đọc. Ví dụ, một số người trong các ý kiến ​​chỉ ra rằng những ý tưởng đó không phải là mới và trên thực tế ít nhiều tương đương với các nguyên tắc thiết kế của các ứng dụng dựa trên Erlang / OTP. Và, tất nhiên, nhìn vào điều này ngày hôm nay, 10 năm sau Valerie và 15 năm sau bài báo gốc, chúng ta có thể nhận thấy rằng sự cường điệu của dịch vụ vi mô hiện tại đang tái phát minh lại những ý tưởng tương tự. Thiết kế trung tâm dữ liệu quy mô đám mây hiện đại cũng là một ví dụ về độ chi tiết thô hơn. (Bất kỳ máy tính nào cũng có thể bị sập bất cứ lúc nào mà không ảnh hưởng đến hệ thống.)

Tuy nhiên, nó không đủ để chỉ phần mềm của bạn bị sập. Nó phải được thiết kế cho nó. Lý tưởng nhất là phần mềm của bạn sẽ được chia thành các thành phần nhỏ, độc lập mà mỗi phần mềm có thể bị sập độc lập. Ngoài ra, "cơ chế sự cố" phải nằm ngoài thành phần đang bị hỏng.


1

Cách thích hợp để xử lý hầu hết các trường hợp ngoại lệ là vô hiệu hóa bất kỳ đối tượng nào có thể ở trạng thái bị hỏng do hậu quả và tiếp tục thực thi nếu các đối tượng bị vô hiệu hóa không ngăn chặn điều đó. Ví dụ: mô hình an toàn để cập nhật tài nguyên sẽ là:

acquire lock
try
  update guarded resource
if exception
  invalidate lock
else
  release lock
end try

Nếu một ngoại lệ không mong muốn xảy ra trong khi cập nhật tài nguyên được bảo vệ, tài nguyên đó sẽ được coi là ở trạng thái bị hỏng và khóa bị vô hiệu, bất kể ngoại lệ đó có phải là loại lành tính hay không.

Thật không may, các nhân viên bảo vệ tài nguyên được triển khai thông qua IDisposable/ usingsẽ được giải phóng bất cứ khi nào khối bảo vệ thoát ra, mà không có cách nào để biết liệu khối thoát ra bình thường hay bất thường. Do đó, mặc dù cần có các tiêu chí được xác định rõ ràng khi nào nên tiếp tục sau một ngoại lệ, không có cách nào để biết khi nào chúng áp dụng.


+1 chỉ đơn giản là để thể hiện quan điểm tương đối không rõ ràng và vẫn không phổ biến này về cách thức phù hợp. Tôi thực sự không biết nếu tôi đồng ý với nó, bởi vì đây là một cuốn tiểu thuyết / quy tắc đối với tôi, vì vậy tôi phải nghiền ngẫm nó trong một thời gian, nhưng nó có vẻ rất khôn ngoan.
mtraceur

0

Bạn có thể sử dụng cách tiếp cận mà mọi ứng dụng iOS và MacOS duy nhất tuân theo: Một ngoại lệ chưa được xử lý sẽ làm mất ứng dụng ngay lập tức. Cộng với nhiều lỗi, như mảng ngoài giới hạn hoặc chỉ tràn số học trong các ứng dụng mới hơn cũng làm như vậy. Không có cảnh báo.

Theo kinh nghiệm của tôi, nhiều người dùng không nhận bất kỳ thông báo nào mà chỉ cần nhấn vào biểu tượng ứng dụng một lần nữa.

Rõ ràng bạn cần chắc chắn rằng một sự cố như vậy không dẫn đến mất dữ liệu đáng kể và chắc chắn không dẫn đến những sai lầm tốn kém. Nhưng một cảnh báo thì ứng dụng của bạn sẽ bị sập ngay bây giờ. Gọi cho bộ phận hỗ trợ nếu điều đó làm phiền bạn.

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.