Mostrando las entradas con la etiqueta bajo acoplamiento. Mostrar todas las entradas
Mostrando las entradas con la etiqueta bajo acoplamiento. Mostrar todas las entradas

sábado, 25 de mayo de 2013

Inversion de Control

//TODO: desarrollar

Principios de diseño de software

El problema fundamental al construir software es conseguir su mantenibilidad en el tiempo, esto es: lograr que el echo de agregarle features o modificarlo, sea lo más fluido posible con pocos contratiempos ni grandes dolores de cabeza (casi una utopía?).
Parece ser que una de las causas es la poca claridad en los requerimientos definidos por el cliente quien muchas veces es exigente pero muy laxo en cuánto a expresar qué es lo que pretende del software que necesita. Eso provoca la falta de claridad en los requerimientos lo cual motiva que muchas decisiones sobre la interpretación del dominio queden en manos de los desarrolladores quienes tienen que imaginar (inclusive pueden llegar a recrear en su cabeza una imagen mítica de un Owner que jamás vieron :) ) cómo serian las cosas en el negocio del cliente, pero hagámonos la idea que tendríamos que ponernos como “expertos del negocio” y comenzar definir cosas (podríamos hacerlo muy bien?). Ojo que esto no tiene que ver con ponerle limites al cliente respecto a lo técnico, sino que me refiero a que él debería ayudar a definir lo mejor posible los requisitos sino nos veremos en problemas a largo plazo.
Por otro lado, la otra causa es que en general , ya sea por falta de skills, porque no hay ganas, porque no queremos poner “cabeza” (lo cual suele pasar), porque estamos en modo “conformistas” o por lo que sea no respetamos lo que se conocen como “Principios de Diseño” los cuales son guías y hasta patrones que nos dicen, en un mundo OO, cómo deberíamos organizar y escribir nuestras clases, métodos, propiedades con el fin de que la mantenibilidad vea la luz.
Por decirlo de otro modo: existen unos cuantos principios (recomendaciones) que si son apropiada y extensivamente aplicados transformarían una pieza de código en una manejable y flexible.
Es importante demarcar que este tipo de código que carece signos claros de un diseño inteligente junto con comportamientos y datos duplicados además de el conocido código spaguetti, se conoce como la Gran Bola de Barro (Big Ball of Mud).
Los síntomas usuales de que tenemos una Gran Bola de Barro son estos:
  1. Al hacer un cambio en un lugar, se rompe el código en otra: un cambio en un modulo repercute en cascada hacia otros módulos, lo cual provoca que sea muy difícil predecir el impacto de un cambio.
  2. Fácil de usar pero difícil de reusar: aquí el problema es la cantidad y profundidad de las dependencias inadecuadas entre módulos. Cuando usar un modulo se vuelve complicado, en general se llega a la opción de reescribir el código desde cero (from scratch), porque entenderlo es duro.
  3. Más fácil de aplicar un solución temporal (workaround) que resolver de acuerdo al diseño: un workaround es una solución de compromiso (“después lo arreglamos mejor”) para un problema puntual del software porque hacer una solución (fix) exacta “de acuerdo al diseño” llevaría mucho tiempo (para entender y ver como encajan las piezas).

Principios universales de diseño

Lo usual que todos conocemos desde la facultad, son los patrones GRASP (object-oriented design General Responsibility Assignment Software Patterns) los cuales nos dan las sugerencias básicas sobre cómo asignar responsabilidades (qué debe hacer cada objeto) para un diseño de software adecuado y claro. Estas buenas practicas  son importantes y deberíamos tenerlas en cuenta para lo que sigue.
Dejo un grafico esquemático, no lo vamos a repasar porque se van a ver implícitos en lo que desarrollaremos un poco más adelante, pero vale la pena recordarlos:

GRASP

