Melhores práticas para tratamento de exceções em um aplicativo Windows Forms?

118

No momento, estou escrevendo meu primeiro aplicativo Windows Forms. Eu li alguns livros sobre C # agora, então tenho um entendimento relativamente bom de quais recursos de linguagem C # tem para lidar com exceções. Eles são todos bastante teóricos, no entanto, o que eu ainda não entendi é como traduzir os conceitos básicos em um bom modelo de tratamento de exceções em meu aplicativo.

Alguém gostaria de compartilhar alguma pérola de sabedoria sobre o assunto? Publique quaisquer erros comuns que você viu iniciantes como eu cometer e qualquer conselho geral sobre como lidar com exceções de uma forma que deixe meu aplicativo mais estável e robusto.

As principais coisas que estou tentando resolver são:

  • Quando devo relançar uma exceção?
  • Devo tentar ter um mecanismo central de tratamento de erros de algum tipo?
  • O tratamento de exceções que podem ser lançadas prejudica o desempenho em comparação com o teste preventivo, como a existência de um arquivo no disco?
  • Todos os códigos executáveis ​​devem ser colocados em blocos try-catch-finally?
  • Há algum momento em que um bloco catch vazio pode ser aceitável?

Todos os conselhos recebidos com gratidão!

Jon Artus
fonte

Respostas:

79

Mais alguns bits ...

Você absolutamente deve ter uma política centralizada de tratamento de exceções em vigor. Isso pode ser tão simples quanto embrulhar Main()em um try / catch, falhando rapidamente com uma mensagem de erro graciosa para o usuário. Este é o manipulador de exceção de "último recurso".

As verificações preventivas estão sempre corretas, se possível, mas nem sempre são perfeitas. Por exemplo, entre o código onde você verifica a existência de um arquivo e a próxima linha onde você o abre, o arquivo pode ter sido excluído ou algum outro problema pode impedir seu acesso. Você ainda precisa tentar / pegar / finalmente naquele mundo. Use a verificação preemptiva e try / catch / finally, conforme apropriado.

Nunca "engula" uma exceção, exceto nos casos mais bem documentados, quando você está absolutamente, positivamente certo de que a exceção lançada é suportável. Isso quase nunca será o caso. (E se for, certifique-se de engolir apenas a classe de exceção específica - nunca engula System.Exception.)

