Melhor maneira de especializar os tipos de erro de um código

Olá.

Gostaria de tirar uma dúvida. Há pouco tempo atrás, tivemos um debate no trabalho sobre como seria a melhor forma de passar o tipo de erro ocorrido na camada de serviço para a camada de visão.

Por padrão nosso, a camada de serviço gera somente uma classe nossa que estende Exception, chamada ServiceException. Também por padrão, ServiceException é a única exceção lançada pelas classes dessa camada.

(...)
public class ServiceException extends Exception
(...)

Eu tive um requisito no qual eu faria uma série de operações na camada de serviço e, caso fosse necessário, devo emitir uma mensagem de erro específica para o usuário, dependendo do problema encontrado. Vamos supor que precise avisar ao usuário, além dos erros comuns, especificamente de violação da regra de negócio X e da violação da regra de negócio Y. Então, o problema é avisar à camada de visão qual tipo de problema ocorreu, para que ela saiba qual mensagem exibir.

Eu havia implementado da seguinte forma:

public class ServiceException extends Exception
(...)

public class RegraXException extends ServiceException  
(...)

public class RegraYException extends ServiceException
(...)

Na camada de serviço, fazia:

(...)
try {
      (...)
      if (violacaoRegraX) {
            throw new RegraXException();
      }
      if (violacaoRegraY) {
            throw new RegraYException();
      }
} catch (Exception e)  {
            throw new ServiceException(e);
}

Na visão, tinha:

(...)
try {
    (...)
} catch (RegraXException xe) {
    //manda msg de violação da Regra X
} catch (RegraYException xe) {
    //manda msg de violação da Regra Y
} catch (ServiceException e) {
    //manda msg de erro inesperado
}

Porém, meus colegas, quando viram meu código, argumentaram que essa era uma péssima prática e que seria melhor usar somente um enum como atributo de ServiceException, que definiria qual foi o erro encontrado. A idéia seria evitar uma subclasse de exceção para cada violação de regra de negócio possível.

(...)
public class ServiceException extends Exception {
    (...)
    public ServiceException(ServiceExceptionEnum tipoExcecao) {
        this.tipoExcecao = tipoExcecao;
    }
    private ServiceExceptionEnum tipoExcecao;
    //getter e setter
}

public enum ServiceExceptionEnum {
    VIOLACAO_REGRA_X,
    VIOLACAO_REGRA_Y
}

e nas camadas de serviço e visão ficaria:

//SERVIÇO
(...)
try {
      (...)
      if (violacaoRegraX) {
            throw new ServiceException(ServiceExceptionEnum.VIOLACAO_REGRA_X);
      }
      if (violacaoRegraY) {
            throw new ServiceException(ServiceExceptionEnum.VIOLACAO_REGRA_Y);
      }
} catch (Exception e)  {
            throw new ServiceException(e);
}


//VISÃO
(...)
try {
    (...)
} catch (ServiceException e) {
    if (e.tipoExcecao == ServiceExceptionEnum.VIOLACAO_REGRA_X) { 
        //manda msg de violação da Regra X
    } else if (e.tipoExcecao == ServiceExceptionEnum.VIOLACAO_REGRA_Y) { 
        //manda msg de violação da Regra Y
    } else {
        //manda msg de erro inesperado
    }
}

Sinceramente, para mim é quase a mesma coisa. Eu só acho ruim a solução dos enums porque obriga que toda exceção de regra de negócios seja cadastrada numa classe só e não sei se isso é bom.

Qual a opinião de vcs?

A sua solução tem as seguintes vantagens:

a) Você pode categorizar mais sub-exceções para uma regra. A informação da hierarquia não é perdida;
b) Você pode criar campos adicionais nas sub-exceções, para descrever melhor o problema para a classe de negócios;
c) Fica mais claro para o usuário que exceptions podem ser disparadas por um método, caso você lance só os tipos possíveis.

A solução com enum tem como vantagem apenas evitar a criação de pequenos arquivos, muitas vezes só com uma definição de classe. Isso geralmente é também considerado um problema de modelagem, pois ocorre quando algo que seria um atributo acabou virando classe.

