Pergunta

Estou no meio da leitura do excelente Código Limpo

Uma discussão é sobre a passagem de nulos para um método.

public class MetricsCalculator {
    public double xProjection(Point p1, Point p2) {
        return (p2.x - p1.x) * 1.5;
    }
}
...
calculator.xProjection(null, new Point(12,13));

Representa diferentes maneiras de lidar com isso:

public double xProjection(Point p1, Point p2) {
    if (p1 == null || p2 == null) {
        throw new IllegalArgumentException("Invalid argument for xProjection");
    }
    return (p2.x - p1.x) * 1.5;
}

public double xProjection(Point p1, Point p2) {
    assert p1 != null : "p1 should not be null";
    assert p2 != null : "p2 should not be null";
    return (p2.x - p1.x) * 1.5;
}

Eu prefiro o afirmações abordagem, mas não gosto do fato de as asserções estarem desativadas por padrão.

O livro finalmente afirma:

Na maioria das linguagens de programação, não existe uma boa maneira de lidar com um valor nulo passado acidentalmente por um chamador.Como esse é o caso, a abordagem racional é proibir a passagem nula por padrão.

Realmente não explica como você aplicaria essa restrição?

Algum de vocês tem opiniões fortes de qualquer maneira.

Foi útil?

Solução

Tanto o uso de asserções quanto o lançamento de exceções são abordagens válidas aqui.Qualquer mecanismo pode ser usado para indicar um erro de programação, não um erro de tempo de execução, como é o caso aqui.

  • As asserções têm a vantagem de desempenho, pois normalmente são desabilitadas em sistemas de produção.
  • As exceções têm a vantagem da segurança, pois a verificação é sempre realizada.

A escolha realmente depende das práticas de desenvolvimento do projeto.O projeto como um todo precisa decidir sobre uma política de afirmação:se a escolha for habilitar asserções durante todo o desenvolvimento, então eu diria para usar asserções para verificar esse tipo de parâmetro inválido - em um sistema de produção, é improvável que uma NullPointerException lançada devido a um erro de programação possa ser capturada e tratada de uma forma significativa e, portanto, agirá como uma afirmação.

Porém, na prática, conheço muitos desenvolvedores que não confiam que as asserções serão habilitadas quando apropriado e, portanto, optam pela segurança de lançar um NullPointerException.

É claro que se você não puder impor uma política para o seu código (se estiver criando uma biblioteca, por exemplo, e portanto depender de como outros desenvolvedores executam seu código), você deve optar pela abordagem segura de lançar NullPointerException para aqueles métodos que fazem parte da API da biblioteca.

Outras dicas

A regra geral é se o seu método não espera null argumentos, então você deve jogar System.ArgumentNullException.Jogando corretamente Exception não apenas protege você contra corrupção de recursos e outras coisas ruins, mas também serve como um guia para os usuários do seu código, economizando tempo gasto na depuração do seu código.

Leia também um artigo sobre Programação defensiva

Também não é de uso imediato, mas está relacionado à menção de Spec#...Há uma proposta para adicionar "tipos seguros para nulos" a uma versão futura do Java: "Manipulação nula aprimorada - Tipos seguros para nulos".

De acordo com a proposta, o seu método tornar-se-ia

public class MetricsCalculator {
    public double xProjection(#Point p1, #Point p2) {
        return (p2.x - p1.x) * 1.5;
    }
}

onde #Point é o tipo de nãonull referências a objetos do tipo Point.

Realmente não explica como você aplicaria essa restrição?

Você aplica isso jogando um Argumento Exceção se eles passarem em nulo.

if (p1 == null || p2 == null) {
    throw new IllegalArgumentException("Invalid argument for xProjection");
}

Prefiro o uso de afirmações.

Tenho uma regra que só uso asserções em métodos públicos e protegidos.Isso ocorre porque acredito que o método de chamada deve garantir que está passando argumentos válidos para métodos privados.

Spec# parece muito interessante!

Quando algo assim não está disponível, geralmente testo métodos não privados com uma verificação nula em tempo de execução e asserções para métodos internos.Em vez de codificar explicitamente a verificação nula em cada método, delego isso a uma classe de utilitários com um método nulo de verificação:

/**
 * Checks to see if an object is null, and if so 
 * generates an IllegalArgumentException with a fitting message.
 * 
 * @param o The object to check against null.
 * @param name The name of the object, used to format the exception message
 *
 * @throws IllegalArgumentException if o is null.
 */
public static void checkNull(Object o, String name) 
    throws IllegalArgumentException {
   if (null == o)
      throw new IllegalArgumentException(name + " must not be null");
}

public static void checkNull(Object o) throws IllegalArgumentException {
   checkNull(o, "object");
} 

// untested:
public static void checkNull(Object... os) throws IllegalArgumentException {
   for(Object o in os) checkNull(o);  
}

Então a verificação se transforma em:

public void someFun(String val1, String val2) throws IllegalArgumentException {
   ExceptionUtilities.checkNull(val1, "val1");
   ExceptionUtilities.checkNull(val2, "val2");

   /** alternatively:
   ExceptionUtilities.checkNull(val1, val2);
   **/

   /** ... **/
} 

Que pode ser adicionado com macros de editor ou um script de processamento de código.Editar: A verificação detalhada também poderia ser adicionada dessa forma, mas acho que é significativamente mais fácil automatizar a adição de uma única linha.

