Compartir a través de


Este artículo proviene de un motor de traducción automática.

Vanguardia

Contratos de código: herencia y el principio de Liskov

Dino Esposito

Dino EspositoAl igual que los contratos reales, contratos de software le unen a restricciones adicionales y costará algo de tiempo. Cuando un contrato es permanente, puede que desee asegurarse de que no rompan sus términos. Cuando se trata de contratos de software, incluidos los contratos de código en Microsoft.NET Framework: casi todos los desarrolladores manifiestan eventualmente dudas acerca de los costos computacionales de tener contratos alrededor de las clases. ¿Son contratos apropiados para su software, independientemente del tipo de construcción? ¿O son contratos en cambio principalmente una ayuda depuración que debe ser despojada fuera código de venta por menor?

Eiffel, el primer lenguaje para introducir software contratos, tiene palabras clave del lenguaje nativo para definir precondiciones, postcondiciones y invariantes. En Eiffel, por lo tanto, los contratos son parte de la lengua. Si se utiliza en el código fuente de una clase, contratos convertido en parte integral del código.

En.NET, aunque, contratos son parte del marco y no pertenecen a los idiomas admitidos. Esto significa que puede habilitar la comprobación en tiempo de ejecución o discapacitados en va. En particular, en.NET puedes decidir sobre contratos sobre una base de configuración por construir. En Java, las cosas son casi los mismos. Utiliza un marco externo y bien añadir código de contrato a las fuentes para compilar o pedir herramientas alrededor del marco para modificar el código de bytes en consecuencia.

En este artículo explicaré algunos escenarios donde contratos código resultar particularmente útiles en conducción le hacia una mayor calidad de diseño de software global.

Sean qué código contratos para

Una mejor práctica perenne de los desarrolladores de software es escribir métodos que comprobación cuidadosamente los parámetros de entrada que reciben. Si el parámetro de entrada no coincide con las expectativas del método, se produce una excepción. Esto se conoce como el Si-entonces-throw patrón. Con contratos de condición previa, este mismo código se ve más bonito y más compacto. Más interesante, también lee mejor, ya que una condición previa permite claramente sólo lo que se requiere en lugar de pruebas contra lo que no es deseable. Por lo tanto, a primera vista, los contratos de software simplemente aspecto un enfoque más agradable de escritura para evitar excepciones en métodos de la clase. Bueno, hay mucho más que eso.

El simple hecho de que crees de contratos para cada método indica que ahora estás pensando más acerca del papel de esos métodos. Al final, diseño obtiene el terser y terser. Y contratos representan también una forma valiosa de documentación, especialmente para propósitos de refactorización.

Código de contratos, sin embargo, no se limitan a las condiciones previas, a pesar de las condiciones previas son la parte más fácil de los contratos de software para recoger. La combinación de precondiciones, postcondiciones y invariantes — y la amplia aplicación de ellos en todo el código, le da una ventaja decisiva y realmente lleva a algún código de mayor calidad.

Afirmaciones vs. El código de contratos vs. Pruebas

Contratos de código no son enteramente como afirmaciones y otros instrumentos de depuración. Mientras que los contratos pueden ayudarle a realizar un seguimiento de errores, que no reemplazan un buen depurador o un conjunto bien hecho de pruebas unitarias. Como afirmaciones, contratos de código indican una condición que debe verificarse en un momento durante la ejecución de un programa.

Una afirmación que no es un síntoma de que algo anda mal en algún lugar. Una afirmación, sin embargo, no puede decir por qué no se y donde se originó el problema. Un contrato de código que no, por el contrario, te dice mucho más. Comparte detalles sobre el tipo de fallo. Así, por ejemplo, se puede aprender si la excepción se produce porque un determinado método recibió un valor inaceptable, error en el cálculo del valor devuelto esperado o tiene un Estado no válido. Considerando que una afirmación le sólo sobre un síntoma malo detectado, un contrato de código puede mostrar información valiosa sobre cómo se debe utilizar el método. Esta información al final puede ayudarle a comprender lo que tiene que fijarse a dejar de violar una afirmación dada.

¿Cómo contratos de software se refieren a las pruebas unitarias? Evidentemente, uno no excluye a la otra y las dos características son una especie de ortogonales. Un arnés de prueba es un programa externo que funciona mediante la aplicación de una entrada fija seleccionadas clases y métodos para ver cómo se comportan. Los contratos son una forma de que las clases a gritarme cuando algo está mal. Para probar los contratos, sin embargo, debe ejecutar el código.

Las pruebas unitarias son una gran herramienta para la captura de regresión después de un profundo proceso de refactorización. Los contratos son tal vez más informativo que pruebas para documentar el comportamiento de los métodos. Para obtener el valor de diseño de pruebas, debe practicar test-driven development (TDD). Los contratos son probablemente una herramienta más simple que TDD para métodos de documento y diseño.

