Por que o exception.printStackTrace () é considerado uma má prática?


126

Há um monte de material de fora o que sugere que a impressão do rastreamento de pilha de uma exceção é uma prática ruim.

Por exemplo, da verificação RegexpSingleline no Checkstyle:

Essa verificação pode ser usada para [...] encontrar más práticas comuns, como chamar ex.printStacktrace ()

No entanto, estou lutando para encontrar um lugar que dê uma razão válida porque, certamente, o rastreamento de pilha é muito útil para rastrear o que causou a exceção. Coisas que eu conheço:

  1. Um rastreamento de pilha nunca deve estar visível para os usuários finais (para fins de experiência e segurança do usuário)

  2. Gerar um rastreamento de pilha é um processo relativamente caro (embora seja improvável que seja um problema na maioria das circunstâncias "excepcionais")

  3. Muitas estruturas de log imprimirão o rastreamento de pilha para você (o nosso não e não, não podemos alterá-lo facilmente)

  4. Imprimir o rastreamento da pilha não constitui tratamento de erros. Deve ser combinado com outro registro de informações e tratamento de exceções.

Quais outros motivos existem para evitar a impressão de um rastreamento de pilha no seu código?


12
Como alguém que precisa solucionar problemas regularmente, nunca omitiria a impressão de um rastreamento de pilha quando algo desse errado. Sim, não o mostre ao usuário, mas sim, despeje-o em um log de erros.
Paul Grime

4
Você realmente precisa de outros motivos? Eu acho que você respondeu sua própria pergunta razoavelmente bem. No entanto, o stacktrace deve ser impresso em exceções excepcionais. :)
Neil

Respostas:


125

Throwable.printStackTrace()grava o rastreamento de pilha no System.errPrintStream. O System.errfluxo e o fluxo de saída "erro" padrão subjacente do processo da JVM podem ser redirecionados por

  • chamando System.setErr()que altera o destino apontado por System.err.
  • ou redirecionando o fluxo de saída de erro do processo. O fluxo de saída de erro pode ser redirecionado para um arquivo / dispositivo
    • cujo conteúdo pode ser ignorado por pessoal,
    • o arquivo / dispositivo pode não ser capaz de girar o log, inferindo que é necessário reiniciar o processo para fechar o identificador de arquivo / dispositivo aberto, antes de arquivar o conteúdo existente do arquivo / dispositivo.
    • ou o arquivo / dispositivo realmente descarta todos os dados gravados nele, como é o caso de /dev/null.

Inferindo-se ao exposto, invocar Throwable.printStackTrace()constitui um comportamento válido (não bom / ótimo) de manipulação de exceção, apenas

  • se você não tiver System.errsido reatribuído ao longo da vida útil do aplicativo,
  • e se você não precisar de rotação de logs enquanto o aplicativo estiver em execução,
  • e se a prática de registro aceita / projetada do aplicativo for gravar System.err(e o fluxo de saída de erro padrão da JVM).

Na maioria dos casos, as condições acima não são satisfeitas. Pode-se não estar ciente de outro código em execução na JVM e não se pode prever o tamanho do arquivo de log ou a duração do processo em tempo de execução, e uma prática de log bem projetada giraria em torno da gravação de arquivos de log "analisáveis ​​por máquina" (um recurso preferível mas opcional em um registrador) em um destino conhecido, para ajudar no suporte.

Finalmente, deve-se lembrar que a saída de Throwable.printStackTrace()seria definitivamente intercalada com outro conteúdo gravado System.err(e possivelmente mesmo System.outse ambos forem redirecionados para o mesmo arquivo / dispositivo). É um aborrecimento (para aplicativos de thread único) com o qual é preciso lidar, pois os dados em torno das exceções não são facilmente analisáveis ​​em um evento como esse. Pior, é altamente provável que um aplicativo multithread produza logs muito confusos, pois Throwable.printStackTrace() não é seguro para threads .

