Jaka jest najlepsza praktyka, gdy w aplikacji komputerowej występują nieobsługiwane wyjątki?
Myślałem o wyświetleniu użytkownikowi wiadomości, aby mógł skontaktować się z pomocą techniczną. Poleciłbym użytkownikowi ponowne uruchomienie aplikacji, ale nie wymuszanie jej. Podobne do omawianych tutaj: ux.stackexchange.com - Jaki jest najlepszy sposób radzenia sobie z nieoczekiwanymi błędami aplikacji?
Projekt jest aplikacją WPF platformy .NET, więc opisana propozycja może wyglądać następująco (zwróć uwagę, że jest to uproszczony przykład. Prawdopodobnie sensowne byłoby ukrycie szczegółów wyjątku, dopóki użytkownik nie kliknie opcji „Pokaż szczegóły” i zapewni funkcjonalność łatwo zgłosić błąd):
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 [email protected]. 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...
}
}
We wdrożeniu (Komendy ViewModels lub moduł obsługi zdarzeń zewnętrznych) wychwyciłbym tylko konkretny wyjątek egzogeniczny i pozwoliłbym, aby wszystkie inne wyjątki (bez kości i nieznane wyjątki) wygasały aż do opisanego powyżej „modułu ostatniej instancji”. Definicję wyjątków egzogenicznych i egzogennych można znaleźć w: Eric Lippert - wyjątki Vexing
Czy ma sens pozwolić użytkownikowi zdecydować, czy aplikacja powinna zostać zakończona? Gdy aplikacja zostanie zakończona, na pewno nie będziesz mieć niespójnego stanu ... Z drugiej strony użytkownik może utracić niezapisane dane lub nie jest w stanie zatrzymać żadnego rozpoczętego procesu zewnętrznego, dopóki aplikacja nie zostanie ponownie uruchomiona.
A może to decyzja, czy zakończyć aplikację z nieobsługiwanymi wyjątkami w zależności od rodzaju pisanej aplikacji? Czy to tylko kompromis między „solidnością” a „poprawnością”, jak opisano w Code Complete, wydanie drugie
Aby dać ci kontekst, o jakim rodzaju aplikacji mówimy: Aplikacja służy głównie do kontroli laboratoryjnych instrumentów chemicznych i pokazywania użytkownikowi zmierzonych wyników. W tym celu aplikacje WPF komunikują się z niektórymi usługami (usługami lokalnymi i zdalnymi). Aplikacja WPF nie komunikuje się bezpośrednio z instrumentami.
źródło
Odpowiedzi:
Musisz się spodziewać, że twój program się zakończy z wielu powodów niż nieobsługiwany wyjątek, taki jak awaria zasilania lub inny proces w tle, który powoduje awarię całego systemu. Dlatego zaleciłbym zakończenie i ponowne uruchomienie aplikacji, ale z pewnymi środkami, aby złagodzić konsekwencje takiego ponownego uruchomienia i zminimalizować możliwą utratę danych .
Zacznij od analizy następujących punktów:
Ile danych faktycznie można utracić w przypadku zakończenia programu?
Jak poważna jest taka strata dla użytkownika? Czy utracone dane mogą zostać zrekonstruowane w mniej niż 5 minut, czy mówimy o utracie dni pracy?
Ile wysiłku wymaga wdrożenie strategii „pośredniego tworzenia kopii zapasowych”? Nie wykluczaj tego, ponieważ „użytkownik musiałby wprowadzić powód zmiany” podczas regularnej operacji składowania, jak napisałeś w komentarzu. Lepiej wymyśl coś w rodzaju pliku tymczasowego lub stanu, który może zostać ponownie załadowany po awarii programu automatycznie. Robi to wiele rodzajów oprogramowania produkcyjnego (na przykład MS Office i LibreOffice mają funkcję „automatycznego zapisywania” i odzyskiwania po awarii).
Czy w przypadku, gdy dane były niepoprawne lub uszkodzone, użytkownik może to łatwo dostrzec (może po ponownym uruchomieniu programu)? Jeśli tak, możesz zaoferować opcję zapisania danych przez użytkownika (z niewielką szansą, że są one uszkodzone), a następnie wymuś restart, załaduj ponownie i pozwól użytkownikowi sprawdzić, czy dane wyglądają dobrze. Pamiętaj, aby nie zastępować ostatniej wersji, która była regularnie zapisywana (zamiast tego zapisz w tymczasowej lokalizacji / pliku), aby uniknąć uszkodzenia starej wersji.
To, czy taka strategia „pośredniego tworzenia kopii zapasowych” jest sensowna, zależy ostatecznie od aplikacji i jej architektury oraz od charakteru i struktury danych. Ale jeśli użytkownik straci mniej niż 10 minut pracy, a taka awaria zdarza się raz w tygodniu lub jeszcze rzadziej, prawdopodobnie nie zainwestowałbym w to zbyt wiele.
źródło
Zależy to w pewnym stopniu od rozwijanej aplikacji, ale ogólnie powiedziałbym, że jeśli aplikacja napotka nieobsługiwany wyjątek, musisz go zakończyć.
Czemu?
Ponieważ nie możesz już mieć zaufania do stanu aplikacji.
Zdecydowanie przekaż użytkownikowi pomocną wiadomość, ale ostatecznie należy zakończyć aplikację.
Biorąc pod uwagę twój kontekst, zdecydowanie chciałbym, aby aplikacja została zakończona. Nie chcesz, aby oprogramowanie działające w laboratorium generowało uszkodzone dane wyjściowe, a ponieważ nie pomyślałeś, aby poradzić sobie z wyjątkiem, nie masz pojęcia, dlaczego został zgłoszony i co się dzieje.
źródło
Biorąc pod uwagę, że jest to przeznaczone dla laboratorium chemicznego i że twoja aplikacja nie kontroluje instrumentów bezpośrednio, ale raczej za pośrednictwem innych usług:
Wymuś zakończenie po wyświetleniu komunikatu. Po nieobsługiwanym wyjątku aplikacja jest w nieznanym stanie. Może wysyłać błędne polecenia. Może nawet wywoływać demony nosowe . Błędna komenda może potencjalnie tracić drogich odczynników lub przynieść niebezpieczeństwo dla sprzętu lub ludzi .
Ale możesz zrobić coś innego: z wdziękiem wyzdrowieć po ponownym uruchomieniu . Zakładam, że twoja aplikacja nie wyłącza tych usług w tle, gdy ulega awarii. W takim przypadku możesz łatwo przywrócić im stan. Lub, jeśli masz więcej stanu, rozważ zapisanie go. W magazynie, który ma warunki dotyczące atomowości i integralności danych (może SQLite?).
Edytować:
Jak stwierdzono w komentarzach, proces, który kontrolujesz, może wymagać zmian na tyle szybko, aby użytkownik nie miał czasu na reakcję. W takim przypadku należy rozważyć dyskretne ponowne uruchomienie aplikacji oprócz płynnego odzyskiwania stanu.
źródło
Próba ogólnej odpowiedzi na to pytanie na najwyższym poziomie programu nie jest mądrą zabawą.
Jeśli coś burzyło się przez całą drogę i w żadnym momencie architektury aplikacji nikt nie rozważał tego przypadku, nie możesz uogólnić, jakie działania są lub nie są bezpieczne do podjęcia.
Tak, nie, zdecydowanie nie jest ogólnie dopuszczalnym projektem pozwalanie użytkownikowi na wybór, czy aplikacja będzie próbowała odzyskać, ponieważ aplikacja i programiści demonstracyjnie nie dołożyli należytej staranności, aby dowiedzieć się, czy jest to możliwe, czy nawet mądre .
Jeśli jednak aplikacja ma wartościowe części swojej logiki lub zachowania, które zostały opracowane z myślą o tym rodzaju odzyskiwania po awarii i możliwe jest wykorzystanie ich w tym przypadku, to zrób to za wszelką cenę - w takim przypadku , może być dopuszczalne poproszenie użytkownika o sprawdzenie, czy chce podjąć próbę odzyskania danych, czy też chciałby po prostu wywołać zamknięcie i zacząć od nowa.
Ten rodzaj odzyskiwania nie jest generalnie konieczny ani zalecany dla wszystkich (a nawet większości) programów, ale jeśli pracujesz nad programem, dla którego wymagany jest taki stopień integralności operacyjnej, może to być okoliczność, w której przedstawienie tego rodzaju monit dla użytkownika byłby rozsądnym posunięciem.
Poza specjalną logiką odzyskiwania po awarii - Nie, nie rób tego. Dosłownie nie masz pojęcia, co się stanie, gdybyś to zrobił, złapałbyś wyjątek i zajął się nim.
źródło
Problem z „wyjątkowymi wyjątkami”, tj. Wyjątkami, których nie przewidziałeś, polega na tym, że nie wiesz, w jakim stanie jest program. Na przykład próba zapisania danych użytkownika może w rzeczywistości zniszczyć jeszcze więcej danych .
Z tego powodu powinieneś zakończyć aplikację.
Istnieje bardzo interesujący pomysł, nazwany przez George Candea i Armando Foxa Crash-only Software . Chodzi o to, że jeśli zaprojektujesz swoje oprogramowanie w taki sposób, że jedynym sposobem na jego zamknięcie jest jego awaria, a jedynym sposobem na jego uruchomienie jest odzyskanie po awarii, wtedy twoje oprogramowanie będzie bardziej odporne, a odzyskiwanie po błędzie ścieżki kodu zostaną znacznie dokładniej przetestowane i wykonane.
Wpadli na ten pomysł po zauważeniu, że niektóre systemy zaczęły działać szybciej po awarii niż po uporządkowanym wyłączeniu.
Dobrym, choć już nieistotnym przykładem, są niektóre starsze wersje Firefoksa, które nie tylko uruchamiają się szybciej po przywróceniu po awarii, ale także zapewniają lepsze uruchamianie w ten sposób ! W tych wersjach normalne zamknięcie przeglądarki Firefox spowoduje zamknięcie wszystkich otwartych kart i uruchomienie pojedynczej pustej karty. Podczas odzyskiwania po awarii przywróciłoby otwarte zakładki w chwili awarii. (I to był jedyny sposób na zamknięcie Firefoksa bez utraty aktualnego kontekstu przeglądania.) Co zrobili ludzie? Po prostu nigdy nie zamykali Firefoksa i zawsze
pkill -KILL firefox
go edytowali.W Linux Weekly News znajduje się ładny artykuł o oprogramowaniu tylko do awarii autorstwa Valerie Aurora . Warto też przeczytać komentarze. Na przykład ktoś w komentarzach słusznie zauważa, że te pomysły nie są nowe i w rzeczywistości są mniej więcej równoważne z zasadami projektowania aplikacji opartych na Erlang / OTP. I oczywiście, patrząc na to dzisiaj, kolejne 10 lat po Valerie i 15 lat po oryginalnym artykule, możemy zauważyć, że obecny szum w zakresie mikrousług jeszcze raz wymyśla te same pomysły. Nowoczesne projektowanie centrum danych w chmurze jest również przykładem grubszej ziarnistości. (Dowolny komputer może ulec awarii w dowolnym momencie bez wpływu na system.)
Nie wystarczy jednak pozwolić na awarię oprogramowania. Musi być do tego zaprojektowany. Idealnie byłoby, gdyby twoje oprogramowanie zostało podzielone na małe, niezależne komponenty, z których każdy może ulec awarii niezależnie. Ponadto „mechanizm awarii” powinien znajdować się poza komponentem, który ulega awarii.
źródło
Właściwym sposobem obsługi większości wyjątków powinno być unieważnienie dowolnego obiektu, który może być w konsekwencji w stanie uszkodzonym, i kontynuowanie wykonywania, jeśli unieważnione obiekty nie zapobiegną temu. Na przykład bezpieczny paradygmat aktualizacji zasobu to:
Jeśli podczas aktualizowania strzeżonego zasobu wystąpi nieoczekiwany wyjątek, zasób należy założyć w stanie uszkodzonym, a blokada unieważniona, niezależnie od tego, czy wyjątek jest typu, który w innym przypadku byłby łagodny.
Niestety strażnicy zasobów zaimplementowani przez
IDisposable
/using
zostaną zwolnieni za każdym razem, gdy strzeżony blok wyjdzie, bez żadnej wiedzy, czy blok opuścił normalnie, czy nienormalnie. Tak więc, chociaż powinny istnieć dobrze zdefiniowane kryteria, kiedy należy kontynuować po wyjątku, nie ma sposobu, aby powiedzieć, kiedy mają zastosowanie.źródło
Możesz zastosować podejście, które stosuje każda aplikacja na iOS i MacOS: Nieprzechwycony wyjątek natychmiast usuwa aplikację. Plus wiele błędów, takich jak przekroczenie granicy tablicy lub po prostu przepełnienie arytmetyczne w nowszych aplikacjach, robi to samo. Bez ostrzeżenia.
Z mojego doświadczenia wynika, że wielu użytkowników nie zwraca na to uwagi, tylko ponownie stukając ikonę aplikacji.
Oczywiście musisz upewnić się, że taka awaria nie prowadzi do znacznej utraty danych i zdecydowanie nie prowadzi do kosztownych błędów. Ale ostrzeżenie „Twoja aplikacja ulegnie teraz awarii. Zadzwoń do wsparcia, jeśli Ci to przeszkadza ”, nikomu nie pomaga.
źródło