Contratos añadir información adicional a su código y dejan usted debe decidir si esta información debe llegar a los binarios desplegados. Pruebas unitarias implica un proyecto externo que puede estimar qué está haciendo el código. Si compila información contractual o no, tener información de contrato claro de antemano ayuda inmensamente como ayuda de diseño y documentación.

Contratos de código y datos de entrada

Contratos se refieren a las condiciones que siempre tienen validez en el flujo normal de ejecución de un programa. Esto parece sugerir que es el lugar ideal donde tal vez desee utilizar contratos en bibliotecas internas que están sujetos a la entrada estrictamente controladas por el desarrollador. Las clases que están expuestas directamente a la entrada de usuario no son necesariamente un buen lugar para los contratos. Si establece condiciones previas en sin filtrar datos de entrada, el contrato puede fallar y se produce una excepción. ¿Pero es esto realmente lo que desea? La mayoría de las veces, desea degradar correctamente o devolver un mensaje Cortés al usuario. No desea una excepción y no desea lanzar y, a continuación, atrapar una excepción sólo para recuperar de forma correcta.

En.NET, código contratos pertenecen a las bibliotecas y puede ser un anotaciones de datos buen complemento a (y, en algunos casos, un sustituto). Las anotaciones de datos hacen un gran trabajo en relación con la interfaz de usuario porque en Silverlight y ASP.NET tiene componentes que entienden esas anotaciones y ajustar el código o el código HTML de salida. En la capa de dominio, sin embargo, a menudo es necesario algo más que atributos y contratos de código son un sustituto ideal. No necesariamente estoy que indica que no se puede obtener las mismas capacidades con atributos que puede con contratos de código. Me parece, sin embargo, que en términos de legibilidad y expresividad, los resultados son mejores con contratos de código que atributos. (Por cierto, esto es precisamente por qué el equipo de código contratos prefiere código claro sobre los atributos).

Contratos heredados

Contratos de software son heredables en casi cualquier plataforma que apoya, y el.NET Framework no es ninguna excepción. Cuando se deriva una nueva clase de uno existente, la clase derivada recoge el comportamiento, contexto y contratos del padre. Ese parece ser el curso natural de las cosas. Herencia de los contratos no plantea ningún problema para invariantes y postcondiciones. Es un poco problemático para condiciones previas, sin embargo. Vamos a abordar invariantes y considere el código en figura 1.

Figura 1 Heredar invariables

public class Rectangle
{
  public virtual Int32 Width { get; set; }
  public virtual Int32 Height { get; set; }

  [ContractInvariantMethod]
  private void ObjectInvariant()
  {
    Contract.Invariant(Width > 0);
    Contract.Invariant(Height > 0);
  }
}
public class Square : Rectangle
{
  public Square()
  {
  }

  public Square(Int32 size)
  {
    Width = size;
    Height = size;
  }

  [ContractInvariantMethod]
  private void ObjectInvariant()
  {
    Contract.Invariant(Width == Height);
  }
  ...
}

La clase base rectángulo tiene dos invariantes: ancho y altura son mayores que cero. La clase derivada Plaza agrega otra condición invariable: anchura y altura deben coincidir. Incluso desde una perspectiva lógica, esto tiene sentido. Un cuadrado es como un rectángulo, excepto que tiene una restricción adicional: anchura y la altura siempre deben ser el mismo.

Para postcondiciones, en su mayoría funcionan de la misma manera las cosas. Una clase derivada que reemplaza un método y agrega más postcondiciones sólo aumenta las capacidades de la clase base y actúa como un caso especial de la clase de padre que hace todo el padre hace y mucho más.

¿Qué condiciones previas, entonces? Eso es exactamente por eso resumiendo contratos a través de una jerarquía de clases es una operación delicada. Un método de clase, lógicamente hablando, es lo mismo que una función matemática. Tanto obtener algunos valores de entrada y producir algunos resultados. En matemáticas, el rango de valores producido por una función es conocido como el codominio; el dominio es el rango de posibles valores de entrada. Mediante la adición de invariantes y postcondiciones a un método de la clase derivada, sólo aumenta el tamaño de codominio del método. Pero al agregar condiciones previas, se restringe el dominio del método. ¿Esto es algo que realmente debe preocuparse? Leyendo.

El principio de Liskov

SÓLIDO es un acrónimo popular que resulta de las iniciales de cinco principios clave del diseño de software única responsabilidad, abierto/cerrado, segregación de interfaz y la inversión de dependencia. L en sólido representa el principio de sustitución de Liskov. Puede aprender mucho más sobre el principio de Liskov en bit.ly/lKXCxF.

