You are on page 1of 7

Tecnologa de vanguardia

Invariantes y herencia en Code Contracts


Dino Esposito

En las entregas pasadas de esta columna habl sobre dos de los tipos ms comunes de contratos de software, las condiciones previas y posteriores, y analic su sintaxis y semntica desde la perspectiva de la API de Code Contracts de Microsoft .NET Framework 4. Este mes primero presentar el tercer tipo de contrato ms importante, el invariante, y luego examinar el comportamiento de las clases basadas en contratos cuando entra en juego la herencia. Las invariantes En trminos generales, una invariante es una condicin que siempre es verdadera en un contexto determinado. En el caso del software orientado a objetos, una invariante indica una condicin que siempre se evala como verdadera en todas las instancias de una clase. La invariante es una herramienta extraordinaria que informa inmediatamente cuando el estado de cualquier instancia de una clase determinada deja de ser vlido. En otras palabras, un contrato de invariantes define formalmente las condiciones bajo las cuales una instancia de una clase se encuentra en un estado correcto. Por muy enftico que pueda sonar, este es slo el primer concepto que hay que entender y luego implementar, a la hora de emplear clases para modelar un dominio empresarial. Actualmente el Diseo controlado por el dominio (DDD) es la metodologa preferida para modelar los escenarios empresariales complejos. Dentro de sta, la lgica invariante ocupa una posicin destacada en el diseo. De hecho, de acuerdo con el DDD nunca se debe operar con instancias de clases que se encuentren en un estado no vlido. El DDD tambin recomienda el uso de fbricas que devuelvan objetos en un estado vlido de las clases, y tambin que los objetos regresen en un estado vlido despus de cada operacin. El DDD es slo una metodologa y la implementacin de los contratos queda en las manos del programador. Code Contracts permite lograr una implementacin satisfactoria con un mnimo esfuerzo en .NET Framework 4. Aprendamos ms sobre las condiciones invariantes en .NET Framework 4. Cul es la lgica de las invariantes?

Se relacionan las invariantes de alguna manera con el buen modelado de los objetos y el diseo de software? Es imprescindible tener un conocimiento profundo del dominio. ste sirve de gua para encontrar en forma natural las invariantes en un modelo. Y en algunas clases no se necesitan invariantes. La ausencia de invariantes no es un sntoma alarmante en si. Una clase que no tiene restricciones en lo que se puede guardar y hacer simplemente no tiene invariantes. Si esto es lo que resulta del anlisis, entonces est perfecto tal como est. Imagine una clase que representa las noticias que se van a publicar. Es probable que clase tenga un ttulo, un resumen y una fecha de publicacin. En este caso, cules son las invariantes? Esto depende del dominio empresarial. Es necesaria la fecha de publicacin? Si la respuesta es s, hay que garantizar que las noticias siempre tengan una fecha vlida y la definicin de qu es una "fecha vlida" tambin proviene del contexto. Si la fecha es opcional, entonces es posible guardar una invariante y garantizar que el contenido de la propiedad sea vlido antes de usarlo en el contexto en el cual est siendo utilizado. Lo mismo se puede decir respecto al ttulo y el resumen. Tiene sentido una noticia sin ttulo ni contenido? Si en el contexto de la empresa que se est evaluando tiene sentido, entonces se tratar de una clase sin invariantes. De lo contrario, habr que agregar pruebas que garanticen que el ttulo y el contenido no sean nulos. En general, es probable que las clases que no tienen comportamiento y que se desempean como contenedores de datos escasamente relacionados no tengan invariantes. Cuando tenga dudas, le sugiero que se haga la siguiente pregunta para cada propiedad de la clase que se establece mediante un mtodo, ya sea pblica, protegida o privada: Puedo almacenar cualquier valor aqu? Esto le ayudar a entender concretamente si an faltan algunos puntos importantes del modelo. Como tantos otros aspectos del diseo, encontrar las invariantes es ms provechoso si se hace a tiempo durante el proceso del diseo. Siempre es posible agregar una invariante al final del proceso del desarrollo, pero la refactorizacin resultar ser ms costosa. En este caso hay que ser prolijo y tener mucho cuidado con las regresiones. Las invariantes en Code Contracts En .NET Framework 4 un contrato de invariantes para una clase es el conjunto de condiciones que siempre deben ser verdaderas para cualquier instancia de la clase. Cuando se agregan contratos a una clase, las condiciones previas sirven para descubrir los errores en el autor de la llamada de la clase, mientras que las condiciones posteriores y las invariantes sirven para descubrir los errores en la clase y en las subclases de sta. El contrato de invariantes se define mediante uno o ms mtodos ad hoc. Estos son: mtodos de instancias, privados, que devuelven void y decorados con un atributo especial, el atributo ContractInvariantMethod. Adems, no est permitido que los mtodos invariantes contengan otro cdigo que no sea el de las llamadas necesarias para definir las condiciones invariantes. No se puede, por ejemplo, incluir ningn tipo de lgica en los mtodos invariantes, sean estos puros o no. Ni si quiera se puede agregar lgica para registrar el estado de la clase. En el siguiente ejemplo se muestra cmo definir el contrato de las invariantes para una clase:
1. 2. public class News { public String Title {get; set;}

3. public String Body {get; set;} 4. 5. [ContractInvariantMethod] 6. private void ObjectInvariant() 7. { 8. Contract.Invariant(!String.IsNullOrEmpty(Title)); 9. Contract.Invariant(!String.IsNullOrEmpty(Body)); 10. } 11. }

