Imágenes

Los dos riesgos de un código fuente difícil de entender

Los dos riesgos de un código fuente difícil de entender

Comentaremos de dos maneras en que algo difícil de entender hace que nuestro trabajo sea más complicado.

1. “No lo entiendo, necesito más tiempo”

Las personas pueden estudiar el texto y terminar sabiendo que no comprendieron lo leído. Esto es problemático en el aspecto de que quedan dudas y nos tomará más tiempo hacer nuestro trabajo.

Probablemente necesitaremos buscar a un programador con experiencia previa en dicho código fuente para que nos ayude a mejorar el código para entender. Incluso podría ser mejor borrar y reescribir esa sección de código. De todas maneras, el código anterior debíamos probarlo completo, así que una reescritura podría ser más eficiente que tratar de trabajar sobre algo riesgoso.

2. “Lo entendí mal, e introduje un defecto”

El primer punto es un problema, sin embargo es más problemático cuando las personas creen que comprendieron el texto pero realmente no lo hicieron. En tal caso, tanto el escritor como el lector del texto tienen interpretaciones diferentes. Cuando un programador tiene solamente al código fuente como la fuente de su conocimiento y no es fácil de entender, entonces tomará decisiones equivocadas al programar y al probar.

Para evitar las consecuencias anteriores, tenemos tres técnicas:

1. Nombres claros: Nos ayudan a úbicarnos más fácilmente en los detalles

Necesitamos escribir el código fuente usando las mismas palabras que usan nuestros clientes. Los nombres deben ser pronunciables de manera que podamos hablar de ellos en voz alta.

En el siguiente ejemplo, cada palabra es pronunciable y nos a ayuda a entender cómo se obtiene el “Valor Transado Bruto como número”:

codigo-final

El esfuerzo que invertimos al pensar buenos nombres nos dará ayudará la próxima vez que alguien necesite leer el algoritmo. El retorno de dicha inversión es que  a futuro tendremos menores costos de desarrollo de cada cambio.

2. Pruebas unitarias Nos ayudan a entender qué hace el algoritmo

El código fuente debería explicarse a través de pruebas unitarias. Estas ayudan a entender qué hace dicho algoritmo. Cada prueba unitaria es un ejemplo concreto de datos de entrada y resultados esperados, así que son la mejor documentación que un algoritmo puede tener para evitar que alguien a futuro tome decisiones equivocadas.

En este ejemplo, tenemos los datos de entrada bien descritos . El algoritmo genera el “Monto de la valoración en colones como número” a partir de los “Datos en UDES”.

prueba-unitaria-es-un-ejemplo

Si un programador a futuro introduce un defecto en ese objeto, la prueba unitaria fallará, indicando que el comportamiento ya no es el esperado. Esto evitará que entreguemos un algoritmo incorrecto a nuestros clientes.

El retorno de la inversión que hacemos al escribir las pruebas es que a futuro ahorarremos los costos de corregir errores durante la realización de cambios y además que tendremos menos errores que llegan hasta los usuarios de la aplicación.

3. Una estructura cohesiva: Nos ayudan a entender lo general sin necesidad de leer todos los detalles.

El código fuente debería poderse visualizar como un grafo de comunicación entre clases. Si cada una de estas partes está estructurada como un objeto de responsabilidad única, entonces cada funcionalidad podremos mirarla en su generalidad sin tener que leer los detalles (las variables y sus operaciones), y tendremos una vista como la siguiente:

arbol-de-objetos

Así podremos navegar la estructura sin perder de vista el todo, y cuando realicemos un cambio este quedará restringido a una rama de llamados. De hecho, las pruebas unitarias nos indicarán el área afectada por cualquier cambio o defecto, lo que nos hará más eficientes. En este otro artículo podemos leer más acerca de cómo Trabajar con las pruebas unitarias en un algoritmo con objetos.

El esfuerzo que invertimos al estructurar el código como piezas de responsabilidad única (cohesión), nos paga sus dividendos cuando volvemos a él y nos permite navegar por el algoritmo sin leer todos los detalles. Los buenos nombres nos permiten una comprensión casi a primera vista, y las pruebas unitarias posibilitan un análisis de impacto metódico y confiable.

En resumen, los dos riesgos de un código fuente difícil de entender son mayores costos y tiempo, y la generación de un software con defectos. Un equipo de desarrollo bien entrenado puede evitarlos con buenos nombres, pruebas unitarias y un código bien estructurado.

A través de la serie Algoritmos Mantenibles estudiamos cómo programar de esta manera. ¿Dudas? ¡Será un gusto conversar al respecto!

Los costos de mantener un software

costos-del-mantenimiento

Una empresa que desarrolla un sistema de software está adquiriendo un activo que le dará ganancias. Esta es la justificación para desarrollarlo en primer lugar. Sin embargo, como desarrolladores de software sabemos que los costos de mantenimiento son de un 80 a un 90% del costo total de un sistema de software en su vida útil. Esto debería llevarnos a pensar:

  • ¿Por qué el costo de mantener el sistema es tan grande comparado al costo de realizar la primera versión?
  • Y ¿cómo podemos ser más eficientes en el desarrollo para reducir estos costos?

