Um método deve validar seus parâmetros? [duplicado]

35

Digamos que você esteja criando um sqrt de método de raiz quadrada. Você prefere validar o parâmetro passado não é um número negativo ou você deixa para o chamador para certificar-se de que o parâmetro passado é válido. Como sua resposta varia se o método / API for para consumo de terceiros ou se for usado apenas para o aplicativo específico em que você está trabalhando

Eu tenho a opinião de que um método deve validar seu parâmetro, no entanto, Pragmatic Programmer na sua seção Design by Contract (capítulo 4) diz que é responsabilidade do chamador passar bons dados (pg 111 e 115) e sugere usar Assertions no método para verificar o mesmo. Eu quero saber o que os outros sentem sobre isso.

    
por Amit Wadhwa 04.04.2011 / 07:01
fonte

10 respostas

29

Em geral, eu desenho minhas APIs da seguinte maneira:
 1. Documente bem os métodos e incentive os chamadores a transmitir dados válidos / válidos.
 2. Valide os parâmetros de qualquer maneira! - lançando exceções quando as pré-condições não são cumpridas.

Eu diria que a validação de parâmetros é necessária na maioria das APIs voltadas para o público. A validação de parâmetros em métodos não públicos não é tão importante - muitas vezes é desejável que a validação ocorra apenas uma vez, no 'ponto de entrada' público - mas se você pode viver com o potencial desempenho atingido, eu gosto de validar parâmetros em todos os lugares, como Isso torna a manutenção e a refatoração de código um pouco mais fáceis.

    
por 04.04.2011 / 07:25
fonte
23

Se você sempre validar os parâmetros, então você está fazendo um trabalho extra que pode não ser necessário.

Pense na situação em que a entrada já foi validada antes da chamada e agora você está revalidando os dados na chamada. OK, uma verificação de validação extra está OK, mas agora estenda essa lógica para todas as funções em seu aplicativo. Toda chamada de função valida os dados, mesmo que tenha sido validada antes (agora várias vezes).

Os dados devem ser validados em UM ponto. É aí que os dados entram no programa (ou subsistema (isso dá alguma margem de manobra, pois a definição do subsistema pode ser flexível)). Se não houver erros de programação, seu código deve funcionar agora (as afirmações de nota para checar códigos ruins são diferentes dos parâmetros de validação).

Se você realmente quiser validar os parâmetros, tenha duas versões da função. Um para validar e outro que não valida. Olhe para std :: vector (operator [] não valida enquanto at () validar).

Então, se eu tivesse que projetar uma função sqrt (), não validaria suas entradas, porque na maioria das situações os dados seriam bons de qualquer maneira e a pequena minoria de situações onde é potencialmente errada o usuário pode fazer uma verificação rápida (como a entrada do usuário, pode estar errado e precisa ser validado antes de usar). A única outra hora em que está errado é para o erro do programador (e seus testes de unidade devem pegá-los).

    
por 04.04.2011 / 07:21
fonte
15

Quanta redundância / robustez deve ser complexa implementar o software? faz uma pergunta relacionada.

Minha resposta é que funções que interagem com o mundo externo (APIs públicas, métodos de UI, leitores de arquivos, etc.) devem validar a entrada e informar o usuário sobre os erros da maneira mais educada e clara possível. No mínimo, a mensagem de erro / código de retorno deve indicar onde ocorreu a entrada incorreta e que tipo de restrição ela violou. Quanto mais específico, melhor.

Por outro lado, funções privadas que lidam apenas com dados gerados internamente ou dados já processados por uma das funções externas devem ter afirmações sobre qualquer uma das pré-condições necessárias para que ele funcione com sucesso. Se um programador escreve código que viola, o programa deve falhar e falhar. Este tipo de bug nunca deve ultrapassar os primeiros estágios de teste.

A motivação aqui é ser o mais agradável possível para os usuários, ao mesmo tempo em que limita as decisões sobre como lidar com entradas ruins no nível mais alto possível. Você não quer descobrir a próxima parte da sua estratégia de tratamento de erros no nível do programa toda vez que escreve uma função de baixo nível.

Então: valide a entrada do usuário, afirme a entrada do programador.

    
por 04.04.2011 / 16:56
fonte
10

Se você tem um sistema de tipos, use isso.

Asserções ajudarão a pegar o mal cedo.

restrições não-nulas são uma coisa razoável.

sqrt (-1) não é um erro em algumas linguagens de programação. Smalltalk com suporte a números complexos apenas retornará i.

    
por 04.04.2011 / 08:08
fonte
7

Acho que depende da aplicação do método, e não da teoria da implementação.

O que quero dizer é: se você está construindo uma biblioteca matemática rápida, mesmo que ela possa ser usada por qualquer pessoa, você não quer ter verificações de tempo de execução, pelo menos não quando construída no modo 'release', porque a velocidade é o critério de julgamento. Você pode implementar verificações em um modo de 'depuração', usando asserções porque deseja que o comportamento seja consistente entre os modos. Claro, você quer documentar esse tipo de comportamento muito bem, então os usuários de sua biblioteca (mesmo que seja você em três meses!) Sabem quais verificações eles devem estar fazendo.