Ao criar bibliotecas (usadas pelo seu aplicativo), não engula as exceções e não tenha medo de deixá-las borbulhar. Não jogue de novo, a menos que tenha algo útil a acrescentar. Nunca (em C #) faça isso:

throw ex;

Como você vai apagar a pilha de chamadas. Se você precisar relançar (o que ocasionalmente é necessário, como ao usar o Bloco de Manipulação de Exceções da Biblioteca Corporativa), use o seguinte:

throw;

No final do dia, a grande maioria das exceções lançadas por um aplicativo em execução deve ser exposta em algum lugar. Eles não devem ser expostos aos usuários finais (já que geralmente contêm dados proprietários ou valiosos), mas geralmente registrados, com os administradores notificados sobre a exceção. O usuário pode ser apresentado a uma caixa de diálogo genérica, talvez com um número de referência, para manter as coisas simples.

O tratamento de exceções no .NET é mais arte do que ciência. Todos terão seus favoritos para compartilhar aqui. Estas são apenas algumas das dicas que aprendi ao usar o .NET desde o primeiro dia, técnicas que salvaram minha pele em mais de uma ocasião. Sua milhagem pode variar.

John Rudy
fonte
1
Se deixarmos as exceções surgirem, como o chamador saberá se a exceção indica que uma operação falhou, mas o sistema está fundamentalmente bem (por exemplo, um usuário tentou abrir um arquivo de documento corrompido; o arquivo não carrega, mas tudo senão deve estar bem), ou se isso indica que a CPU está em chamas e se deve ir para as saídas o mais rápido possível? Algo como ArgumentException pode indicar qualquer um, dependendo das circunstâncias em que é lançado.
supercat
2
@supercat Escrevendo subclasses específicas de ApplicationExceptionpara aqueles casos de falha que o aplicativo deve ser razoavelmente capaz de diferenciar e tratar.
Matt Enright
@Matt Enright: Certamente é possível capturar as próprias exceções, mas não estou ciente de nada remotamente parecido com uma convenção pela qual os módulos que lançam exceções indicam se indicam a corrupção de qualquer estado fora do que está implícito na falha. Idealmente, um método como SuperDocument.CreateFromFile () seria bem-sucedido, lançaria uma exceção CleanFailure ou lançaria um SomethingReallyBadHappenedException. Infelizmente, a menos que um empacote tudo que possa lançar uma exceção dentro de seu próprio bloco catch, não há como saber se um InvalidOperationException ...
supercat
@Matt Enright: ... deve ser agrupado em um CleanFailureException ou um SomethingReallyBadHappenedException. E envolver tudo em blocos individuais de try-catch anularia todo o propósito de ter exceções em primeiro lugar.
supercat
2
Eu acho que é um projeto de biblioteca pobre, para ocultar modos de falha com um tipo de exceção impreciso. Não lance uma FileNotFoundException se o que você realmente quer dizer é uma IOException ou uma InvalidDataException, uma vez que o aplicativo precisa responder de maneira diferente a cada caso. Coisas como StackOverflowException ou OutOfMemoryException não podem ser razoavelmente manipuladas por um aplicativo, então apenas deixe-os borbulhar, já que um aplicativo bem comportado tem o manipulador de "último recurso" centralizado no lugar de qualquer maneira.
Matt Enright
63

Há um excelente artigo CodeProject de código aqui . Aqui estão alguns destaques:

  • Plano para o pior *
  • Verifique mais cedo
  • Não confie em dados externos
  • Os únicos dispositivos confiáveis ​​são: o vídeo, o mouse e o teclado.
  • As gravações também podem falhar
  • Codifique com Segurança
  • Não lance uma nova exceção ()
  • Não coloque informações de exceção importantes no campo Mensagem
  • Coloque um único catch (Exception ex) por thread
  • Exceções genéricas detectadas devem ser publicadas
  • Log Exception.ToString (); nunca registre apenas Exception.Message!
  • Não pegue (Exceção) mais de uma vez por tópico
  • Nunca engula exceções
  • O código de limpeza deve ser colocado em blocos finally
  • Use "usando" em todos os lugares
  • Não retorne valores especiais em condições de erro
  • Não use exceções para indicar a ausência de um recurso
  • Não use o tratamento de exceções como meio de retornar informações de um método
  • Use exceções para erros que não devem ser ignorados
  • Não limpe o rastreamento de pilha ao lançar novamente uma exceção
  • Evite alterar exceções sem adicionar valor semântico
  • As exceções devem ser marcadas como [Serializable]
  • Em caso de dúvida, não afirme, lance uma exceção
  • Cada classe de exceção deve ter pelo menos os três construtores originais
  • Tenha cuidado ao usar o evento AppDomain.UnhandledException
  • Não reinvente a roda
  • Não use tratamento de erros não estruturados (VB.Net)
Micah
fonte
3
Você se importaria de explicar um pouco mais sobre este ponto para mim? "Não use exceções para indicar a ausência de um recurso" Não tenho certeza se entendi o motivo por trás disso. Também apenas uma observação: esta resposta não explica o "porquê" de forma alguma. Eu sei que tem 5 anos, mas ainda me incomoda um pouco
Rémi
O link para o artigo referenciado expirou. O Code Project sugere que o artigo pode ter sido movido para .
DavidRR
15

Observe que o Windows Forms tem seu próprio mecanismo de tratamento de exceções. Se um botão no formulário for clicado e seu manipulador lançar uma exceção que não foi capturada no manipulador, o Windows Forms exibirá sua própria caixa de diálogo de exceção não tratada.

Para evitar que o Unhandled Exception Dialog seja exibido e capturar tais exceções para registro e / ou para fornecer seu próprio diálogo de erro, você pode anexar ao evento Application.ThreadException antes da chamada para Application.Run () em seu método Main ().

user95680
fonte
Obrigado por esta sugestão, fiquei sabendo disso tarde demais, acabei de verificar no linqpad, funciona como esperado, o delegado é: void Form1_UIThreadException (object sender, ThreadExceptionEventArgs t) Outra boa fonte sobre este tópico é richnewman.wordpress.com/2007/ 04/08 /… Para tratamento geral de exceções não tratadas: o evento AppDomain.UnhandledException é para exceções não tratadas lançadas de um thread não principal da IU.
zhaorufei
14

Todos os conselhos postados aqui até agora são bons e valem a pena ser ouvidos.

Uma coisa que eu gostaria de expandir é a sua pergunta "O tratamento de exceções que podem ser lançadas prejudica o desempenho em comparação com o teste preventivo, como a existência de um arquivo no disco?"

A regra ingênua é "os blocos try / catch são caros." Isso não é verdade. Tentar não é caro. É a captura, onde o sistema tem que criar um objeto Exception e carregá-lo com o rastreamento de pilha, que é caro. Existem muitos casos em que a exceção é, bem, excepcional o suficiente para que seja perfeitamente possível agrupar o código em um bloco try / catch.

Por exemplo, se você estiver preenchendo um Dicionário, este:

try
{
   dict.Add(key, value);
}
catch(KeyException)
{
}

geralmente é mais rápido do que fazer isso:

if (!dict.ContainsKey(key))
{
   dict.Add(key, value);
}

para cada item que você está adicionando, porque a exceção só é lançada quando você adiciona uma chave duplicada. (As consultas agregadas do LINQ fazem isso.)

No exemplo que você deu, eu usaria try / catch quase sem pensar. Primeiro, só porque o arquivo existe quando você o verifica, não significa que ele existirá quando você o abrir, portanto, você realmente deve lidar com a exceção de qualquer maneira.

Em segundo lugar, e acho mais importante, a menos que seu a) seu processo esteja abrindo milhares de arquivos eb) as chances de um arquivo que está tentando abrir não existir não sejam trivialmente baixas, o impacto no desempenho de criar a exceção não é algo que você ' nunca vai notar. De modo geral, quando seu programa está tentando abrir um arquivo, ele está apenas tentando abrir um arquivo. Esse é um caso em que escrever um código mais seguro quase certamente será melhor do que escrever o código mais rápido possível.