La respuesta requiere que razonemos en cómo es que un activo como el software es modificado en el tiempo, así que les invito a pensar en los tres tipos de cambios que podemos realizar al dar mantenimiento:

Extender una funcionalidad

  1. Para extender, primero debemos entender lo existente.

Cuando se le pide a un equipo de desarrolladores de software que se debe agregar o extender alguna funcionalidad del sistema, estos van a requerir tiempo para entender cómo funciona el sistema en primer lugar; y para hacerlo deben leer y entender el código fuente del programa.

Dependiendo de cómo se haya escrito ese documento al que llamamos “código fuente”, el desarrollador tendrá dudas, el equipo tendrá malos entendidos y cometerán errores. Todo esto suma costos al esfuerzo de agregar una funcionalidad.

El código fuente no es un documento cualquiera, sino que es uno donde sus páginas pueden ser muy extensas en líneas de texto, o incluso extensas a lo ancho, y todo esto dificulta que nuestro cerebro humano entienda todos los detalles que están sucediendo.

En ocasiones este documento fue escrito con palabras que no son claras para los lectores, y esto resulta curioso pues el autor original sí lo comprendió en un momento del tiempo, pero ahora incluso él mismo podría no entender lo que escribió. Esto no es un problema de la herramienta con la que escribimos estos documentos, sino que es un problema de la actitud con el que el autor original lo escribió. La actitud de un buen autor es escribir para que sus lectores disfruten de una lectura sencilla y provechosa.

Además de tener páginas difíciles de leer, el código fuente es un compuesto por muchas páginas que se relacionan entre sí y el lector tiene que comprender cómo cada parte se relaciona con otras y cuáles son las consecuencias de cambiar dichas relaciones. La complejidad de esta comprensión crece asombrosamente: entre más grande sea cada pieza del documento, se relacionará con más partes y requerirá más esfuerzo mental. Todo esto siempre redunda en más costos para el esfuerzo de extender esa funcionalidad.

2. Debemos probar que funciona correctamente

Una vez que el desarrollador de software ha entendido el documento que debe modificar, debe asegurar que funciona correctamente. Estos documentos son ejecutables, pues les podemos dar ciertos datos de entrada y estos nos darán resultados que debemos comparar para determinar si son lo que esperamos; y es cuando sabemos que el software funciona correctamente que podemos hacer un cambio con confianza.

Existen técnicas para que esta labor pueda realizarse en milisegundos. Les llamamos “pruebas unitarias”, y con ellas casi al instante podríamos saber si la lógica escrita en esos documentos es correcta, sin embargo, no todos los documentos fueron escritos pensados para facilitar esta labor. Esto obliga a los lectores a realizar revisiones manuales que son lentas y propensas a errores. Aquí sumamos más costos al mantenimiento.

En ocasiones, estos documentos no pueden ni siquiera ser revisados en el computador del programador y se deben de instalar en servidores especializados. Incluso este proceso de instalación muchas veces es manual y burocrático. Cada vez que mencionamos que algo es “manual” tenemos que agregar con certeza que habrá costos por el error humano, retrabajos y más tareas manuales.

Una vez que hemos probado que el documento funciona como se espera, podemos realizar el cambio tan esperado por nuestro cliente.

3. Debemos hacer los cambios correctos y probarlos

Un programador profesional de software sabe que antes de modificar cualquier elemento de software debe primero entenderlo y probar que funciona como se espera. Luego de esto, debería proceder con ciencia y habilidad a hacer un corte quirúrgico al estilo de un cirujano: Debe identificar el sitio correcto donde se hace el cambio y analizar que el impacto de ese cambio sea el mínimo.

El cirujano hace una incisión lo más enfocada posible y aisla la herida para que no se extienda. Igualmente un programador profesional aisla un área de su código fuente y lo cubre con lo que llamamos “pruebas unitarias” para que el cambio que va a realizar no afecte áreas inesperadas.

Para lograr tal exactitud, los documentos deben escribirse de una manera que permitan este tipo de cambios enfocados. En el lenguaje de diseño de software, a esto le llamamos cohesión.

Cuando un código fuente no es escrito con cohesión, tendremos muchas dificultades, pues la incisión del cirujano producirá una herida grande y difícil de controlar: cada corte llevará a hacer otros cortes y entre mayor sea la zona afectada mayor es el riesgo de una infección.

Luego de realizar el cambio, el programador podría “suturar la herida” pero esto le tomó mucho tiempo y posiblemente cometió errores que tuvo que correrir.

Entonces, la siguiente tarea es probar que el cambio funciona tal y como se espera. Aquí tenemos nuevamente la posibilidad de verificarlo rápida y automáticamente con “pruebas unitarias”, pero es muy usual que se deba hacer de manera manual por cómo se escribió el cambio mismo en los documentos del código fuente.

