domingo, 28 de septiembre de 2014

Regla N°5 de la Ingeniería de Software: No te repitas

"He redactado esta carta más extensa de lo usual porque carezco de tiempo para escribirla más breve.", Pascal

Aunque pareciera que la frase anterior, perteneciente a Pascal (el matemático, no el lenguaje de programación), no tuviera que ver con el tema "No te repitas", quise mencionarla porque igual que dicho tema, representa la idea de que hacer las cosas bien lleva tiempo, y que un aparente volumen superior de "líneas" no representan más trabajo, ni más esfuerzo, sino un conjunto de datos innecesarios (o repetidos) que restan valor al resultado de nuestro trabajo. Por ejemplo la pregunta correcta no sería ¿que pesa más un kilo de paja o un kilo de plomo?, sino ¿que es mas manejable un kilo de paja o un kilo de plomo?

La regla "No te repitas", hace referencia a que no se deben repetir porciones de código, con funcionalidades iguales, "casi iguales", o parecidas a los largo del código. Aunque técnicamente funcione y de el resultado correcto, aumenta el grado de "degeneración del software" (el tiempo en que se echa a perder un software, hasta que ya no es útil su funcionalidad).

Recuerdo una vez en que me toco auditar un sistema auxiliar en que se tenía que hacer una serie de sencillas copias desde una fuente de información a otra, aplicándole una serie de transformaciones. Las fuentes eran diversas y los destinos también, en este caso se cayó en el ejemplo de "casi se parece", mas sin embargo, si se elijo hacer un proceso completamente independiente para cada fuente de información, pensando en la sencillez de hacerlo así, por la rapidez y por resultar así más cómodo. Inevitablemente se aplico la regla "Va a cambiar" y la regla "Va a fallar". A lo largo del proyecto cambiaron en multitud de ocasiones el formato y las características de las fuentes de datos de los orígenes y de los destinos, teniendo que modificar una y otra vez todo el código fuente y replicando ese código a cada uno de los escenarios "que casi se parecían". Evidentemente todo esto se pudiera haber evitado con una correcta programación orientada a objetos, donde el código "casi igual" se hubiera abstraído para convertirse en la clase base, y las particularidades de cada fuente en clases heredadas.

En el caso anterior solo es un ejemplo sobre como querer hacer algo rápido, acaba convirtiéndose en retrabajos innecesarios e inevitables horas extras.

Un sistema lleno de código repetido, oculta un diseño pobre del sistema, que evidentemente tenderán a "salir a flote" en el peor momento posible.

Algunos motivos de un código repetido son:

  • Como mencionaba, un pobre diseño de la solución a resolver (precedido posiblemente por un pobre análisis).
  • Un equipo poco integrado y con poca comunicación. Me ha tocado ver que cada integrante de un equipo de construcción, creara exactamente una misma funcionalidad, pero cada uno implementándola de una manera diferente según sus necesidades y criterios.
  • Una arquitectura deficiente, que obliga una y otra vez a repetir lo mismo.
  • Programadores que quieren tomar atajos, o lo que es lo mismo "pan para hoy y hambre para mañana" (y generalmente "mañana", es el día siguiente, literalmente).
  • Falta de comprensión de la programación orientada a objetos: Esta es más frecuente de lo que se pudiera pensar, si bien muchos codificadores tiene claras las bases de la programación orientada a objetos, les difícil situar dichas bases en un contexto practico y real.
  • Miedo a tomar el código existente por si llegara a dejar de funcionar: unos tres programadores "miedosos" encadenados y el código será imposible de comprender...

Para evitar tener código repetido, algunas recomendaciones serian las siguientes:

  • Tener el principio de abstracción siempre en la mente, buscar siempre lo abstracto y común de cada problema, y paramétrizar (o heredar más bien) las particularidades de cada solución
  • Conocer los patrones de software, y buscar soluciones de que nos permitan reutilizar código (o más bien objetos) en ellos.
  • Equipos integrados con buena comunicación, que compartan una serie estándares.
  • Refactoring para eliminar las partes de código repetidas.

domingo, 21 de septiembre de 2014

Regla N°4 de la Ingeniería de Software: Todo sistema tiene un propósito.

Olvidar el propósito de un sistema, es condenarlo al fracaso.

