Por que eu usaria contratos de código

26

Recentemente, deparei com a estrutura da Microsoft para contratos de código.

Eu li um pouco da documentação e me perguntei constantemente: "Por que eu iria querer fazer isso, pois ele não faz e geralmente não pode executar uma análise estática"?

Agora, eu já tenho um tipo de estilo de programação defensivo, com exceções de proteção como esta:

if(var == null) { throw new NullArgumentException(); }

Também estou usando muito o NullObject Pattern e raramente tenho problemas. Adicione testes de unidade a ele e tudo estará pronto.

Eu nunca usei afirmações e nunca senti falta delas. Pelo contrário. Eu realmente odeio código que tenha muitas afirmações sem sentido, o que é apenas um ruído para mim e me distrai do que realmente quero ver. Contratos de código, pelo menos da maneira da Microsoft, são os mesmos - e ainda pior. Eles adicionam muito ruído e complexidade ao código. Em 99%, uma exceção será lançada de qualquer maneira - por isso não me importo se é da afirmação / contrato ou do problema real. Apenas muito, muito poucos casos permanecem nos quais os estados do programa realmente ficam corrompidos.

Então, francamente, qual é o benefício do uso de contratos de código? Existe algum? Se você já usa testes de unidade e código defensivamente, sinto que a introdução de contratos não vale o custo e coloca barulho no código que um mantenedor amaldiçoará ao atualizar esse método, assim como acontece quando não consigo ver o que o código está fazendo devido a afirmações inúteis. Ainda não vi uma boa razão para pagar esse preço.

Falcão
fonte
1
Por que você diz "não faz e nem sempre pode executar uma análise estática"? Eu pensei que esse era um dos pontos deste projeto (em vez de apenas usar Asserts ou lançar exceções defensivas)?
precisa saber é o seguinte
@ Carson63000 Leia a documentação com atenção e verá que o verificador estático emitirá muitos e muitos avisos, os mais desnecessários (os desenvolvedores admitem que sim) e que os contratos mais definidos lançam uma exceção e não fazem mais nada .
Falcon,
@ Falcon: estranhamente, minha experiência é completamente diferente. A análise estática não funciona perfeitamente, mas muito bem, e raramente vejo avisos desnecessários, mesmo quando trabalho no nível 4 (o mais alto).
Arseni Mourzenko
Acho que vou ter que tentar descobrir como se comporta.
Falcon,

Respostas:

42

Você também pode ter perguntado quando a digitação estática é melhor que a digitação dinâmica. Um debate que se arrasta há anos sem fim à vista. Portanto, dê uma olhada em perguntas como estudos de idiomas de forma dinâmica ou estática

Mas o argumento básico seria o potencial de descobrir e corrigir problemas em tempo de compilação que, de outra forma, poderiam ter entrado em produção.

Contratos vs. Guardas

Mesmo com proteções e exceções, você ainda enfrenta o problema de o sistema falhar em executar a tarefa pretendida em algum exemplo. Possivelmente uma instância que será bastante crítica e dispendiosa para falhar.

Contratos vs. testes de unidade

O argumento usual sobre este é que testes provam a presença de bugs, enquanto tipos (contratos) provam a ausência. F.ex. usando um tipo que você sabe que nenhum caminho no programa poderia fornecer entrada inválida, enquanto um teste poderia apenas informar que o caminho coberto fornecia a entrada correta.

Contratos vs. Padrão de Objeto Nulo

Agora isso é pelo menos no mesmo parque. Idiomas como Scala e Haskell tiveram grande sucesso com essa abordagem para eliminar referências nulas inteiramente dos programas. (Mesmo que Scala permita formalmente nulos, a convenção é nunca usá-los)

Se você já utiliza esse padrão para eliminar as ERNs, basicamente removeu a maior fonte de falhas de tempo de execução, basicamente existe da maneira que os contratos permitem que você faça isso.

A diferença pode ser que os contratos têm uma opção para exigir automaticamente todo o seu código para evitar nulo e, assim, forçar você a usar esse padrão em mais locais para passar a compilação.

Além disso, os contratos também oferecem a flexibilidade de segmentar coisas além de nulo. Portanto, se você não vir mais NRE em seus erros, convém usar contratos para estrangular o próximo problema mais comum que você possa ter. Fora por um? Índice fora do intervalo?

Mas...

Tudo isso sendo dito. Concordo que os contratos de ruído sintático (e até mesmo estrutural) adicionados ao código são bastante substanciais e o impacto da análise no tempo de construção não deve ser subestimado. Portanto, se você decidir adicionar contratos ao seu sistema, provavelmente seria prudente fazê-lo com muito cuidado, com um foco limitado em qual classe de bugs se tenta resolver.

John Nilsson
fonte
6
Essa é uma ótima primeira resposta para programadores. Fico feliz em ter sua participação na comunidade.
13