Não há mecanismo de sincronização para sincronizar a gravação do rastreamento de pilha System.errquando vários encadeamentos são invocados Throwable.printStackTrace()ao mesmo tempo. Para resolver isso, na verdade, é necessário sincronizar o código no monitor associado System.err(e também System.out, se o arquivo / dispositivo de destino for o mesmo), e esse é um preço bastante alto a pagar pela integridade do arquivo de log. Para dar um exemplo, as classes ConsoleHandlere StreamHandlersão responsáveis ​​por anexar registros de log ao console, no recurso de registro fornecido por java.util.logging; a operação real de publicação dos registros de log é sincronizada - todo encadeamento que tenta publicar um registro de log também deve adquirir o bloqueio no monitor associado aoStreamHandlerinstância. Se você deseja ter a mesma garantia de ter registros de log não intercalados usando System.out/ System.err, deverá garantir o mesmo - as mensagens são publicadas nesses fluxos de maneira serializável.

Considerando todos os itens acima, e os cenários muito restritos nos quais Throwable.printStackTrace()é realmente útil, muitas vezes acontece que invocá-lo é uma má prática.


Estendendo o argumento de um dos parágrafos anteriores, também é uma má escolha usar Throwable.printStackTraceem conjunto com um criador de logs que grava no console. Isso ocorre em parte devido ao motivo pelo qual o criador de logs sincronizaria em um monitor diferente, enquanto o aplicativo (possivelmente, se você não quiser registros de log intercalados) sincronizaria em um monitor diferente. O argumento também é válido quando você usa dois registradores diferentes que gravam no mesmo destino, em seu aplicativo.


Ótima resposta, obrigado. No entanto, embora eu concorde que na maioria das vezes seja barulhento ou desnecessário, há momentos em que é absolutamente crítico.
22611 Chris Knight

1
@ Chris Sim, há momentos em que você não pode deixar de usar System.out.printlne Throwable.printStackTrace, é claro, é necessário o julgamento do desenvolvedor. Fiquei um pouco preocupado com a falta de parte da segurança do thread. Se você observar a maioria das implementações do criador de logs, notará que elas sincronizam a parte em que os registros de log são gravados (até no console), embora não adquiram monitores no System.errou System.out.
Vineet Reynolds

2
Seria errado observar que nenhum desses motivos se aplica às substituições printStackTrace que usam um objeto PrintStream ou PrintWriter como parâmetro?
ESRogs

1
@ Geek, o último é o descritor de arquivo de processo com o valor 2. O primeiro é apenas uma abstração.
Vineet Reynolds

1
No código-fonte JDK de printStackTrace (), ele é sincronizado para bloquear o System.err PrintStream, portanto deve ser um método seguro para threads.
27418 EyouGo

33

Você está abordando vários problemas aqui:

1) Um rastreamento de pilha nunca deve ser visível para os usuários finais (para experiência do usuário e fins de segurança)

Sim, ele deve estar acessível para diagnosticar problemas dos usuários finais, mas o usuário final não deve vê-los por dois motivos:

  • Eles são muito obscuros e ilegíveis, o aplicativo parecerá muito hostil ao usuário.
  • Mostrar um rastreamento de pilha para o usuário final pode apresentar um risco potencial à segurança. Corrija-me se eu estiver errado, o PHP realmente imprime parâmetros de função no rastreamento de pilha - brilhante, mas muito perigoso - se você receber uma exceção ao conectar-se ao banco de dados, o que é provável no stacktrace?

2) Gerar um rastreamento de pilha é um processo relativamente caro (embora seja improvável que seja um problema na maioria das circunstâncias "excepcionais")

Gerar um rastreamento de pilha acontece quando a exceção está sendo criada / lançada (é por isso que lançar uma exceção tem um preço), a impressão não é tão cara. Na verdade, você pode substituir Throwable#fillInStackTrace()sua exceção personalizada efetivamente, tornando a exceção quase tão barata quanto uma simples instrução GOTO.