Finalmente, antes de dar por terminada la tarea, el programador debe probar que todo el sistema sigue funcionando como se espera. En esta profesión no podemos simplemente darnos por satisfechos cuando el cambio que realizamos funciona bien, sino que debemos asegurar que no se introdujo un defecto inesperado en otra parte del sistema. El riesgo de que esto suceda depende igualmente de la cohesión, y el costo de encontrar estos errores es muy alto, pues un sistema de software usualmente tiene cientos de escenarios que se deben revisar.

Corregir

Asumiendo que las pruebas fueron realizadas y que instalamos el sistema para nuestros clientes, tenemos que enfrentarnos a otro costo: el costo de corregir defectos.

Estos documentos de software podrían escribirse de manera tan compleja que a pesar de haber sido cuidadosos al realizar el cambio y las pruebas, hay alguna parte que no funciona correctamente y este mal funcionamiento llega a los usuarios quien se disgustan al encontrar que no pueden lograr sus metas porque el software no funciona bien.

Cuando ellos reportan este error, las tareas de mantenimiento se repiten y se agravan: un programador debe entender el código fuente, probarlo, encontrar el defecto, corregirlo y hacer las pruebas de todo el sistema de nuevo.

¿Por qué se dan los defectos?

En ocasiones, el programador no entendió bien el código fuente y escribió algo erróneo aunque creía estar escribiendo correctamente. En ocasiones, no hizo las pruebas suficientes. Y en ocasiones, el código fuente mismo está escrito con trampas sutiles que empujan al progrador a equivocarse. A estas trampas les llamamos rigidez y fragilidad.

Todos estos factores suman día a día a tener un sistema de software costoso de mantener. Entre más costoso sea este mantenimiento, más tiempo y oportunidades pierden nuestros clientes, por lo que también hay costos intangibles que en ocasiones son más determinantes que el dinero.

Actualizar

A diferencia del mundo físico donde los activos se van deteriorando por los elementos naturales o por su uso, el software permanece inmutable sin importar la cantidad de años que permanezca almacenado. Puede ejecutarse millones de veces y su código fuente no se deteriorará.

Sin embargo, un sistema de software es afectado por otros factores: las nuevas tecnologías. Cada pocos meses las tecnologías van cambiando: el sistema operativo, los equipos donde se instala, las herramientas que usa, los otros sistemas con los que se relaciona. Todo va cambiando y es clave que nuestro software sea sencillo de actualizar con todo este entorno.

Entonces podríamos decir que si la erosión, el sol y la lluvia afectan a un edicio, entonces las actualizaciones de hardware y otras herramientas de software deterioran a nuestros programas.

En el mundo físico cuando una secadora de pelo se quema, solamente la desconectamos de la pared y la reemplazamos por otra. Esto nos habla de conectores estándar y de adaptadores que facilitan estos cambios.

En el código fuente de un programa, podríamos lograr lo mismo y así tener costos de actualización bajos, sin embargo la norma en muchas aplicaciones es el equivalente a que la secadora de pelo esté soldada a la pared. Para cambiar la secadora tendríamos que romper la pared. En el software esto se manifiesta como reescrituras completas de programas cuando se desea cambiar de una tecnología a otra. Se les llama “modernizaciones” o “migraciones”, pero realmente estamos deshechando el trabajo de miles de horas y empezando de nuevo.

Un software que no se puede adaptar fácilmente a nuevas tecnologías agrava muchísimo los costos de mantenerlo, pues todo el aprendizaje ganado anteriormente se pierde para empezar de nuevo.

Usualmente llamamos “código legado” a cualquier programa que debemos mantener que presenta las dificultadas que acabo de introducir. ¡Y muchas veces nosotros mismos fuimos los autores de ese código legado!

¿Por qué aun escribirmos programas difíciles de mantener?

Muchas veces es por desconocimiento de las técnicas que nos ayudarían a hacerlo de una mejor manera.

En otras ocasiones es porque nos dejamos llevar por alguna urgencia del momento, pero esto es como adquirir una deuda que nos cobrará intereses con creces en el futuro.

Y finalmente, creo que nuestra industria carece de un sentido de urgencia respecto a este tema. Quisiera hacer un poco de conciencia al respecto y que juntos podamos extender este conocimiento por el bien de nuestra sociedad. Después de todo, miremos que toda nuestra civilización ahora depende del software para su existencia…

¿Hay buenas noticias?

A través de los años muchos colegas han meditado en el tema y han creado técnicas para que nuestros costos de mantenimiento no sean tan altos. Estas ideas las podemos encontrar en papers, libros y sitios web, y en www.softwaremantenible.com presento una manera ordenada de estudiarlas y aplicarlas.

  • ¿Cómo crear un código fuente mantenible? ¿Cómo hacerlo fácil de entender, de probar y de cambiar?
  • ¿Cómo transformar un código legado en uno mantenible?

Estas son las preguntas que trato de responder a través de cada técnica, así que les invito con entusiasmo a que me acompañen a estudiar, innovar y aplicar nuevas ideas. ¡Creo que sí tenemos buenas noticias que compartir!