Hace bastantes años unos de mis primeros acercamientos al mundo de sus sistemas fue cuando estaba probando un sistema de PC que permitía dos personas compartir información (casi siempre texto), a través de un módem de 3200 baudios conectado a un puerto COM y a la línea telefónica convencional. No era a través de Internet (que no era ni barata ni común en aquel entonces), sino directamente con un módem llamando a otro módem, a través de un numero de teléfono convencional. Una vez, establecida la conexión, ambos usuarios se les permita intercambia frases y pequeños datos a través de esta con una velocidad extremadamente lenta. No era gran cosa, y sus posibilidades eran limitadas (tan limitadas que hubiera sido mejor una llamada telefónica normal), pero quede fascinado con el mecanismo que permitía a dos computadoras estaban unidas, a kilómetros de distancia, intercambiando bytes entre ellas. Cuando intente compartir con entusiasmo mi entusiasmo sobre la posibilidad en la que dos ordenadores podían intercambiar datos entre ellos, una persona me señalo que lo realmente impresionante, es que dos personas podían intercambiar datos entre ellas. Jamás se me ocurrió verlo así, para mí solo era un intercambio de bytes, cuando realmente representaba una comunicación e intercambio de información entre personas, objetivo real del software.

Esa fue una de las primeras lecciones de ingeniera de software que recibí. No podemos tener una visión puramente técnica de los sistemas y olvidar las necesidades operativas o de negocio para los cuales son realmente creados.

Es común que olvidemos que lo que estamos creando no es un conjunto de variables, y líneas de código, sino que tiene que un significado y relevancia especial fuera del lenguaje de programación en el que los estamos creando. Por ejemplo, una trasferencia bancaria de un millón de dólares errónea no es una posición de memoria mal inicializada, sino que puede ser una pérdida real, o un sistema médico de monitores mal construido puede poner en juego vidas humanas. Hay que pensar en nuestros sistemas por lo que van a poner aportar en un negocio o los problemas que van a poder resolver y no como simples entes tecnológicos.

En metodologías clásicas, en las que esta separado el programador del cliente, y se llega a construir el software por una serie de fases encadenadas claramente diferenciadas, en normal que el programador se centre en los aspectos tecnológicos de la solución olvidando el motivo de la solución misma. Esto crea sistemas técnicamente correctos, pero que sirven de poco, al no satisfacer las necesidades del cliente. En la nuevas metodologías agiles, se intenta que el cliente sea parte del equipo, con lo que es mas fácil comprender su punto de vista y aprovechar su conocimiento para crear un sistema funcional.

Es importante recordar que nuestro sistema es un ente que después va a ser usado por alguien, si nuestra sistema no se pueda usar más que en un ambiento de desarrollo y por nosotros mismos, no resuelve nada. Igualmente Hay que poner el propósito (la necesidad, el por qué se hacen las cosas) en primer lugar y usarlo como nuestro camino y nuestra guía para encontrar la solución.

martes, 16 de septiembre de 2014

El problema del martillo de oro.

Un martillo es una cosa maravillosa, es fácil de manejar, ergonómico, y transportable, maximiza mi eficiencia y minimiza mi esfuerzo. ¡No debiera tener problemas en cambiar esta rueda pinchada a martillazo!

Es frecuente que tengamos herramientas, tecnologías o lenguajes de programación, que parecen idóneos para resolver las necesidades de construcción de un software, a veces parecen incluso idóneos para resolver cualquier problema. Pero sin darnos cuenta habitualmente moldeamos nuestros requerimientos para que se ajusten a nuestra herramienta, y no estamos buscando herramientas que se ajustes a nuestros requerimientos, lo cual limita el alcance y las posibilidades de nuestra solución. Es necesario usar la herramienta adecuada, para resolver cada problema, no podemos quitar un tornillo a martillazos.

El ciclo de vida de estos "martillos" es generalmente el siguiente:

  1. Se propone usar el martillo, ya sea por una orden gerencial, estratégica, porque ya se compro o por decisión del programador.
  2. El algún momento se ensalza tanto el martillo, que se convierte en un estándar, y en la herramienta que todo el equipo (o la empresa), debe usar para resolver sus problemas.
  3. El "martillo" causa "desastres", como errores, mal código (o código poco comprensible), retrasos, y diversas frustraciones en el equipo (junto con horas extras y desencanto en los proyectos).
  4. Se desecha el martillo, bajándolo de su pedestal y se busca un nuevo martillo.

En todo este ciclo, posiblemente se desaprovecho una buena herramienta, usándola en circunstancia en donde no era idónea. Con lo que al final de su "ciclo", se explotaron todas sus desventajas y ninguna de sus ventajas.

