Como posso lidar com um IOException que eu sei que nunca pode ser jogado, de forma segura e de fácil leitura?
-
20-08-2019 - |
Pergunta
"A principal diferença entre uma coisa que pode dar errado e uma coisa que não pode dar errado é que, quando uma coisa que não pode dar errado der errado, geralmente, acaba por ser impossível de obter em ou reparação." -Douglas Adams
Eu tenho um FileItems classe. FileItems construtor recebe um arquivo, e lança uma exceção (FileNotFoundException) se o arquivo não existe. Outros métodos dessa classe também envolvem operações de arquivo e, portanto, têm a capacidade jogar a FileNotFoundException. Eu gostaria de encontrar uma solução melhor. Uma solução que não requer que outros programadores lidar com todas essas FileNotFoundExceptions extremamente improváveis.
Os fatos da matéria:
- O arquivo foi verificada a existir, mas existe a possibilidade extremamente improvável que através de alguma falha grave da realidade o arquivo pode ser excluído antes que este método é chamado.
- Uma vez que a probabilidade de um acontecimento é extremamente diferente de e irrecuperável, eu preferiria para definir uma exceção não verificada.
- O arquivo é já foi constatada, forçando outros programadores a escrever código e pegar o verificado FileNotFoundException parece tedioso e inútil. O programa deve apenas falhar completamente nesse ponto. Por exemplo, há sempre a chance de que um computador pode disparar captura, mas ninguém é suficiente louco para forçar outros programadores alça que como um exceção verificada.
- Eu executar para esse tipo de problema de Exceção de vez em quando, e definir exceções costume desmarcada cada vez que eu encontrar esse problema (meu velho solução) é cansativo e acrescenta ao código-bloat.
O código atualmente se parece com isso
public Iterator getFileItemsIterator() {
try{
Scanner sc = new Scanner(this.fileWhichIsKnowToExist);
return new specialFileItemsIterator(sc);
} catch (FileNotFoundException e){ //can never happen}
return null;
}
Como posso fazer isso melhor, sem definir um FileNotFoundException desmarcada personalizado? Existe alguma maneira de lançar um checkedException a um uncheckException?
Solução
O padrão usual de lidar com isso é exceção encadeamento . Você só embrulhar o FileNotFoundException em um RuntimeException:
catch(FileNotFoundException e) {
throw new RuntimeException(e);
}
Este padrão não só é aplicável quando uma exceção não pode ocorrer na situação específica (como a sua), mas também quando você não tem meios ou intenção de realmente tratar a exceção (como uma falha no link de banco de dados).
Editar : Cuidado com este anti-padrão semelhante de aparência, que eu tenho visto em estado selvagem com demasiada frequência:
catch(FileNotFoundException e) {
throw new RuntimeException(e.getMessage());
}
Ao fazer isso, você joga fora toda a informação importante no stacktrace original, que, muitas vezes, tornar problemas difícil de rastrear.
Outro edit: Como Thorbjørn Ravn Andersen corretamente aponta em sua resposta, não faz mal para o estado por que você está encadeamento a exceção, seja em um comentário ou, melhor ainda, como o mensagem de exceção:
catch(FileNotFoundException e) {
throw new RuntimeException(
"This should never happen, I know this file exists", e);
}
Outras dicas
Você pode converter uma exceção verificada a um desmarcada por aninhada lo dentro de um RuntimException. Se a exceção é detectada mais acima na pilha e emitida usando printStackTrace (), a pilha para a exceção original será exibido também.
try {
// code...
}
catch (IOException e) {
throw new RuntimeException(e);
}
Esta é uma boa solução, que você não deve hesitar em utilizar nestas situações.
Considere usar a forma
throw new RuntimeException("This should never happen", e);
em seu lugar. Isso permite que você transmitir um significado para o mantenedor para segui-lo, tanto ao ler o código, mas também deve acontecer para ser jogado em algum cenário estranho a exceção.
EDIT: Esta é também uma boa maneira de passar exceções através de um mecanismo não esperava essas exceções. Por exemplo. se você tem um "ficar mais linhas do banco de dados" iterador a interface Iterator não permite a atirar por exemplo um FileNotFoundException assim você pode colocá-lo como este. No código usando o iterador, então você pode pegar o RuntimeException e inspecionar o excpetion original com getCause (). Muito útil quando passar por caminhos de código legado.
Se a sua posição é que isso é tão improvável e deve apenas terminar o programa, use uma exceção tempo de execução existente, até mesmo a própria RuntimeException (se não IllegalStateException).
try {
....
} catch (FileNotFoundException e) {
throw new RuntimeException(e);
}
Você é provavelmente melhor fora de jogar a superclasse e IOException
exceção mais genérica em qualquer ponto no seu código que envolve a leitura ou gravação do arquivo.
O arquivo pode existir quando é executado o construtor de sua classe, mas isso não garante que:
- Existe quando os métodos são chamados
- É gravável / legível
- Outro segmento não acessá-lo e de alguma forma estragar seus fluxos
- O recurso não vai embora no meio do processamento
etc.
Em vez de reinventar a roda, eu diria apenas re-lançar IOException onde as classes JDK / java.io
você está usando força você a fazê-lo.
Também eu para aulas de um ódio que lançam exceções de seu construtor -. Eu me livrar destes se eu fosse você
Eu fiz um pouco de googling e encontrei este glob de código. É um pouco mais flexível de uma abordagem me pensa
Compliments desta artigo
class SomeOtherException extends Exception {}
public class TurnOffChecking {
private static Test monitor = new Test();
public static void main(String[] args) {
WrapCheckedException wce = new WrapCheckedException();
// You can call f() without a try block, and let
// RuntimeExceptions go out of the method:
wce.throwRuntimeException(3);
// Or you can choose to catch exceptions:
for(int i = 0; i < 4; i++)
try {
if(i < 3)
wce.throwRuntimeException(i);
else
throw new SomeOtherException();
} catch(SomeOtherException e) {
System.out.println("SomeOtherException: " + e);
} catch(RuntimeException re) {
try {
throw re.getCause();
} catch(FileNotFoundException e) {
System.out.println(
"FileNotFoundException: " + e);
} catch(IOException e) {
System.out.println("IOException: " + e);
} catch(Throwable e) {
System.out.println("Throwable: " + e);
}
}
monitor.expect(new String[] {
"FileNotFoundException: " +
"java.io.FileNotFoundException",
"IOException: java.io.IOException",
"Throwable: java.lang.RuntimeException: Where am I?",
"SomeOtherException: SomeOtherException"
});
}
} ///:~
Eu experimentei o mesmo problema.
No caso mais simples você informar o usuário sobre o erro e sugerir, quer repetir ou cancelar a operação.
No caso geral, o fluxo de trabalho é uma sequência de acções (I / O, incluindo), em que cada acção "assume" que o anterior tinha tido sucesso.
A abordagem que eu escolhi é criar uma lista de ações 'roll-back'. Se o fluxo de trabalho for bem sucedida, eles são ignorados. Se ocorrer uma exceção, eu executar reversões e apresentar uma exceção para o usuário.
Este:
- mantém a integridade dos dados
- permite ao código muito mais fácil
função típica é semelhante:
returntype func(blah-blah-blah, Transaction tr)
{
// IO example
Stream s = null;
try
{
s = new FileStream(filename);
tr.AddRollback(File.Delete(filename));
}
finally
{
if (s != null)
s.close();
}
}
O uso típico é:
Transaction tr = new Transaction();
try
{
DoAction1(blah1, tr);
DoAction2(blah2, tr);
//...
}
catch (Exception ex)
{
tr.ExecuteRollbacks();
// queue the exception message to the user along with a command to repeat all the actions above
}
Esta é uma pouco complicado bit no mundo real, porque
- às vezes é necessário para obter um monte de bloqueios antes ações de execução
- código de reversão deve ser exceção silenciosa em si (por exemplo)
Mas eu já me acostumar com esta abordagem, agora minhas aplicações são mais estáveis.
Não basta tanque de todo o seu aplicativo com um RuntimeException
quando você encontrar uma condição de erro improvável. RuntimeException
deve ser reservada para erros programador, e IOException
é mais provável não causado por erro do programador.
Em vez disso, encapsular a exceção de nível mais baixo em uma exceção de nível superior e relançar. Em seguida, tratar a exceção nível mais acima na cadeia de chamada.
Por exemplo:
class SomeClass {
public void doActions() {
try {
someAction();
} catch (HigherLevelException e) {
notifyUser();
}
someOtherUnrelatedAction();
}
public void someAction() throws HigherLevelException {
try {
// user lower-level abstraction to do our bidding
} catch(LowerLevelException e) {
throw new HigherLevelException(e);
}
}
public void someOtherUnrelatedAction() {
// does stuff
}
}
O mais provável é a pilha de chamadas que gerou a exceção estava realizando alguma tarefa na sua aplicação. Em vez de força de bater seu aplicativo inteiro com uma figura RuntimeException
o que fazer quando o problema ocorre durante essa tarefa. Por exemplo, você estava tentando salvar um arquivo? Não falhar, em vez notificar o usuário que havia um problema.