Robert Rossney
fonte
3
no caso de dict, você pode apenas fazer: o dict[key] = valueque deve ser tão rápido, senão mais rápido ..
nawfal 01 de
9

Aqui estão algumas diretrizes que sigo

  1. Fail-Fast: Esta é mais uma diretriz geradora de exceções. Para cada suposição que você fizer e cada parâmetro que estiver inserindo em uma função, faça uma verificação para ter certeza de que está começando com os dados corretos e que as suposições que você está fazendo estão corretas. As verificações típicas incluem argumento não nulo, argumento no intervalo esperado etc.

  2. Ao relançar, preservar o rastreamento de pilha - Isso simplesmente se traduz em lançar ao relançar em vez de lançar um novo Exception (). Alternativamente, se você sentir que pode adicionar mais informações, agrupe a exceção original como uma exceção interna. Mas se você estiver capturando uma exceção apenas para registrá-la, use definitivamente throw;

  3. Não pegue exceções que você não pode controlar, então não se preocupe com coisas como OutOfMemoryException porque se elas ocorrerem você não poderá fazer muito de qualquer maneira.

  4. Enganche manipuladores de exceção globais e certifique-se de registrar o máximo de informações possível. Para winforms, conecte os eventos de exceção não tratada de appdomain e thread.

  5. O desempenho só deve ser levado em consideração quando você analisa o código e vê que ele está causando um afunilamento de desempenho; por padrão, otimize para legibilidade e design. Então, sobre sua pergunta original sobre a verificação de existência do arquivo, eu diria que depende, se você pode fazer algo sobre o arquivo não estar lá, então sim, faça essa verificação, caso contrário, se tudo o que você vai fazer é lançar uma exceção se o arquivo não há então eu não vejo o ponto.

  6. Definitivamente, há momentos em que blocos catch vazios são necessários. Acho que as pessoas que dizem o contrário não trabalharam em bases de código que evoluíram ao longo de várias versões. Mas eles devem ser comentados e revisados ​​para garantir que sejam realmente necessários. O exemplo mais típico são os desenvolvedores usando try / catch para converter string em inteiro em vez de usar ParseInt ().

  7. Se você espera que o chamador de seu código seja capaz de lidar com as condições de erro, crie exceções personalizadas que detalhem qual é a situação inesperada e forneçam informações relevantes. Caso contrário, apenas se atenha aos tipos de exceção embutidos tanto quanto possível.

Sijin
fonte
Qual é a utilidade de conectar os manipuladores de exceção não tratada de appdomain e thread? Se você apenas usar Appdomain.UnhandledException, não é o mais genérico que pega tudo?
Quagmire
Você quis dizer manipular o Application.ThreadExceptionevento quando referenciou o evento "thread não tratada de exceção"?
Jeff B
4

