Quando uma IllegalArgumentException deve ser lançada?

98

Estou preocupado que esta seja uma exceção de tempo de execução, portanto, provavelmente deve ser usada com moderação.
Caso de uso padrão:

void setPercentage(int pct) {
    if( pct < 0 || pct > 100) {
         throw new IllegalArgumentException("bad percent");
     }
}

Mas parece que isso forçaria o seguinte design:

public void computeScore() throws MyPackageException {
      try {
          setPercentage(userInputPercent);
      }
      catch(IllegalArgumentException exc){
           throw new MyPackageException(exc);
      }
 }

Para voltar a ser uma exceção verificada.

Ok, mas vamos com isso. Se você fornecer informações incorretas, receberá um erro de tempo de execução. Em primeiro lugar, essa é uma política bastante difícil de implementar uniformemente, porque você pode ter que fazer a conversão exatamente o oposto:

public void scanEmail(String emailStr, InputStream mime) {
    try {
        EmailAddress parsedAddress = EmailUtil.parse(emailStr);
    }
    catch(ParseException exc){
        throw new IllegalArgumentException("bad email", exc);
    }
}

E pior - embora se 0 <= pct && pct <= 100possa esperar que a verificação do código do cliente seja feita estaticamente, isso não é verdade para dados mais avançados, como um endereço de e-mail, ou pior, algo que precisa ser verificado em um banco de dados, portanto, em geral, o código do cliente não pode validar.

Basicamente, o que estou dizendo é que não vejo uma política consistente significativa para o uso de IllegalArgumentException. Parece que não deve ser usado e devemos nos ater às nossas próprias exceções verificadas. Qual é um bom caso de uso para lançar isso?

Djechlin
fonte

Respostas:

80

O documento da API para IllegalArgumentException:

Lançado para indicar que um método recebeu um argumento ilegal ou impróprio.

Observando como ele é usado nas bibliotecas JDK , eu diria:

  • Parece uma medida defensiva reclamar de uma entrada obviamente ruim antes que a entrada entre em funcionamento e faça com que algo falhe no meio do caminho com uma mensagem de erro absurda.

  • É usado para casos em que seria muito incômodo lançar uma exceção verificada (embora apareça no código java.lang.reflect, onde a preocupação com níveis ridículos de lançamento de exceção verificada não é aparente de outra forma).

Eu usaria IllegalArgumentExceptionpara fazer a última verificação de argumento defensivo para utilitários comuns (tentando permanecer consistente com o uso do JDK). Ou onde a expectativa é que um argumento ruim seja um erro do programador, semelhante a um NullPointerException. Eu não o usaria para implementar a validação no código de negócios. Eu certamente não o usaria para o exemplo de e-mail.

Nathan Hughes
fonte
8
Acho que o conselho "onde a expectativa é que um argumento ruim é um erro do programador" é mais consistente com a forma como já vi isso ser usado, portanto, aceitando essa resposta.
Djechlin
22

Ao falar sobre "entrada incorreta", você deve considerar de onde vem a entrada.

Se for a entrada inserida por um usuário ou outro sistema externo que você não controla, você deve esperar que a entrada seja inválida e sempre valide-a. É perfeitamente normal lançar uma exceção verificada neste caso. Seu aplicativo deve 'se recuperar' dessa exceção, fornecendo uma mensagem de erro ao usuário.

Se a entrada se origina de seu próprio sistema, por exemplo, seu banco de dados ou alguma outra parte de seu aplicativo, você deve poder confiar que ela é válida (ela deveria ter sido validada antes de chegar lá). Nesse caso, é perfeitamente normal lançar uma exceção não verificada como IllegalArgumentException, que não deve ser capturada (em geral, você nunca deve capturar exceções não verificadas). É um erro do programador que o valor inválido tenha chegado lá em primeiro lugar;) Você precisa corrigi-lo.