Alta Cohesión
La cohesión quiere decir que todo el código de un modulo (entiéndase como una clase o método o un assembly) debe estar lógicamente relacionado. La cohesión se mide de Alto a Bajo y siempre se prefiere que un modulo tenga una Alta Cohesión.
Ejemplo de baja cohesión (indeseable)
   1: List<Proveedor> FindProveedoresByNombre(string nombre)

   2:       {

   3:           var contentXML = System.IO.File.ReadAllText("config.xml");

   4:           //aqui se busca la conexion en el archivo leido;

   5:           var connectionStr = "....";

   6:           SqlConnection sqlConn = new SqlConnection(connectionStr);

   7:           var cmd = sqlConn.CreateCommand();

   8:           cmd.CommandText = "SELECT * from proveedor where nombre like '%" + nombre + "'%";

   9:           var sqlReader=cmd.ExecuteReader();

  10:  

  11:           List<Proveedor> resultados = new List<Proveedor>();

  12:           var counter=0;

  13:           while (sqlReader.Read())

  14:           {

  15:              // mapear objeto de salida

  16:               counter++;

  17:           }

  18:  

  19:           sqlReader.Close();

  20:  

  21:           //Aqui se envia un mail con la cantidad de resultados.

  22:           var body = string.Format("Se encontraron {0} proveedores con nombre'{1}'", counter, nombre);

  23:           mail.Send(body);

  24:  

  25:           return resultados;

  26:       }

Este método como está escrito tiene muy baja cohesión porque su fin es buscar proveedores con un criterio especifico pero hace de todo: busca el archivo de configuración, extrae la cadena de conexión, crea la conexión a la base de datos, elabora la query SQL, ejecuta, mapea y encima envía un mail!!. Pésimo, se pierde totalmente la coherencia porque si bien son tareas que se requieren para buscar un proveedor, estas son demasiadas responsabilidades para un solo método. Imaginemos que después tenemos otro tipo de filtro que requerirá la misma cadena de conexión, entonces vamos a repetir el mismo bloque de código? De eso se trata la alta cohesión: hacer que un método se concentre en una tarea lo mas especifica posible.

Bajo Acoplamiento

El acoplamiento tiene que ver con el grado de dependencia de dos módulos lo cual implica que si A depende excesivamente de B, entonces un cambio en B puede provocar efectos colaterales en A. El acoplamiento dice que que si la superficie de contacto de dos módulos es menor, es mucho mejor. El bajo acoplamiento esta relacionado con el ocultamiento de información y las abstracciones: mientras más ocultos esten sus detalles de implementación y más clara y estables sean sus puntos de contacto con el exterior (interface), menor será el acoplamiento. Es característica es siempre lo deseable.

Ejemplo de alto acoplamiento (indeseable)

   1: class Articulo

   2:    {

   3:        public decimal precio;

   4:        public int cantidad;

   5:        public string descripcion;

   6:    }

   7:  

   8:    class Inventario

   9:    {

  10:        public Articulo[] Articulos;

  11:    }

  12:  

  13:    class InventarioValorado

  14:    {

  15:        private decimal impuesto;

  16:        private Inventario inventario;

  17:  

  18:        public InventarioValorado(decimal _impuesto,Inventario _inventario)

  19:        {

  20:            this.inventario = _inventario;

  21:            this.impuesto = _impuesto;

  22:        }

  23:  

  24:        public decimal CalcularTotal()

  25:        {

  26:            decimal total = 0;

  27:            //esta invadiendo el ocultamiento de los detalles de implementacion.

  28:            foreach (var item in this.inventario.Articulos)

  29:            {

  30:                var subtotal = item.precio * item.cantidad;

  31:                var subtotalConImpuesto = subtotal * (1 + subtotal * (item.cantidad * 0.02m * impuesto));

  32:                total += subtotalConImpuesto;

  33:            }

  34:  

  35:            return total;

  36:        }

  37:    }


En este ejemplo, la clase “InventarioValorizado” tiene un alto acoplamiento a las clases Articulo e Inventario porque depende de sus detalles internos (tiene que saber que guarda en un array los artículos) y además InventarioValorizado esta calculando la valorización de cada articulo (esto debería saberlo el Articulo, no?). Esto es indeseable.

Separación de intereses (SC)

Este principio es clave para conseguir la Alta Cohesión y el Bajo Acoplamiento. La separación de intereses (Separation of Concerns) nos dice que hay que dividir un sistema en features que no se solapen. Lo importante es que cada feature represente un “concern” o “aspecto” de nuestro sistema de manera que disminuyamos considerablemente la duplicación de funcionalidades.  Cada concern se mapea en módulos.

Mencionaré al paper “On  the role of  scientific thought” del prócer de la informática como fue Edsger W. Dijkstra, el ensayo introduce la noción de separación de intereses

SC nos dice que en un momento dado nos concentremos en un aspecto del sistema a la vez, ignorando temporalmente los demás.

SC se consigue a partir de:

