¿Por qué usaría códigos de contratos?

26

Recientemente me topé con el marco de Microsoft para contratos de código.

Leí un poco de documentación y me encontré constantemente preguntando: "¿Por qué querría hacer esto, ya que no y a menudo no puede realizar un análisis estático?".

Ahora, ya tengo una especie de estilo de programación defensiva, con excepciones como esta:

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

También estoy usando mucho NullObject Pattern y rara vez tengo problemas. Agregue pruebas unitarias y estará listo.

Nunca he usado afirmaciones y nunca las extrañé. Todo lo contrario. Realmente odio el código que tiene muchas afirmaciones sin sentido, lo cual es solo ruido para mí y me distrae de lo que realmente quiero ver. Los contratos de código, al menos a la manera de Microsoft, son muy parecidos, y aún peor. Añaden mucho ruido y complejidad al código. En el 99%, se lanzará una excepción de todos modos, por lo que no me importa si es por la afirmación / contrato o el problema real. Solo quedan muy pocos casos en los que los estados del programa realmente se corrompan.

Francamente, ¿cuál es el beneficio de usar contratos de código? ¿Hay alguna? Si ya usa pruebas unitarias y codifica a la defensiva, creo que la introducción de contratos simplemente no vale la pena y pone ruido en su código que un responsable maldecirá cuando actualice ese método, al igual que yo cuando no puedo ver lo que está haciendo el código debido a afirmaciones inútiles. Todavía tengo que ver una buena razón para pagar ese precio.

Halcón
fuente
1
¿Por qué dices "no y a menudo no puede realizar un análisis estático"? ¿Pensé que ese era uno de los puntos de este proyecto (en lugar de solo usar Asserts o lanzar excepciones defensivas)?
Carson63000
@ Carson63000 Lea la documentación detenidamente y encontrará que el verificador estático solo generará montones y montones de advertencias, las más innecesarias (los desarrolladores admiten que es así) y que los contratos más definidos arrojarán una excepción y no harán nada más .
Falcon
@ Falcon: extrañamente, mi experiencia es completamente diferente. El análisis estático funciona no sin fallas, sino bastante bien, y rara vez veo advertencias innecesarias, incluso cuando trabajo en el nivel de advertencia 4 (el más alto).
Arseni Mourzenko
Supongo que tendré que probarlo para averiguar cómo se comporta.
Falcon

Respuestas:

42

También podría haber preguntado cuándo la escritura estática es mejor que la escritura dinámica. Un debate que se ha desatado durante años sin un final a la vista. Así que eche un vistazo a preguntas como estudios de idiomas dinámicamente vs estáticamente escritos

Pero el argumento básico sería el potencial para descubrir y solucionar problemas en tiempo de compilación que de otro modo podrían haberse deslizado a la producción.

Contratos contra guardias

Incluso con guardias y excepciones, aún enfrenta el problema de que el sistema no podrá realizar su tarea prevista en algún caso. Posiblemente una instancia en la que será bastante crítico y costoso fallar.

Contratos versus pruebas unitarias

El argumento habitual en este caso es que las pruebas prueban la presencia de errores, mientras que los tipos (contratos) prueban la ausencia. F.ex. usando un tipo que sabe que ninguna ruta en el programa podría proporcionar una entrada no válida, mientras que una prueba solo podría decirle que la ruta cubierta sí proporcionó la entrada correcta.

Contratos contra patrón de objeto nulo

Ahora esto es al menos en el mismo parque de pelota. Idiomas como Scala y Haskell han tenido un gran éxito con este enfoque para eliminar referencias nulas por completo de los programas. (Incluso si Scala permite nulos formalmente, la convención es nunca usarlos)

Si ya emplea este patrón para eliminar las NRE, básicamente ha eliminado la mayor fuente de fallas en tiempo de ejecución que existe básicamente en la forma en que los contratos le permiten hacerlo.

La diferencia podría ser que los contratos tienen una opción para requerir automáticamente todo su código para evitar nulos, y así obligarlo a usar este patrón en más lugares para pasar la compilación.

Además de eso, los contratos también te dan la flexibilidad para apuntar a cosas más allá de lo nulo. Entonces, si ya no ve ninguna NRE en sus errores, es posible que desee usar contratos para estrangular el siguiente problema más común que pueda tener. ¿A la una? Índice fuera de rango?

Pero...

Todo eso dicho. Estoy de acuerdo en que el ruido sintáctico (e incluso el ruido estructural) que los contratos agregan al código es bastante sustancial y el impacto que el análisis tiene en su tiempo de construcción no debe subestimarse. Por lo tanto, si decide agregar contratos a su sistema, probablemente sea aconsejable hacerlo con mucho cuidado y con un enfoque limitado en qué clase de errores se intenta solucionar.

John Nilsson
fuente
66
Esa es una excelente primera respuesta para programadores. Me alegra tener su participación en la comunidad.
13

