Depende. A decisão de onde colocar a validação deve basear-se na descrição e na força do contrato implícito (ou documentado) pelo método. A validação é uma boa maneira de reforçar a adesão a um contrato específico. Se, por qualquer motivo, o método tiver um contrato muito rígido, sim, é sua responsabilidade verificar antes de ligar.
Esse é um conceito especialmente importante quando você cria um método público , porque basicamente anuncia que algum método executa alguma operação. É melhor fazer o que você diz que faz!
Tome o seguinte método como exemplo:
public void DeletePerson(Person p)
{
_database.Delete(p);
}
Qual é o contrato implícito DeletePerson
? O programador pode apenas assumir que, se algum Person
for passado, ele será excluído. No entanto, sabemos que isso nem sempre é verdade. E se p
for um null
valor? E se p
não existir no banco de dados? E se o banco de dados estiver desconectado? Portanto, o DeletePerson não parece cumprir bem seu contrato. Às vezes, ele exclui uma pessoa e, às vezes, lança uma NullReferenceException, ou uma DatabaseNotConnectedException, ou às vezes não faz nada (como se a pessoa já tiver sido excluída).
APIs como essa são notoriamente difíceis de usar, porque quando você chama essa "caixa preta" de um método, todo tipo de coisa terrível pode acontecer.
Aqui estão algumas maneiras de melhorar o contrato:
Adicione validação e adicione uma exceção ao contrato. Isso fortalece o contrato , mas exige que o chamador realize a validação. A diferença, no entanto, é que agora eles conhecem seus requisitos. Nesse caso, comunico isso com um comentário XML em C #, mas você pode adicionar um throws
(Java), usar um Assert
ou usar uma ferramenta de contrato como o Code Contracts.
///<exception>ArgumentNullException</exception>
///<exception>ArgumentException</exception>
public void DeletePerson(Person p)
{
if(p == null)
throw new ArgumentNullException("p");
if(!_database.Contains(p))
throw new ArgumentException("The Person specified is not in the database.");
_database.Delete(p);
}
Nota lateral: O argumento contra esse estilo geralmente é que causa pré-validação excessiva em todos os códigos de chamada, mas, na minha experiência, esse geralmente não é o caso. Pense em um cenário em que você está tentando excluir uma Pessoa nula. Como isso aconteceu? De onde veio a Pessoa nula? Se essa é uma interface do usuário, por exemplo, por que a tecla Delete foi manipulada se não há uma seleção atual? Se já foi excluído, já não deveria ter sido removido da exibição? Obviamente, há exceções a isso, mas à medida que o projeto cresce, muitas vezes você agradece a códigos como esse por impedir que bugs penetrem profundamente no sistema.
Adicione validação e código defensivamente. Isso torna o contrato mais flexível , porque agora esse método faz mais do que apenas excluir a pessoa. Alterei o nome do método para refletir isso, mas pode não ser necessário se você for consistente em sua API. Essa abordagem tem seus prós e contras. O ponto principal é que agora você pode chamar a TryDeletePerson
passagem com todo tipo de entrada inválida e nunca se preocupar com exceções. O truque, é claro, é que os usuários do seu código provavelmente chamarão muito esse método, ou isso poderá dificultar a depuração nos casos em que p for nulo. Isso pode ser considerado uma violação leve do Princípio da Responsabilidade Única ; portanto, lembre-se disso se ocorrer uma guerra de chamas.
public void TryDeletePerson(Person p)
{
if(p == null || !_database.Contains(p))
return;
_database.Delete(p);
}
Combine abordagens. Às vezes, você quer um pouco de ambos, em que deseja que os chamadores externos sigam as regras de perto (para forçá-los a codificar responsáveis), mas deseja que seu código privado seja flexível.
///<exception>ArgumentNullException</exception>
///<exception>ArgumentException</exception>
public void DeletePerson(Person p)
{
if(p == null)
throw new ArgumentNullException("p");
if(!_database.Contains(p))
throw new ArgumentException("The Person specified is not in the database.");
TryDeletePerson(p);
}
internal void TryDeletePerson(Person p)
{
if(p == null || !_database.Contains(p))
return;
_database.Delete(p);
}
Na minha experiência, concentrar-se nos contratos que você implicou, em vez de uma regra rígida, funciona melhor. A codificação defensiva parece funcionar melhor nos casos em que é difícil ou difícil para o chamador determinar se uma operação é válida. Contratos estritos parecem funcionar melhor quando você espera que o chamador faça apenas chamadas de método quando realmente faz muito sentido.
É uma questão de convenção, documentação e caso de uso.
Nem todas as funções são iguais. Nem todos os requisitos são iguais. Nem toda validação é igual.
Por exemplo, se seu projeto Java tenta evitar ponteiros nulos sempre que possível (consulte as recomendações de estilo Guava , por exemplo), você ainda valida todos os argumentos de função para garantir que não sejam nulos? Provavelmente não é necessário, mas é provável que você ainda faça isso, para facilitar a localização de bugs. Mas você pode usar uma declaração onde você lançou anteriormente um NullPointerException.
E se o projeto estiver em C ++? A convenção / tradição em C ++ é documentar pré-condições, mas apenas verificá-las (se houver) nas compilações de depuração.
Nos dois casos, você tem uma condição prévia documentada em sua função: nenhum argumento pode ser nulo. Em vez disso, você pode estender o domínio da função para incluir nulos com comportamento definido, por exemplo, "se algum argumento for nulo, gera uma exceção". Obviamente, isso é novamente minha herança C ++ falando aqui - em Java, é comum o suficiente documentar pré-condições dessa maneira.
Mas nem todas as pré-condições podem ser verificadas razoavelmente. Por exemplo, um algoritmo de pesquisa binária tem a pré-condição de que a sequência a ser pesquisada deve ser classificada. Mas verificar se definitivamente é assim é uma operação O (N), fazendo isso em cada chamada meio que derrota o ponto de usar um algoritmo O (log (N)) em primeiro lugar. Se você estiver programando defensivamente, poderá fazer verificações menores (por exemplo, verificando se, para cada partição pesquisada, os valores inicial, intermediário e final são classificados), mas isso não captura todos os erros. Normalmente, você só precisa confiar na condição prévia a ser cumprida.
O único local real em que você precisa de verificações explícitas é nos limites. Entrada externa para o seu projeto? Validar, validar, validar. Uma área cinza é os limites da API. Realmente depende de quanto você deseja confiar no código do cliente, quanto dano a entrada inválida causa e quanta assistência você deseja fornecer na busca de bugs. Qualquer limite de privilégio deve contar como externo, é claro - os syscalls, por exemplo, são executados em um contexto de privilégio elevado e, portanto, devem ter muito cuidado para validar. É claro que qualquer validação desse tipo deve ser interna ao syscall.
fonte
A validação de parâmetro deve ser a preocupação da função que está sendo chamada. A função deve saber o que é considerado entrada válida e o que não é. Os chamadores podem não saber disso, especialmente quando não sabem como a função é implementada internamente. A função deve lidar com qualquer combinação de valores de parâmetros dos chamadores.
Como a função é responsável pela validação de parâmetros, é possível gravar testes de unidade nessa função para garantir que ela se comporte conforme o esperado com valores de parâmetros válidos e inválidos.
fonte
Dentro da própria função. Se a função for usada mais de uma vez, você não deseja verificar o parâmetro para cada chamada de função.
Além disso, se a função for atualizada de maneira a afetar a validação do parâmetro, você deverá procurar todas as ocorrências da validação de chamadas para atualizá-las. Não é adorável :-).
Você pode consultar a Cláusula de Guarda
Atualizar
Veja minha resposta para cada cenário que você forneceu.
quando o tratamento de variável inválida pode variar, é bom validá-la no lado do chamador (por exemplo,
sqrt()
função - em alguns casos, talvez eu queira trabalhar com números complexos, por isso trato a condição no chamador)Responda
A maioria das linguagens de programação suporta números inteiros e reais por padrão, número não complexo, portanto, sua implementação
sqrt
aceita apenas números não negativos. O único caso de umasqrt
função que retorna um número complexo é quando você usa uma linguagem de programação orientada para a matemática, como o MathematicaAlém disso,
sqrt
para a maioria das linguagens de programação já está implementada, portanto, você não pode modificá-la e, se você tentar substituir a implementação (consulte a aplicação de patches de macaco), seus colaboradores ficarão totalmente chocados com o porquê desqrt
aceitar números negativos repentinamente.Se você quiser um, pode envolvê-lo em sua
sqrt
função personalizada , que lida com números negativos e retorna números complexos.quando a condição de verificação é a mesma em todos os chamadores, é melhor verificar dentro da função, para evitar duplicações
Responda
Sim, essa é uma boa prática para evitar a dispersão da validação de parâmetro no seu código.
a validação do parâmetro de entrada no chamador ocorre apenas uma antes da chamada de muitas funções com esse parâmetro. Portanto, a validação de um parâmetro em cada função não é eficaz
Responda
Seria bom se o chamador fosse uma função, você não acha?
Se as funções dentro do chamador são usadas por outro chamador, o que impede você de validar o parâmetro nas funções chamadas pelo chamador?
a solução certa depende do caso particular
Responda
Procure o código de manutenção. Mover a validação de parâmetro garante uma fonte de verdade sobre o que a função pode aceitar ou não.
fonte
Uma função deve indicar suas condições pré e pós.
As pré-condições são as condições que devem ser atendidas pelo chamador antes que ele possa usar corretamente a função e possa (e geralmente inclua) incluir a validade dos parâmetros de entrada.
As pós-condições são as promessas que a função faz aos seus interlocutores.
Quando a validade dos parâmetros de uma função faz parte das pré-condições, é responsabilidade do chamador garantir que esses parâmetros sejam válidos. Mas isso não significa que todo chamador deve verificar explicitamente cada parâmetro antes da chamada. Na maioria dos casos, nenhum teste explícito é necessário porque a lógica interna e as pré-condições do chamador já garantem que os parâmetros sejam válidos.
Como medida de segurança contra erros de programação (bugs), você pode verificar se os parâmetros passados para uma função realmente atendem às pré-condições indicadas. Como esses testes podem ser caros, é uma boa ideia desativá-los para compilações de versões. Se esses testes falharem, o programa deverá ser encerrado, porque provavelmente ocorreu um erro.
Embora, à primeira vista, a verificação no chamador pareça convidar a duplicação de código, na verdade é o contrário. A verificação no chamado resulta na duplicação de código e em muito trabalho desnecessário.
Basta pensar nisso: com que frequência você passa parâmetros por várias camadas de funções, fazendo apenas pequenas alterações em algumas delas ao longo do caminho. Se você aplicar consistentemente o método de check-in-call , cada uma dessas funções intermediárias precisará refazer a verificação para cada um dos parâmetros.
E agora imagine que um desses parâmetros seja uma lista classificada.
Com a verificação no chamador, apenas a primeira função precisaria garantir que a lista seja realmente classificada. Todos os outros sabem que a lista já está classificada (como foi o que declararam em sua pré-condição) e podem transmiti-la sem verificações adicionais.
fonte
Na maioria das vezes, você não pode saber quem, quando e como chamará a função que você escreveu. É melhor assumir o pior: sua função será chamada com parâmetros inválidos. Então você definitivamente deveria cobrir isso.
No entanto, se o idioma que você usa suportar exceções, talvez você não verifique certos erros e certifique-se de que uma exceção seja lançada, mas, neste caso, você deve descrever o caso na documentação (você precisa ter documentação). A exceção fornecerá ao chamador informações suficientes sobre o que aconteceu e também direcionará a atenção para os argumentos inválidos.
fonte