archivo

Archivo de la etiqueta: programador

Para Donald Norman: «Los objetos bien diseñados son fáciles de interpretar y de comprender ya que contienen pistas visibles de su funcionamiento».

Un código fácil de leer y de entender en entornos altamente colaborativos como son los equipos de trabajo ágiles resulta esencial. También lo es para otros tipos de enfoques.

No es sencillo, se requiere tiempo, conocimientos y experiencia, también nos podemos apoyar con herramientas que nos ayuden a aplicar buenas prácticas y también se requiere ser sistemático y creer en los beneficios que aporta hacer el trabajo de esta manera, de lo contrario se te pondrá muy en cuesta arriba refactorizar, tanto tanto, que no lo harás.

Hacer esto bien requiere dedicarle el tiempo que necesita, cuando estamos en proyectos ligados a plazos y los recursos disponibles son muy limitados, se convierte en objetivo ejecutar todo el trabajo posible y se deja de lado la calidad del código ya que en esos momentos conceptos como la deuda técnica no se encuentran entre las prioridades del desarrollador. Es cierto que es contraproducente ya que una deuda técnica elevada hace más pesado el proceso de desarrollo pero cuando el fuego te está llegando a los pies solo piensas en correr.

Por ese motivo creo mucho más en el valor del software que en las agendas, un buen software requiere del tiempo y de las personas necesarias para hacerlo, sin equilibrio la calidad del software sufre (también la cuenta de resultados del proyecto).

Teniendo en cuenta que el desarrollo de software tiene una naturaleza colaborativa (participan personas y se necesita de todas ellas para sacar el trabajo adelante) se entiende que cuanto menor sea la separación (y no hablo de distancia física) entre las diferentes personas que participan en el proyecto, mayor probabilidad de éxito tendremos.

Por eso soy de la opinión de que los desarrolladores deben tratar con los usuarios en todas las etapas o ciclos de desarrollo de un producto. El feedback no solo se trata de palabras sino también de sensaciones y esto último te lo pierdes si no tratas con las personas que van a usar la aplicación. Por mucho que te lo cuenten no es lo mismo.

Es posible que existan proyectos donde la relación entre las partes sea tan mala que tal vez lo mejor sea utilizar intermediarios, en cualquier caso debería tratarse de una situación transitoria porque normalmente la mejor decisión que se puede tomar en estos casos, si no se consigue reconducir, es llegar a un acuerdo para dar por terminada la relación contractual.

Crear barreras e interfaces artificiales entre personas solo originan distancia y ese incremento de la misma afecta inversamente al conocimiento. Deja que el desarrollador tenga su propia perspectiva, deja que los usuarios interaccionen con los desarrolladores, dejan que compartan opiniones, deja que aprendan juntos.

Recordemos que se trata de ir eliminando esa distancia entre lo que los desarrolladores creen que hay que hacer y lo que los usuarios creen que quieren. Las diferentes versiones del producto irán aproximando esas visiones a la del producto que se espera y, como es lógico, es más efectivo si todas las partes contrastan sus perspectivas.

Sistemas altamente acoplados, con clases y métodos kilométricos, con código que resulta complicado de entender son ejemplos en donde realmente se programa con miedo una vez que se comprueba que tocarlo supone implicaciones hasta en las partes más insospechadas de la aplicación.

Si el sistema además es crítico el miedo pasa a convertirse en pánico.

Tal vez las palabras miedo o pánico sean demasiado exageradas (y no es nada positivo trabajar de esa manera) pero por lo menos el programador debe sentir respeto y estar alerta cuando realiza modificaciones en un sistema de este tipo. El exceso de confianza no es bueno y más en un caso como este.

Hay que evaluar si el cambio que se va a hacer en el sistema es algo puntual o si se sabe que van a existir una serie de cambios a corto y medio plazo, así como la magnitud de los mismos y la magnitud del sistema. El esfuerzo que supone realizar estos cambios (mucho mayor que en un sistema con una deuda técnica ajustada a sus características), el esfuerzo en testing y el riesgo que tiene liberar nuevas versiones de estos sistemas debe ser analizado antes de afrontar la mejor estrategia para el cambio.

