Qual é a melhor prática para validação de parâmetro do construtor?
Suponha um bit simples de C #:
public class MyClass
{
public MyClass(string text)
{
if (String.IsNullOrEmpty(text))
throw new ArgumentException("Text cannot be empty");
// continue with normal construction
}
}
Seria aceitável lançar uma exceção?
A alternativa que encontrei foi a pré-validação, antes de instanciar:
public class CallingClass
{
public MyClass MakeMyClass(string text)
{
if (String.IsNullOrEmpty(text))
{
MessageBox.Show("Text cannot be empty");
return null;
}
else
{
return new MyClass(text);
}
}
}
c#
validation
constructors
MPelletier
fonte
fonte
Respostas:
Costumo executar toda a minha validação no construtor. Isso é obrigatório, porque quase sempre crio objetos imutáveis. Para o seu caso específico, acho que isso é aceitável.
Se você estiver usando o .NET 4, poderá fazer isso. Obviamente, isso depende se você considera uma sequência que contém apenas espaço em branco inválida.
fonte
Init
para receber algum tipo de código de erro quando uma exceção funcionar da mesma forma e forçar seu objeto a manter sempre um estado válido?ArgumentNullException
e outra que testa vazio e lança umArgumentException
. Permite que o chamador seja mais exigente se desejar no tratamento de exceções.Muitas pessoas afirmam que os construtores não devem lançar exceções. O KyleG nesta página , por exemplo, faz exatamente isso. Honestamente, não consigo pensar em uma razão pela qual não.
No C ++, lançar uma exceção de um construtor é uma má idéia, pois deixa com memória alocada contendo um objeto não inicializado ao qual você não tem referência (ou seja, é um vazamento de memória clássico). Provavelmente é daí que vem o estigma - um monte de desenvolvedores de C ++ da velha escola gastaram metade do aprendizado de C # e apenas aplicaram o que sabiam do C ++ a ele. Por outro lado, no Objective-C, a Apple separou a etapa de alocação da etapa de inicialização, para que os construtores nesse idioma possam lançar exceções.
C # não pode vazar memória de uma chamada sem êxito para um construtor. Mesmo algumas classes na estrutura .NET lançam exceções em seus construtores.
fonte
Lance uma exceção IFF, a classe não pode ser colocada em um estado consistente com relação ao seu uso semântico. Caso contrário, não. NUNCA permita que um objeto exista em um estado inconsistente. Isso inclui não fornecer construtores completos (como ter um construtor vazio + inicializar () antes que seu objeto seja completamente construído) ... APENAS DIGA NÃO!
Em uma pitada, todo mundo faz isso. Fiz isso outro dia para um objeto usado de maneira muito restrita, dentro de um escopo restrito. Algum dia, no futuro, eu ou outra pessoa provavelmente pagaremos o preço por esse recibo na prática.
Devo observar que, por "construtor", quero dizer o que o cliente chama para criar o objeto. Isso poderia facilmente ser algo diferente de uma construção real chamada de "Construtor". Por exemplo, algo assim em C ++ não violaria o princípio IMNSHO:
Como a única maneira de criar um
funky_object
é chamarbuild_funky
o princípio de nunca permitir que um objeto inválido exista permaneça intacto, mesmo que o "Construtor" real não termine o trabalho.Isso dá muito trabalho extra para ganho questionável (talvez até perda). Eu ainda preferiria a rota de exceção.
fonte
Nesse caso, eu usaria o método de fábrica. Basicamente, defina sua classe apenas com construtores particulares e com um método de fábrica que retorna uma instância do seu objeto. Se os parâmetros iniciais forem inválidos, basta retornar nulo e solicitar que o código de chamada decida o que fazer.
Nunca lance exceções, a menos que as condições sejam excepcionais. Estou pensando que, neste caso, um valor vazio pode ser passado facilmente. Se for esse o caso, o uso desse padrão evitaria exceções e as penalidades de desempenho em que incorrem, mantendo o estado MyClass válido.
fonte
Alguém poderia razoavelmente questionar essas diretrizes e dizer: "Mas eu não sigo essas regras, e meu código funciona bem!" A isso, eu respondia: "Bem, isso pode ser verdade, até que não seja".
fonte
Depende do que o MyClass está fazendo. Se MyClass fosse realmente uma classe de repositório de dados e o texto do parâmetro fosse uma cadeia de conexão, a melhor prática seria lançar uma ArgumentException. No entanto, se MyClass for a classe StringBuilder (por exemplo), você poderá deixá-la em branco.
Portanto, depende de quão essencial é o texto do parâmetro para o método - o objeto faz sentido com um valor nulo ou em branco?
fonte
Minha preferência é definir um padrão, mas eu sei que o Java tem a biblioteca "Apache Commons" que faz algo assim, e acho que é uma boa idéia também. Não vejo um problema ao lançar uma exceção se o valor inválido colocar o objeto em um estado inutilizável; uma string não é um bom exemplo, mas e se fosse para o DI do pobre homem? Você não poderia operar se um valor de
null
fosse passado no lugar de, digamos, umaICustomerRepository
interface. Em uma situação como essa, lançar uma exceção é a maneira correta de lidar com as coisas, eu diria.fonte
Quando eu trabalhava em c ++, não costumava lançar exceções no construtor por causa do problema de vazamento de memória que isso pode levar, eu aprendi da maneira mais difícil. Qualquer pessoa que trabalha com c ++ sabe como pode ser difícil e problemático o vazamento de memória.
Mas se você estiver em c # / Java, não terá esse problema, porque o coletor de lixo coletará a memória. Se você estiver usando C #, acho preferível usar a propriedade de maneira agradável e consistente para garantir que as restrições de dados sejam garantidas.
fonte
Lançar uma exceção será uma verdadeira dor de cabeça para os usuários de sua classe. Se a validação é um problema, geralmente faço da criação do objeto um processo de duas etapas.
1 - Crie a instância
2 - Chame um método Initialize com um resultado de retorno.
OU
Chame um método / função que crie a classe para você que possa fazer a validação.
OU
Tenho uma bandeira isValid, da qual eu particularmente não gosto, mas algumas pessoas gostam.
fonte
isValid = false
. Ter que testar após a criação de uma classe se ela é válida é um design horrível e propenso a erros. E, você disse, tenha um construtor e chame initialize ... E se eu não chamar initialize? O que então? E se Initialize obtiver dados incorretos, posso lançar uma exceção agora? Sua classe não deve ser difícil de usar.