3) Muitas estruturas de registro imprimirão o rastreamento de pilha para você (o nosso não e não, não podemos alterá-lo facilmente)

Muito bom ponto. A questão principal aqui é: se o quadro registra a exceção para você, não fazer nada (mas certifique-se que ele faz!) Se você quiser registrar a exceção mesmo, estrutura de log uso como Logback ou Log4J , não para colocá-los no console cru porque é muito difícil controlá-lo.

Com a estrutura de log, você pode redirecionar facilmente os rastreamentos de pilha para arquivo, console ou até enviá-los para um endereço de email especificado. Com codificado printStackTrace()você tem que viver com o sysout.

4) Imprimir o rastreamento da pilha não constitui tratamento de erros. Deve ser combinado com outro registro de informações e tratamento de exceções.

Novamente: efetue log SQLExceptioncorretamente (com o rastreamento de pilha completo, usando a estrutura de log) e mostre nice: " Desculpe, no momento não podemos processar sua solicitação ". Você realmente acha que o usuário está interessado nos motivos? Você viu a tela de erro do StackOverflow? É muito bem-humorado, mas não revela quaisquer pormenores. No entanto, garante ao usuário que o problema será investigado.

Mas ele ligará para você imediatamente e você precisará diagnosticar o problema. Portanto, você precisa de ambos: log de exceção adequado e mensagens amigáveis.


Para finalizar: sempre registre exceções (de preferência usando a estrutura de log ), mas não as exponha ao usuário final. Pense com cuidado e nas mensagens de erro em sua GUI, mostre rastreamentos de pilha apenas no modo de desenvolvimento.


sua resposta é a que eu recebi.
Blasanka # 6/17

"circunstâncias mais 'excepcionais'". legais.
awwsmm

19

A primeira coisa que printStackTrace () não é cara como você afirma, porque o rastreamento de pilha é preenchido quando a exceção é criada.

A idéia é passar tudo o que for enviado aos logs por meio de uma estrutura de logger, para que o log possa ser controlado. Portanto, em vez de usar o printStackTrace, basta usar algo comoLogger.log(msg, exception);


11

A impressão do rastreamento de pilha da exceção por si só não constitui uma prática recomendada, mas apenas a impressão do rastreamento stace quando ocorre uma exceção é provavelmente o problema aqui - muitas vezes, apenas imprimir um rastreamento de pilha não é suficiente.

Além disso, há uma tendência a suspeitar que o tratamento adequado de exceções não está sendo executado se tudo o que está sendo executado em um catchbloco é a e.printStackTrace. O manuseio inadequado pode significar, na melhor das hipóteses, que um problema está sendo ignorado e, na pior, um programa que continua executando em um estado indefinido ou inesperado.

Exemplo

Vamos considerar o seguinte exemplo:

try {
  initializeState();

} catch (TheSkyIsFallingEndOfTheWorldException e) {
  e.printStackTrace();
}

continueProcessingAssumingThatTheStateIsCorrect();

Aqui, queremos fazer algum processamento de inicialização antes de continuar com algum processamento que requer que a inicialização tenha ocorrido.

No código acima, a exceção deveria ter sido capturada e tratada adequadamente para impedir que o programa prossiga para o continueProcessingAssumingThatTheStateIsCorrectmétodo que poderíamos assumir que causaria problemas.

Em muitos casos, e.printStackTrace()é uma indicação de que alguma exceção está sendo engolida e o processamento é permitido continuar como se nenhum problema ocorresse.

Por que isso se tornou um problema?

Provavelmente, uma das maiores razões pelas quais o tratamento inadequado de exceções se tornou mais prevalente se deve à maneira como IDEs, como o Eclipse, geram automaticamente código que executará um e.printStackTracepara o tratamento de exceções:

try {
  Thread.sleep(1000);
} catch (InterruptedException e) {
  // TODO Auto-generated catch block
  e.printStackTrace();
}

(O exemplo acima é um real try-catchgerado automaticamente pelo Eclipse para lidar com um InterruptedExceptionlançamento Thread.sleep.)

