La nefasta obsesión por “entregar valor al cliente”

Y uno al leer el título podría pensar que se esta incurriendo en un desprecio hacia el cliente que nos ha encargado el desarrollo de un software, pero nada más lejos de la realidad, la reflexión viene a algo que aun sigue siendo mal entendido por quienes estamos involucrados en el desarrollo de software.

No cuento nada nuevo que tradicionalmente (modelo “cascada”) el desarrollo de software tiene un historial de fracasos totales o parciales, ya sea en funcionalidad incompleta, sobrecostos de tiempo y dinero, lo cual lleva como consecuencia en que los aun medianamente exitosos tengan la presión por conseguir entregar “lo que se pide” “a tiempo”, y claro muchas veces sin importar el “cómo” que es a lo que nos lleva el título de este post, el cómo la obsesión por entregar algo que el cliente pueda usar hace que se pierda la perspectiva global del problema.

Y el problema (deuda técnica) ya es conocido: métodos de más de 400 líneas de código, código muerto y/o duplicado, lo cual deriva en poca mantenibilidad, bugs como consecuencia de la extensión de la funcionalidad, pobre performance, etc; síntomas que hacen que el que hereda un código desee ir corriendo tras el que hizo el código originalmente, (y un cliente pagando resignado los costos de implementar un parche o una mejora menor).

En este punto se nos podría decir que esto ocurre porque el equipo de desarrollo no aplicó un buen patrón de diseño, que no siguió los principios S.O.L.I.D., lo cual si bien tiene algo de cierto no es sino la segunda capa de un problema mayor que veremos en breve.

Se pensaría que con la irrupción de las metodologías ágiles la situación ha estado cambiando, pero no, la realidad no es tan hermosa, pues si bien ahora tenemos más visibilidad sobre los elementos (historias de usuario) que se están desarrollando no debemos de olvidar que las metodologías imponen un proceso de priorización, en el cual lamentablemente las historias funcionales (si, las que “dan valor”) toman precedencia siempre sobre las técnicas, las cuales tienen que rogar para hacerse un lugar en el sprint o en el flujo del Kanban, y muchas veces nunca logran entrar por la sencilla razón de que “no hay como justificar ante el cliente algo que no se ve que le reporte valor”. Así pues, se cumple con la entrega de funcionalidad continuamente, pero seguimos acumulando deuda técnica pues, a pesar de seguir perfectamente el proceso, sólo nos estamos enfocando en entregar un software que funcione sin fijarnos en qué es lo que hay detrás de este software que se entregó perfectamente en plazos pues lo importante es que funcione, ¿no? ¿qué hay deuda técnica? tranquilos, eso lo solucionamos con una refactorización circunstancial (léase: arreglamos cuando podamos y no nos quite mucho tiempo).

Recomiendo leer este interesante articulo de Lucas Ontivero donde nos cuenta como las cosas han seguido yendo mal en el lado técnico a pesar del agilísimo.

Adicionalmente tenemos el factor del QA (Quality Assurance), uno podría creer que en empresas grandes este equipo tendría una visión integral de la calidad del producto, lo cual debería incluir tanto la verificación de la mantenibilidad y calidad del código como de la validación de la arquitectura, pero no, al parecer su visión va sólo al lado funcional, o sea a lo “visible” del gráfico anterior, siendo que temas como la mantenibilidad “lo ve el equipo de desarrollo”, así pues por un lado se exige un cumplimiento funcional (muchas veces sin entender las razones por las que “la gente de desarrollo” opto por implementar de una manera diferente a como QA lo pensaba) estricto pero por otro lado se omite la validación de la calidad de lo técnico, pues claro… no es lo que ve el cliente de manera directa, por más que este sea el que luego tenga que pagar los “intereses” de la deuda técnica(*). No se ustedes pero si un equipo que se supone se encarga de la “calidad” durante el ciclo de desarrollo ignora la parte técnica de la implementación (¿alguien dijo auditorías?) , le esta haciendo un flaco favor al proyecto y al cliente.

