Como posso lidar com um IOException que eu sei que nunca pode ser jogado, de forma segura e de fácil leitura?

StackOverflow https://stackoverflow.com/questions/1045829

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:

  1. 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.
  2. Uma vez que a probabilidade de um acontecimento é extremamente diferente de e irrecuperável, eu preferiria para definir uma exceção não verificada.
  3. 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.
  4. 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?

Foi útil?

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:

  1. Existe quando os métodos são chamados
  2. É gravável / legível
  3. Outro segmento não acessá-lo e de alguma forma estragar seus fluxos
  4. 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.

Licenciado em: CC-BY-SA com atribuição
Não afiliado a StackOverflow
scroll top