En pocas palabras, el principio de Liskov establece que debe ser siempre seguro de utilizar una subclase en cualquier lugar donde se espera que la clase principal. Enfático que parezca, esto es no algo que salir de la caja de orientación a objetos simple. Ningún compilador de cualquier lenguaje orientado a objetos puede hacer la magia de asegurar que el principio es siempre.

Es responsabilidad del desarrollador precisa para garantizar que es seguro utilizar cualquier clase derivada en lugares donde se espera que la clase principal. Tenga en cuenta que lo dicho "segura". Orientación a objetos simple permite utilizar cualquier clase derivada en lugares donde se espera que la clase principal. "Posible" no es lo mismo como "seguro". Para cumplir con el principio de Liskov, usted necesita cumplir una regla simple: no se puede reducir el dominio de un método en una subclase.

Contratos de código y el principio de Liskov

Aparte de la definición abstracta y formal, el principio de Liskov tiene mucho que ver con contratos de software y puede ser fácilmente redactarse en términos de una tecnología específica como.NET código contratos. El punto clave es que una clase derivada sólo puede agregar condiciones previas. De esta manera, restringirá el rango de valores posibles de ser aceptado para un método, posiblemente crear errores en tiempo de ejecución.

Es importante que tenga en cuenta que viola el principio no necesariamente en una excepción de tiempo de ejecución o mala conducta. Sin embargo, es un signo que un contraejemplo posible rompe el código. En otras palabras, los efectos de violación pueden rizo en el código de base de todo y mostrar síntomas nefastos en zonas aparentemente no relacionadas. Hace todo el código base más difícil de mantener y evolucionar: un pecado mortal estos días. Supongamos que tiene el código figura 2.

Figura 2 que ilustra el principio Liskov

public class Rectangle
{
  public Int32 Width { get; private set; }
  public Int32 Height { get; private set; }

  public virtual void SetSize(Int32 width, Int32 height)
  {
    Width = width;
    Height = height;
  }
}
public class Square : Rectangle
{
  public override void SetSize(Int32 width, Int32 height)
  {
    Contract.Requires<ArgumentException>(width == height);
    base.SetSize(width, width);
  }
}

La Plaza de clase hereda de rectángulo y sólo agrega una condición previa. En este punto, se producirá un error en el código siguiente (que representa un contraejemplo posible):

private static void Transform(Rectangle rect)
  {
    // Height becomes twice the width
    rect.SetSize(rect.Width, 2*rect.Width);
  }

El método de transformación fue escrita originalmente para hacer frente a las instancias de la clase Rectangle y hace su trabajo muy bien. Supongamos que un día extender el sistema y empezar a pasar instancias de plaza para el mismo código (Virgen), como se muestra aquí:

var square = new Square();
square.SetSize(20, 20);
Transform(square);

Dependiendo de la relación entre el cuadrado y el rectángulo, el método de transformación puede comenzar a fallar sin una explicación aparente.

Peor aún, usted puede fácilmente detectar cómo solucionar el problema, pero debido a la jerarquía de clases, no puede ser algo que desea tomar a la ligera. Así que uno termina corregir el error con una solución, como se muestra aquí:

private static void Transform(Rectangle rect)
{
  // Height becomes twice the width
  if (rect is Square)
  {
    // ...
return;
  }
  rect.SetSize(rect.Width, 2*rect.Width);
}

Pero independientemente de su esfuerzo, la famosa bola de lodo acaba de empezar a crecer más grande. Lo bueno de.NET y el compilador de C# es que si utiliza código contratos para expresar condiciones previas, se obtiene una advertencia desde el compilador si está violando el principio de Liskov (ver figura 3).

The Warning You Get When You’re Violating the Liskov Principle

Figura 3 la advertencia cuando está violando el principio de Liskov

El principio sólido entiende menos y menos aplicado

Haber enseñado una.Clase de diseño neto para un par de años ahora, creo que puedo decir con seguridad que de sólidos principios, el principio de Liskov es por mucho el menos comprendido y aplicado. Muy a menudo, un raro comportamiento detectado en un sistema de software puede ser rastreado a una violación del principio de Liskov. Muy bien, contratos de código puede ayudar significativamente en este ámbito, si sólo dar un vistazo cuidado a las advertencias del compilador.

Dino Esposito es el autor de "programación Microsoft ASP.NET 4 "(Microsoft Press, 2011) y coautor de"Microsoft.NET: Architecting Applications for the Enterprise” (Microsoft Press, 2008). Con base en Italia, Esposito es un orador frecuente en eventos del sector en todo el mundo. Puedes seguir en Twitter en twitter.com/despos.

Gracias a los siguiente experto técnico para revisar este artículo: Manuel fahndrich