Na literatura, você encontra os dois casos. Por exemplo, uma SQLException traz consigo o código do erro. Já o java é cheio de exemplos de árvores de exception, como é o caso das IOExceptions.

Eu faria o seguinte questionamento: Em que casos é interessante a inclusão de informações adicionais nas sub-exceptions? Qual a chance disso ocorrer? É possível trabalhar também com as duas abordagens? (muitas vezes, você pode criar poucas exceptions com enums dentro)

[quote=ViniGodoy]A sua solução tem as seguintes vantagens:

a) Você pode categorizar mais sub-exceções para uma regra. A informação da hierarquia não é perdida;
b) Você pode criar campos adicionais nas sub-exceções, para descrever melhor o problema para a classe de negócios;
c) Fica mais claro para o usuário que exceptions podem ser disparadas por um método, caso você lance só os tipos possíveis.

A solução com enum tem como vantagem apenas evitar a criação de pequenos arquivos, muitas vezes só com uma definição de classe. Isso geralmente é também considerado um problema de modelagem, pois ocorre quando algo que seria um atributo acabou virando classe.

Na literatura, você encontra os dois casos. Por exemplo, uma SQLException traz consigo o código do erro. Já o java é cheio de exemplos de árvores de exception, como é o caso das IOExceptions.

Eu faria o seguinte questionamento: Em que casos é interessante a inclusão de informações adicionais nas sub-exceptions? Qual a chance disso ocorrer? É possível trabalhar também com as duas abordagens? (muitas vezes, você pode criar poucas exceptions com enums dentro)
[/quote]

Excelente resposta…

Eu costumo olhar códigos fonte de vários frameworks e ferramentas Java… de todos os frameworks que já vi… o mais organizado e que trata melhor os diferentes tipo de exceção é o Spring… e vejo grande vantagem no estilo de tratamento de exceção dele… que segue mais ou menos o seu modelo…

A vantagem de se ter tipos de exceção diferentes, exemplo:

try {
     // código que pode lançar vários tipos de exceçao
} catch (NullPointerException e) {
     throw new XException("O valor nao foi informado");
} catch (ClassCastException e){
     throw new XException("O valor informado não é do tipo da classe especificada");
} catch (ArrayIndexOutOfBoundsException e){
     throw new XException("O valor informado não tem tamanho suficiente para acessar o indice "+i);
}

Veja que nesse exemplo, várias exceções podem ocorrer… e é possivel informar ao usuário o que ele fez de errado dependendo do tipo de exceção

Mas como o Vini falou, tem que ver se é pertinente… pois se voce tiver vários tipos de exceção e não fizer nada diferente com elas, não faz muito sentido…

Não necessariamente é uma péssima pratica…

Observações

O catch nesse código nao faz muito sentido

try {  
      (...)  
      if (violacaoRegraX) {  
            throw new ServiceException(ServiceExceptionEnum.VIOLACAO_REGRA_X);  
      }  
      if (violacaoRegraY) {  
            throw new ServiceException(ServiceExceptionEnum.VIOLACAO_REGRA_Y);  
      }  
} catch (Exception e)  {  
            throw new ServiceException(e);  
}  

Se realmente tiver que fazer esse if nesse código:

try {  
    (...)  
} catch (ServiceException e) {  
    if (e.tipoExcecao == ServiceExceptionEnum.VIOLACAO_REGRA_X) {   
        //manda msg de violação da Regra X  
    } else if (e.tipoExcecao == ServiceExceptionEnum.VIOLACAO_REGRA_Y) {   
        //manda msg de violação da Regra Y  
    } else {  
        //manda msg de erro inesperado  
    }  
}  

Eu acho melhor a estratégia das várias exceções…

Agora… se puder ser assim:

try {  
    (...)  
} catch (ServiceException e) {  
    String mensagem = "Erro no programa, violação da regra "+e.getTipoExcecao();
    //manda mensagem de violacao
}  

Já faz mais sentido usar o ENUM