Por que existe um suporte tão limitado ao Design by Contract na maioria das linguagens de programação modernas?


40

Descobri recentemente o Design by Contract (DbC) e considero uma maneira extremamente interessante de escrever código. Entre outras coisas, parece oferecer:

  • Melhor documentação. Como o contrato é a documentação, é impossível ficar desatualizado. Além disso, como o contrato especifica exatamente o que uma rotina faz, ajuda a dar suporte à reutilização.
  • Depuração mais simples. Como a execução do programa para no momento em que um contrato falha, os erros não podem se propagar e a asserção específica violada será presumivelmente destacada. Isso oferece suporte durante o desenvolvimento e a manutenção.
  • Melhor análise estática. DbC é basicamente apenas uma implementação da lógica Hoare, e os mesmos princípios devem ser aplicados.

Os custos, em comparação, parecem ser bastante pequenos:

  • Digitação extra com os dedos. Desde que os contratos têm que ser explicitados.
  • É necessário algum treinamento para se sentir confortável com a redação de contratos.

Agora, familiarizando-me principalmente com o Python, percebo que é de fato possível escrever pré-condições (apenas lançando exceções para entrada inadequada) e é ainda possível usar asserções para testar novamente certas pós-condições. Mas não é possível simular certos recursos, como 'antigo' ou 'resultado', sem alguma mágica extra que seria considerada não-pitonica. (Além disso, existem algumas bibliotecas que oferecem suporte, mas, no final das contas, entendi que seria errado usá-las, como a maioria dos desenvolvedores não.) Suponho que seja um problema semelhante para todos os outros idiomas (exceto, é claro) Eiffel).

Minha intuição me diz que a falta de apoio deve ser resultado de algum tipo de rejeição da prática, mas a pesquisa on-line não foi proveitosa. Gostaria de saber se alguém pode esclarecer por que a maioria das linguagens modernas parece oferecer tão pouco apoio. O DbC é defeituoso ou muito caro? Ou é apenas obsoleto devido à programação extrema e outras metodologias?


Parece uma maneira excessivamente complicada de fazer programação orientada a teste, sem o benefício de também testar seu programa.
Dan

3
@ Dan, não realmente, penso nisso mais como uma extensão do sistema de tipos. por exemplo, uma função não apenas tomar um argumento inteiro, leva um inteiro que está contratualmente obrigado a ser maior que zero
Carson63000

4
Os contratos de código @Dan reduzem significativamente o número de testes que você precisa fazer.
Rei Miyasaka

24
@ Dan, eu prefiro dizer que o TDD é o contrato do pobre, não o contrário.
SK-logic

Em linguagem dinâmica, você pode "decorar" seus objetos com contratos com base em um sinalizador opcional. Eu tenho um exemplo de implementação que usa sinalizadores ambientais para, opcionalmente, corrigir os objetos existentes nos contratos. Sim, o suporte não é nativo, mas é fácil de adicionar. O mesmo se aplica aos chicotes de teste, eles não são nativos, mas são fáceis de adicionar / gravar.
Raynos

Respostas:


9

Indiscutivelmente, eles são suportados em praticamente todas as linguagens de programação.

O que você precisa são "afirmações".

Estes são facilmente codificados como "if":

if (!assertion) then AssertionFailure();

Com isso, você pode escrever contratos colocando essas afirmações no topo do seu código para restrições de entrada; aqueles nos pontos de retorno são restrições de saída. Você pode até adicionar invariantes em todo o seu código (embora não faça parte do "design by contract").

Então, eu argumento que eles não são comuns porque os programadores são muito preguiçosos para codificá-los, não porque você não pode fazê-lo.

Você pode torná-los um pouco mais eficientes na maioria dos idiomas, definindo uma constante "booleana" em tempo de compilação e revisando um pouco as instruções:

if (checking & !Assertion) then AssertionFailure();

Se você não gosta da sintaxe, pode recorrer a várias técnicas de abstração de idioma, como macros.

Algumas linguagens modernas fornecem uma boa sintaxe para isso, e é isso que acho que você quer dizer com "suporte à linguagem moderna". Isso é apoio, mas é bem fino.

O que a maioria das linguagens modernas ainda não fornece são asserções "temporais" (sobre estados arbitrários anteriores ou seguintes [operador temporal "eventualmente"]), que você precisa para escrever contratos realmente interessantes. As declarações IF não ajudarão você aqui.


O problema que considero ter apenas acesso a afirmações é que não há uma maneira eficaz de verificar pós-condições nos comandos, pois muitas vezes você precisa comparar o estado de pós-condição com o estado de pré-condição (Eiffel chama isso de 'antigo' e passa automaticamente para a rotina de pós-condição) .) No Python, essa funcionalidade pode ser recriada trivialmente usando decoradores, mas fica curta quando chega a hora de desativar as asserções.
Ceasar Bautista