No sé de dónde viene la afirmación de que "a menudo no se puede realizar un análisis estático". La primera parte de la afirmación es claramente errónea. El segundo depende de lo que quiere decir con "a menudo". Prefiero decir que a menudo realiza análisis estáticos, y rara vez falla. En la aplicación comercial ordinaria, rara vez se acerca mucho más que nunca .

Así que aquí viene, el primer beneficio:

Ventaja 1: análisis estático

Las afirmaciones ordinarias y la comprobación de argumentos tienen un inconveniente: se posponen hasta que se ejecuta el código. Por otro lado, los contratos de código se manifiestan a un nivel mucho más temprano, ya sea en el paso de codificación o al compilar la aplicación. Cuanto antes detecte un error, menos costoso será solucionarlo.

Beneficio 2: tipo de documentación siempre actualizada

Los contratos de código también proporcionan una especie de documentación que siempre está actualizada. Si el comentario XML del método SetProductPrice(int newPrice)indica que newPricedebe ser superior o igual a cero, puede esperar que la documentación esté actualizada, pero también puede descubrir que alguien cambió el método para que newPrice = 0arroje un documento ArgumentOutOfRangeException, pero nunca cambió la documentación correspondiente. Dada la correlación entre los contratos de código y el código en sí, no tiene el problema de documentación fuera de sincronización.

El tipo de documentación proporcionada por los contratos de código también es valioso de una manera que a menudo, los comentarios XML no explican bien los valores aceptables. ¿Cuántas veces me preguntaba si era nullo string.Emptyo \r\nes un valor autorizado para un método y comentarios XML guardaron silencio en eso!

En conclusión, sin contratos de código, muchos códigos son así:

Aceptaré algunos valores pero no otros, pero tendrá que adivinar o leer la documentación, si corresponde. En realidad, no lea la documentación: está desactualizada. Simplemente recorra todos los valores y verá los que me hacen lanzar excepciones. También tiene que adivinar el rango de valores que pueden devolverse, porque incluso si le contara un poco más sobre ellos, puede que no sea cierto, dados los cientos de cambios que me hicieron durante los últimos años.

Con los contratos de código, se convierte en:

El argumento del título puede ser una cadena no nula con una longitud de 0..500. El entero que sigue es un valor positivo, que puede ser cero solo cuando la cadena está vacía. Finalmente, devolveré un IDefinitionobjeto, nunca nulo.

Ventaja 3: contratos de interfaces

Un tercer beneficio es que los contratos de código potencian las interfaces. Digamos que tienes algo como:

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

    public void CommitChanges();

    ...
}

¿Cómo podría, utilizando solo afirmaciones y excepciones, garantizar que solo CommitChangesse puede llamar cuando PendingChangesno está vacío? ¿Cómo garantizarías que PendingChangesnunca es así null?

Beneficio 4: hacer cumplir los resultados de un método

Finalmente, el cuarto beneficio es poder obtener Contract.Ensurelos resultados. ¿Qué pasa si, al escribir un método que devuelve un número entero, quiero estar seguro de que el valor nunca es inferior o igual a cero? ¿Incluso cinco años después, después de sufrir muchos cambios de muchos desarrolladores? Tan pronto como un método tiene múltiples puntos de retorno, se Assertconvierte en una pesadilla de mantenimiento para eso.


Considere los contratos de código no solo como un medio de corrección de su código, sino como una forma más estricta de escribir código. De manera similar, una persona que usaba lenguajes dinámicos exclusivamente puede preguntar por qué impondría los tipos a nivel de lenguaje, mientras que puede hacer lo mismo en afirmaciones cuando sea necesario. Puede hacerlo, pero la escritura estática es más fácil de usar, menos propensa a errores en comparación con un montón de aserciones y la autodocumentación.

La diferencia entre la escritura dinámica y la escritura estática es extremadamente cercana a la diferencia entre la programación ordinaria y la programación por contratos.

MainMa
fuente
3

Sé que esto es un poco tarde para la pregunta, pero hay otro beneficio para Code Contracts que vale la pena mencionar

Los contratos se verifican fuera del método

Cuando tenemos condiciones de protección dentro de nuestro método, ese es el lugar donde se realiza la verificación y donde se informan los errores. Entonces podríamos tener que investigar el seguimiento de la pila para descubrir dónde está la fuente real del error.

Los contratos de código se encuentran dentro del método, pero las condiciones previas se verifican fuera del método cuando algo intenta llamar a ese método. Los contratos se convierten en parte del método y determina si se puede invocar o no. Eso significa que recibimos un error informado mucho más cerca de la fuente real del problema.

Si tiene un método protegido por contrato que se requiere para muchos lugares, esto puede ser un beneficio real.

Alex White
fuente
2

Dos cosas realmente:

  1. Compatibilidad con herramientas, VS puede diferenciar los datos del contrato de la inteligencia para que forme parte de la ayuda de autocompletar.
  2. Cuando una subclase anula un método, pierde todos sus cheques (que aún deberían ser válidos si sigue el LSP) con contratos que siguen sus clases secundarias automáticamente.
metal de piedra
fuente