La clase News establece como condiciones invariantes que Title y Body nunca sean nulos ni vacos. Hay que tener en cuenta, que para que este cdigo funcione se debe habilitar la revisin completa del tiempo de ejecucin en la configuracin del proyecto para las distintas compilaciones, segn sea necesario (consulte la Figura 1).

Figura 1 Las condiciones invariantes requieren una revisin completa del tiempo de ejecucin para los contratos Ahora pruebe el siguiente cdigo sencillo:
1. var n = new News();

La excepcin de error en el contrato puede resultar sorprendente. Se cre exitosamente una instancia de la clase News, pero desafortunadamente se encontraba en un estado no vlido. Hace falta una nueva perspectiva de las condiciones invariantes. En el DDD las invariantes estn asociadas al concepto de una fbrica. Una fbrica es simplemente un mtodo pblico que es responsable de crear las instancias de una clase. En el DDD cada fbrica es responsable de devolver las instancias de las entidades de dominio en un estado vlido. Lo fundamental es que al utilizar las invariantes hay que garantizar que las condiciones se cumplan en todo momento.

Pero en qu momento especfico? Tanto el DDD como la implementacin real de Code Contracts coinciden en que las invariantes se deben comprobar a la salida de cualquier mtodo pblico; esto incluye a los establecedores y los constructores. En la Figura 2 se muestra una versin modificada de la clase News con un constructor. Una fbrica es igual a un constructor, pero como se trata de un mtodo esttico puede tener un nombre personalizado adecuado al contexto, lo que resulta en un cdigo ms legible. Figura 2 Invariantes y constructores que reconocen las invariantes
1. public class News 2. { 3. public News(String title, String body) 4. { 5. Contract.Requires<ArgumentException>( 6. !String.IsNullOrEmpty(title)); 7. Contract.Requires<ArgumentException>( 8. !String.IsNullOrEmpty(body)); 9. 10. Title = title; 11. Body = body; 12. } 13. 14. public String Title { get; set; } 15. public String Body { get; set; } 16. 17. [ContractInvariantMethod] 18. private void ObjectInvariant() 19. { 20. Contract.Invariant(!String.IsNullOrEmpty(Title)); 21. Contract.Invariant(!String.IsNullOrEmpty(Body)); 22. } 23. }

La clase News se puede consumir con el siguiente cdigo:


1. var n = new News("Title", "This is the news");

Este cdigo no genera ninguna excepcin porque la instancia se crea y retorna en un estado que cumple con las invariantes. Pero si se agrega la siguiente lnea:
1. 2. var n = new News("Title", "This is the news"); n.Title = "";

Al establecer la propiedad de ttulo con la cadena vaca, el objeto queda en un estado no vlido. Como las invariantes se comprueban a la salida de los mtodos pblicos (y los establecedores de propiedad son mtodos pblicos) nuevamente se genera una excepcin. Es interesante observar que al emplear campos pblicos en vez de propiedades pblicas las invariantes no se comprueban, y por lo tanto el cdigo se ejecuta correctamente. Pero el objeto, sin embargo, queda en un estado no vlido. Observe, que el hecho de tener objetos en un estado no vlido no es necesariamente un problema. Sin embargo, para una mayor seguridad en los sistemas grandes, podra ser preferible que se genere automticamente una excepcin cada vez que se detecta un estado no vlido. Esto ayuda en la administracin del desarrollo y en la realizacin de las pruebas. En las aplicaciones pequeas es posible obviar las invariantes, incluso cuando el anlisis arroje algunas.

Aunque a la salida de los mtodos pblicos las invariantes se deben cumplir, el estado puede no ser vlido temporalmente dentro del cuerpo del mtodo. Lo importante es que las condiciones invariantes sean verdaderas antes y despus de la ejecucin de los mtodos pblicos. Cmo prevenir que el objeto entre en un estado no vlido? Una herramienta de anlisis esttico como el Comprobador de cdigo esttico de Microsoft es capaz de detectar cuando una asignacin determinada puede infringir una invariante. Las invariantes entregan proteccin frente a un comportamiento con errores, pero tambin pueden ayudar a identificar las entradas mal especificadas. Al especificar las invariantes correctamente, es posible descubrir los errores ms fcilmente en el cdigo al usar una clase determinada. Herencia de contratos En la Figura 3 se muestra otra clase que define un mtodo invariante. Esta clase puede servir como la raz de un modelo de dominio. Figura 3 Clase de raz basada en una condicin invariante para un modelo de dominio
1. public abstract class DomainObject 2. { 3. public abstract Boolean IsValid(); 4. 5. [Pure] 6. private Boolean IsValidState() 7. { 8. return IsValid(); 9. } 10. 11. [ContractInvariantMethod] 12. private void ObjectInvariant() 13. { 14. Contract.Invariant(IsValidState()); 15. } 16. }

