¿Los nuevos patrones ayudan a la mantenibilidad de aplicaciones?

Si tuviera que decir que funcionalidad del Visual Studio (y de VB6 ya puestos) he estado usando mas ultimante, la respuesta seria «Go to Definition» (seguida de «Find All References»).

La razón de esto es debido a que ahora me corresponde estar en mantenimiento de aplicaciones, lo cual implica que cuando hay un error tratar de encontrar que metodo o clase fallo y ver que hacer para solucionar el error, y en otras ocasiones añadirle una funcionalidad a cualquiera de las aplicaciones ya existentes (en cuya creación yo no intervine).

Como ya explique anteriormente esta es una situación en la que no se empieza de cero, y se requiere tratar de entender de manera rápida el como esta montado todo a fin de encontrar la raiz del problema, por lo cual usualmente tengo dos caminos:

– Ubicar la aplicación e ir haciendo una «depuración sin ejecutar» mediante el «Go to definition» tratando de ubicar los puntos de paso críticos que hay durante la cascada de llamadas de los sucesivos métodos involucrados en la funcionalidad.
– Buscar el texto con el mensaje de error reportado, y de esa manera mediante el «Find all references» identificar de abajo hacia arriba la posible pila de llamadas y en el camino encontrar lo que ocasiono el problema.

Pero llegados a este punto vemos que se ha incrementado la demanda por desacoplamiento y pruebas unitariasdentro de nuestros codigos, todo como parte de los patrones SOLID:

* SRP: The Single Responsibility Principle (Principio de Responsabilidad Única)
* OCP: The Open/Closed Principle (Principio Abierto / Cerrado)
* LSP: The Liskov Substitution Principle (Principio de Sustitución de Liskov)
* ISP: Interface Segregation Principle (Principio de Segregación de Interfaces)
* DIP: The Dependency Inversion Principle (Principio de Inversión de Dependencias)

Principios muy interesantes, que valen la pena revisar, pero que introducen tendencias como la Inyección de Dependencias y la Inversión de Control, para lo cual recomiendo este muy bien explicado articulo de Gisela Torres de donde extraigo esta explicacion;

Generalmente, cuando tenemos una clase que depende de otras para ciertas acciones, necesitamos inicializar instancias de las mismas para poder utilizarlas posteriormente. En ocasiones, una de las alternativas puede ser crear un objeto de dichas clases de forma privada e inicializarlas, utilizando el constructor de la clase principal.

Si vemos estas acciones desde el punto de vista de la Inyección de Dependencias y la Inversión de Control, no sería la forma más óptima debido a que la clase que sufre estas dependencias no debería ser la responsable de la creación de las mismas.

¿QUÉ CONSEGUIMOS?

  • Desacoplamiento.
  • Mejora la posibilidad de testeo de la aplicación.
  • Mejor mantenimiento a la hora de realizar cambios de los componentes, gracias a la modularidad.
  • […]

Ambos conceptos están tan ligados que, en ocasiones, no se hace distinción. Se utiliza el concepto Inversión de Control para delegar en otro componente, un framework por ejemplo, la responsabilidad de crear las instancias necesarias en lugar de crearlas nosotros mismos. Por otro lado, la Inyección de Dependencias es el término utilizado cuando una clase depende de otra y, a través del constructor generalmente acepta un parámetro del tipo del cual depende. 

Siguiendo el ejemplo, podemos ver que en lugar de el tradicional mecanismo de instanciación de la clase que necesitamos, por detras existe un interesante mecanismo de Configuración y Factoria de Objetos que nos permiten que en lugar de:
public class HomeController : Controller
{
private readonly ITwitterService _twitterService;
public HomeController()
{
_twitterService = new TwitterService();
}

public ActionResult Index()
{
return View(_twitterService.FetchTweets());
}
}

Tengamos:
public class HomeController : Controller
{
private readonly ITwitterService _twitterService;

public HomeController(ITwitterService twitterService)
{
_twitterService = twitterService;
}

public ActionResult Index()
{
return View(_twitterService.FetchTweets());
}
}

Nada mal según parece, usamos interfaces, y se logra una mayor facilidad para pruebas unitarias, lo cual es uno de los objetivos a lograr, pero oh sorpresa… si ahora le hacemos «Go To Definition» al FetchTweets notaremos que ya no vamos a la implementación del método en cuestión (definido en la clase TwitterService), sino al cuerpo de la Interfaz ITwitterService, lo cual detendría nuestra búsqueda en cascada, al no tener mucha idea de cual es la implementación real que se esta invocando, por lo cual nos quedan dos potenciales soluciones:

– Correr la aplicación y en tiempo de ejecución ver la implementación real para así tomar las medidas de análisis necesarias a fin de resolver el problema.
– Tratar de entender la configuración de las dependencias y así inferir cual es la clase que se esperaba instanciar.

Como se comprenderá esta circunstancia puede hacer complicado una tarea de mantenimiento, y la idea de que esas tendencias pueden significar un problema, no en el tiempo de desarrollo, sino en posteriores mantenimientos me han dado vueltas en la cabeza durante un buen tiempo, hasta que encontré este interesante articulo, del cual cito y traduzco:

Yo lo he experimentado de primera mano, en muchos diferentes proyectos donde he tenido que hacerme cargo de codigo despues de que un desarrollador dejo la compañia, y puede ser una experiencia exasperante. Una mejora es solicitada, o un bug es reportado, digamos que se trata de un bug. Se me puede haber dado una excepcion en la pila, y eso al menos me lleva al codigo fuente en donde empezar. Lo que usualmente encuentro es un bonito set de pruebas unitarias que puedo correr y comprobar que todas se ejecutan correctamente, lo cual no es una gran sorpresa; pero, todos los tests usan «mocks» por lo que no puedo reproducir inmediatamente el bug como no sea escribiendo un lindo mock test para simular como el código debería responder ante un particular error de entrada, puesto que yo puedo «mockear» facilmente el error. Eso esta bien, pero no es util si mi objetivo principal es evitar que el error ocurra.

¿Que clase en concreto es la responsable del bug? Aqui es donde se pone complicado, en la mayoria de los casos, la pila no te da ninguna pista. Leer el codigo y examinar la configuración puede ser una enorme perdida de tiempo y termina siendo una tediosa búsqueda en amplitud.

Como el autor apunta «la capacidad para obtener información rápidamente y entender un programa al leer el código es degradada por IoC debido al desacoplamiento», y así.. esa seria la alerta con la que nos deberíamos quedar, ver cual es el lugar de estos patrones, y si la búsqueda de objetivos en este momento como el desacoplamiento y las pruebas unitarias, no vendrían a significar un lastre en el largo plazo, cuando la aplicación tenga que ser mantenida por un equipo diferente.

El paso por mantenimiento de aplicaciones te hace tener en cuenta estas otras consideraciones.

Otro articulo interesante:
Dependency Injection makes code unintelligible