Así pues mucho del problema viene dado por la importancia que se le quiere dar al elemento técnico del proyecto, el cual viene tradicionalmente es relegado por debajo del administrativo, siendo que muchos profesionales hacen gala de haber tocado muy poco los elementos correspondientes a la programación en su formación profesional, lo cual en mi opinión generalmente (he conocido excepciones) ocasiona que no se sepa (debido a que a veces no se entiende) el impacto de una decisión técnica que se tome en cualquier etapa del desarrollo, razón por la cual prime el componente funcional/visible a la hora de definir prioridades en el desarrollo.

Afortunadamente hay caminos de solución, uno de ellos es tratar de entender qué es lo que ha derivado en la creación del Manifesto for Software Craftsmanship como una evolución del Manifiesto Agile (esto lo explica muy bien Edson en esta presentación); y por otro lado, dentro del marco de las metodologías ágiles, reconocer de manera visible el rol que deben de jugar los componentes técnicos, en ese sentido me sorprendió muy gratamente como en su reciente conferencia "Lean from the Trenches" Henrik Kniberg indicó claramente cómo dentro de su proceso ágil (basado en Kanban) había reservado, dentro de su tablero, el espacio necesario para las historias técnicas, de esta manera

Es que como Henrik dijo, en un mundo ideal sólo habría historias de negocio, pero no estamos en un mundo ideal, los problemas técnicos existen y no podemos pretender seguir ignorándolos, o creer ingenuamente que efectivamente lo “solucionaremos después”, o ya en el lado extremo seguir en la ilusión de que cualquier programador podrá implementar algo si le damos una especificación funcional completa y detallada.

Entonces, regresando al título de este post, creo que debe de quedar claro que no es que no tengamos como meta el entregar funcionalidad y software, sino también validar el cómo llegamos a ese entregable, procurando (entre otras cosas):

  • Dar el espacio a las historias técnicas cuando corresponda, y no relegándolas a cuando “haya tiempo”.
  • Sospechar del programador que defiende una mala implementación con un “pero funciona”.
  • Ser consciente de los riesgos que significa ir por el camino rápido en aras de entregar la funcionalidad pedida.
  • Que todo el equipo (y no sólo el que implementa) sea consciente de lo que hay en la implementación (arquitectura, algoritmos, etc) y de como eso condiciona los tiempos de desarrollo, especialmente cuando se trata de agregar una nueva funcionalidad.
  • Valorar el factor técnico, procurar la mejora de las habilidades técnicas de los desarrolladores: mentorship, revisión por pares, dojos, etc.

Claro que mucho de esto tendría que ir de la mano con un reconocimiento dentro de las organizaciones al rol que juegan las TI en su crecimiento como negocio, pero por ese lado la cosa esta bien complicada.

(*) Se entiende como “intereses” de la deuda técnica al hecho de que conforme pasa el tiempo será más difícil corregir las malas decisiones o malas implementaciones de código, ya sea porque el desarrollador que implementó algo de manera críptica ya no se encuentra en el equipo, porque nueva funcionalidad superpuesta generó más dependencias o simplemente porque por el tiempo que pasó ya es más difícil acordarse el porque se tomó esa decisión que “parecía buena en su momento”.

¿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

No siempre empezaras desde cero

Como desarrolladores generalmente esperamos estar desde el arranque de un proyecto, con la idea de que asi “esta vez hacer las cosas bien” usando una buena arquitectura y claro, con lo ultimo de la tecnología, al final terminamos llegando al estado de “parecia una buena idea en su momento” y que hay que terminar el proyecto “como sea”, bueno… exagero un poco, pero debe quedar claro que estar en el proyecto desde un inicio no es una garantia para que todo vaya como una seda todo el tiempo.

Pero para bien o para mal, si uno es un desarrollador “desde las trincheras” y no un “guru”, no sera rara la situación en que nos veamos en la necesidad de dar mantenimiento a un programa que alguien mas haya hecho o empezado (en cualquier grado de avance), y ahí es donde empieza nuestra historia…..

Antes de proseguir, debo decir que cuando he estado una buena temporada en un proyecto, trato de pensar (sobre todo en las ultimas etapas) en “como hacerle mas simple la vida al pobre tipo que se hara cargo de esto” para lo cual opto por un enfoque realista, dejar comentarios en las secciones de código que se que pueden ser mas crípticas, y de ser el caso dejar documentación útil, ¿y cuando una documentación es útil? ¿no se supone que lo útil es documentar todo lo posible? ¡Error! una documentación es util cuando uno honestamente sabe que leyéndola proveerá una idea para entender la lógica detras de la implementación de un código.