Modularidad: cada feature tendrá su propio modulo con una interfaz pública fija y estable con la que se comunicará con otros módulos. Se relaciona con el Ocultamiento de la Información.

Ocultamiento de la información: significa que detrás de una interfaz estable se ocultan detalles, sujetos a variación, sobre cómo esta implementado (programado) un modulo. Así, los módulos se atan a una interfaz fija y no serían afectados por los cambios en la implementación.

Un ejemplo práctico de ocultamiento de la información es el de las propiedades (property) de C#:

Information Hiding with property

Finalmente dejo una observación que es importante y clara: SC induce a la aplicación de la alta cohesión, ya que separar aspectos implica dividir en responsabilidades únicas.


En el siguiente post veremos los Principios SOLID, los que se derivan como una consecuencia de los Principios universales de diseño.
SOLID
Proximo Post...




S.O.L.I.D

SOLID en wikipedia

Los principios SOLID han ganado popularidad como buenas practicas efectivas y las formuló Robert Martin. Cuando son aplicados a lo largo de toda una aplicación se conjugan para obtener un sistema modular, fácil de probar y receptivo al cambio.

Por otro lado, estos principios no son nuevos, siempre estuvieron presentes hasta que se los formuló y son una especie de refinamiento de los Principios Universales de Diseño y de las técnicas orientada a objetos.

Se observa que son relativamente fáciles de definir pero la dificultosos de adoptar como practica, ya que se requiere un criterio que se forma mayormente con la experiencia del desarrollador y una muy “buena educación” sobre la base de la orientación a objetos.

S Single Responsability
O Open/Closed
L Liskov Substitution principle
I Interface Segregation
D Dependency Inversion

Vamos a aclarar que los principios SOLID son una guía sobre cómo proceder, porque el abuso de ellos nos llevará al SOBRE-DISEÑO que es un efecto indeseable.

Single Responsability (Principio de responsabilidad única o acotada)

Este principio nos dice que un modulo de un sistema debe tener de una sola y única responsabilidad , no hacerse cargo de varios aspectos a la vez. Un aspecto único e irrepetible es la razón de su existencia, en otras palabras: si lo aplicamos a un modulo este tendrá una sola razón para cambiar cuando su responsabilidad se vea afectada.
En el fondo vemos que es una reformulación del principio de Alta Cohesión.
Debe haber una sola razón para que una clase cambie. Aquí se entiende que Razón de Cambio es igual  a Responsabilidad.

En general se debería considerar dividir responsabilidades en dos o más clases si aquellas tienen conjuntos de métodos con poco en común. Así se aumentará la probabilidad de que un cambio afecte a pocas clases, en lugar de que muchos cambios afecten a una sola clase: varios cambios afectan a distintas clases y no a una sola. Una clase tiene una sola razón para modificarse.

Veamos un ejemplo de cómo este principio es roto en una clase  trivial:

public class ErrorLoggerManager
    {
        public void LogErrorToEventLog(Exception e)
        {
            // Logging code event system 
        }
        public void LogErrorToFile(Exception e)
        {
            // Logging code to file
        }
    }

La implementación de esta clase parece inofensiva pero se nota que tiene las responsabilidades de saber loguear errores al sistema de eventos del SO y también a un archivo. Sin duda son muchas responsabilidades que se acentuarán cuando imaginamos que cambiaría a medida que sumemos otros canales de logueo,  tales como enviar mail (agregaríamos otro método?).


Una recomendación es que al nombrar una clase con el aditamento “ManagerXXXX”, se descubrirá que probablemente estemos violando Single Responsability.

 

single

Open/Closed principle (abierto a la extensión, cerrado a la modificación)

Este principio nos da una guía sobre cómo escribir entidades de software (clases, módulos o métodos) que se puedan extender sin la necesidad de tocar el código fuente. Una clase debería estar abierta a la extensión y cerrada a la modificación.
No es sencilla la aplicación de este principio, pero se recomienda hacerlo abstrayendo los aspectos de las clases que se consideran pueden cambiar en el tiempo.Estos aspectos mutables se abstraen con interfaces, generics o inyección de código.


Otra formulación interesante es que OCP tienen que ver con que en lugar de agregar más y más responsabilidades y comportamientos a una clase (poniendole más código), por medio de abstracciones reorganicemos el concepto que la clase expresa de forma tal que nuevos comportamientos se adicionen usando la herencia.


Veamos un ejemplo de una clase diseñada según este principio:



public abstract class Shape
{
    public abstract void Render();
}
 