Tom
fonte
2
Por que "você nunca deve capturar exceções não verificadas"?
Koray Tugay
9
Porque uma exceção não verificada deve ser lançada como resultado de um erro de programação. Não se pode esperar que o chamador de um método que lança tais exceções se recupere dele e, portanto, normalmente não faz sentido detectá-lo.
Tom
1
Because an unchecked exception is meant to be thrown as a result of a programming errorme ajudou a limpar um monte de coisas na minha cabeça, obrigado :)
svarog
14

Lançar exceções de tempo de execução "com moderação" não é realmente uma boa política - o Effective Java recomenda que você use exceções verificadas quando o chamador pode razoavelmente se recuperar . (O erro do programador é um exemplo específico: se um caso particular indicar erro do programador, você deve lançar uma exceção não verificada; você deseja que o programador tenha um rastreamento de pilha de onde o problema lógico ocorreu, e não tente resolvê-lo sozinho.)

Se não houver esperança de recuperação, sinta-se à vontade para usar exceções não verificadas; não há motivo para pegá-los, então está perfeitamente bem.

No entanto, não está 100% claro em seu exemplo em qual caso esse exemplo está em seu código.

Louis Wasserman
fonte
Eu acho que "razoavelmente esperado para se recuperar" é uma mentira. Qualquer operação foo(data)pode ter acontecido como parte da for(Data data : list) foo(data);qual o chamador deseja que o maior número possível de operações, embora alguns dados estejam malformados. Inclui erros de programação também, se a falha do meu aplicativo significar que uma transação não será realizada, é provavelmente melhor, se isso significa que o resfriamento nuclear fica offline, isso é ruim.
Djechlin
StackOverflowErrore tais são os casos dos quais o chamador não pode esperar uma recuperação razoável. Mas parece que qualquer caso de nível de lógica de aplicativo ou dados deve ser verificado. Isso significa que faça suas verificações de ponteiro nulo!
Djechlin
4
Em uma aplicação de resfriamento nuclear, eu prefiro reprovar nos testes do que permitir que um caso que o programador considerou impossível passar despercebido.
Louis Wasserman
Boolean.parseBoolean (..), lança uma IllegalArugmentException, mesmo que "seja razoável esperar que o chamador se recupere." então ... depende do seu código lidar com isso ou retornar ao chamador.
Jeryl Cook
5

Conforme especificado no tutorial oficial do oracle, ele afirma que:

Se for razoável esperar que um cliente se recupere de uma exceção, torne-a uma exceção verificada. Se um cliente não puder fazer nada para se recuperar da exceção, torne-a uma exceção não verificada.

Se eu tiver um aplicativo interagindo com o banco de dados usando JDBC, E tenho um método que leva o argumento como int iteme double price. A . Espero ter expressado meu ponto de vista claramente ..price item correspondente é lido da tabela do banco de dados. Simplesmente multiplico o número total de itemadquiridos pelo pricevalor e retorno o resultado. Embora eu tenha sempre certeza no meu final (final do aplicativo) que o valor do campo de preço na tabela nunca poderia ser negativo. Mas e se o valor do preço for negativo ? Isso mostra que há um problema sério com o lado do banco de dados. Talvez entrada de preço errada pela operadora. Esse é o tipo de problema que a outra parte do aplicativo que chama esse método não pode antecipar e não pode se recuperar dele. É umBUG em seu banco de dados. Então eIllegalArguementException()deve ser lançado neste caso, o que indicaria issothe price can't be negative

Vishal K
fonte
Não gosto deste conselho (do oracle) porque o tratamento de exceções é sobre como recuperar, não se recuperar. Por exemplo, uma solicitação de usuário malformada não vale a pena travar um servidor web inteiro.
Djechlin
5

Qualquer API deve verificar a validade de cada parâmetro de qualquer método público antes de executá-lo:

void setPercentage(int pct, AnObject object) {
    if( pct < 0 || pct > 100) {
        throw new IllegalArgumentException("pct has an invalid value");
    }
    if (object == null) {
        throw new IllegalArgumentException("object is null");
    }
}

