Melhor maneira de definir códigos / strings de erro em Java?

118

Estou escrevendo um serviço da web em Java e estou tentando descobrir a melhor maneira de definir códigos de erro e suas sequências de erro associadas . Preciso ter um código de erro numérico e uma string de erro agrupados. Tanto o código de erro quanto a string de erro serão enviados ao cliente que acessa o serviço da web. Por exemplo, quando ocorre uma SQLException, posso querer fazer o seguinte:

// Example: errorCode = 1, 
//          errorString = "There was a problem accessing the database."
throw new SomeWebServiceException(errorCode, errorString);

O programa cliente pode ver a mensagem:

"Ocorreu o erro nº 1: houve um problema ao acessar o banco de dados."

Meu primeiro pensamento foi usar um Enumdos códigos de erro e substituir os toStringmétodos para retornar as strings de erro. Aqui está o que eu descobri:

public enum Errors {
  DATABASE {
    @Override
    public String toString() {
      return "A database error has occured.";
    }
  },

  DUPLICATE_USER {
    @Override
    public String toString() {
      return "This user already exists.";
    }
  },

  // more errors follow
}

Minha pergunta é: existe uma maneira melhor de fazer isso? Eu preferiria uma solução em código, em vez de ler de um arquivo externo. Estou usando Javadoc para este projeto e seria útil documentar os códigos de erro in-line e atualizá-los automaticamente na documentação.

William Brendel
fonte
Comentário atrasado, mas vale a pena mencionar Eu acho ... 1) Você realmente precisa de códigos de erro aqui na exceção? Veja a resposta blabla999 abaixo. 2) Você deve ter o cuidado de passar muitas informações de erro de volta para o usuário. Informações úteis sobre o erro devem ser gravadas nos logs do servidor, mas o cliente deve ser informado do mínimo (por exemplo, "houve um problema ao efetuar login"). Esta é uma questão de segurança e de prevenção de spoofers.
wmorrison365

Respostas:

161

Bem, certamente há uma implementação melhor da solução enum (que geralmente é muito boa):

public enum Error {
  DATABASE(0, "A database error has occured."),
  DUPLICATE_USER(1, "This user already exists.");

  private final int code;
  private final String description;

  private Error(int code, String description) {
    this.code = code;
    this.description = description;
  }

  public String getDescription() {
     return description;
  }

  public int getCode() {
     return code;
  }

  @Override
  public String toString() {
    return code + ": " + description;
  }
}

Você pode querer substituir toString () para retornar apenas a descrição - não tenho certeza. De qualquer forma, o ponto principal é que você não precisa substituir separadamente para cada código de erro. Observe também que especifiquei explicitamente o código em vez de usar o valor ordinal - isso torna mais fácil alterar a ordem e adicionar / remover erros posteriormente.

Não se esqueça de que isso não é internacionalizado de forma alguma - mas, a menos que seu cliente de serviço da web envie uma descrição de localidade, você não pode internacionalizá-la facilmente. Pelo menos eles terão o código de erro para usar para i18n no lado do cliente ...

Jon Skeet
fonte
13
Para internacionalizar, substitua o campo de descrição por um código de string que pode ser consultado em um pacote de recursos.
Marcus Downing
@Marcus: Gosto dessa ideia. Estou me concentrando em lançar essa coisa, mas quando olharmos para a internacionalização, acho que farei o que você sugeriu. Obrigado!
William Brendel
@marcus, se toString () não for sobrescrito (o que não precisa ser), então o código da string poderia ser apenas o valor enum toString () que seria DATABASE ou DUPLICATE_USER neste caso.
rublo
@Jon Skeet! Eu gosto dessa solução, como alguém poderia produzir uma solução que seja fácil de localizar (ou traduzir para outros idiomas etc.) Pensando em usá-la no Android posso usar o R.string.IDS_XXXX em vez de strings codificadas lá?
AB
1
@AB: Bem, uma vez que você tenha o enum, você poderia facilmente escrever uma classe para extrair o recurso localizado relevante do valor do enum, por meio de arquivos de propriedades ou qualquer outra coisa.
Jon Skeet,
34

No que me diz respeito, prefiro externalizar as mensagens de erro em arquivos de propriedades. Isso será muito útil no caso de internacionalização de sua aplicação (um arquivo de propriedades por idioma). Também é mais fácil modificar uma mensagem de erro e não precisa de nenhuma recompilação das fontes Java.

Em meus projetos, geralmente tenho uma interface que contém códigos de erros (String ou inteiro, não importa muito), que contém a chave nos arquivos de propriedades para este erro:

public interface ErrorCodes {
    String DATABASE_ERROR = "DATABASE_ERROR";
    String DUPLICATE_USER = "DUPLICATE_USER";
    ...
}

no arquivo de propriedades:

DATABASE_ERROR=An error occurred in the database.
DUPLICATE_USER=The user already exists.
...

Outro problema com sua solução é a manutenção: você tem apenas 2 erros e já 12 linhas de código. Então imagine seu arquivo de Enumeração quando você terá centenas de erros para gerenciar!