Gosto da filosofia de não pegar nada que não pretendo manipular, seja o que for que o manuseio signifique em meu contexto particular.

Eu odeio quando vejo códigos como:

try
{
   // some stuff is done here
}
catch
{
}

Eu tenho visto isso de vez em quando e é muito difícil encontrar problemas quando alguém 'come' as exceções. Um colega de trabalho que tive faz isso e tende a contribuir para um fluxo constante de problemas.

Eu lanço novamente se há algo que minha classe particular precisa fazer em resposta a uma exceção, mas o problema precisa ser borbulhado para o método chamado onde isso aconteceu.

Acho que o código deve ser escrito de forma proativa e que as exceções devem ser para situações excepcionais, não para evitar o teste de condições.

itsmatt
fonte
@Kiquenet Desculpe, não fui claro. O que eu quis dizer: Não faça essas capturas vazias, em vez disso, adicione um manipulador para Dispatcher.UnhandledException e adicione pelo menos um log para que não seja comido sem migalhas de pão :) Mas a menos que você tenha o requisito de um componente silencioso, você deve incluir exceções à alaways em seu design. Jogue-os quando for preciso, faça contratos claros para você Interfaces / API / qualquer classe.
LuckyLikey
4

Estou saindo, mas apresentarei uma breve descrição de onde usar o tratamento de exceções. Tentarei abordar seus outros pontos quando retornar :)

  1. Verifique explicitamente todas as condições de erro conhecidas *
  2. Adicione um try / catch em torno do código se você não tiver certeza se foi capaz de lidar com todos os casos
  3. Adicione um try / catch em torno do código se a interface .NET que você está chamando lançar uma exceção
  4. Adicione um try / catch em torno do código se ele ultrapassar um limite de complexidade para você
  5. Adicione um try / catch em torno do código se for para uma verificação de sanidade: Você está afirmando que ISTO NUNCA DEVE ACONTECER
  6. Como regra geral, não uso exceções como um substituto para códigos de retorno. Isso é bom para .NET, mas não para mim. Eu tenho exceções (hehe) a esta regra, no entanto, ela depende da arquitetura do aplicativo em que você está trabalhando.

* Dentro do razoável. Não há necessidade de verificar se, digamos, um raio cósmico atingiu seus dados causando a inversão de alguns bits. Entender o que é "razoável" é uma habilidade adquirida por um engenheiro. É difícil quantificar, mas fácil de intuir. Ou seja, posso explicar facilmente por que uso um try / catch em qualquer instância particular, embora seja difícil imbuir outro com esse mesmo conhecimento.

Eu, por exemplo, tendo a evitar arquiteturas fortemente baseadas em exceções. try / catch não tem um acerto de desempenho como tal, o acerto surge quando a exceção é lançada e o código pode ter que subir vários níveis da pilha de chamadas antes que algo o trate.

pezi_pink_squirrel
fonte
4

A regra de ouro que tentei seguir é tratar a exceção o mais próximo possível da fonte.

Se você precisar relançar uma exceção, tente adicionar a ela, relançar uma FileNotFoundException não ajuda muito, mas lançar uma ConfigurationFileNotFoundException permitirá que ela seja capturada e aplicada em algum lugar na cadeia.

Outra regra que tento seguir é não usar try / catch como uma forma de fluxo de programa, então eu verifico arquivos / conexões, garanto que os objetos foram iniciados, etc., antes de usá-los. Try / catch deve ser para exceções, coisas que você não pode controlar.

Quanto a um bloco catch vazio, se você estiver fazendo algo importante no código que gerou a exceção, você deve relançar a exceção no mínimo. Se não houver consequências para o código que gerou a exceção não funcionar, por que você o escreveu em primeiro lugar?


fonte
Essa "regra de ouro" é, na melhor das hipóteses, arbitrária.
Evan Harper
3