Na maioria das linguagens de programação, não existe uma boa maneira de lidar com um valor nulo passado acidentalmente por um chamador.Como esse é o caso, a abordagem racional é proibir a passagem nula por padrão.

eu encontrei JetBrains' @Nullable e @NotNull abordagem de anotações para lidar com isso é a mais engenhosa até agora.É específico do IDE, infelizmente, mas muito limpo e poderoso, IMO.

http://www.jetbrains.com/idea/documentation/howto.html

Ter isso (ou algo semelhante) como padrão Java seria muito bom.

Embora não esteja estritamente relacionado, você pode querer dar uma olhada em Especificação#.

Acho que ainda está em desenvolvimento (pela Microsoft), mas alguns CTP estão disponíveis e parece promissor.Basicamente, ele permite que você faça isso:

  public static int Divide(int x, int y)
    requires y != 0 otherwise ArgumentException; 
  {
  }

ou

  public static int Subtract(int x, int y)
    requires x > y;
    ensures result > y;
  {
    return x - y;
  } 

Ele também fornece outros recursos, como tipos Notnull.Ele foi desenvolvido com base no .NET Framework 2.0 e é totalmente compatível.A sintaxe, como você pode ver, é C#.

@Chris Karcher, eu diria absolutamente correto.A única coisa que eu diria é verificar os parâmetros separadamente e fazer com que a exceção relate o parâmetro que era nulo, pois torna muito mais fácil rastrear de onde vem o nulo.

@wvdschel uau!Se escrever o código exigir muito esforço para você, você deve procurar algo como PostSharp (ou um equivalente Java, se houver) que pode pós-processar seus assemblies e inserir verificações de parâmetros para você.

Como o off-topic parece ter se tornado o tópico, Scala adota uma abordagem interessante para isso.Todos os tipos são considerados não nulos, a menos que você os envolva explicitamente em um Option para indicar que pode ser nulo.Então:

//  allocate null
var name : Option[String]
name = None

//  allocate a value
name = Any["Hello"]

//  print the value if we can
name match {
  Any[x] => print x
  _ => print "Nothing at all"
}

Geralmente prefiro não fazer nada disso, pois isso apenas desacelera as coisas.NullPointerExceptions são lançadas mais tarde de qualquer maneira, o que levará rapidamente o usuário a descobrir que está passando nulo para o método.Eu costumava verificar, mas 40% do meu código acabou sendo verificação de código, e nesse ponto decidi que simplesmente não valia a pena as boas mensagens de afirmação.

Eu concordo ou discordo postagem de wvdschel, depende do que ele está dizendo especificamente.

Neste caso, claro, este método irá falhar null portanto, a verificação explícita aqui provavelmente não é necessária.

No entanto, se o método simplesmente armazena os dados passados, e há algum outro método que você chamará mais tarde que irá lidar com isso, descobrir entradas incorretas o mais cedo possível é a chave para corrigir bugs mais rapidamente.Nesse ponto posterior, pode haver uma infinidade de maneiras pelas quais dados incorretos foram fornecidos à sua turma.É uma espécie de tentativa de descobrir como os ratos entraram em sua casa depois do fato, tentando encontrar o buraco em algum lugar.

Um pouco fora do assunto, mas uma característica do encontrar bugs o que considero muito útil é poder anotar os parâmetros dos métodos para descrever quais parâmetros não devem receber um valor nulo.

Usando análise estática do seu código, encontrar bugs pode então apontar locais onde o método é chamado com um valor potencialmente nulo.

Isto tem duas vantagens:

  1. A anotação descreve sua intenção de como o método deve ser chamado, auxiliando na documentação
  2. FindBugs pode apontar possíveis chamadores de problemas do método, permitindo que você rastreie possíveis bugs.

Útil apenas quando você tem acesso ao código que chama seus métodos, mas geralmente é esse o caso.

Jogando C# ArgumentException, ou Java IllegalArgumentException logo no início do método me parece a solução mais clara.

Deve-se sempre ter cuidado com Runtime Exceptions - exceções que não são declaradas na assinatura do método.Como o compilador não obriga você a capturá-los, é muito fácil esquecê-los.Certifique-se de ter algum tipo de tratamento de exceção "pega tudo" para evitar que o software seja interrompido abruptamente.Essa é a parte mais importante da experiência do usuário.

A melhor maneira de lidar com isso seria realmente o uso de exceções.Em última análise, as afirmações vão acabar dando uma semelhante experiência para o usuário final, mas não fornece nenhuma maneira para o desenvolvedor que chama seu código lidar com a situação antes de mostrar uma exceção ao usuário final.Em última análise, você deseja garantir o teste de entradas inválidas o mais cedo possível (especialmente em código voltado para o público) e fornecer as exceções apropriadas que o código de chamada pode capturar.

De uma forma Java, assumindo que o nulo vem de um erro de programação (ou seja,nunca deve sair da fase de teste), então deixe o sistema jogá-lo ou, se houver efeitos colaterais chegando a esse ponto, verifique se há nulo no início e lance IllegalArgumentException ou NullPointerException.

Se o nulo pudesse vir de um real exceçãoCaso contrário, mas você não deseja usar uma exceção verificada para isso, então você definitivamente deseja seguir a rota IllegalArgumentException no início do método.

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