Para a maioria dos aplicativos, apenas imprimir o rastreamento da pilha com erro padrão provavelmente não será suficiente. O tratamento inadequado de exceções pode, em muitos casos, levar a um aplicativo sendo executado em um estado inesperado e levar a um comportamento inesperado e indefinido.


1
Este. Muitas vezes, simplesmente não capturar a exceção, pelo menos no nível do método, é melhor do que capturar, imprimindo o rastreamento da pilha e continuando como se não houvesse problema.
eis

10

Penso que a sua lista de razões é bastante abrangente.

Um exemplo particularmente ruim que encontrei mais de uma vez é o seguinte:

    try {
      // do stuff
    } catch (Exception e) {
        e.printStackTrace(); // and swallow the exception
    }

O problema com o código acima é que o tratamento consiste inteiramente na printStackTracechamada: a exceção não é realmente tratada adequadamente nem é permitida a fuga.

Por outro lado, como regra, eu sempre registro o rastreamento de pilha sempre que há uma exceção inesperada no meu código. Ao longo dos anos, essa política me salvou muito tempo de depuração.

Finalmente, em uma nota mais clara, a Exceção Perfeita de Deus .


"exceção não tem permissão para escapar" - você quis dizer que a exceção é propagada pela pilha? Qual a diferença entre o registro e o printStackTrace ()?
MasterJoe

5

printStackTrace()imprime em um console. Nas configurações de produção, ninguém está assistindo a isso. Suraj está correto, deve passar essas informações para um criador de logs.


Ponto válido, embora observemos atentamente nossa saída do console de produção.
Chris Knight

Você pode explicar o que você quer dizer com observar atentamente? Como e quem? Em que frequência?
precisa saber é o seguinte

Observando atentamente, eu deveria ter dito quando requer. É um arquivo de log contínuo, que é um dos vários portos de escala se algo der errado com nosso aplicativo.
22611 Chris Knight

3

Em aplicativos de servidor, o stacktrace explode seu arquivo stdout / stderr. Ele pode se tornar cada vez maior e é preenchido com dados inúteis, porque geralmente você não tem contexto nem carimbo de data e hora e assim por diante.

por exemplo catalina.out ao usar o tomcat como contêiner


Bom ponto. Uso abusivo de printStackTrace () poderia explodir nossos arquivos de log, ou, no mínimo, preenchê-lo com resmas de informações inúteis
Chris Knight

@ ChrisKnight - você poderia sugerir um artigo que explica como os arquivos de log podem explodir com informações inúteis? obrigado.
MasterJoe

3

Não é uma prática ruim porque algo está 'errado' em PrintStackTrace (), mas porque é 'cheiro de código'. Na maioria das vezes, a chamada PrintStackTrace () ocorre porque alguém falha ao manipular corretamente a exceção. Depois que você lida com a exceção de maneira adequada, geralmente não se importa mais com o StackTrace.

Além disso, exibir o rastreamento de pilha no stderr geralmente é útil apenas na depuração, não na produção, porque muitas vezes o stderr não leva a lugar algum. Registrá-lo faz mais sentido. Mas apenas substituir PrintStackTrace () pelo log da exceção ainda deixa você com um aplicativo que falhou, mas continua sendo executado como se nada tivesse acontecido.


0

Como alguns caras já mencionaram aqui, o problema é com a exceção de deglutição, caso você apenas ligue e.printStackTrace()no catchbloco. Não interromperá a execução do encadeamento e continuará após o bloco try, como em condições normais.

Em vez disso, você precisa tentar se recuperar da exceção (no caso de ser recuperável), ou lançar RuntimeExceptionou enviar a exceção para o chamador para evitar falhas silenciosas (por exemplo, devido à configuração incorreta do criador de logs).


0

Para evitar o problema do fluxo de saída emaranhado referido por @Vineet Reynolds

você pode imprimi-lo no stdout: e.printStackTrace(System.out);

Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.