En condiciones como esta, cambios que sean fruto de un capricho no deben ser contemplados y cambios muy relevantes que supongan una modificación sustancial del sistema podrían dar lugar a que se tome la decisión de desarrollar una nueva aplicación.

El programador por su parte puede tomar sus precauciones: testing unitario, automatización de determinado testing de mayor nivel, etc…

Por otro lado, el paso a producción de nuevas versiones de sistemas de estas características no debe tratarse a la ligera y esperar cruzando los dedos a que no pase nada.

El testing debe ser realizado en profundidad y desde el punto de vista funcional debería ser la combinación de casos de prueba y testing exploratorio.

Puedes trabajar con factorías de software siguiendo el modelo que más pueda convenir: Offshore, Nearshore, Onshore y conforme exista más distancia entre los equipos que tratan las especificaciones y el equipo que las desarrolla más mecánico se convierte el trabajo del equipo de programadores: «toma las especificaciones, te las modelo y desarrolla según la arquitectura y framework pactado».

Es posible que esa distancia la puedan reducir los equipos de trabajo aplicando distintos tipos de técnicas y herramientas porque como he dicho en otras ocasiones el impacto de la distancia depende mucho de la actitud de todas las partes. Sin embargo cuando por encima de los técnicos, hay jefes de proyecto o gerentes «contables» la actitud (si existe) queda difuminada y se entra en un peligroso modelo de trabajo basado en el antipatrón «arrojar al otro lado del muro».

Como bien dice un amigo, no hacen falta factorías de software para caer en ese antipatrón, te puede pasar perfectamente con el proveedor incluso compartiendo oficina.

En el momento en que se entra en ese juego la programación desgraciadamente se convierte en una actividad mecánica: el programador se limita a ejecutar tareas, conociendo solo el sistema que se desarrolla a bajo nivel. Con esto perdemos el feedback del programador tanto a nivel técnico: tal vez sean recomendables ciertos cambios en la arquitectura o el framework para adaptarlos mejor a la naturaleza del software que se desarrolla, como funcional: Esto no termina de ser coherente con otros módulos del sistema y es necesario que las especificiones sean más claras o que se estudie esa falta de consistencia con el usuario.

En un proyecto todos suman, los programadores por supuesto también (y mucho). Todas las personas que están en el proyecto están capacitadas para aportar ideas y soluciones, tengan el rol que tengan, a veces estarán acertadas y otras se equivocarán pero lo que no podemos hacer es dejar de escuchar sus opiniones porque estaremos despreciando toda la capacidad y talento de los componentes de nuestro equipo.

Jerry Weinberg considera que: «Cualquiera que haya visto a un programador trabajando… sabe lo que es la programación en sí, si al programador se le da la oportunidad de hacerlo a su manera, es la mayor motivación que puede tener el programador».

Podemos extender la reflexión de Weinberg al concepto de desarrollador.

Estoy de acuerdo si bien es importante diferenciar autonomía de anarquía. Trabajas de manera autónoma si se te indican cuáles son tus objetivos (o bien desde tu rol en el proyecto o en la organización te los defines tu) y conociendo y respetando las reglas del juego (procesos, enfoques, metodologías, estrategias de diseño, herramientas, etc…) además de tener siempre presente que trabajas en equipo tienes libertad para realizar la solución.

Para que exista autonomía es necesario que las reglas del juego den el suficiente margen de maniobra. También es necesario entender que lo mismo todos los roles (y dentro de cada rol las personas que lo componen) no tienen la misma autonomía para realizar su trabajo ya que dependerá del tipo de proyecto, del tipo de sistema que se desarrolla, de las propias reglas del juego y de la experiencia y conocimientos del desarrollador.

El trabajo que se realiza de forma autónoma motiva y por eso es importante desarrollar este esquema de trabajo. Es cierto que al principio costará un poco que la maquinaria funcione ya que será necesario que cada uno entienda que la autonomía implica responsabilidad contigo mismo, con tus compañeros y con el proyecto y para que se pueda trabajar de manera adecuada hay que respetar las reglas el juego.