Romain Linsolas
fonte
2
Eu aumentaria mais de 1 se pudesse. Codificar as cordas é feio para manutenção.
Robin de
3
Armazenar constantes de String na interface é uma má ideia. Você pode usar enums ou usar constantes String em classes finais com construtor privado, por pacote ou área relacionada. Por favor, John Skeets responda com Enums. Por favor, verifique. stackoverflow.com/questions/320588/…
Anand Varkey Philips
21

Sobrecarregar toString () parece um pouco nojento - parece um pouco forçado do uso normal de toString ().

A respeito:

public enum Errors {
  DATABASE(1, "A database error has occured."),
  DUPLICATE_USER(5007, "This user already exists.");
  //... add more cases here ...

  private final int id;
  private final String message;

  Errors(int id, String message) {
     this.id = id;
     this.message = message;
  }

  public int getId() { return id; }
  public String getMessage() { return message; }
}

parece muito mais limpo para mim ... e menos prolixo.

Cowan
fonte
5
Sobrecarregar toString () em qualquer objeto (sem falar em enums) é bastante normal.
cletus de
+1 Não é tão flexível quanto a solução de Jon Skeet, mas ainda resolve o problema muito bem. Obrigado!
William Brendel
2
Eu quis dizer que toString () é mais comum e útil para fornecer informações suficientes para identificar o objeto - geralmente inclui o nome da classe ou alguma forma de dizer o tipo de objeto de maneira significativa. Um toString () que retorna apenas 'Ocorreu um erro de banco de dados' seria surpreendente em muitos contextos.
Cowan
1
Eu concordo com Cowan, usar toString () dessa forma parece um pouco 'hackeado'. Apenas um rápido retorno do investimento e não um uso normal. Para o enum, toString () deve retornar o nome da constante enum. Isso pareceria interessante em um depurador quando você deseja o valor de uma variável.
Robin de
19

No meu último trabalho, fui um pouco mais fundo na versão enum:

public enum Messages {
    @Error
    @Text("You can''t put a {0} in a {1}")
    XYZ00001_CONTAINMENT_NOT_ALLOWED,
    ...
}

@Error, @Info, @Warning são retidos no arquivo de classe e estão disponíveis no tempo de execução. (Tínhamos algumas outras anotações para ajudar a descrever a entrega da mensagem também)

@Text é uma anotação em tempo de compilação.

Eu escrevi um processador de anotações para isso que fazia o seguinte:

  • Verifique se não há números de mensagem duplicados (a parte antes do primeiro sublinhado)
  • Verifique a sintaxe do texto da mensagem
  • Gere um arquivo messages.properties que contém o texto, codificado pelo valor enum.

Eu escrevi algumas rotinas de utilitários que ajudaram a registrar erros, agrupá-los como exceções (se desejado) e assim por diante.

Estou tentando fazer com que eles me deixem abrir o código ... - Scott

Scott Stanchfield
fonte
Ótima maneira de lidar com mensagens de erro. Você já abriu o código?
bobbel
5

Eu recomendo que você dê uma olhada em java.util.ResourceBundle. Você deve se preocupar com o I18N, mas vale a pena mesmo se não o fizer. Externalizar as mensagens é uma ideia muito boa. Descobri que era útil poder dar uma planilha para o pessoal de negócios que lhes permitisse colocar no idioma exato que eles queriam ver. Escrevemos uma tarefa Ant para gerar os arquivos .properties em tempo de compilação. Isso torna o I18N trivial.

Se você também estiver usando o Spring, melhor ainda. Sua classe MessageSource é útil para esse tipo de coisa.

duffymo
fonte
4

Apenas para continuar a açoitar este cavalo morto em particular - fizemos um bom uso de códigos de erro numéricos quando os erros são mostrados aos clientes finais, uma vez que eles freqüentemente esquecem ou interpretam mal a mensagem de erro real, mas às vezes podem reter e relatar um valor numérico que pode fornecer você uma pista do que realmente aconteceu.

telcopro
fonte
3

Existem muitas maneiras de resolver isso. Minha abordagem preferida é ter interfaces:

public interface ICode {
     /*your preferred code type here, can be int or string or whatever*/ id();
}

public interface IMessage {
    ICode code();
}

Agora você pode definir qualquer número de enums que fornecem mensagens:

public enum DatabaseMessage implements IMessage {
     CONNECTION_FAILURE(DatabaseCode.CONNECTION_FAILURE, ...);
}

Agora você tem várias opções para transformá-los em Strings. Você pode compilar as strings em seu código (usando anotações ou parâmetros do construtor enum) ou pode lê-las de um arquivo de configuração / propriedade ou de uma tabela de banco de dados ou uma mistura. A última é minha abordagem preferida porque você sempre precisará de algumas mensagens que você pode transformar em texto muito cedo (ou seja, enquanto você se conecta ao banco de dados ou lê a configuração).

Estou usando testes de unidade e estruturas de reflexão para encontrar todos os tipos que implementam minhas interfaces para garantir que cada código seja usado em algum lugar e que os arquivos de configuração contenham todas as mensagens esperadas, etc.

