¿Por qué hay un soporte tan limitado para Design by Contract en la mayoría de los lenguajes de programación modernos?

40

Recientemente descubrí Design by Contract (DbC) y me parece una forma extremadamente interesante de escribir código. Entre otras cosas, parece ofrecer:

  • Mejor documentación Como el contrato es la documentación, es imposible que uno esté desactualizado. Además, debido a que el contrato especifica exactamente lo que hace una rutina, ayuda a admitir la reutilización.
  • Depuración más simple. Dado que la ejecución del programa se detiene en el momento en que falla un contrato, los errores no pueden propagarse y la afirmación específica violada se resaltará presumiblemente. Esto ofrece soporte durante el desarrollo y durante el mantenimiento.
  • Mejor análisis estático. DbC es básicamente una implementación de la lógica de Hoare, y deberían aplicarse los mismos principios.

Los costos, en comparación, parecen ser bastante pequeños:

  • Mecanografía extra con los dedos. Dado que los contratos tienen que ser explicados.
  • Toma cierta cantidad de entrenamiento para sentirse cómodo con la redacción de contratos.

Ahora, estando familiarizado con Python principalmente, me doy cuenta de que de hecho es posible escribir condiciones previas (simplemente lanzando excepciones para entradas inapropiadas) e incluso es posible usar aserciones para probar nuevamente ciertas condiciones posteriores. Pero no es posible simular ciertas características como 'viejo' o 'resultado' sin un poco de magia adicional que, en última instancia, se consideraría no Pythonic. (Además, hay algunas bibliotecas que ofrecen soporte, pero en última instancia obtengo la sensación de que sería un error usarlas, ya que la mayoría de los desarrolladores no lo hacen). Supongo que es un problema similar para todos los demás idiomas (excepto, por supuesto, , Eiffel).

Mi intuición me dice que la falta de apoyo debe ser el resultado de algún tipo de rechazo a la práctica, pero la búsqueda en línea no ha sido fructífera. Me pregunto si alguien puede aclarar por qué la mayoría de los idiomas modernos parecen ofrecer tan poco soporte. ¿La DbC es defectuosa o demasiado cara? ¿O es simplemente obsoleto debido a la programación extrema y otras metodologías?

Ceasar Bautista
fuente
Suena como una forma demasiado complicada de hacer una programación basada en pruebas, sin el beneficio de probar también su programa.
Dan
3
@Dan, en realidad no, lo considero más como una extensión del sistema de tipos. por ejemplo, una función no solo toma un argumento entero, sino que toma un número entero que está obligado por contrato a ser mayor que cero
Carson63000
44
Los contratos de código @Dan reducen significativamente la cantidad de pruebas que debe realizar.
Rei Miyasaka
24
@ Dan, prefiero decir que TDD son los contratos de los pobres, no al revés.
SK-logic
En lenguaje dinámico, puede "decorar" sus objetos con contratos basados ​​en una bandera opcional. Tengo una implementación de ejemplo que usa indicadores ambientales para opcionalmente parchear objetos existentes con los contratos. Sí, el soporte no es nativo, pero es fácil de agregar. Lo mismo se aplica a los arneses de prueba, no son nativos pero son fáciles de agregar / escribir.
Raynos

Respuestas:

9

Podría decirse que son compatibles en prácticamente todos los lenguajes de programación.

Lo que necesitas son "afirmaciones".

Estos se codifican fácilmente como declaraciones "if":

if (!assertion) then AssertionFailure();

Con esto, puede escribir contratos colocando tales afirmaciones en la parte superior de su código para restricciones de entrada; aquellos en los puntos de retorno son restricciones de salida. Incluso puede agregar invariantes a todo su código (aunque en realidad no son parte del "diseño por contrato").

Así que sostengo que no están muy extendidos porque los programadores son demasiado vagos para codificarlos, no porque no puedas hacerlo.

Puede hacer que estos sean un poco más eficientes en la mayoría de los idiomas definiendo una "verificación" constante booleana en tiempo de compilación y revisando un poco las declaraciones:

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

Si no le gusta la sintaxis, puede recurrir a varias técnicas de abstracción del lenguaje, como las macros.

Algunos idiomas modernos le dan una buena sintaxis para esto, y eso es lo que creo que quiere decir con "soporte de idiomas modernos". Eso es soporte, pero es bastante delgado.

