Como tratar exceções não tratadas? (Encerre o aplicativo x Mantenha-o vivo)

30

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.

Jonas Benz
fonte
27
Se você não esperava uma exceção, como pode ter certeza de que o aplicativo pode continuar trabalhando com segurança?
Deduplicator
2
@ Reduplicador: Claro, você não pode ter certeza. Como já foi escrito como comentário à resposta de Matthew : "Sim, é claro que o aplicativo pode estar em um estado inválido. Talvez alguns ViewModel só tenham sido atualizados parcialmente. Mas isso pode causar algum dano? O usuário pode recarregar os dados e se algo inválido for enviar para o serviço, então o serviço não o aceitará. Não é melhor para o usuário se ele conseguir salvar antes de reiniciar o aplicativo? "
Jonas Benz
2
@Voo Então, você garante que o aplicativo possa continuar com segurança, sempre esperando uma exceção? Parece que você está negando a premissa de receber uma exceção inesperada.
Deduplicator
2
De qualquer forma, torne a mensagem de erro copiável. Como alternativa, diga em qual arquivo de log foi gravado.
ComFreek
2
O manuseio não implica necessariamente ação explícita. Se você pode ter certeza de que o aplicativo pode continuar com segurança, você tratou da exceção.
chepner 8/10

Respostas:

47

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.

Doc Brown
fonte
10
pt.wikipedia.org/wiki/Crash-only_software , e é assim que os aplicativos Android funcionam por necessidade.
Mooing Duck
3
Uma excelente resposta - e um bom exemplo de como considerar as coisas em um contexto mais amplo (neste caso "como podemos evitar a perda de dados em qualquer caso de falha?") Leva a uma solução melhor.
sleske 8/10
1
Fiz uma pequena edição para observar que você não deve substituir dados antigos - espero que não se importe.
sleske 8/10
1
@MooingDuck Muitos aplicativos Android (como jogos) perdem seu estado em um acidente.
user253751 8/10
1
@immibis: Sim, o Android tem um grande número de aplicativos de qualidade realmente baixa.
Mooing Duck
30

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.

Mateus
fonte
Tentei adicionar algumas informações de contexto sobre o aplicativo na última parte.
Jonas Benz
10
@JonasBenz Não é melhor para o usuário se ele pode salvar antes de reiniciar o aplicativo? Sim, mas como você sabe se os dados que o usuário salvaria são válidos e não estão corrompidos? Neste ponto, você tem uma exceção inesperada e realmente não sabe o porquê. Seu caminho mais seguro, embora irritante para o usuário, é finalizar o aplicativo. Se você estiver preocupado com o trabalho de economia de usuário, empregaria uma estratégia de economia constante. Novamente, tudo depende do aplicativo que você está escrevendo.
Matthew
4
Sim, posso argumentar da mesma maneira aqui: discordo da presença de um botão Continuar. O problema é simplesmente que, se você, o desenvolvedor do aplicativo, não sabe se pode continuar com segurança, como o usuário pode saber? Se você receber uma exceção não tratada, significa que você tem um erro que não esperava e não pode dizer com certeza o que está acontecendo neste momento. O usuário vai querer continuar porque não vai querer perder o trabalho, eu entendo isso, mas você deseja deixá-lo continuar mesmo que seu aplicativo possa estar produzindo resultados ruins devido a esse erro?
Matthew
3
@ Matthew "se você, o desenvolvedor do aplicativo, não sabe se pode continuar com segurança, como o usuário pode saber" , o desenvolvedor não sabia quando escreveu o código. Quando o usuário encontra um bug específico como este, ele pode ser conhecido. E o usuário pode descobrir em qualquer fórum do usuário, canal de suporte e outros enfeites, ou simplesmente testando e vendo o que acontece com seus dados ... Concordo que ainda é um pouco obscuro e perigoso como o recurso do usuário, apenas indicando que o tempo o torna possível que o usuário realmente saiba se "continuar" é sensato ou não.
hyde
1
@JonasBenz, no Windows 3.1, a caixa de diálogo exibida quando um programa executava um acesso ilegal à memória tinha um botão "ignorar" que permitia que o programa continuasse em execução. Você notará que todas as versões subseqüentes do Windows não possuem esse botão.
Mark
12

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.

Jan Dorniak
fonte
A rescisão em um estado que requer comandos de acompanhamento AGORA MESMO poderia ser igualmente perigosa em laboratório químico.
Oleg V. Volkov
@ OlegV.Volkov então talvez você se reinicie após o término? Em um computador decente iniciando uma GUI, a ordem das centenas de milissegundos é superior. Se o processo exigir tempos mais difíceis, o controle não será implementado em um sistema operacional não em tempo real. Embora seja OP quem deve fazer a avaliação final dos riscos.
Jan Dorniak 10/10
@ OlegV.Volkov esse é um bom argumento, então eu adicionei minha opinião sobre isso na resposta.
Jan Dorniak 10/10
8

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.

Iron Gremlin
fonte
Infelizmente, muitos métodos para coisas como "Construir um objeto com dados recebidos de algum local" não fazem distinção entre exceções que indicam que a ação não pôde ser concluída, mas a tentativa não teve efeitos colaterais, em comparação com aqueles que indicam algo mais sério deu errado. O fato de uma tentativa de carregar um recurso falhar por algum motivo que não havia previsto não deve forçar um erro fatal se geralmente estiver preparado para a incapacidade de construir o objeto. O que importa são os efeitos colaterais, que infelizmente são algo que as estruturas de exceção ignoram.
supercat
@ supercat - Se você conseguir identificar um erro, poderá solucioná-lo. Se você não conseguir identificá-lo, não poderá identificá-lo, a menos que escreva uma rotina para executar uma verificação de integridade no estado do aplicativo para tentar investigar o que pode ter dado errado. Não importa qual possa ter sido o erro ', estabelecemos expressamente que não sabemos disso pelo fato de estarmos tentando geralmente lidar com exceções não capturadas.
Iron Gremlin
3

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 firefoxcriavam.

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.

Jörg W Mittag
fonte
1

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:

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

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/ usingserã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.

supercat
fonte
+1 simplesmente por expressar essa perspectiva relativamente não óbvia e ainda não comum sobre o que é a maneira correta. Na verdade, ainda não sei se concordo com isso, porque essa é uma nova regra / heurística para mim, então tenho que ponderá-la por um tempo, mas parece plausivelmente sábia.
mtraceur 9/10
0

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.

gnasher729
fonte