Hay quien se pierde trabajando de esa manera pero principalmente es provocado por el hecho de no entender o eludir esa responsabilidad y por la falta de una visión colaborativa en el desarrollo de software. Como es lógico implantar este esquema de trabajo requiere de personas que funcionen en él.

«¡Esto está terminado!».

¡Cuánto daño ha hecho esa frase!. Un mal endémico en los desarrolladores es que entendemos que hemos terminado nuestro trabajo tras la última línea de código de la tarea que tenemos encomendada.

Consecuencia: estamos dando por completadas tareas, historias de usuario o funcionalidades con incidencias que incluso pueden afectar colateralmente a otras partes del sistema. Estos errores a veces salen a la luz pronto, otras veces más tarde y otras demasiado tarde. Esta mala práctica pone en jaque al cumplimiento de los compromisos de una iteración y por encima de esto a los propios hitos que nos hayamos marcado en el proyecto.

¿Por qué? Se deja nuestro compromiso en manos de la inspiración y todos sabemos que si hay algo que hace daño a los mismos son las sorpresas. El desarrollo de software debe alejarse todo lo que se pueda del azar y precisamente eso es lo que estamos haciendo cuando no se hace testing sobre lo que se desarrolla o el testing que se realiza es demasiado débil.

Las pruebas unitarias son insuficientes en muchos casos porque no debemos olvidar que muchas piezas de software hay que integrarlas. Tampoco debemos olvidar que el hecho de que un algoritmo haga lo esperado no quiere decir que hayamos cumplido el objetivo funcional porque lo mismo las bases sobre las que se ha implementado el mismo son incorrectas.

Es un hecho conocido por todos que el coste de resolver un problema es mayor cuánto más lejos se detecte del momento en que se originó ya que se ha seguido construyendo sobre esa base y porque la deuda técnica habrá crecido.

Cuesta mucho menos dedicar algo más de atención al testing que todas las tareas que hay que realizar después para corregir esos defectos. Y hay todavía un coste mayor que ese, que no es otro que la pérdida de confianza que se produce por el incumplimiento de los compromisos y la falta de calidad de los productos que entregamos.

Interesante la reflexión que Martin Fowler realiza en su libro «Refactoring: Improving the Design of Existing Code» (traducción libre): «Al compilador no le preocupa que el código sea feo o limpio. Pero cuando cambiamos el sistema, hay una persona involucrada y ahí sí que importa. Un sistema mal diseñado es complicado de modificar».

En última instancia somos las personas las que tenemos responsabilidad sobre la deuda técnica no hay nadie más entre nosotros y el código. Es cierto que podemos apoyarnos en generadores de código y en frameworks pero es decisión de la organización, del departamento o nuestra elegirlos y en muchos casos de su evolución. Son importantes en cuanto a que te evitan la realización de tareas mecánicas y en cuanto a que marcan un camino común en el desarrollo pero hay que tener en cuenta que es importante que el código generado sea de calidad porque es posible que en un futuro el equipo que se vaya a encargar de seguir evolucionando el producto no disponga de ese generador (desgraciadamente es algo que casi nunca se tiene en cuenta).

Hay software como Sonar que utilizado de manera conjunto con un software de integración continua nos permite conocer al día el estado de la deuda técnica pero se trata solo de métricas que nos pueden alertar de situaciones anómalas (o potencialmente anómalas) al final habrá una persona (no necesariamente un gestor ya que el programador también tiene mucho que ver en esto) que tomará la decisión de refactorizar o no determinadas partes de la aplicación.

Lo más recomendable es que la propia refactorización sea parte del proceso de desarrollo que surja como algo natural y que nos apoyemos en software para conocer si hay módulos en donde el trabajo de refactorizar no ha sido suficiente o no ha sido bueno.