Quanto do estado anterior Eiffel realmente salva? Como razoavelmente não pode saber qual parte você pode acessar / modificar sem resolver o problema de parada (analisando sua função), ela precisa salvar o estado completo da máquina ou, como huerístico, apenas uma parte muito superficial. Eu suspeito do último; e eles podem "simular" por atribuições escalares simples antes da pré-condição. Eu ficaria feliz em saber que Eiffel faz o contrário.
Ira Baxter

7
... apenas verifiquei como Eiffel funciona. "old <exp>" é o valor de <exp> na entrada da função, portanto, ele está fazendo cópias rasas na entrada da função, como eu esperava. Você pode fazê-los também. Concordo que fazer com que o compilador implemente a sintaxe para pre / post / old é mais conveniente do que fazer tudo isso manualmente, mas o ponto é que podemos fazer isso manualmente e realmente não é difícil. Estamos de volta aos programadores preguiçosos.
Ira Baxter

@IraBaxter No. O código se tornará mais simples se você puder separar o contrato da lógica real. Além disso, se o compilador pode dizer contrato, eo código à parte, ele pode reduzir a duplicação de um monte . Por exemplo, em D, você pode declarar um contrato em uma interface ou superclasse e as asserções serão aplicadas a todas as classes de implementação / extensão, independentemente do código em suas funções. Por exemplo, com python ou Java, você precisa invocar todo o supermétodo e, possivelmente, jogar fora os resultados se desejar apenas verificar os contratos sem duplicação. Isso realmente ajuda a implementar código limpo compatível com LSP.
marstato

@ marstato: Eu já concordei que o suporte no idioma é uma coisa boa.
Ira Baxter

15

Como você diz, o Design by Contract é um recurso da Eiffel, que há muito tempo é uma daquelas linguagens de programação que são bem respeitadas na comunidade, mas que nunca foram usadas.

O DbC não está em nenhuma das linguagens mais populares porque é relativamente recente que a comunidade de programação convencional aceitou que adicionar restrições / expectativas ao seu código é algo "razoável" a ser esperado dos programadores. Agora é comum os programadores entenderem o quão valioso é o teste de unidade, e isso fez com que os programadores aceitassem mais colocar código para validar seus argumentos e obter benefícios. Mas há uma década, provavelmente a maioria dos programadores diria "isso é apenas trabalho extra para coisas que você sabe que sempre vai ficar bem".

Eu acho que se você fosse ao desenvolvedor médio hoje e falasse sobre pós-condições, eles acenariam entusiasticamente e diriam "OK, isso é como um teste de unidade". E se você falar sobre pré-condições, eles diriam "OK, isso é como validação de parâmetro, o que nem sempre fazemos, mas, você sabe, acho que está tudo bem ..." E então, se você falar sobre invariantes , eles começavam a dizer "Nossa, quanta sobrecarga é essa? Quantos bugs mais vamos capturar?" etc.

Então, acho que ainda há um longo caminho a percorrer antes que o DbC seja amplamente adotado.


OTOH, os principais programadores estavam acostumados a escrever as afirmações há bastante tempo. A falta de um pré-processador utilizável nas linguagens mainstream mais modernas tornou essa prática agradável ineficiente, mas ainda é comum para C e C ++. Ele está voltando agora com os Contratos de Código da Microsoft (com base no AFAIK, uma reescrita de bytecode para as compilações da versão).
SK-logic

8

Minha intuição me diz que a falta de apoio deve ser resultado de algum tipo de rejeição da prática, ...

Falso.

É uma prática de design . Ele pode ser incorporado explicitamente no código (estilo Eiffel) ou implicitamente no código (na maioria dos idiomas) ou em testes de unidade. A prática de design existe e funciona bem. O suporte ao idioma está em todo o mapa. Está, no entanto, presente em muitos idiomas na estrutura de teste da unidade.

Gostaria de saber se alguém pode esclarecer por que a maioria das linguagens modernas parece oferecer tão pouco apoio. O DbC é defeituoso ou muito caro?

É caro. E. Mais importante, existem algumas coisas que não podem ser comprovadas em um determinado idioma. A terminação de loop, por exemplo, não pode ser comprovada em uma linguagem de programação, requer uma capacidade de prova de "ordem superior". Portanto, alguns tipos de contratos são tecnicamente inexprimíveis.

Ou é apenas obsoleto devido à programação extrema e outras metodologias?

Não.