Lo que la mayoría de los lenguajes modernos no le dan son afirmaciones "temporales" (sobre estados arbitrarios anteriores o siguientes [operador temporal "eventualmente"], que necesita si desea escribir contratos realmente interesantes. Las declaraciones IF no ayudarán tu aquí.

Ira Baxter
fuente
El problema que encuentro con solo tener acceso a las afirmaciones es que no hay una forma efectiva de verificar las condiciones posteriores en los comandos, ya que a menudo es necesario comparar el estado posterior a la condición previa (Eiffel llama a esto 'viejo' y lo pasa automáticamente a la rutina posterior a la condición .) En Python, esta funcionalidad podría recrearse trivialmente usando decoradores, pero se queda corta cuando llega el momento de desactivar las afirmaciones.
Ceasar Bautista
¿Cuánto del estado anterior ahorra realmente Eiffel? Como razonablemente no puede saber a qué parte puede acceder / modificar sin resolver el problema de detención (analizando su función), tiene que guardar el estado completo de la máquina o, como característica huérgica, solo una parte muy superficial. Sospecho lo último; y estos pueden "simularse" mediante asignaciones escalares simples antes de la precondición. Me encantaría saber que Eiffel hace lo contrario.
Ira Baxter
77
... acabo de comprobar cómo funciona Eiffel. "old <exp>" es el valor de <exp> en la entrada a la función, por lo que está haciendo copias superficiales en la entrada de la función como esperaba. Tú también puedes hacerlos. Estoy de acuerdo en que el compilador implemente la sintaxis para pre / post / old es más conveniente que hacer todo esto a mano, pero el punto es que se puede hacer esto a mano y realmente no es difícil. Hemos vuelto a los programadores perezosos.
Ira Baxter
@IraBaxter No. El código se vuelve más simple si puede separar el contrato de la lógica real. Además, si el compilador puede distinguir el contrato y el código, puede reducir la duplicación en gran medida . Por ejemplo, en D puede declarar un contrato en una interfaz o superclase y las aserciones se aplicarán a todas las clases de implementación / extensión, independientemente del código en sus funciones. Por ejemplo, con Python o Java, debe invocar todo el supermétodo y posiblemente descartar los resultados si solo desea que se verifiquen los contratos sin duplicación. Esto realmente ayuda a implementar un código limpio que cumpla con LSP.
marstato
@marstato: ya estuve de acuerdo en que el apoyo en el idioma es algo bueno.
Ira Baxter
15

Como usted dice, Design by Contract es una característica de Eiffel, que durante mucho tiempo ha sido uno de esos lenguajes de programación que es muy respetado en la comunidad, pero que nunca ha tenido éxito.

DbC no se encuentra en ninguno de los lenguajes más populares porque es relativamente reciente que la comunidad de programación principal ha aceptado que agregar restricciones / expectativas a su código es algo "razonable" que los programadores esperan. Ahora es común que los programadores comprendan cuán valiosas son las pruebas unitarias, y eso se tradujo en que los programadores acepten más poner código para validar sus argumentos y ver los beneficios. Pero hace una década, probablemente la mayoría de los programadores dirían "eso es solo trabajo extra para cosas que sabes que siempre estarán bien".

Creo que si tuviera que acudir al desarrollador promedio hoy y hablar sobre las condiciones posteriores, ellos asentirían con entusiasmo y dirían "OK, eso es como una prueba unitaria". Y si habla de condiciones previas, dirían "OK, eso es como la validación de parámetros, que no siempre hacemos, pero, ya sabes, supongo que está bien ..." Y luego, si hablas de invariantes , empezarían a decir "Caramba, ¿cuánto cuesta esto? ¿Cuántos errores más vamos a atrapar?" etc.

Así que creo que todavía queda un largo camino por recorrer antes de que DbC sea adoptado de manera muy amplia.

Larry OBrien
fuente
OTOH, los programadores principales habían estado acostumbrados a escribir las afirmaciones durante bastante tiempo. La falta de un preprocesador utilizable en los lenguajes principales más modernos hizo que esta práctica agradable fuera ineficiente, pero aún es común para C y C ++. Ahora está regresando con los Contratos de Código de Microsoft (basados ​​en AFAIK, una reescritura de bytecode para las versiones de lanzamiento).
SK-logic
8

Mi intuición me dice que la falta de apoyo debe ser el resultado de algún tipo de rechazo a la práctica ...

Falso.

Es una práctica de diseño . Se puede incorporar explícitamente en código (estilo Eiffel) o implícitamente en código (la mayoría de los idiomas) o en pruebas unitarias. La práctica del diseño existe y funciona bien. El soporte de idiomas está en todo el mapa. Sin embargo, está presente en muchos idiomas en el marco de prueba de la unidad.

Me pregunto si alguien puede aclarar por qué la mayoría de los idiomas modernos parecen ofrecer tan poco soporte. ¿La DbC es defectuosa o demasiado cara?

Es caro. Y. Más importante aún, hay algunas cosas que no se pueden probar en un idioma determinado. La terminación de bucle, por ejemplo, no se puede probar en un lenguaje de programación, requiere una capacidad de prueba de "orden superior". Entonces, algunos tipos de contratos son técnicamente inexpresables.

¿O es simplemente obsoleto debido a la programación extrema y otras metodologías?

No.

Usamos principalmente pruebas unitarias para demostrar que se cumple DbC.

Para Python, como notó, el DbC va en varios lugares.

  1. Los resultados de la prueba de docstring y docstring.

  2. Afirmaciones para validar entradas y salidas.

  3. Pruebas unitarias.

Promover.

Puede adoptar herramientas alfabetizadas de estilo de programación para escribir un documento que incluya su información de DbC y que genere scripts de prueba unitarios Python plus limpios. El enfoque de programación alfabetizada le permite escribir una buena literatura que incluye los contratos y la fuente completa.

S.Lott
fuente
Puede probar casos triviales de terminación de bucle, como la iteración sobre una secuencia finita fija. Es un bucle generalizado que no se puede mostrar que termine trivialmente (ya que podría estar buscando soluciones a conjeturas matemáticas "interesantes"); esa es toda la esencia del problema de detención.
Donal Fellows
+1. Creo que eres el único que ha cubierto el punto más crítico there are some things which cannot be proven. La verificación formal puede ser excelente, ¡pero no todo es verificable! ¡Entonces esa característica realmente restringe lo que el lenguaje de programación puede hacer realmente!
Dipan Mehta
@DonalFellows: Dado que el caso general no se puede probar, es difícil incorporar un conjunto de características que son (a) caras y (b) se sabe que están incompletas. Mi punto en esta respuesta es que es más fácil evitar todas esas características y evitar establecer falsas expectativas de pruebas formales de corrección en general, cuando hay limitaciones. Como ejercicio de diseño (fuera del lenguaje) se pueden (y deberían) utilizar muchas técnicas de prueba.
S.Lott
No es nada costoso en un lenguaje como C ++ donde la verificación de contratos se compila en la versión de lanzamiento. Y el uso de DBC tiende a liberar código de compilación que es más liviano, ya que realiza menos comprobaciones de tiempo de ejecución para que el programa se encuentre en un estado legal. Perdí la cuenta de la cantidad de bases de código increíbles que he visto en las que numerosas funciones comprueban un estado ilegal y devuelven falso, cuando NUNCA debería estar en ese estado en una versión de lanzamiento debidamente probada.
Kaitain
6

Solo adivinando. Quizás parte de la razón por la que no es tan popular es porque "Design by Contract" es una marca registrada de Eiffel.

DPD
fuente
3

Una hipótesis es que para un programa complejo suficientemente grande, especialmente aquellos con un objetivo en movimiento, la masa de contratos en sí puede volverse tan defectuosa y difícil de depurar, o más que el código del programa solo. Al igual que con cualquier patrón, puede haber un uso más allá de los rendimientos decrecientes, así como claras ventajas cuando se usa de una manera más específica.

Otra posible conclusión es que la popularidad de los "lenguajes administrados" es la prueba actual de soporte de diseño por contrato para las características administradas seleccionadas (límites de matriz por contrato, etc.)

hotpaw2
fuente
> la masa de contratos en sí mismos puede volverse tan defectuosa y difícil de depurar, o más, que el código del programa solo, nunca he visto esto.
Kaitain
2

La razón por la que la mayoría de los idiomas principales no tienen características de DbC en el idioma es que la relación costo / beneficio de implementarlo es demasiado alta para el implementador del lenguaje.

un lado de esto ya se ha visto en las otras respuestas, las pruebas unitarias y otros mecanismos de tiempo de ejecución (o incluso algunos mecanismos de tiempo de compilación con metaprogramación de plantillas) ya pueden brindarle gran parte de la bondad de DbC. Por lo tanto, si bien hay un beneficio, es probable que se considere bastante modesto.

El otro lado es el costo, la adaptación retroactiva de DbC en un idioma existente es probablemente un cambio demasiado grande y muy complejo para arrancar. Introducir una nueva sintaxis en un idioma sin romper el código antiguo es difícil. La actualización de su biblioteca estándar existente para usar un cambio de tan largo alcance sería costosa. Por lo tanto, podemos concluir que implementar funciones de DbC en un lenguaje existente tiene un alto costo.

También señalaría que los conceptos que son más o menos contratos para plantillas y, por lo tanto, algo relacionados con DbC, se eliminaron del último estándar C ++, ya que incluso después de años de trabajo en ellos, se estimó que todavía necesitaban años de trabajo. Este tipo de cambios grandes, amplios y amplios a los idiomas son demasiado difíciles de implementar.

jk.
fuente
2

DbC se usaría más ampliamente si los contratos pudieran verificarse en el momento de la compilación para que no fuera posible ejecutar un programa que violara cualquier contrato.

Sin el soporte del compilador, "DbC" es solo otro nombre para "verificar invariantes / supuestos y lanzar una excepción si se viola".

Ingo
fuente
¿No se encuentra con el problema de detención?
Ceasar Bautista
@Ceasar Depende. Algunos supuestos se pueden verificar, otros no. Por ejemplo, hay sistemas de tipos que permiten evitar pasar una lista vacía como argumento o devolver una.
Ingo
Buen punto (+1) aunque Bertrand Meyer en su parte de "Masterminds of programation" también mencionó su sistema de creación de clases aleatorias y pidió que se verificara la violación de los contratos. Por lo tanto, es un enfoque mixto de tiempo de compilación / tiempo de ejecución, pero dudo que esta técnica funcione en todas las situaciones
Maksee
Eso es cierto hasta cierto punto, aunque debería ser una falla catastrófica en lugar de una excepción (ver más abajo). La ventaja clave de DBC es la metodología, que en realidad conduce a programas mejor diseñados, y la garantía de que cualquier método DEBE estar en un estado legal al ingresar, lo que simplifica gran parte de la lógica interna. En general, no debe dedicarse a lanzar excepciones cuando se viola un contrato. Deben usarse excepciones donde el programa LEGALMENTE puede estar en estado ~ X, y el código del cliente debe manejar esto. Un contrato dirá que ~ X es simplemente ilegal.
Kaitain
1

Tengo una explicación simple, la mayoría de las personas (incluidos los programadores) no quieren trabajo extra a menos que lo vean necesario. Programación de aviónica donde la seguridad se considera muy importante No he visto la mayoría de los proyectos sin ella.

Pero si está considerando la programación de sitios web, computadoras de escritorio o dispositivos móviles, los bloqueos y el comportamiento inesperado a veces no se consideran malos y los programadores simplemente evitarán el trabajo adicional al informar errores y luego corregirlos se considera lo suficientemente adecuado.

Esta es probablemente la razón por la que creo que Ada nunca se recuperó fuera de la industria de programación de aviación porque requiere más trabajo de codificación, aunque Ada es un lenguaje increíble y si desea construir un sistema confiable, es el mejor lenguaje para el trabajo (excluyendo SPARK, que es propietario lenguaje basado en Ada).

Las bibliotecas de diseño por contrato para C # fueron experimentales por Microsoft, y son muy útiles para construir software confiable, pero nunca cobraron impulso en el mercado, de lo contrario, las habría visto ahora como parte del lenguaje central de C #.

Las afirmaciones no son lo mismo que el soporte completamente funcional para condiciones previas / posteriores e invariante. Aunque puede intentar emularlos, un lenguaje / compilador con el soporte adecuado realiza el análisis del "árbol de sintaxis abstracta" y comprueba si hay errores lógicos que simplemente no pueden hacer las aserciones.

Editar: Hice una búsqueda y a continuación hay una discusión relacionada que podría ser útil: https://stackoverflow.com/questions/4065001/are-there-any-provable-real-world-languages-scala

HENO
fuente
-2

La mayoría de las razones son las siguientes:

  1. Solo está disponible en idiomas que no son populares
  2. Es innecesario ya que cualquier persona que quiera hacerlo puede hacer lo mismo de manera diferente en los lenguajes de programación existentes.
  3. Es difícil de entender y usar: requiere conocimientos especializados para hacerlo correctamente, por lo que hay un pequeño número de personas que lo hacen.
  4. requiere grandes cantidades de código para hacerlo; los programadores prefieren minimizar la cantidad de caracteres que escriben; si se necesita un código largo, algo debe estar mal.
  5. las ventajas no están ahí: simplemente no puede encontrar suficientes errores para que valga la pena
tp1
fuente
1
Su respuesta no está bien argumentada. Simplemente está expresando su opinión de que no hay ventajas, que requiere grandes cantidades de código y que es innecesario ya que se puede hacer con los idiomas existentes (¡el OP abordó específicamente este problema!).
Andres F.
No estoy pensando en aserciones, etc. como un reemplazo para ello. Esa no es la forma correcta de hacerlo en los idiomas existentes (es decir, aún no se abordó)
tp1
@ tp1, si los programadores realmente quisieran minimizar la escritura, nunca caerían en algo tan detallado y elocuente como Java. Y sí, la programación en sí misma requiere "conocimiento especializado" "para hacerlo correctamente". Aquellos que no poseen tal conocimiento simplemente no deberían poder codificar.
SK-logic
@ Sk-logic: Bueno, parece que la mitad del mundo está haciendo mal OO simplemente porque no quieren escribir funciones de reenvío desde las funciones miembro a las funciones miembro de los miembros de datos. Es un gran problema en mi experiencia. Esto se debe directamente al minimizar la cantidad de caracteres a escribir.
tp1
@ tp1, si las personas realmente quisieran minimizar la escritura, ni siquiera tocarían el OO. OOP es naturalmente elocuente, incluso en sus mejores implementaciones, como Smalltalk. No diría que es una mala propiedad, la elocuencia ayuda a veces.
SK-logic