Entonces como decia, nos encontramos en la situación de que hay una aplicación existente, en cuya creación no hemos intervenido, y se nos pide ver porque ha dejado de funcionar o que le agreguemos una nueva funcionalidad, la documentación es escasa y con suerte tenemos las referencias de los servidores involucrados.

Asi que con esta premisa vamos tras el codigo fuente a ver que podemos sacar en claro (*), con lo que nuestra rutina es mas o menos como sigue:
– Ubicar el punto de entrada de la aplicacion, o sino, los controles y sus eventos respectivos que desencadenan la accion a investigar.
– Ubicado el punto de entrada o el evento deseado (capa de Interfaz de Usuario), ir viendo como se van creando las sucesivas instancias de las capas de Negocio, Fachada, Datos y los métodos involucrados.
– De ser necesario ir depurando para evaluar los valores que van tomando las variables criticas…

En fin, este es el mundo real y creo que alguna vez todos habremos pasado por este lió, siendo nuestros dilemas el saber a que clase/metodo se esta invocando desde un punto del código, así que me he sentido afortunado cuando he podido hacerme una idea del código usando las herramientas para encontrar la “Definicion” de un método invocado.

Es que así es el código típico:

– Método f1 de la clase A necesita llamar al método f2b de la Clase D.
– Método f1 instancia un objeto “miD” de la Clase D.
– Método f1 ejecuta el método miD.f2b.
– Método f2b de la clase D necesita……

Si el código esta mas o menos estructurado (y no es un spagetti por supuesto), esa es la lógica que uno busca, con las debidas excepciones de cuando se tienen que ejecutar instancias remotas y/o usando Interfaces en vez de clases directas.

Una de las cosas que tienen de bueno los entornos RAD como Delphi y .NET (WebForms y Winforms) es que brindan un mecanismo comprensible para empezar a depurar, sabes el control y quieres ver que hace, pues entonces… a ver los eventos vinculados y de ahi para adelante, de capa en capa. Quienes hemos trabajado con ASP Clasico recordamos la pesadilla que eran los puntos de entrada de las paginas, revisando todas las posibles combinaciones de parametros que venia en el Request razon por la que la introducción de WebForms fue la respuesta a nuestros problemas, y para mi lo sigue siendo.

Pero la evolución del desarrollo de software es imparable, y asi hemos visto como han surgido tendencias que han procurado añadir valor a nuestros ciclos de desarrollo como: WebServices, Refactorizacion, SOA y varios intentos de mecanismos para construir una arquitectura distribuida: DCOM, COM+, .Net Remoting, WCF….

Pero al margen de como vayan evolucionando las tendencias, una cosa que siempre procuro en mis desarrollos, es dejar el camino mas o menos despejado para que el que venga luego pueda entender el código que esta leyendo, procurando tener las inevitables sofisticaciones bajo control. Teniendo esto en cuenta… cabe preguntarse si tendencias como el “desacoplamiento” y la necesidad de “pruebas unitarias” facilitan o no la vida del que luego tiene que enfrentarse a mantener tu código, pero ese análisis lo dejo para un posterior post.

De momento quiero dejar la reflexión de que a veces estamos tentados a emplear unos patrones novedosos, potentes, pero… también debemos pensar que no todos los desarrolladores siempre desarrollan desde cero, que se llega a proyectos ya empezados, a código terminado en producción que hay que mantener, y que no hay que complicarle la vida en aras de lucirte con lo mas novedoso en metodologias y arquitecturas si es que no es de veras necesario, ya que estas no solo deben prometerte mas facilidades para ti, si no para el que luego tendra que mantener tu codigo.

(*)Aunque alguna vez no contamos ni siquiera con el código fuente, como me paso en un cliente hace 3 años, por lo que toca ver si podemos desensamblar el código, pero eso ya es otra historia y mas bien un extremo de irresponsabilidad por parte del cliente.

El verdadero monopolio?