Este antipatrón surge en aquellos casos en los que los desarrolladores (o el equipo de desarrollo) dan por correctas determinadas funcionalidades sin haber pasado el suficiente número de casos de prueba que permitan testearlas teniendo en cuenta diversas casuísticas en los juegos de datos, en la interacción con terceros sistemas o en la propia operativa del usuario.

El testing de fogueo es una de las causas que provocan que un mayor número de defectos se detecten más tarde de lo que debieran, como por ejemplo en la integración con otros módulos, con otros sistemas o incluso en producción.

Aplicando testing de fogueo se obtiene una imagen del estado del proceso de desarrollo que no se corresponde con la realidad, por eso insisto mucho en que las funcionalidades tienen que estar bien probadas porque de lo contrario se crean expectativas en cuanto al avance de los trabajos que pueden provocar que, cuando nos demos cuenta de los problemas, sea demasiado tarde para reaccionar.

No se debe confundir con el antipatrón: «No probado pero finalizado, ya que este tipo de prácticas no son necesariamente provocadas por la necesidad de cumplir unos plazos más o menos complicados, sino que vienen «de serie» con los desarrolladores.

Ya lo decía Doug Linder: «Un buen programador es aquel que siempre mira a ambos lados antes de cruzar una calle que tiene un único sentido«.

Este antipatrón surge como consecuencia de múltiples cambios en una aplicación que se llevan a cabo de forma simultánea en el mismo, de manera que es posible que se haya aplicado la misma solución en diferentes partes del código, de manera que tendremos clases y/o métodos con un comportamiento parecido aunque tengan codificación diferente.

También es posible que se haya llegado a él, no por el hecho de no poder controlar la programación de personas distintas en diversas funcionalidades de la aplicación, sino por la mala práctica de ir copiando secciones de código, realizando las modificaciones oportunas, achacándolo a las prisas y a la presión por la entrega y/o por terceros.

Como consecuencia tenemos un código más complejo, con más deuda técnica y, por tanto, con un mayor esfuerzo de evolución y mantenimiento (hay que tener en cuenta que es posible que con estas prácticas, para hacer un cambio sea necesario hacer modificaciones en tantos puntos como ese código haya sido replicado).

El principio de Peter fue popularizado por los canadienses Laurence Johnston Peter y Raymond Hull en un libro homónimo publicado en 1969, con la famosa cita: «En una jerarquía, todo empleado tiende a ascender hasta su nivel de incompetencia… con el tiempo todos los puestos tienden a ser ocupados por un empleado que es incompetente para desempeñar sus funciones… El trabajo es realizado por aquellos empleados que todavía no han alcanzado su nivel de incompetencia».

En ella trata de exponer uno de los principales inconvenientes de las organizaciones con promoción profesional vertical, en las que personas que son altamente competentes en unos puestos concretos, dejan de serlo en el momento en que ascienden a un puesto para que el que se requieren otras aptitudes.

De por sí, por típico en nuestro sector, podría ser considerado un antipatrón más. Sin embargo, en este artículo vamos a ver una versión de dicho principio directamente aplicada al desarrollo de software.

¿En qué consiste? Pues en el deterioro progresivo que sufre un software como consecuencia de su evolución y mantenimiento. Esto es una circunstancia que de manera general se produce en el desarrollo de software si bien puede ser contenida con buenas prácticas o acelerada en el caso contrario.

El antipatrón surge cuando un sistema está en continua evolución y no se toman medidas para mantener bajo control la deuda técnica y para seguir simplificando en lo posible su arquitectura y sus funcionalidades, de manera que si el sistema fue alguna vez útil, dejará de serlo poco a poco.

Como es algo que se suele producir lentamente y que tarda en detectarse, sobre todo si no estás acostumbrado a tener en cuenta estos aspectos, muchas personas consideran que es una forma de muerte silenciosa del sistema de información.

El principal motivo es la falta de cultura de producto por parte de los desarrolladores centrados principalmente en una cultura de proyecto orientada a la agenda y, por encima de ellos, de las organizaciones proveedoras y clientas de este tipo de servicios, que dejan en un segundo plano la calidad del software para centrarse en costes y beneficios.