Agora, se você está construindo uma biblioteca de comunicação de rede, você quer adicionar tanta segurança quanto possível, porque 1) ela será alimentada principalmente pela entrada do usuário, portanto, perigo 2) o desempenho bruto será vinculado principalmente pela rede I / O, não pela operação da CPU na maioria dos casos, portanto, adicionar alguma validação nem será notado.

    
por 04.04.2011 / 10:35
fonte
2

Toda função deve verificar sua entrada , mesmo aquelas funções internas que não fazem parte de nenhuma API ou interface pública.

Programadores são humanos, e humanos são notoriamente ineptos em manter restrições relacionadas em sincronia conforme grandes bases de código evoluem - eventualmente, a parte de "entrada de verificação" que acontece antes de "função de chamada" desaparecer em outro lugar ou ficar incompleto, e a função será chamada com entrada incorreta. Quando isso acontecer, seus dois principais objetivos serão:

  • Detectar o problema o mais rápido possível (tempo de compilação sendo a melhor opção)
  • Não quebre nada até que o problema seja corrigido

Para muitas coisas, você pode usar os recursos de idioma ou o sistema de tipos para transportar informações em tempo de compilação sobre quais propriedades foram verificadas. Isso é extremamente rápido (sem penalidade de tempo de execução) e detecta erros em tempo de compilação. A maioria das minhas verificações está nessa categoria, por exemplo.

Se o seu idioma não oferecer suporte à verificação em tempo de compilação para o que você está fazendo (o que, nos idiomas modernos, é bastante raro), adicione uma declaração em tempo de execução.

Somente se a falha do seu código não puder ter conseqüências adversas além de um bug facilmente detectado e inócuo, e você espera que esse código seja chamado com muita frequência, e a verificação não é uma parte natural do código de função de qualquer maneira, você pode deixar as verificações. sqrt provavelmente estaria aqui.

    
por 04.04.2011 / 11:02
fonte
2

Para C / C ++ e outras linguagens que fornecem o pré-processador funcional, você pode validar os parâmetros de entrada somente quando construir para Depuração / Teste e produzir uma compilação de versão não validada.

A biblioteca do Visual C ++ MFC é um dos bons exemplos.

Abaixo está o código de exemplo das amostras públicas do MFC:

void CServerNode::CalcBounding(CDC* pDC, CPoint& ptStart, CSize& sizeMax)
{
    ASSERT(sizeMax.cx >= 0 && sizeMax.cy >= 0);
    ASSERT(ptStart.x >= 0 && ptStart.y >= 0);

    CSize sizeNode;
    CalcNodeSize(pDC, sizeNode);

    ptStart.y += sizeNode.cy + CY_SEPARATOR;
    if (ptStart.y > sizeMax.cy)
        sizeMax.cy = ptStart.y;

    if (ptStart.x + sizeNode.cx > sizeMax.cx)
        sizeMax.cx = ptStart.x + sizeNode.cx;
    ptStart.x += CX_INDENT;
    // add in the kids
    if (!m_bHideChildren)
    {
        POSITION pos = m_listChild.GetHeadPosition();
        while (pos != NULL)
        {
            CServerNode* pNode = (CServerNode*)m_listChild.GetNext(pos);
            pNode->CalcBounding(pDC, ptStart, sizeMax);
        }
    }
    ptStart.x -= CX_INDENT;
}
    
por 04.04.2011 / 16:02
fonte
1

Eu sempre coloco validação perto da origem dos dados recebidos, sejam dados de um banco de dados, dados de um HTTP POST ou dados de um soquete de rede.

As razões para isso são:

  • redução de código
  • eliminação de operações desnecessárias
  • código mais simples (geralmente)

No entanto, sempre haverá exceções para a maioria das regras de programação ou melhores práticas. A chave para reconhecer essas exceções é um pensamento cuidadoso e consideração de cada situação, em vez de uma adesão cega a um conjunto de regras.

    
por 04.04.2011 / 07:32
fonte
0

Eu sempre uso o esquema simples: UI e código simples para chamar métodos (validar parâmetros da interface do usuário) - > Métodos (valida não parâmetros da interface do usuário) - > Funções adicionais (elas não validam nada)

    
por 04.04.2011 / 10:51
fonte
0

Eu acho que cada função deve verificar a validade de seus parâmetros de entrada, e é melhor se ela puder ser verificada em tempo de compilação (com a ajuda do sistema de tipos, se você estiver usando uma linguagem com tipagem estática).

A ideia de usar asserções para garantir que os parâmetros de entrada são válidos parece um pouco estranha para mim - essencialmente, significa que você precisa escrever as mesmas verificações duas vezes - uma vez na função chamador e na segunda vez no próprio método, na forma de asserções. Isso também significa que quando os requisitos nos parâmetros de entrada mudam, você terá que alterar as verificações em todos os lugares nas funções de chamada, não apenas no receptor.

Então, por que não apenas validar os parâmetros no próprio método e levantar uma exceção (ou fazer o que for apropriado) quando uma inconsistência é encontrada?

    
por 04.04.2011 / 16:12
fonte