Usamos principalmente testes de unidade para demonstrar que o DbC é cumprido.

Para Python, como você observou, o DbC vai em vários lugares.

  1. A docstring e os resultados do teste de docstring.

  2. Asserções para validar entradas e saídas.

  3. Testes unitários.

Mais distante.

Você pode adotar ferramentas alfabetizadas de estilo de programação para escrever um documento que inclua suas informações de DbC e que gere scripts limpos de teste de unidade Python plus. A abordagem de programação alfabética permite que você escreva uma boa peça de literatura que inclua os contratos e a fonte completa.


Você pode provar casos triviais de terminação de loop, como iteração em uma sequência finita fixa. É um loop generalizado que não pode ser mostrado para terminar trivialmente (já que ele poderia estar procurando soluções para conjecturas matemáticas "interessantes"); essa é toda a essência do problema da parada.
Donal Fellows

+1. Eu acho que você é o único que cobriu o ponto mais crítico - there are some things which cannot be proven. A verificação formal pode ser ótima, mas nem tudo é verificável! Portanto, esse recurso realmente restringe o que a linguagem de programação pode realmente fazer!
Dipan Mehta 16/01/12

@DonalFellows: Como o caso geral não pode ser provado, é difícil incorporar vários recursos que são (a) caros e (b) conhecidos por estarem incompletos. Meu argumento nesta resposta é que é mais fácil evitar todos esses recursos e evitar definir falsas expectativas de provas formais de correção em geral, quando há limitações. Como exercício de design (fora do idioma), muitas técnicas de prova podem (e devem) ser usadas.
S.Lott

Não é nada caro em uma linguagem como C ++, na qual a verificação de contrato é compilada na compilação do release. E o uso do DBC tende a levar ao lançamento de um código de construção mais leve, porque você realiza menos verificações de tempo de execução para que o programa esteja em um estado legal. Eu perdi a conta do número de bases de código que vi, onde várias funções verificam um estado ilegal e retornam falso, quando NUNCA deveria estar nesse estado em uma versão devidamente testada.
Kaitain 29/04

6

Apenas adivinhando. Talvez parte da razão de não ser tão popular seja porque "Design by Contract" é marca registrada da Eiffel.


3

Uma hipótese é que, para um programa complexo suficientemente grande, especialmente aqueles com um objetivo em movimento, a massa de contratos em si pode se tornar tão problemática e difícil de depurar, quanto mais do que apenas o código do programa. Como em qualquer padrão, pode haver um uso após retornos decrescentes, bem como vantagens claras quando usado de maneira mais direcionada.

Outra conclusão possível é que a popularidade das "linguagens gerenciadas" é a prova atual do suporte a design por contrato para os recursos gerenciados selecionados (matriz limitada por contrato, etc.)


> a massa de contratos em si pode se tornar tão complicada e difícil de depurar, ou mais ainda, do que apenas o código do programa que eu nunca vi isso.
Kaitain 29/04

2

A razão pela qual a maioria dos idiomas tradicionais não possui recursos de DbC no idioma é a relação custo-benefício da implementação, que é alta para o implementador de idioma.

um lado disso já foi examinado nas outras respostas, testes de unidade e outros mecanismos de tempo de execução (ou mesmo alguns mecanismos de tempo de compilação com meta-programação de modelos) já podem lhe dar grande parte da bondade do DbC. Portanto, embora haja um benefício, provavelmente é visto como bastante modesto.

O outro lado é o custo, o ajuste retroativo do DbC em um idioma existente provavelmente é uma mudança muito grande e muito complexa para inicializar. É difícil introduzir nova sintaxe em um idioma sem quebrar o código antigo. Atualizar sua biblioteca padrão existente para usar uma alteração tão abrangente seria caro. Portanto, podemos concluir que a implementação de recursos DbC em um idioma existente tem um alto custo.

Eu também observaria que conceitos que são basicamente contratos para modelos e, portanto, um pouco relacionados ao DbC, foram retirados do padrão C ++ mais recente, pois mesmo após anos de trabalho neles, foi estimado que eles ainda precisavam de anos de trabalho. Esse tipo de mudança grande, ampla e abrangente nos idiomas é muito difícil de ser implementada.


2

O DbC seria usado mais amplamente se os contratos pudessem ser verificados em tempo de compilação, para que não fosse possível executar um programa que violasse qualquer contrato.

Sem o suporte do compilador, "DbC" é apenas outro nome para "verificar invariantes / suposições e lançar uma exceção se violada".


Isso não ocorre com o problema da parada?
Ceasar Bautista

@ Ceasar Depende. Algumas suposições podem ser verificadas, outras não. Por exemplo, existem sistemas de tipos que permitem evitar passar uma lista vazia como argumento ou retornar uma.
Ingo