Debo confesar que esta noticia habia pasado desapercibida para mi, pero no puedo dejar de comentarla, me refiero a la compra la compra de Siebel por parte de Oracle (elcarO para los amigos).

Y ahora que se avecina, para mi es claro: menos competencia en el sector, con este movimiento Oracle se vuelve lider del sector CRM sumando las cuentas que ha heredado de JD Edwards, PeopleSoft y ahora Siebel, como dice Enrique Dans la integracion de cartera sera una cosa de locos, es por eso que Oracle tenia desde antes su iniciativa “Project Fusion” para lograr la convergencia de su ya crecidita cartera, a la que ahora tenemos que sumar Siebel la cual segun Larry Ellison sera la pieza central de su cartera CRM (obvio, habiendo sido Siebel el producto de referencia).

Lo curioso es que Ellison dice que esta adquisicion fue motivada por solicitud de partners y clientes que preferian trabajar con un solo proveedor, personalmente tengo mi escepticismo al respecto pero eso nos lleva a el dilema que muchas empresas se planteen sobre el tener multiples proveedores de TI o uno solo, personalmente creo que con variantes la respuesta es: pocos, pues si dependemos de uno solo cortamos nuestras vias de escape ante la presion del mega proveedor, si no… por que los mainframes aguantan? no es solo por el legado y una supuesta solidez sino tambien por presiones economicas debido a la dependencia con el proveedor acumulada en los años.

Esto nos lleva a la situacion en la que puede estar un cliente de las empresas absorbidas, si su aplicacion estaba en Oracle no habia problemas, pero si estaba en SQL Server o DB2 la cosa se complicaba un poco, pues ya habia que plantearse la migracion a Oracle (pese a los anuncios de que se mantendria el soporte por unos años, el mensaje no era tranquilizador) o cambiar a otro proveedor de la aplicacion empresarial, en todo caso al parecer existe la posibilidad de que Oracle decida soportar las BD rivales dentro de sus planes de integracion, pero yo (como los clientes de Siebel) soy esceptico nuevamente pues se supone que la BD es el producto estrella de la corporacion, el gancho para jalar mas hacia su catalogo de productos, aunque en todo caso ya ha decidido soportar el IBM WebSphere lo cual es un avance. Es claro que parte de estos planteamientos tienen en mente el reto que significa SAP que puede atacar el mercado con alternativas que faciliten el no estar atados a Oracle, pues total… ellos no tienen una BD ni un middleware, simplemente los usan.

Esta nueva posicion de Oracle, nos deberia hacer pensar cual es el monopolio real y donde se mueve el dinero, solo nos fijamos en Microsoft pues es lo que vemos en los escritorios todos los dias, pero el core de las grandes empresas va por el lado de IBM y Oracle (me acuerdo cuando en la PUCP en una charla, uno de Oracle dijo “el sueño de todo DBA es que su BD este en Oracle”), segmento donde Microsoft lo ha tenido complicado (por eso plantea su lucha en el sector CRM enfocandose a las PYME, bueeenooo lo que USA y Europa entienden por PYME, no lo que nosotros entendemos) pero se resiste a ceder en la lucha.

Es curioso, un amigo me decia que por que la gente de este sector le tiene bronca a Microsoft y Bill Gates “si justamente Bill Gates ha sido un programador que ha sabido triunfar” y claro si lo ponemos desde ese punto de vista tiene razon, mucha estrategia de la compañia ha sido centrada en los desarrolladores (en un libro se decia que en Microsoft si eras un programador “eras alguien y podias ir con la cabeza en alto”) sino recordemos los mantras de Steve Ballmer “developers, developers, developers….”, por el contrario Larry Ellison, playboy, impecablemente bronceado, hablando con poses estudiadas, fanatico de los campeonatos de vela es la antitesis de un Bill Gates (al que vi en una conferencia en Madrid) desgarbado, informal, algo inseguro para hablar y que siguio programando hasta mediados de los 80, cosa que dudo que Larry haya hecho alguna vez. En manos de uno de estos dos estaremos (si ya se que alguno quisiera meter a Linus Tovarlds en la disputa)….. la pregunta es quien? Alguna idea? Espero sus comentarios.