você pode interceptar o evento ThreadException.

  1. Selecione um projeto de aplicativo do Windows no Solution Explorer.

  2. Abra o arquivo Program.cs gerado clicando duas vezes nele.

  3. Adicione a seguinte linha de código ao topo do arquivo de código:

    using System.Threading;
  4. No método Main (), adicione o seguinte como a primeira linha do método:

    Application.ThreadException += new ThreadExceptionEventHandler(Application_ThreadException);
  5. Adicione o seguinte abaixo do método Main ():

    static void Application_ThreadException(object sender, ThreadExceptionEventArgs e)
    {
        // Do logging or whatever here
        Application.Exit();
    }
  6. Adicione código para tratar a exceção não tratada dentro do manipulador de eventos. Qualquer exceção que não seja tratada em nenhum outro lugar do aplicativo é tratada pelo código acima. Mais comumente, esse código deve registrar o erro e exibir uma mensagem ao usuário.

refrence: https://blogs.msmvps.com/deborahk/global-exception-handler-winforms/

Omid-RH
fonte
@Kiquenet desculpe, não sei sobre VB
Omid-RH
2

As exceções são caras, mas necessárias. Você não precisa envolver tudo em um try catch, mas precisa garantir que as exceções sempre sejam detectadas eventualmente. Muito disso dependerá do seu projeto.

Não re-lance se permitir que a exceção aumente servirá tão bem. Nunca deixe os erros passarem despercebidos.

exemplo:

void Main()
{
  try {
    DoStuff();
  }
  catch(Exception ex) {
    LogStuff(ex.ToString());
  }

void DoStuff() {
... Stuff ...
}

Se DoStuff der errado, você vai querer que ele saia de qualquer maneira. A exceção será lançada para principal e você verá a seqüência de eventos no rastreamento de pilha de ex.

Echostorm
fonte
1

Quando devo relançar uma exceção?

Em todos os lugares, mas métodos de usuário final ... como manipuladores de clique de botão

Devo tentar ter um mecanismo central de tratamento de erros de algum tipo?

Eu escrevo um arquivo de log ... muito fácil para um aplicativo WinForm

O tratamento de exceções que podem ser lançadas prejudica o desempenho em comparação com o teste preventivo, como a existência de um arquivo no disco?

Não tenho certeza sobre isso, mas acredito que seja uma boa prática para as exceções ... Quer dizer, você pode perguntar se um arquivo existe e se ele não lança um FileNotFoundException

Todos os códigos executáveis ​​devem ser colocados em blocos try-catch-finally?

sim

Há algum momento em que um bloco catch vazio pode ser aceitável?

Sim, digamos que você queira mostrar uma data, mas você não tem ideia de como essa data foi armazenada (dd / mm / aaaa, mm / dd / aaaa, etc.), tente tp analisá-la, mas se falhar, continue. . se for irrelevante para você ... Eu diria que sim, há

Sebagomez
fonte
1

A única coisa que aprendi muito rapidamente foi encerrar absolutamente cada pedaço de código que interage com qualquer coisa fora do fluxo do meu programa (isto é, sistema de arquivos, chamadas de banco de dados, entrada do usuário) com blocos try-catch. O try-catch pode causar um impacto no desempenho, mas geralmente nesses lugares do seu código isso não será perceptível e se pagará com segurança. realmente precisa postar uma mensagem, mas se você não pegá-la, ela lançará um erro de exceção não tratada para o usuário.

Eu usei catch-blocks vazios em lugares onde o usuário pode fazer algo que não é realmente "incorreto", mas pode lançar uma exceção ... um exemplo que vem à mente é em um GridView se o usuário DoubleCLicks o marcador cinza célula no canto superior esquerdo irá disparar o evento CellDoubleClick, mas a célula não pertence a uma linha. Nesse caso, você não

BKimmel
fonte
1

Ao relançar uma exceção, a palavra-chave é lançada por ela mesma. Isso lançará a exceção capturada e ainda será capaz de usar o rastreamento de pilha para ver de onde veio.

Try
{
int a = 10 / 0;
}
catch(exception e){
//error logging
throw;
}

fazer isso fará com que o rastreamento da pilha termine na instrução catch. (evite isso)

catch(Exception e)
// logging
throw e;
}
Brad8118
fonte
isso ainda se aplica às versões mais recentes do .NET Framework e do .NET Core?
LuckyLikey
1

Em minha experiência, achei adequado capturar exceções quando sei que vou criá-las. Por exemplo, quando estou em um aplicativo da web e estou fazendo um Response.Redirect, sei que receberei um System.ThreadAbortException. Uma vez que é intencional, eu apenas tenho uma captura para o tipo específico e apenas engulo.