Algunos martillos tradicionales son:

  • Todo debe estar en un procedimiento almacenado: Los procedimientos almacenados se convierte prácticamente en todo el sistema, y ellos tiene toda la lógica de nuestro negocio. Usando este martillo, tendremos un sistema difícil de mantener, cuyo diseño se base prácticamente en cómo se almacenan los datos (y no en lo que esos datos representan), y con limitaciones, como falta de programación orientada a objetos, que generalmente no ofrecen los gestores de base de datos, finalmente a ser la base de datos un elemento centralizado, se complica la implementación de un sistema por equipos, incluyendo el uso de herramientas de control de versiones.
  • Todo debe estar en un servicio: Se crea todo un sistema como una serie de llamadas a un servicio (puede ser un servicio web). Básicamente todas las funciones de nuestro sistema están imprentadas como métodos de un servicio, Se pierde el diseño de un software orientado a objetos, y generalmente se construye un software lento.
  • Todo debe ser accesible por un ORM: Toda las consultas a base de datos son a través de un ORM, no importa qué tipo de consulta, ni cuales sean las necesidades, generalmente caemos en este caso en software lento.
  • Construir el software en C (o C++) porque es rápido: Aquí habría que plantearse, primero, si es cierto eso sea tan rápido, y si dicha rapidez compensa en el uso de dichos lenguajes, cuya curva de aprendiza en mayor, y no cuidan mucho que no hagamos locuras al momento de usarlos (como por ejemplo Java y C#, donde se nos protege de usos indebidos de la memoria).
  • Programar en el blog de notas (o similares): Esto es querer hacerlo todo a mano perdiendo la cantidad de posibilidades que nos facilita IDE, como depuración, sintaxis coloreada, o IntelliSense.


La idea final es usar la herramienta adecuada en cada momento y necesidad, no casarnos con una sola tecnología, metodología o lenguaje de programación y poder de extraer lo mejor de cada una.

lunes, 15 de septiembre de 2014

Regla N°3 de la Ingeniería de Software: Se va a mantener

El tiempo que vas a estar creando software nuevo, es incomparablemente menor al tiempo que vas a estar manteniéndolo.

Gran parte nuestros esfuerzos de desarrollo van a estar enfocados en el mantenimiento de código que bien puede ser código ajeno o código de otras personas.

El peor escenario donde hay que mantener un software, es donde el código se ha ajustado durante años al gusto, y costumbres particulares de un analista-programador, que no ha seguido ninguna regla en concreto para creación del producto. Generalmente estos sistemas se vuelven contra su creador, convirtiéndose lo que debiera ser un aparente software sencillo, en vacaciones pasadas en la oficina en lugar de en la playa. La situación solo puede acabar de varias formas

  • Rehacer el sistema desde cero (con un nuevo programador).
  • No actualizar nunca el sistema, y por lo tanto limitar las posibilidades del negocio para el cual se aplica (hasta que se hace demasiado evidente que el sistema ya no sirve, y se cae en el primer punto).
  • Explotar al programador (horas extras, falta de vacaciones), hasta que este renuncie, y se vuelve a caer en el punto número uno.

A veces noto a ciertos programadores querer hacerse imprescindible para la continuidad de un sistema, mi consejo es que no caigan en ese juego. Al final se hacen esclavos del sistema, en lugar de hacer que el sistema les reporte beneficios. Lo ideal es que a un empleado le quieran por lo que es capaz de hacer en el futuro, y no que dependan de él por lo que ya ha hecho, debido a que generalmente las empresas buscaran cortar esa dependencia lo antes posible y de la manera más beneficiosa para la ellas y no para el empleado. Diferente es el caso en el que un empleado es requerido por sus conocimientos y capacidades ya que es una inversión a largo plazo. Resumiendo la idea seria "ser un buen programador tiene beneficios, aunque a veces no son tan visibles".

Volviendo a la idea del mantenimiento de software, nuevamente es una idea que debemos asumir como cierta, y para la cual debemos estar preparados, algunos consejos son:

  • No programes de forma artística: la programación no es un arte, eso no quiere decir que no sea creativa, ni importante, ni interesante, sino que generalmente es una creación "objetiva" a la que deben llegar un equipo de desarrollo en conjunto. Si lo vemos como un arte, lo veremos como algo muy personal (y subjetivo), que acabara siendo difícil de mantener.
  • Sigue estándares: antes de comenzar un desarrollo, es importante tener claro los estándares y que sean conocidos (y afectados) por todo el equipo de desarrolladores.
  • Haz un plan de manteniendo de un software, mas cuando este no tiene estándares o lógica aparente: Si te toca un software de este tipo, planea como vas a hacer las cosas a partir de ahora, donde podrás el acceso a datos, donde la seguridad, servicios globales, etc.
  • Intenta comprender el software desde el punto de vista de las personas que lo hicieron: Muchos proyectos reflejan el cansancio y la frustración desde las horas extras, al momento de su construcción. Para comprender el código piensa desde esa perspectiva, y ve donde esta los huecos de "lógica" y los posibles errores cometidos.
  • Haz refactoring siempre que puedas: El refactoring no es trabajar doble, es trabajar ahora para descansar mañana (y pasado mañana). Nunca subestimes el refactoring, no tengas miedo de mover algo que no entiendes, al contrario, buscar entender lo que estas moviendo (apóyate en el punto anterior).
  • Siempre utiliza control de versiones (el que sea, GIT, SVN,...). No hay proyecto demasiado pequeño para usar control de versiones, no importa que sea un proyecto individual o en equipo, siempre úsalo.

Finalizando, recuerda que el valor del software, está en su capacidad de ser mantenibles, dicho de otra manera, si un software no se puede mantener, perderá su valor muy rápido a través del tiempo, ya sean meses o escasos años (uno o dos). Las necesidades profesionales o empresariales del mundo cambian demasiado rápido, evolucionan siempre en nuevas necesidades, y cualquier necesidad requerirá de un software que la gestione. Esforzarse en crear un software mantenibles desde sus raíces, es una inversión en recursos y tiempo, que dará beneficios en un corto tiempo en la vida del sistema donde esta implementado.

sábado, 6 de septiembre de 2014

Regla N°2 de la Ingeniería de Software: Va a fallar

Si no falla es que no se usa…

Es imposible determinar si cierto software continuara funcionando en un futuro, porque las condiciones en las que se verá envuelto son impredecibles (actualizaciones de software con el que convive, actualizaciones de hardware… incluso la fecha y hora del ordenador). Si bien es cierto que podría decirse que la informática es determinista, el costo de determinar todas las opciones posibles, es demasiado alto.

Muchos de los primeros problemas que nos encontramos son al pasar de un ambiente de desarrollo, donde todo funciona, a otro ambiente de productivo donde parece que cada paso que damos en un problema a corregir.

Hay que evitar actitudes optimistas con respecto a la posibilidad que ocurran fallos, puesto que es muy probable que estos ocurran, por otro lado considerar que el usuario del sistema hará un uso “lógico” o coherente de este, es un desatino, generalmente nos sorprenderá descubriendo errores, que nunca se nos hubieran pasado por la cabeza.

Una vez más la visión correcta es aceptar que los fallos van a ocurrir e intentar establecer mecanismos para mitigar su impacto.

Algunos de estos mecanismos son:

  • Poseer un ambiente de pruebas, semejante al ambiente de producción, pero que no tengas ninguna afectación real en las operaciones, además de estar completamente aislado del ambiente de desarrollo.
  • Asegurar que tenemos trazabilidad de los errores (esto es que podamos reproducir el camino que se siguió para generar el error). Esta es una de las más difíciles y opacas, con lo que de antemano debemos prever esta situación y diseñar nuestro software para tal efecto.
  • Mantener el código sencillo, en base a estándares, que sea fácilmente comprensible por el equipo que está trabajando actualmente con el codigo (no solo por el que genero el código).
  • Usar herramientas de control de versiones. Frecuentemente una “pequeña” modificación de código genera “desastrosos” resultados en nuestras operaciones, es importante poder localizar fácilmente estos cambios.
  • Creación de pruebas unitarias, que nos ayuden a probar de forma automática y completa todos los elementos de un sistema (aunque sea por separado), evidentemente esto tiene que venir de la mano de una arquitectura, cuyo diseño nos permite probar el sistema de forma unitaria.

Un último consejo, “No repitas código”, a veces me he encontrado con inmensos bloques de código completamente iguales, salvo algún pequeño detalle. Es necesario estructurar correctamente nuestro software, y evitar la duplicidad del código (mediante funciones, herencia o patrones de software), porque basándonos en la regla “Va a fallar”, cuando más código igual tengamos en nuestras aplicaciones, mas tendremos que trabajar para repararlo (con la casi certeza que no lo vamos a corregir en todos los lados donde esta duplicado).