//Cumple con el principio OCP
public class Renderer
{
    public void Draw(IList<Shape> shapes)
    {
        foreach (Shape s in shapes)
            s.Render();
    }
}

La clase Renderer está cerrada a la modificación, pero puede extenderse su comportamiento mediante la abstracción Shape la cual nos dice que cualquier objeto que la implemente puede ser usado por Renderer sin que esta tenga que modificarse.
Aquí se cumplió que las partes que pueden variar en el tiempo (Shape) se abstrajeron en una interface y la clase que no se puede alterar, Renderer, puede evolucionar su comportamiento con implementaciones de Shape.



Consideraciones finales: se dice que este principio muchas veces es una utopía, porque no se puede predecir todos los aspectos cambiantes de una responsabilidad para ser abstraídas, no se podría predecir todas las futuras extensiones de una clase. Pero se conviene en que si logramos para algunas clases obtener una o dos abstracciones y hacer que esa clase funcione contra esas abstracciones ya tendríamos una excelente aplicación del principio. Recordemos que buscar que todas las clases sean “pluggables” nos llevará al sobre-diseño tan innecesario e indeseable.


OpenClosedPrinciple


Para el entendimiento de este principio me basé mucho en lo publicado por Dino Esposito. Así que gracias!! Un genio. Ver su libro sobre ASP.NET.


Liskov’s Substitution Principle


Fue formulado por una mujer, Bárbara Liskov, y nos dice que un objeto debería fácilmente ser reemplazable por la instancia de un subtipo sin suscitar inconvenientes en el comportamiento y reglas del objeto que lo usa. En otras palabras, si tenemos una Clase A, cuyo subtipo es B, si usamos una instancia de B donde se espera A, al uso  debería ser totalmente transparente y no aparecer problemas en el objeto receptor de B.
Es muy abstracta la idea hasta que veamos un ejemplo:


public interface ISecurityProvider
{
User GetUser(string name);
void RemoveUser(User user);
}
public class DatabaseProvider : ISecurityProvider
{
public User GetUser(string name)
{
// Codigo para obtener un usuario desde la bd
}
public void RemoveUser(User user)
{
// Codigo para borrar un usuario
}
}
public class ActiveDirectoryProvider : ISecurityProvider
{
public User GetUser(string name)
{
// Codigo para obtener un usuario desde AD
}
public void RemoveUser(User user)
{
// Active directory no permite el borrado de usuario, asi
//que no se implementa.
throw new NotImplementedException();
}
}


public class UserController : Controller
{
private ISecurityProvider securityProvider;
public ActionResult RemoveUser(string name)
{
User user = securityProvider.GetUser(name);
if (securityProvider is DatabaseProvider) // rompe LSP
securityProvider.RemoveUser(user);

//retornar....
}
}


En el ejemplo DatabaseProvider y ActiveDirectoryProvider implementan ISecurityProvider, pero si nos fijamos bien ActiveDIrectoryProvider no permite el borrado de usuarios, por lo tanto no implementaría el método RemoveUser. Lo cual quiere decir que cuando la clase UserController use una instancia de ActiveDirectoryProvider y el método RemoveUser se encontrará con una excepción; como se ve claramente las instancias de un subtipo no son intercambiables por su padre ya que pueden ocurrir comportamientos inesperados al usar una de ellas.


De aquí se deduce que a la hora de usar clases bases o interfaces como ancestro común se tenga un cuidado importante sobre qué van a implementar sus subtipos. El principio de Liskov nos dice que los subtipos de un ancestro común deben poder reemplazar a su padre sin inconvenientes.


LSP


Resumen: “cada clase derivada debería proveer no más que el padre y no menos que él”.



Interface Segregation Principle


Este principio es simple y nos dice que un cliente no debería ser obligado a “mirar” interfaces que no necesita y que los componentes no deberían ser obligados a implementar interfaces que no planean usar. Esto nos quiere decir que los contratos (interfaces) que construyamos deberían ser lógicamente cohesivos (una vez mas un principio universal dando vueltas), en lugar de interfaces con muchas responsabilidades se prefieren varias interfaces con pequeñas responsabilidades. En otras palabras: interfaces “generosas” son un mal bicho.


Vamos a tomar un ejemplo prestado:


public interface IDoor
{
void Lock();
void Unlock();
Boolean IsDoorOpen { get; }
Int32 OpenTimeout { get; set; }
event EventHandler DoorOpenForTooLong;
}