En la clase DomainObject la condicin invariante se expresa con un mtodo privado que se declara como mtodo puro (es decir, que no altera el estado). Internamente el mtodo privado llama un mtodo abstracto que se emplear por las clases derivadas para indicar sus propias invariantes. En la Figura 4 se muestra una clase posible derivada de DomainObject que reemplaza el mtodo IsValid. Figura 4 Reemplazar un mtodo utilizado por las invariantes
1. public class Customer : DomainObject 2. { 3. private Int32 _id; 4. private String _companyName, _contact; 5. 6. public Customer(Int32 id, String company) 7. { 8. Contract.Requires(id > 0); 9. Contract.Requires(company.Length > 5); 10. Contract.Requires(!String.IsNullOrWhiteSpace(company)); 11. 12. Id = id; 13. CompanyName = company;

14. } 15. ... 16. public override bool IsValid() 17. { 18. return (Id > 0 && !String.IsNullOrWhiteSpace(CompanyName)); 19. } 20. }

Esta solucin parece bastante elegante y eficiente. Ahora pasemos datos vlidos para intentar crear una nueva instancia de la clase Customer:
1. var c = new Customer(1, "DinoEs");

Si pasamos por alto el constructor de Customer, todo se ve bien. Sin embargo, como Customer se hereda de DomainObject, se invoca el constructor de DomainObject y se comprueba la invariante. Debido a que el mtodo IsValid en la clase DomainObject es virtual (en realidad, abstracto) la llamada se redirige al mtodo IsValid que se define en la clase Customer. Desgraciadamente la comprobacin se produce en una instancia que no ha sido iniciada completamente. Se genera una excepcin, pero no es nuestra culpa. (Este problema se resolvi en la ltima versin de Code Contracts, y la comprobacin de las invariantes en los constructores se retrasa hasta que se haya llamado el ltimo constructor.) Este escenario se relaciona con un problema conocido: no llamar a los miembros virtuales desde un constructor. En este caso el problema no se produjo por haberlo codificado directamente de esta manera, sino que se produjo como un efecto secundario de la herencia del contrato. Existen dos soluciones: o se elimina el mtodo abstracto IsValid de la clase base, o se recurre al cdigo que se muestra en la Figura 5. Figura 5 Invalidar un mtodo utilizado por las invariantes
1. 2. 3. 4. 5. 6. 7. 8. 9. 10. 11. 12. 13. 14. 15. 16. 17. 18. 19. 20. 21. 22. 23. 24. 25. 26. public abstract class DomainObject { protected Boolean Initialized; public abstract Boolean IsValid(); [Pure] private Boolean IsInValidState() { return !Initialized || IsValid(); } [ContractInvariantMethod] private void ObjectInvariant() { Contract.Invariant(IsInValidState()); } } public class Customer : DomainObject { public Customer(Int32 id, String company) { ... Id = id; CompanyName = company; Initialized = true;

27. } 28. ... 29. }

El miembro protegido Initialized acta como defensa y no realizar llamadas al mtodo reemplazado IsValid hasta que el objeto real se haya inicializado. El miembro Initialized puede ser un campo o una propiedad. Pero cuando sea una propiedad se vuelve a hacer un segundo recorrido por todas las invariantes. Esto no es estrictamente necesario, ya que todo ha sido comprobado al menos una vez. En este aspecto el uso de un campo genera un cdigo un poco ms rpido. La herencia de contratos es automtica en el sentido que una clase derivada recibe automticamente los contratos definidos en la clase base. De esta manera, una clase derivada agrega sus propias condiciones previas a las condiciones previas de la clase base. Lo mismo ocurre con las condiciones posteriores y las invariantes. Al tratar con una cadena de herencia, el traductor del lenguaje intermedio resume los contratos y los llama en el orden correcto cuando y donde sea necesario. Atencin! Las invariantes no son infalibles. A veces las invariantes pueden ayudarlo por un lado y causarle problemas por otro, especialmente si las emplea en todas las clases y en el contexto de una jerarqua de clases. Aunque siempre debe intentar identificar la lgica invariante de las clases, si al implementar las invariantes se topa con casos lmite, lo mejor ser que los deje fuera de la implementacin. Sin embargo, siempre debe tener en cuenta que si se encuentra con casos lmite, la verdadera causa probablemente sea la complejidad del modelo. Las condiciones invariantes son las herramientas que necesita para administrar los problemas de la implementacin; no son el problema en si. Existe al menos una razn por la cual resumir todos los contratos de una jerarqua de clases es una operacin delicada. Este tema es demasiado largo para analizarlo aqu, pero entrega un buen material para el artculo del prximo mes.

You might also like