Eles representam 99,9% das vezes erros no aplicativo porque ele está pedindo operações impossíveis então no final são bugs que deveriam travar o aplicativo (então é um erro irrecuperável).

Nesse caso, e seguindo a abordagem de falha rápida, você deve deixar o aplicativo terminar para evitar corromper o estado do aplicativo.

Ignacio Soler Garcia
fonte
Pelo contrário, se um cliente API me dá uma entrada ruim, eu deveria não bater meu servidor API inteira.
Djechlin
2
Claro, ele não deve travar seu servidor API, mas retornar uma exceção para o chamador. Isso não deve travar nada além do cliente.
Ignacio Soler Garcia,
O que você escreveu no comentário não é o que você escreveu na resposta.
Djechlin,
1
Deixe-me explicar, se a chamada à API com parâmetros errados (um bug) for feita por um cliente de terceiros, o cliente deve travar. Se for o servidor API que está com um bug ao chamar o método com parâmetros errados, o servidor API deve travar. Verifique: en.wikipedia.org/wiki/Fail-fast
Ignacio Soler Garcia
1

Trate IllegalArgumentExceptioncomo uma verificação de pré - condições e considere o princípio de design: Um método público deve conhecer e documentar publicamente suas próprias pré-condições.

Eu concordaria que este exemplo está correto:

void setPercentage(int pct) {
    if( pct < 0 || pct > 100) {
         throw new IllegalArgumentException("bad percent");
     }
}

Se EmailUtil for opaco , o que significa que há algum motivo para as pré-condições não poderem ser descritas para o usuário final, uma exceção verificada está correta. A segunda versão, corrigida para este design:

import com.someoneelse.EmailUtil;

public void scanEmail(String emailStr, InputStream mime) throws ParseException {
    EmailAddress parsedAddress = EmailUtil.parseAddress(emailStr);
}

Se EmailUtil for transparente , por exemplo, talvez seja um método privado pertencente à classe em questão, IllegalArgumentExceptionestá correto se e somente se suas pré-condições podem ser descritas na documentação da função. Esta é uma versão correta também:

/** @param String email An email with an address in the form [email protected]
 * with no nested comments, periods or other nonsense.
 */
public String scanEmail(String email)
  if (!addressIsProperlyFormatted(email)) {
      throw new IllegalArgumentException("invalid address");
  }
  return parseEmail(emailAddr);
}
private String parseEmail(String emailS) {
  // Assumes email is valid
  boolean parsesJustFine = true;
  // Parse logic
  if (!parsesJustFine) {
    // As a private method it is an internal error if address is improperly
    // formatted. This is an internal error to the class implementation.
    throw new AssertError("Internal error");
  }
}

Este projeto pode ir de qualquer maneira.

  • Se as pré-condições forem caras de descrever ou se a classe se destinar a ser usada por clientes que não sabem se seus e-mails são válidos, use ParseException. O método de nível superior aqui é nomeado, o scanEmailque sugere que o usuário final pretende enviar e-mail não estudado, portanto, provavelmente está correto.
  • Se as pré-condições puderem ser descritas na documentação da função e a classe não tiver como objetivo uma entrada inválida e, portanto, um erro do programador for indicado, use IllegalArgumentException. Embora não "marcada", a "verificação" se move para o Javadoc documentando a função, à qual o cliente deve aderir. IllegalArgumentExceptiononde o cliente não pode dizer que seu argumento é ilegal de antemão está errado.

Uma observação sobre IllegalStateException : Isso significa que "o estado interno deste objeto (variáveis ​​de instância privadas) não é capaz de executar esta ação." O usuário final não pode ver o estado privado, falando vagamente, ele tem precedência IllegalArgumentExceptionno caso em que a chamada do cliente não tem como saber se o estado do objeto é inconsistente. Não tenho uma boa explicação sobre quando é preferível a exceções verificadas, embora coisas como inicializar duas vezes ou perder uma conexão de banco de dados que não é recuperada sejam exemplos.

Djechlin
fonte