Usando estruturas que podem analisar Java como https://github.com/javaparser/javaparser ou a do Eclipse , você pode até verificar onde as enums são usadas e encontrar as não usadas.

Aaron Digulla
fonte
2

Eu (e o restante de nossa equipe em minha empresa) preferimos levantar exceções em vez de retornar códigos de erro. Os códigos de erro devem ser verificados em todos os lugares, repassados ​​e tendem a tornar o código ilegível quando a quantidade de código se torna maior.

A classe de erro definiria a mensagem.

PS: e na verdade também prezamos pela internacionalização!
PPS: você também pode redefinir o método de aumento e adicionar registro, filtragem etc., se necessário (pelo menos em ambientes, onde as classes de exceção e amigos são extensíveis / alteráveis)

blabla999
fonte
desculpe, Robin, mas então (pelo menos a partir do exemplo acima), essas deveriam ser duas exceções - "erro de banco de dados" e "usuário duplicado" são tão completamente diferentes que duas subclasses de erro separadas devem ser criadas, que podem ser capturadas individualmente ( um sendo um sistema, o outro sendo um erro de administrador)
blabla999
e para que servem os códigos de erro, senão para diferenciar entre uma ou outra exceção? Portanto, pelo menos acima do manipulador, ele é exatamente isso: lidando com códigos de erro que são passados ​​e se ativados.
blabla999
Acho que o nome da exceção seria muito mais ilustrativo e autodescritivo do que um código de erro. Melhor pensar mais em descobrir bons nomes de exceção, IMO.
duffymo de
@ blabla999 ah, exatamente meus pensamentos. Por que capturar uma exceção de baixa granularidade e então testar "if errorcode == x, ou y, ou z". Uma dor tão grande e vai contra a corrente. Além disso, você não pode capturar diferentes exceções em diferentes níveis de sua pilha. Você teria que detectar em todos os níveis e testar o código de erro em cada um. Isso torna o código do cliente muito mais detalhado ... +1 + mais se eu pudesse. Dito isso, acho que temos que responder à pergunta dos OPs.
wmorrison365
2
Lembre-se de que isso é para um serviço da web. O cliente só pode analisar strings. No lado do servidor, ainda haveria exceções lançadas com um membro errorCode, que pode ser usado na resposta final ao cliente.
pkrish
1

Um pouco tarde, mas, eu só estava procurando uma solução bonita para mim. Se você tiver diferentes tipos de mensagem de erro, poderá adicionar uma fábrica de mensagens simples e personalizadas para especificar mais detalhes e o formato que desejar posteriormente.

public enum Error {
    DATABASE(0, "A database error has occured. "), 
    DUPLICATE_USER(1, "User already exists. ");
    ....
    private String description = "";
    public Error changeDescription(String description) {
        this.description = description;
        return this;
    }
    ....
}

Error genericError = Error.DATABASE;
Error specific = Error.DUPLICATE_USER.changeDescription("(Call Admin)");

EDIT: ok, usar enum aqui é um pouco perigoso, pois você altera determinado enum permanentemente. Eu acho que seria melhor mudar para a classe e usar campos estáticos, mas você não pode mais usar '=='. Então eu acho que é um bom exemplo do que não fazer (ou fazer apenas durante a inicialização) :)

pprzemek
fonte
1
Concordo totalmente com seu EDIT, não é uma boa prática alterar um campo enum em tempo de execução. Com este design, todos podem editar a mensagem de erro. Isso é muito perigoso. Os campos Enum devem ser sempre finais.
b3nyc
0

enum para código de erro / definição de mensagem ainda é uma boa solução, embora tenha uma preocupação com i18n. Na verdade, podemos ter duas situações: o código / mensagem é exibido para o usuário final ou para o integrador do sistema. Para o último caso, I18N não é necessário. Acho que os serviços da web são provavelmente o caso posterior.

Jimmy
fonte
0

Usar interfacecomo constante de mensagem geralmente é uma má ideia. Ele vazará para o programa do cliente permanentemente como parte da API exportada. Quem sabe se os programadores de cliente posteriores podem analisar essas mensagens de erro (públicas) como parte de seu programa.

Você ficará bloqueado para sempre para suportar isso, pois as alterações no formato da string podem / podem interromper o programa cliente.

Awan Biru
fonte
0

Siga o exemplo abaixo:

public enum ErrorCodes {
NO_File("No file found. "),
private ErrorCodes(String value) { 
    this.errordesc = value; 
    }
private String errordesc = ""; 
public String errordesc() {
    return errordesc;
}
public void setValue(String errordesc) {
    this.errordesc = errordesc;
}

};

Em seu código, chame-o assim:

fileResponse.setErrorCode(ErrorCodes.NO_FILE.errordesc());
Chinmoy
fonte
0

Eu uso PropertyResourceBundle para definir os códigos de erro em um aplicativo corporativo para gerenciar recursos de código de erro local. Esta é a melhor maneira de lidar com códigos de erro em vez de escrever código (pode ser válida para alguns códigos de erro) quando o número de códigos de erro é enorme e estruturado.

Consulte o documento java para obter mais informações sobre PropertyResourceBundle

Mujibur Rahman
fonte