La idea de expresar contratos en código (CC de ahora en adelante) no es precisamente nueva. Como muchas ideas que ahora nos parecen relativamente nuevas y que van entrando en el uso habitual de todos los programadores (lambdas, linq) o no tan nuevas ya (garbage collection, por ejemplo), la idea de los CC viene ya de antiguo. Bertrand Meyer los consideraba una parte crucial de Eiffel, allá por los 80.
La idea de expresar contratos en código (CC de ahora en adelante) no es precisamente nueva. Como muchas ideas que ahora nos parecen relativamente nuevas y que van entrando en el uso habitual de todos los programadores (lambdas, linq) o no tan nuevas ya (garbage collection, por ejemplo), la idea de los CC viene ya de antiguo. Bertrand Meyer los consideraba una parte crucial de Eiffel, allá por los 80.
El concepto fundamentalmente es el de hacer el código más claro eliminando funcionalidad boilerplate mediante la declaración de contratos, de manera que nuestros métodos sean más reducidos eliminando mucha funcionalidad no relacionada con la propia del método, como chequear si los argumentos pasados son o no null, etc. Se trata, en suma, de separar los requerimientos – y las garantías – de una API de su implementación per se.
Por ejemplo, en un método como este
static int CountWhitespace(string text) { if (text == null) { throw new ArgumentNullException("text"); } return text.Count(char.IsWhiteSpace); }
Vemos que lo que podía ser una implementación de una sola línea, en realidad son ahora 5. De esas 5, 4 no tienen nada que ver con la funcionalidad del método. Es cierto que podemos eliminar las llaves e incluso condensar la condición en una sola línea, pero aún así al método parece que le “sobrase” algo, algo que podríamos eliminar expresándolo de otra manera, anotaciones, atributos customizados, contratos, etc.
Los CC en el entorno .net viene de un lenguaje experimental que sacó Microsoft Research, llamado Spec#, que básicamente era un dialecto de C#, pero con esta funcionalidad de contratos ya incorporados. El método anterior, expresado en C# quedaría así
static int CountWhitespace(string! text) { return text.Count(char.IsWhiteSpace); }
En mi opinión mucho más limpio. El signo de admiración tras el tipo string
indica que el atributo no puede ser nulo. El entorno ya se ocupa de validar esto en tiempo de ejecución.
El namespace a usar es System.Diagnostics.Contracts
, donde reside la clase Contract
, como era de esperar (no hay que importar ninguna referencia a ninguna dll, cuidado con liarse e importar como referencia Microsoft.Contracts
, que nos dará errores al compilar). La única pega es que las herramientas que nos permiten usar CC en .net no vienen en VS 2010 o .net 4, si bien los tipos si están expuestos en mscorlib. Descargamos las herramientas para Code Contracts en esta página de Microsoft Research.
Precondiciones: más que al método en sí, este tipo de contratos impone condiciones sobre el caller del método. El ejemplo anterior de chequear que un argumento es null o no pertenece a este tipo de chequeo, y es que no podemos fiarnos del código ajeno que llama a nuestros métodos. No es seguro asumir nada.
static int CountWhitespace(string! text) { Contract.Requires(text != null); return text.Count(char.IsWhiteSpace); }
Si la condición no se cumple, se lanzará una excepción de tipo ContractException
. Podemos controlar el tipo de excepción anotando la llamada de esta forma:
static int CountWhitespace(string! text) { Contract.Requires<ArgumentNullException>(text != null, "text"); return text.Count(char.IsWhiteSpace); }
Postcondiciones: como cabe esperar, este tipo de contrato expresa condiciones sobre el output de un método, valores de retorno, parámetros afectados por out
o ref
, cambios de estado, etc.
static int CountWhitespace(string text) { Contract.Requires(text != null, "text"); Contract.Ensures(Contract.Result<int>() >= 0); return text.Count(char.IsWhiteSpace); }
Es interesante notar que podemos indicar la condición sobre el valor de retorno antes del cálculo de dicho valor. Estamos declarando una condición, no realmente comprobando nada. Esto ya lo hace bastante diferente a cómo lo haríamos dentro del métodlo con un if
. Ya no tenemos el código de funcionalidad y el código de comprobación mezclados en nuestro método. Así, toda pre o post condición aparece antes y tras la última referencia a Contract, empieza la implementación.
Invariants: son contratos que afectan al estado de un objeto en todo momento en el que el estado de dicho objeto sea visible (accesible y modificable). Los métodos en los que queramos aplicar este tipo de contrato deben ser decorados con el atributo [ContractInvariantMethod]
. Durante la ejecución de un método decorado con este atributo, podemos cambiar el estado como queramos, pero al salir de áquel, el estado debe ser el mismo que cuando entramos en el método.
[ContractInvariantMethod] private void ObjectInvariant() { Contract.Invariant( this.var1 == 50 ); Contract.Invariant( this.var2 + this.var3 == 50 ); }
Este método debe ser siempre private
. Ya se ocupa el binary rewriter ( ver más abajo ) de recovertirlo en un método protected para que las clases derivadas puedan llamarlo.
Aserciones y asunciones (assertions and assumptions). Este tipo de contratos nos sirven para ir chequeando lo que ocurre dentro de nuestros métodos, estableciendo unos controles mediante el uso de los métodos Assume
y Assert
, de funcionamiento parecido a Debug.Assert
.
Por ejemplo Contract.Assume(aVar >= 1);
o Contract.Assert(myVar != null);
. La diferencia es que Assume
hace lo que indica su nombre y asume, no comprueba, mientras que Assert
efectivamente verifica que myVar
tenga un valor. Este último más útil si se trata de un objeto cuyos métodos vamos a llamar, en cuyo caso sin duda nos interesa verificar que no sea nulo.
Legacy: sirven para decorar código ya existente que no queremos refactorizar. Permiten hacer que código ya existente (no contractual) nos sirva de contrato, marcando solo el final del bloque contractual y el comienzo del bloque de implementación. Por ejemplo:
static int CountWhitespace(string text) { if (text == null) { throw new ArgumentNullException("text"); } Contract.EndContractBlock(); return text.Count(char.IsWhiteSpace); }
Aquí Contract.EndContractBlock();
nos indica donde termina el bloque de contrato y comienza la implementación.
© 1997 - - La Güeb de Joaquín | |||||
Joaquín Medina Serrano
|
|||||
|
Codificación | |
Fecha de creación | |
Última actualización | |