En síntesis, la interface IDoor representa la abstracción de una puerta con comportamiento regular (Lock y UnLock) y con la posibilidad también de disparar una alarma (DoorOpenForToLoong). Se nota que desde el vamos, la interface acoge mas de una responsabilidad y que puede haber clases que la implementen que no requieren el manejo de alarmas.


Veamos ahora cómo quedaría la versión aplicando el principio de Segregación de Interfaces:


public interface IDoor
{
void Lock();
void Unlock();
Boolean IsDoorOpen { get; }
}
public interface ITimedDoor
{
Int32 OpenTimeout { get; set; }
event EventHandler DoorOpenForTooLong;
}


public class RegularDoor : IDoor
{

public void Lock()
{
throw new NotImplementedException();
}

public void Unlock()
{
throw new NotImplementedException();
}

public bool IsDoorOpen
{
get { throw new NotImplementedException(); }
}


}
public class TimedDoor : IDoor, ITimedDoor
{

public void Lock()
{
throw new NotImplementedException();
}

public void Unlock()
{
throw new NotImplementedException();
}

public bool IsDoorOpen
{
get { throw new NotImplementedException(); }
}

public int OpenTimeout
{
get;
set;
}

public event EventHandler DoorOpenForTooLong;
}


Aquí hemos dividido una interface “generosa” en dos interfaces con responsabilidades cohesivas. De eso se trata la segregación de interfaces: crear contratos con alta cohesión.


InterfaceSegregation


 


Dependency Inversion Principle


Este principio nos dice simplemente que deberíamos programar contra interfaces y no contra implementaciones concretas. Se debería abstraer en interfaces las partes que consideramos sujetas al cambio. En otras palabras, componentes que dependen de otros deberían interactuar a través de abstracciones (interfaces) en lugar de clases especificas.
La ventaja principal del principio es que programando contra interfaces los diferentes componentes pueden desarrollarse y cambiarse independientemente de quien los usa (porque el cliente emplea su “contrato”). La otra ventaja es que obtenemos código mas testeable.


Tomaré un ejemplo prestado una vez más:


interface ISearchProvider
{
List<string> FindAll();
}

public class SearchController : Controller
{
//Se programo contra una interfaz y no contra una
//implementacion
private ISearchProvider searchProvider;
public SearchController(ISearchProvider provider)
{

this.searchProvider = provider;

}
}
public class ProductRepository : ISearchProvider
{
public List<string> FindAll()
{
throw new NotImplementedException();
}
}


La clase SearchController usa un objeto que implementara la interface ISearchProvider. Es decir, no especificó qué clase empleará con el contrato ISearchProvider, sino que indico cuál es esa interface y nada más.


DependencyInversionPrinciple


Para profundizar muy buena esta presentación: http://ircmaxell.github.io/DontBeStupid-Presentation/


Estos son todos los principios SOLID, pero existe otro principio unificador que de alguna forma aglutina todos estos para darle un sentido más preciso: INVERSION DE CONTROL….en otro post.Guiño

jueves, 16 de mayo de 2013

Mock



El "mockeo" de objetos es una tecnica para construir un objeto "falso" que se comporta de forma fija y con poca "inteligencia"  ante las llamadas a sus métodos y propiedades. En otras palabras es una implementacion "semi-harcodeada" de un contrato (inteface).
El uso habitual del mockeo se da cuando estamos trabajando con Unit Tests y en general dentro de una activdad Guiada Por Pruebas (TDD).

//Falta desarrollar

Vamos a probar primero MOQ que es free y open.


QuickStart oficial de MOQ

Algo interesante ocurre cuando con el moq.setup() configuramos la llamada "mockeada" a un método el cual como respuesta necesitá setear una propiedad de su objeto contenedor (que en este caso estamos"mokeando"): para eso nos ayudamos con moq.SetupProperty() y Callback(). Pero para que CallBack() “sepa” cuál es el parametro con que se llamó el “metodo” del Setup(), hacemos asi:

view.SetupProperty(o => o.LastMessageStatus); //se habilita la property.view.Setup(o => o.NotifyStatus(Moq.It.IsAny<object>()))
    .Callback((object s) => view.Object.LastMessageStatus = s);
// el callback toma el parametro


Ejemplo básico de uso de MOQ

Un muy buen artículo del creador de Moq sobre cómo mocks & stubs son complementarios en la práctica.