Não sei de onde vem a afirmação de que "ela não realiza e frequentemente não pode executar uma análise estática". A primeira parte da afirmação está claramente errada. O segundo depende do que você quer dizer com "frequentemente". Eu prefiro dizer isso frequentemente realiza análises estáticas e raramente falha. Em aplicativos de negócios comuns, raramente fica muito mais próximo de nunca .

Então, aqui está, o primeiro benefício:

Benefício 1: análise estática

Asserções comuns e verificação de argumentos têm uma desvantagem: elas são adiadas até que o código seja executado. Por outro lado, os contratos de código se manifestam em um nível muito anterior, na etapa de codificação ou durante a compilação do aplicativo. Quanto mais cedo você detectar um erro, menos caro será corrigi-lo.

Benefício 2: tipo de documentação sempre atualizada

Os contratos de código também fornecem um tipo de documentação sempre atualizada. Se o comentário XML do método indicar SetProductPrice(int newPrice)que newPricedeve ser superior ou igual a zero, você pode esperar que a documentação esteja atualizada, mas também pode descobrir que alguém alterou o método para quenewPrice = 0 gerar uma ArgumentOutOfRangeException, mas nunca alterou a documentação correspondente. Dada a correlação entre os contratos de código e o próprio código, você não tem o problema de documentação fora de sincronia.

O tipo de documentação fornecida pelos contratos de código também é preciosa de uma maneira que, frequentemente, os comentários XML não explicam bem os valores aceitáveis. Quantas vezes eu estava me perguntando se nullou string.Emptyou\r\n é um valor autorizado para um método, e os comentários XML ficaram em silêncio sobre isso!

Em conclusão, sem contratos de código, muitos pedaços de código são assim:

Aceitarei alguns valores, mas não outros, mas você terá que adivinhar ou ler a documentação, se houver. Na verdade, não leia a documentação: está desatualizada. Basta percorrer todos os valores e você verá aqueles que me fazem lançar exceções. Você também precisa adivinhar a faixa de valores que pode ser retornada, porque, mesmo que eu lhe conte um pouco mais sobre isso, pode não ser verdade, dadas as centenas de alterações feitas nos últimos anos.

Com contratos de código, torna-se:

O argumento do título pode ser uma sequência não nula com um comprimento de 0..500. O número inteiro a seguir é um valor positivo, que pode ser zero apenas quando a sequência está vazia. Finalmente, retornarei um IDefinitionobjeto, nunca nulo.

Benefício 3: contratos de interfaces

Um terceiro benefício é que os contratos de código capacitam interfaces. Digamos que você tenha algo como:

public interface ICommittable
{
    public ICollection<AtomicChange> PendingChanges { get; }

    public void CommitChanges();

    ...
}

Como você, usando apenas declarações e exceções, garante que só CommitChangespode ser chamado quando PendingChangesnão está vazio? Como você garantiria que PendingChangesnuncanull ?

Benefício 4: aplicar os resultados de um método

Finalmente, o quarto benefício é poder obter Contract.Ensureos resultados. E se, ao escrever um método que retorne um número inteiro, eu quero ter certeza de que o valor nunca é inferior ou igual a zero? Incluindo cinco anos depois, depois de sofrer muitas mudanças de muitos desenvolvedores? Assim que um método tiver vários pontos de retorno, isso Assertse tornará um pesadelo de manutenção.


Considere os contratos de código não apenas como um meio de correção do seu código, mas uma maneira mais rigorosa de escrever código.De maneira semelhante, uma pessoa que usou linguagens exclusivamente dinâmicas pode perguntar por que você aplicaria tipos no nível da linguagem, enquanto você pode fazer o mesmo em afirmações quando necessário. Você pode, mas a digitação estática é mais fácil de usar, menos propensa a erros em comparação com várias asserções e com a documentação própria.

A diferença entre tipagem dinâmica e tipagem estática é extremamente próxima da diferença entre programação comum e programação por contrato.

MainMa
fonte
3

Sei que isso é um pouco tarde para a pergunta, mas há outro benefício no Code Contracts que vale mencionar

Os contratos são verificados fora do método

Quando temos condições de proteção dentro de nosso método, é nesse local que a verificação acontece e onde os erros são relatados. Talvez seja necessário investigar o rastreamento da pilha para descobrir onde está a origem real do erro.

Os contratos de código estão localizados dentro do método, mas as condições prévias são verificadas fora do método quando algo tenta chamar esse método. Os contratos se tornam parte do método e determina se ele pode ser chamado ou não. Isso significa que temos um erro relatado muito mais próximo da fonte real do problema.

Se você possui um método protegido por contrato, chamado para muitos lugares, isso pode ser um benefício real.

Alex White
fonte
2

Duas coisas realmente:

  1. Com o suporte de ferramentas, o VS pode diferenciar os dados do contrato da inteligência, de modo a fazer parte da ajuda completa automática.
  2. Quando uma subclasse substitui um método, você perde todas as suas verificações (que ainda devem ser válidas se você seguir o LSP) com contratos que seguem suas classes filho automaticamente.
pedregoso
fonte