try
{
/*Doing stuff that may cause an exception*/
Response.Redirect("http:\\www.somewhereelse.com");
}
catch (ThreadAbortException tex){/*Ignore*/}
catch (Exception ex){/*HandleException*/}
Jeff Keslinke
fonte
1

Eu concordo profundamente com a regra de:

  • Nunca deixe os erros passarem despercebidos.

O motivo é que:

  • Quando você escreve o código pela primeira vez, provavelmente não terá o conhecimento completo do código de terceiros, do arquivo .NET FCL ou das contribuições mais recentes de seus colegas de trabalho. Na realidade, você não pode se recusar a escrever código até que conheça bem todas as possibilidades de exceção. assim
  • Eu constantemente acho que uso try / catch (Exception ex) apenas porque quero me proteger de coisas desconhecidas e, como você notou, eu pego Exception, não a mais específica, como OutOfMemoryException etc. E, sempre faço a exceção sendo enviado para mim (ou o controle de qualidade) por ForceAssert.AlwaysAssert (false, ex.ToString ());

ForceAssert.AlwaysAssert é minha forma pessoal de Trace.Assert, independentemente de a macro DEBUG / TRACE estar definida ou não.

O ciclo de desenvolvimento talvez: notei a caixa de diálogo Assert feia ou outra pessoa reclamará comigo sobre isso, então eu volto para o código e descubro o motivo para levantar a exceção e decido como processá-la.

Desta forma posso escrever o MEU código em um curto espaço de tempo e me proteger de domínio desconhecido, mas sempre sendo notado se coisas anormais acontecerem, desta forma o sistema fica seguro e com mais segurança.

Sei que muitos de vocês não concordarão comigo porque um desenvolvedor deve saber todos os detalhes de seu código, francamente, também sou um purista nos velhos tempos. Mas hoje em dia aprendi que a política acima é mais pragmática.

Para o código WinForms, uma regra de ouro que sempre obedeço é:

  • Sempre tente / capture (exceção) o código do manipulador de eventos

isso protegerá sua interface do usuário sendo sempre utilizável.

Para o impacto no desempenho, a penalidade no desempenho só acontece quando o código atinge o catch, a execução do código try sem a exceção real gerada não tem efeito significativo.

A exceção deve acontecer com pouca chance, caso contrário, não são exceções.

zhaorufei
fonte
-1

Você tem que pensar no usuário. O travamento do aplicativo é o últimocoisa que o usuário deseja. Portanto, qualquer operação que possa falhar deve ter um bloco try catch no nível da interface do usuário. Não é necessário usar try catch em todos os métodos, mas sempre que o usuário faz algo, ele deve ser capaz de lidar com exceções genéricas. Isso de forma alguma o libera de verificar tudo para evitar exceções no primeiro caso, mas não há aplicativo complexo sem bugs e o sistema operacional pode facilmente adicionar problemas inesperados, portanto, você deve antecipar o inesperado e certificar-se de que um usuário deseja usar um operação não haverá perda de dados porque o aplicativo trava. Não há necessidade de deixar seu aplicativo travar, se você pegar exceções, ele nunca estará em um estado indeterminado e o usuário SEMPRE será incomodado por um travamento. Mesmo se a exceção estiver no nível mais alto, não travar significa que o usuário pode reproduzir rapidamente a exceção ou pelo menos registrar a mensagem de erro e, portanto, ajudá-lo muito a corrigir o problema. Certamente muito mais do que receber uma simples mensagem de erro e ver apenas a caixa de diálogo de erro do Windows ou algo parecido.

É por isso que você NUNCA deve ser convencido e pensar que seu aplicativo não tem bugs, isso não é garantido. E é um esforço muito pequeno envolver alguns blocos try catch sobre o código apropriado e mostrar uma mensagem de erro / registrar o erro.

Como um usuário, eu certamente fico seriamente irritado sempre que um aplicativo de escritório ou navegador travar. Se a exceção for tão alta que o aplicativo não possa continuar, é melhor exibir essa mensagem e dizer ao usuário o que fazer (reiniciar, corrigir algumas configurações do sistema operacional, relatar o bug, etc.) do que simplesmente travar e pronto.

Terry Bogard
fonte
Você pode tentar escrever respostas menos como um discurso retórico e, em vez disso, tentar ser mais específico e tentar provar seu ponto? Também dê uma olhada em Como responder .
LuckyLikey