código contratos / afirma: o que com verificações duplicadas?


10

Sou um grande fã de escrever declarações, contratos ou qualquer outro tipo de cheque disponível no idioma que estou usando. Uma coisa que me incomoda um pouco é que não tenho certeza de qual é a prática comum para lidar com verificações duplicadas.

Exemplo de situação: primeiro escrevo a seguinte função

void DoSomething( object obj )
{
  Contract.Requires<ArgumentNullException>( obj != null );
  //code using obj
}

algumas horas depois, escrevo outra função que chama a primeira. Como tudo ainda está fresco na memória, decido não duplicar o contrato, pois sei que já DoSomethingverificaremos um objeto nulo:

void DoSomethingElse( object obj )
{
  //no Requires here: DoSomething will do that already
  DoSomething( obj );
  //code using obj
}

O problema óbvio: DoSomethingElseagora depende DoSomethingpara verificar se obj não é nulo. Portanto, deve DoSomethingsempre decidir não verificar mais, ou se eu decidir usar outra função, o obj pode não ser mais verificado. O que me leva a escrever essa implementação, afinal:

void DoSomethingElse( object obj )
{
  Contract.Requires<ArgumentNullException>( obj != null );
  DoSomething( obj );
  //code using obj
}

Sempre seguro, não se preocupe, exceto que, se a situação aumentar, o mesmo objeto poderá ser verificado várias vezes e é uma forma de duplicação e todos sabemos que isso não é tão bom.

Qual é a prática mais comum para situações como essas?


3
ArgumentBullException? Esse é um novo :)
um CVn

lol @ minhas habilidades de digitação ... eu vou editá-lo.
Stijn

Respostas:


13

Pessoalmente, eu verificaria nulo em qualquer função que falhará se receber um nulo, e não em qualquer função que não.

Portanto, no seu exemplo acima, se doSomethingElse () não precisar desreferenciar obj, então eu não verificarei obj para null aqui.

Se DoSomething () desreferenciar obj, deverá procurar nulo.

Se ambas as funções a desreferenciarem, elas deverão verificar. Portanto, se as desreferências DoSomethingElse forem objetivas, ele deve procurar nulo, mas o DoSomething também deve procurar nulo, pois pode ser chamado de outro caminho.

Dessa forma, você pode deixar o código razoavelmente limpo e ainda garantir que as verificações estejam no local correto.


11
Eu concordo plenamente. As pré-condições de cada método devem ser independentes. Imagine que você reescreva de DoSomething()forma que a pré-condição não seja mais necessária (improvável nesse caso específico, mas possa ocorrer em uma situação diferente) e remova a verificação de pré-condição. Agora, algum método aparentemente totalmente não-relacionado está quebrado por causa da falta de pré-condição. Vou usar um pouco de duplicação de código para ter clareza sobre falhas estranhas como essa de um desejo de salvar algumas linhas de código, a qualquer dia.
um CVn

2

Ótimo! Vejo que você descobriu o Code Contracts for .NET. Os contratos de código vão muito além das afirmações médias, das quais o verificador estático é o melhor exemplo. Talvez isso não esteja disponível para você se você não tiver o Visual Studio Premium ou superior instalado, mas é importante entender a intenção por trás disso, se você usará os Contratos de Código.

Quando você aplica um contrato a uma função, literalmente é um contrato . Essa função garante um comportamento de acordo com o contrato e é garantida para ser usada apenas conforme definido pelo contrato.

No seu exemplo, a DoSomethingElse()função não cumpre o contrato especificado por DoSomething(), pois nulo pode ser passado e o verificador estático indicará esse problema. A maneira de resolver isso é adicionando o mesmo contrato a DoSomethingElse().

Agora, isso significa que haverá duplicação, mas essa duplicação é necessária quando você optar por expor a funcionalidade em duas funções. Essas funções, embora privadas, também podem ser chamadas de lugares diferentes da sua classe; portanto, a única maneira de garantir que, a partir de qualquer chamada, o argumento nunca seja nulo, é duplicar os contratos.

Isso deve fazer você reconsiderar por que você dividiu o comportamento em duas funções em primeiro lugar. Sempre foi minha opinião ( contrária à crença popular ) que você não deve dividir funções que são chamadas apenas de um lugar . Ao expor o encapsulamento aplicando os contratos, isso se torna ainda mais evidente. Parece que encontrei uma argumentação extra para minha causa! Obrigado! :)


em relação ao seu último parágrafo: no código real, ambas as funções eram membros de duas classes diferentes, e é por isso que são divididas. Além disso, estive na situação a seguir tantas vezes: escreva uma função longa, decida não dividi-la. Mais tarde, descubra que parte da lógica está duplicada em outro lugar, portanto, divida-a assim mesmo. Ou um ano depois, leia-o novamente e ache-o ilegível, então divida-o assim mesmo. Ou ao depurar: funções divididas = menos batidas na tecla F10. Há mais razões, então, pessoalmente, prefiro dividir, mesmo que isso signifique que às vezes seja muito extremo.
Stijn

(1) "Mais tarde, descubra que parte da lógica está duplicada em outro lugar" . É por isso que acho mais importante sempre "desenvolver uma API" do que simplesmente dividir funções. Pense constantemente na reutilização, não apenas na classe atual. (2) "Ou um ano depois, leia-o novamente e o considere ilegível" Como as funções têm nomes, isso é melhor? Você tem ainda mais legibilidade se usar o que um comentarista no meu blog descreveu como "parágrafos de código". (3) "funções divididas = menos batidas na tecla F10" ... não vejo o porquê.
Steven Jeuris

(1) concordar (2) legibilidade é a preferência pessoal, de modo que isso não é realmente algo discutido para mim. (3) passar por uma função de 20 linhas requer acertar F10 20 vezes. Passar por uma função que possui 10 dessas linhas em uma função dividida exige a opção de ter que pressionar F10 apenas 11 vezes. Sim, no primeiro caso, posso colocar pontos de interrupção ou selecionar 'pular para o cursor', mas isso ainda é mais um esforço do que o segundo caso.
9111 stjn

@stijn: (2) concordou; p (3) obrigado por esclarecer!
Steven Jeuris
Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.