Qual é a melhor prática quando ocorre uma exceção não tratada em um aplicativo de desktop?
Eu estava pensando em mostrar uma mensagem ao usuário, para que ele possa entrar em contato com o suporte. Eu recomendaria ao usuário reiniciar o aplicativo, mas não forçá-lo. Semelhante ao discutido aqui: ux.stackexchange.com - Qual é a melhor maneira de lidar com erros inesperados de aplicativos?
O projeto é um aplicativo .NET WPF, portanto, a proposta descrita pode ter a seguinte aparência (Observe que este é um exemplo simplificado. Provavelmente faria sentido ocultar os detalhes da exceção até o usuário clicar em "Mostrar Detalhes" e fornecer algumas funcionalidades para reporte facilmente o erro):
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...
}
}
Na implementação (Comandos do ViewModels ou manipulador de eventos de eventos externos), eu capturava apenas a exceção exógena específica e deixava todas as outras exceções (exceções de cabeça para osso e desconhecidas) chegarem ao "manipulador de último recurso" descrito acima. Para uma definição de exceções exageradas e exageradas, dê uma olhada em: Eric Lippert - Vexing exceptions
Faz sentido deixar o usuário decidir se o aplicativo deve ser encerrado? Quando o aplicativo é encerrado, você certamente não possui um estado inconsistente ... Por outro lado, o usuário pode perder dados não salvos ou não pode mais parar nenhum processo externo iniciado até que o aplicativo seja reiniciado.
Ou é a decisão de encerrar o aplicativo em exceções não tratadas, dependendo do tipo de aplicativo que você está gravando? É apenas uma troca entre "robustez" vs. "correção", como descrito em Code Complete, Second Edition
Para contextualizar qual tipo de aplicação estamos falando: A aplicação é usada principalmente para controlar instrumentos químicos de laboratório e mostrar os resultados medidos ao usuário. Para isso, os aplicativos WPF se comunicam com alguns serviços (serviços locais e remotos). O aplicativo WPF não se comunica diretamente com os instrumentos.
fonte
Respostas:
Você deve esperar que seu programa seja encerrado por mais motivos do que apenas uma exceção não tratada de qualquer maneira, como uma falta de energia ou um processo em segundo plano diferente que trava todo o sistema. Portanto, eu recomendaria encerrar e reiniciar o aplicativo, mas com algumas medidas para mitigar as consequências de uma reinicialização e minimizar a possível perda de dados .
Comece analisando os seguintes pontos:
Quantos dados podem realmente ser perdidos em caso de encerramento do programa?
Quão grave é essa perda realmente para o usuário? Os dados perdidos podem ser reconstruídos em menos de 5 minutos ou estamos falando de perder um dia de trabalho?
Quanto esforço é necessário para implementar alguma estratégia de "backup intermediário"? Não descarte isso porque "o usuário precisaria inserir um motivo de alteração" em uma operação de salvamento regular, como você escreveu em um comentário. É melhor pensar em algo como um arquivo ou estado temporário, que pode ser recarregado após uma falha do programa automaticamente. Muitos tipos de software de produtividade fazem isso (por exemplo, o MS Office e o LibreOffice têm um recurso de "salvamento automático" e recuperação de falhas).
Caso os dados estejam errados ou corrompidos, o usuário pode ver isso facilmente (talvez após a reinicialização do programa)? Em caso afirmativo, você pode oferecer uma opção para permitir que o usuário salve os dados (com algumas chances de que estejam corrompidos), forçar uma reinicialização, recarregá-los e permitir que o usuário verifique se os dados estão corretos. Certifique-se de não substituir a última versão que foi salva regularmente (em vez disso, grave em um local / arquivo temporário) para evitar danificar a versão antiga.
Se essa estratégia de "backup intermediário" for uma opção sensata, depende, em última análise, do aplicativo e de sua arquitetura, e da natureza e estrutura dos dados envolvidos. Mas se o usuário perder menos de 10 minutos de trabalho e esse acidente ocorrer uma vez por semana ou mais raramente, provavelmente não investirei muito nisso.
fonte
Depende até certo ponto do aplicativo que você está desenvolvendo, mas, em geral, eu diria que, se o seu aplicativo encontrar uma exceção não tratada, será necessário encerrá-lo.
Por quê?
Porque você não pode mais ter confiança no estado do aplicativo.
Definitivamente, forneça um mensagem útil ao usuário, mas você deve finalizar o aplicativo.
Dado o seu contexto, eu definitivamente gostaria que o aplicativo fosse encerrado. Você não deseja que o software executado em laboratório produza saída corrompida e, como não pensou em lidar com a exceção, não tem idéia do motivo pelo qual ela foi lançada e o que está acontecendo.
fonte
Considerando que isso se destina a um laboratório químico e que seu aplicativo não controla os instrumentos diretamente, mas através de outros serviços:
Forçar encerramento após mostrar a mensagem. Após uma exceção não tratada, seu aplicativo está em um estado desconhecido. Pode enviar comandos errados. Pode até invocar demônios nasais . Um comando incorreto pode potencialmente desperdiçar reagentes caros ou trazer perigo para equipamentos ou pessoas .
Mas você pode fazer outra coisa: recuperar normalmente após reiniciar . Presumo que seu aplicativo não reduz esses serviços em segundo plano quando falha. Nesse caso, você pode facilmente recuperar o estado deles. Ou, se você tiver mais estado, considere salvá-lo. Em um armazenamento que possui provisões para atomicidade e integridade dos dados (talvez SQLite?).
Editar:
Conforme declarado nos comentários, o processo que você controla pode exigir alterações com rapidez suficiente para que o usuário não tenha tempo para reagir. Nesse caso, considere reiniciar o aplicativo silenciosamente, além da recuperação normal do estado.
fonte
Tentar geralmente responder a essa pergunta no nível superior do programa não é uma jogada inteligente.
Se algo borbulhou por todo o caminho, e em nenhum momento da arquitetura do aplicativo alguém considerou esse caso, não há generalizações que você possa fazer sobre quais ações são ou não são seguras.
Portanto, não, definitivamente não é um design geralmente aceitável permitir ao usuário escolher se o aplicativo tenta ou não se recuperar, porque o aplicativo e os desenvolvedores demonstrativamente não fizeram a devida diligência necessária para descobrir se isso é possível ou sábio .
No entanto, se o aplicativo tiver partes de alto valor de sua lógica ou comportamento que foram projetadas com esse tipo de recuperação de falhas em mente, e for possível aproveitá-las nesse caso, faça isso de qualquer maneira - nesse caso , pode ser aceitável solicitar ao usuário que verifique se deseja tentar a recuperação ou se deseja apenas encerrar e começar de novo.
Esse tipo de recuperação geralmente não é necessário ou aconselhável para todos (ou mesmo para a maioria) dos programas, mas, se você estiver trabalhando em um programa para o qual esse grau de integridade operacional é necessário, pode ser uma circunstância na qual apresentar esse tipo de solicitar a um usuário seria algo sensato a ser feito.
Em vez de qualquer lógica especial de recuperação de falhas - Não, não faça isso. Você literalmente não tem idéia do que acontecerá; se o fizesse, teria capturado a exceção mais adiante e resolvido isso.
fonte
O problema com "exceções excepcionais", ou seja, exceções que você não previu, é que você não sabe em que estado o programa está. Por exemplo, tentar salvar os dados do usuário pode realmente destruir ainda mais dados .
Por esse motivo, você deve encerrar o aplicativo.
Há uma ideia muito interessante chamada Crash-only Software de George Candea e Armando Fox . A idéia é que, se você projetar seu software de forma que a única maneira de fechá-lo seja travá-lo e a única maneira de iniciá-lo seja se recuperar de um travamento, seu software será mais resiliente e a recuperação de erros caminhos de código serão muito mais minuciosamente testados e exercitados.
Eles tiveram essa ideia depois de perceberem que alguns sistemas iniciaram mais rapidamente após uma falha do que após um desligamento ordenado.
Um bom exemplo, embora não seja mais relevante, são algumas versões mais antigas do Firefox que não apenas iniciam mais rapidamente quando se recuperam de uma falha, mas também têm uma melhor experiência de inicialização dessa maneira ! Nessas versões, se você desligasse o Firefox normalmente, ele fecharia todas as guias abertas e iniciaria com uma única guia vazia. Enquanto que, ao se recuperar de uma falha, ela restaura as guias abertas no momento da falha. (E essa foi a única maneira de fechar o Firefox sem perder o contexto atual de navegação.) Então, o que as pessoas fizeram? Eles simplesmente nunca fechavam o Firefox e sempre o
pkill -KILL firefox
criavam.Há uma boa descrição sobre o software somente para falhas de Valerie Aurora no Linux Weekly News . Os comentários também merecem uma leitura. Por exemplo, alguém nos comentários aponta com razão que essas idéias não são novas e são de fato mais ou menos equivalentes aos princípios de design de aplicativos baseados em Erlang / OTP. E, é claro, olhando para isso hoje, mais 10 anos após o artigo de Valerie e 15 anos após o artigo original, podemos notar que o atual hype dos microsserviços está reinventando essas mesmas idéias mais uma vez. O design moderno do data center em escala de nuvem também é um exemplo de granularidade mais grossa. (Qualquer computador pode travar a qualquer momento sem afetar o sistema.)
No entanto, não basta apenas deixar seu software travar. Tem que ser projetado para isso. Idealmente, seu software seria dividido em componentes pequenos e independentes, cada um com falha independente. Além disso, o "mecanismo de travamento" deve estar fora do componente que está sendo travado.
fonte
A maneira correta de lidar com a maioria das exceções deve ser invalidar qualquer objeto que possa estar em um estado corrompido como consequência e continuar a execução se objetos invalidados não impedirem isso. Por exemplo, o paradigma seguro para atualizar um recurso seria:
Se ocorrer uma exceção inesperada durante a atualização do recurso protegido, presume-se que o recurso esteja em um estado corrompido e o bloqueio seja invalidado, independentemente de a exceção ser de um tipo que, de outra forma, seria benigno.
Infelizmente, os protetores de recursos implementados via
IDisposable
/using
serão liberados sempre que o bloco protegido sair, sem nenhuma maneira de saber se o bloco saiu normalmente ou de forma anormal. Portanto, mesmo que haja critérios bem definidos para quando continuar após uma exceção, não há como saber quando eles se aplicam.fonte
Você pode usar a abordagem que todo aplicativo iOS e MacOS segue: Uma exceção não capturada desativa o aplicativo imediatamente. Além disso, muitos erros, como a matriz fora dos limites ou apenas o estouro aritmético em aplicativos mais recentes, fazem o mesmo. Nenhum aviso.
Na minha experiência, muitos usuários não prestam atenção, mas apenas toque no ícone do aplicativo novamente.
Obviamente, você precisa garantir que tal falha não leve a perda significativa de dados e definitivamente não leve a erros caros. Mas um alerta “Seu aplicativo falhará agora. Ligue para o suporte se isso o incomodar ”não está ajudando ninguém.
fonte