Ponto interessante (+1), embora Bertrand Meyer, em sua parte de "Masterminds of programming", também mencionasse seu sistema de criação aleatória de classes e solicitando a verificação de violação dos contratos. Portanto, é uma abordagem tempo de compilação mista / run-time, mas eu duvido que esta técnica funciona em todas as situações
Maksee

Isso é verdade até certo ponto, embora deva ser uma falha catastrófica e não uma exceção (veja abaixo). A principal vantagem do DBC é a metodologia, que na verdade leva a programas mais bem projetados, e a garantia de que qualquer método DEVE estar em um estado legal na entrada, o que simplifica grande parte da lógica interna. Geralmente, você não deve lançar exceções quando um contrato for violado. Exceções devem ser usadas onde o programa pode legalmente estar no estado ~ X, e o código do cliente deve lidar com isso. Um contrato dirá que ~ X é simplesmente ilegal.
Kaitain 29/04

1

Eu tenho uma explicação simples, a maioria das pessoas (incluindo programadores) não deseja trabalho extra, a menos que ache necessário. Programação aviônica em que a segurança é considerada muito importante, eu não vi a maioria dos projetos sem ela.

Mas se você está pensando em programação para sites, computadores ou dispositivos móveis - falhas e comportamentos inesperados às vezes não são considerados ruins e os programadores apenas evitam trabalho extra ao relatar erros e depois corrigi-los é considerado adequado o suficiente.

Esta é provavelmente a razão pela qual acho que Ada nunca foi escolhido fora do setor de programação de aviação, porque é preciso mais trabalho de codificação, embora Ada seja uma linguagem incrível e, se você deseja criar um sistema confiável, é a melhor linguagem para o trabalho (exceto SPARK, proprietário) idioma baseado em Ada).

O projeto de bibliotecas de contrato para C # foi experimental pela Microsoft e é muito útil para a criação de software confiável, mas nunca ganhou impulso no mercado, caso contrário, você já os teria visto como parte da linguagem C # principal.

As asserções não são iguais ao suporte totalmente funcional para condições pré / pós e invariáveis. Embora ele possa tentar emulá-los, mas uma linguagem / compilador com suporte adequado faz uma análise da 'árvore de sintaxe abstrata' e verifica erros de lógica que simplesmente não podem ser feitas.

Edit: Eu fiz uma pesquisa e as discussões relacionadas a seguir podem ser úteis: https://stackoverflow.com/questions/4065001/are-there-any-provable-real-world-languages-scala


-2

Principalmente os motivos são os seguintes:

  1. Está disponível apenas em idiomas que não são populares
  2. É desnecessário, pois o mesmo material pode ser feito de maneira diferente nas linguagens de programação existentes por qualquer pessoa que realmente queira fazê-lo
  3. É difícil de entender e usar - requer conhecimento especializado para fazê-lo corretamente, portanto, há um pequeno número de pessoas fazendo isso
  4. requer grandes quantidades de código para isso - os programadores preferem minimizar a quantidade de caracteres que escrevem - se for preciso um longo pedaço de código, algo deve estar errado com ele
  5. as vantagens não existem - ele simplesmente não consegue encontrar bugs suficientes para fazer valer a pena

11
Sua resposta não está bem argumentada. Você está simplesmente afirmando que não há vantagens, que exige grandes quantidades de código e que é desnecessário, pois isso pode ser feito com os idiomas existentes (o OP abordou especificamente esse problema!).
Andres F.

Eu não estou pensando em afirmações etc como um substituto para isso. Isso não é maneira correta de fazê-lo em línguas existentes (isto é, que ainda não foi abordado).
tp1

@ tp1, se os programadores realmente quisessem minimizar a digitação, nunca cairiam em algo tão detalhado e eloqüente quanto o Java. E sim, a própria programação requer "conhecimento especializado" "para fazê-lo corretamente". Aqueles que não possuem esse conhecimento simplesmente não devem ter permissão para codificar.
SK-logic

@ Sk-logic: Bem, parece que metade do mundo está fazendo OO errado, simplesmente porque eles não querem escrever funções de encaminhamento de funções membro para funções membro dos membros de dados. É um grande problema na minha experiência. Isso é causado diretamente pela minimização do número de caracteres a serem gravados.
tp1

@ tp1, se as pessoas realmente quisessem minimizar a digitação, elas nem tocariam no OO. OOP é naturalmente eloquente, mesmo em suas melhores implementações, como o Smalltalk. Eu não diria que é uma propriedade ruim, a eloquência às vezes ajuda.
SK-logic
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.