As instruções try-catch do aninhamento ainda são um cheiro de código se aninhadas em um loop?

8

Ouvi dizer que o aninhamento de instruções try-catch geralmente pode ser um cheiro de código, por isso estou me perguntando se essa situação é uma exceção. Caso contrário, quais seriam algumas boas maneiras de refatorar?

Meu código é semelhante ao seguinte:

try{
    X x = blah;
    otherStuff;
    for (int i = 0; i < N; i++){
        try{
            String y = Integer.toString(i);
            f(x);
        }
        catch(Exceptions1 e1){
            System.err.println(... + y + ...);
            System.exit(0);
        }
}
catch(Exceptions2 e2){
    ...
}

Estou usando Exceptionsaqui para indicar uma captura múltipla.

e2é usado para capturar exceções lançadas ao inicializar xe executar otherStuff. Idealmente, meu try-catch envolveria apenas essas duas linhas, mas eu uso xdentro do meu loop e queria evitar os avisos de "atribuição não utilizada" causados ​​pela inicialização de null fora do try-catch.

e1não foi capturado várias vezes e2porque eu quero fornecer ao usuário informações sobre detalhes da iteração e, portanto, queria um bloco catch dentro do loop.

Weasemunk
fonte

Respostas:

6

Se a e2captura for, como você diz, apenas para capturar erros na inicialização xe execução, otherStuffvocê poderá extraí-la para um método separado. Isso também separa bem a lógica e permite que você dê potencialmente um nome significativo otherStuff.

public X Foo() {
    try{
        X x = blah;
        otherStuff;

        return x;
    }
    catch(Exceptions2 e2){
        ...
    }
}

public void Bar() {    
    X x = Foo();
    for (int i = 0; i < N; i++){
        try{
            String y = Integer.toString(i);
            f(x);
        }
        catch(Exceptions1 e1){
            System.err.println(... + y + ...);
            System.exit(0);
        }
    }
}
Martin Brenden
fonte
Essa resposta fez mais sentido para a minha situação, pois me permitiu separar funcionalmente meu erro captura em vez de fornecer um alias para a captura interna, que ainda iria acontecer de qualquer maneira
Weasemunk
7

A menos que você pretenda processar todo o loop interno, ocorra ou não uma exceção, seu código é essencialmente equivalente a

try{
    X x = blah;
    otherStuff;
    for (...){ 
       f(x)
    }
}
catch(Exceptions1 e1){
    ...
}
catch(Exceptions2 e2){
    ...
}

o que não requer aninhamento.

Se você ainda precisar do tratamento de exceção interna, refatorar a exceção interna e seu método anexo para um novo método. Isso também tem o benefício adicional de forçar você a pensar em outras maneiras de processar o loop interno; as exceções podem se tornar muito caras em termos de desempenho, se muitas delas forem lançadas.

Robert Harvey
fonte
Eu vejo o que você quer dizer. Adicionei alguns detalhes para especificar melhor minha situação. No loop interno, quero usar o valor de y, que depende da iteração i (na verdade, estou usando a para cada, mas isso deve esclarecer meu ponto de vista). Minha captura interna dirá algo sobre o valor de y quando for capturada, enquanto a captura externa é independente do loop. Ainda posso contornar o aninhamento ou estou bloqueado pela minha decisão de fornecer informações dependentes de loop?
Weasemunk
Eu ainda acho que refatoraria o loop interno em seu próprio método, onde você pode fazer o que quiser com a exceção.
Robert Harvey
Obrigado, combinei sua resposta com a @Martin Brendan's para produzir uma solução limpa.
Weasemunk
3

Tenho algumas observações sobre sua pergunta:

  1. No exemplo que você forneceu, você não precisa de exceções. Você só precisaria verificar a string y, registrar qualquer erro e sair do loop. Nada é excepcional sobre y ser inválido e precisar ser registrado. O fato de você estar usando exceções nesse caso é uma indicação do cheiro do código.

  2. As exceções de captura múltipla não devem ser usadas para agrupar grandes faixas de código e capturar qualquer coisa que possa dar errado. Eles servem para ajudar a distinguir entre exceções no caso em que uma única seção do código pode produzir uma série de exceções diferentes. Como você está usando exceções de mutli-catch como uma espécie de abordagem "catch all", isso indica o cheiro do código.

  3. Não faz sentido colocar uma tentativa de captura dentro do loop, a menos que você planeje continuar repetindo o restante do loop, mesmo que uma exceção seja encontrada em um dos elementos. Seu código de amostra mostra que você planeja sair do loop se algum item for excepcional.

Eu acho que você pode estar melhor com algo como:

try
{
     var x = blah;
     DoStuff();
     foreach(var i in icollection) // i is an int
     {
          var y = SomethingDependentOnI(i);

          // check y for explicit exceptional states
          if (IsSomethingWrongWithY(y))
               throw new Exception1(y);
     }
}
catch (Exception1 e1)
{
}
catch (Exception2 e2)
{
}

Isso é muito mais claro, mas ainda sofre com os problemas descritos acima.

Preço Jones
fonte
Obrigado pelo comentário. 1 + 2 não eram realmente relevantes no que diz respeito ao código real, mas acho que a sua perspectiva sobre jogando dentro do loop faz sentido
Weasemunk