¿Podemos ser mantenibles en cualquier lenguaje de programación?

Siempre podemos tratar de crear código mantenible. Aunque algún lenguaje no cuente con todas las facilidades (objetos, polimorfismo, inversión de dependencias, etc.) siempre podemos buscar maneras de lograr un código bueno que nos ayude a ser eficientes. Les comento a continuación algunas recomendaciones que podemos aplicar al aprender un lenguaje de programación. Aquí no me refiero a cómo aprender la sintaxis de un lenguaje, sino a decidir el estilo de programación que nos permita escribir un código fuente fácil de entender, probar y cambiar.

Fácil de entender

Empecemos pensando en algunas maneras que facilitan el buscar y entender código en un lenguaje:

Nombres claros

Al aprender un nuevo lenguaje de programación, podemos preguntarnos si nos permite utilizar nombres pronunciables. Los lenguajes modernos deberían permitirnos asignar nombres pronunciables a las variables, a  los métodos, a las clases o a las tablas de una base de datos. Algunas tecnologías antiguas nos producían restricciones en la cantidad de letras, como por ejemplo en mainframes en donde las tablas debían llamarse “CxTrEST”, pero esto es menos usual hoy en día. Incluso en un contexto de mainframes, podemos buscar maneras de explicar a los lectores con comentarios o con un índice de nombres.

Hablando de comentarios, recordamos investigar cómo se escribe comentarios en el lenguaje, y escríbanos los que sean útiles, no los que distraen, mienten y confunden.

Orden

Los primeros programadores creaban sus algoritmos con hardware, es decir, con cintas, cables y transistores. Por ejemplo, veamos en la siguiente imagen cómo lograron orden y simetría en la ubicación de las piezas. Además, tenían cuidado de que los cables no se mezclaran.

6E7.jpg

Apreciemos las líneas rectas y el cuidado profesional que se percibe en dicha máquina. Podemos apreciar el orden aunque en este momento no comprendamos el lenguaje en que el programador está trabajando. Al aprender cualquier lenguaje de programación, podemos preguntar y buscar maneras de trabajar con orden. Por ejemplo, podemos preguntarnos:

  • ¿Puedo separar las clases o módulos en archivos de texto que sean pequeños?
  • ¿Puedo organizar los objetos de los algoritmos en namespaces, carpetas o paquetes?

En cualquier lenguaje, deberíamos encontrar maneras de organizar con carpetas y archivos pequeños. Todo esto nos da un orden jerárquico que facilita encontrar y entender.

Abstracción: Ocultar los detalles facilita la comprensión

También, podemos mirar la evolución de estas máquinas, donde siempre hemos tratado de ocultar los detalles para que todo sea más sencillo de entender. Por ejemplo, en la siguiente imagen, se ocultan los cables u otras piezas para observar solamente las teclas:

c17da4f5560cfc61c4d5625a5dd27ca2.jpg

Ocultar los detalles y mostrar algo más sencillo de entender se llama “Abstracción”. Cuando abstraemos, exponemos un concepto más claro a los lectores o usuarios.

En cualquier lenguaje, podemos ocultar los detalles de alguna manera. Si tenemos un lenguaje orientado a objetos, deberíamos poder graficar los Parameter Objects con un diagrama de clases, o también diagramar el flujo de los objetos. Si no se pudiera diagramar, siempre podríamos colocar un archivo Powerpoint mostrando estas relaciones.

El principio es que no deseamos que las personas tengan que leer cientos de líneas de código solamente para entender qué hace el algoritmo. Los diagramas siempre ayudan a abstraer.

Además, la programación con objetos de responsabilidad única nos ayuda a ocultar los detalles de los cálculos detrás de un objeto bien nombrado, como cuando creamos objetos llamados “ValorTransado” o “ImpuestoRedondeado”. En todo caso, no deseamos trabajar haciendo un constante “scroll hacia abajo y hacia arriba” pues todo es un solo archivo de texto. El orden y la abstracción siempre van de la mano.

Fácil de probar

¿En este nuevo lenguaje, podemos crear un algoritmos que sean aislados y observables? ¿Podemos realizar pruebas unitarias?

Aislado y Observable

  • ¿El lenguaje me permite entregar las entradas a cada algoritmo con un Parameter Object que cumple el principio “Tell, Don‘t Ask“?
  • ¿Puedo utilizar la herencia para realizar el polimorfismo y así lograr un diseño robusto?
  • ¿Puedo utilizar parameter objects abstractos para realizar la inversión de dependencias?

Pruebas unitarias

Usualmente, los ejemplos con los que iniciamos aprendiendo un lenguaje de programación es un “Hola mundo” en una pantalla o consola. ¿Qué tal si hacemos una prueba unitaria como parte de ese estudio inicial?

En casi todos los lenguajes hay un framework de pruebas unitarias estilo xUnit, como por ejemplo, “jUnit” o “pyUnit”. Usualmente son gratuitos y tienen buena documentación, así que es solamente de buscar en Google y hacer un “hola mundo” para saber cómo programar un “Assert.AreEquals”.

Por ejemplo busquemos “python hello world unit test” en Google:

google-p-unit-test.PNG

El primer resultado nos lleva a un ejemplo muy sencillo, un punto de partida para realizar buenos unit tests en Python:

python-basic-unit-test.PNG

Algunas preguntas útiles relacionadas a las pruebas unitarias son:

  • ¿Cómo puedo organizar las clases de pruebas unitarias en namespaces y carpetas?
  • ¿Cómo puedo ejecutar todas las pruebas unitarias rápidamente? ¿Hay teclas rápidas (shortcuts) para lograrlo?
  • ¿Cómo puedo visualizar que todas las pruebas son exitosas?
  • ¿Cómo se compara strings, fechas, booleanos, números enteros y números reales?
  • ¿Cómo podemos reutilizar código con Escenarios de pruebas?
  • ¿Hay alguna librería para crear parameter objects sustitutos? Usualmente se les llama “Mocking frameworks”. Básicamente, buscamos cómo simular objetos concretos a partir de una clase abstracta.
  • ¿Cómo se examina la cobertura de las pruebas unitarias sobre el código fuente? En ocasiones el entorno de desarrollo (IDE) tiene la facilidad de visualizarlo como un reporte al ejecutar las pruebas unitarias. En otras ocasiones necesitamos instalar otra herramienta para hacerlo. Por ejemplo, en .NET el Visual Studio Enterprise lo hace sin necesidad de instalar otra herramienta, pero esta es la versión paga del IDE. En el caso de los ejemplos de este website, siempre utilizo la versión Community que es gratuita para profesores y estudiantes. En este caso, necesitamos otras herramientas que analizamos aquí: ¿Cómo analizar la cobertura de las pruebas unitarias en Visual Studio Community?.

Refactoring

Todo lo anterior es requisito para que busquemos cómo mejorar el diseño del código para hacerlo mantenible. Si tenemos un orden y pruebas unitarias, entonces busquemos qué facilitades de refactoring nos da el entorno de desarrollo. Al menos, deberíamos poder realizar los siguiente:

  • ¿Puedo extraer variables fácilmente? ¿Hay shortcuts para ello? (Introduce Local, Generate field)
  • ¿Cómo puedo mover líneas completas? Por ejemplo, esto lo necesitamos para mejorar la ubicación de la declaración y asignación de variables
  • ¿Puedo extraer métodos para lograr funciones de responsabilidad única? (Extract Method o Generate Property)
  • Algunos entornos permiten crear clases fácilmente (Extract Object) o hasta extraer Parameter Objects (Extract Parameter Object).

Resumamos, mientras aprendemos la sintaxis de un lenguaje, deberíamos poner igual atención al estilo de programación. Este estilo es crucial para que podamos escribir código fuente profesional. Hemos observado algunas guías para que en cualquier lenguaje logremos:

  • Orden para encontrar y entenderlo todo con mayor facilidad
  • Abstracción para ocultar los detalles
  • Algoritmos aislados y observables para poder probarlos con pruebas unitarias
  • Pruebas unitarias para tener código confiable y reducir la cantidad las pruebas manuales
  • Refactorings para lograr el mejor diseño posible

Juntas, estas cualidades nos permiten lograr un software fácil de cambiar:

  • Flexibilidad: Podemos hacer cambios con un impacto muy bien definido y con menos riesgos. Todas las piezas son claras (objetos de responsabilidad única) y están aisladas (namespaces).
  • Robustés: Podemos extender el software con menos riesgos de afectar partes no relacionadas. Cada algoritmo tiene un solo flujo de ejecución.
  • Reutilización: Podemos crear algoritmos que funcionan sin depender de tecnologías y así reutilizarlos cuando cambiamos herramientas. Programamos con pruebas unitarias y aplicamos la inversión de dependencias.

¿Qué sucede si el lenguaje que utilizo no es orientado a objetos, sino solamente procedimental?

En este caso, igualmente buscamos tener orden en los archivos de texto pequeños y la declaración de las variables.

  • Flexible: Aunque no tengamos la posibilidad de extraer objetos, podríamos extraer las funciones coordinadoras en archivos separados. Recordemos que cada objeto de extrae a partir de una función coordinadora.
  • Robusto: Puede que el polimorfismo no sea posible, pero esto no nos evita diseñar algoritmos que sean robustos al nunca recibir “tipos” en la forma de booleanos o enumerados, por ejemplo. Es decir, podríamos lograr la robustez sin requerir el polimorfismo.
  • Reutilizable: Igualmente podríamos realizar diseños aislados y observables, junto con la programación con pruebas unitarias, así facilitar que el código se reutilice a futuro.

Cuidado y análisis

Finalmente, recordemos que los lenguajes de programación usualmente tienen muchas maneras de programar una misma lógica, y no todas son necesariamente adecuadas para efectos de lograr la mantenibilidad, como por ejemplo:

  • Parámetros de salida (out ó ref en C#, ByRef en Visual Basic): Produce que los algoritmos tengan múltiples puntos de salida, lo que los hace más difíciles de entender y riesgosos de cambiar.
  • Inline IFs,o cualquier sobre simplificación del código: Es usual que leamos una noticia de una nueva característica en donde se promueve que el código sea más fácil de escribir (asumen que menos líneas de código es siempre bueno). Por ejemplo, la siguiente instrucción, coloca un if en una sola línea de texto.

inline-if.PNG

Aunque es perfectamente válido utilizar este tipo de instrucciones, estás nos ponen en riesgo de introducir defectos. Por ejemplo, si tenemos una prueba unitaria en donde “value” es igual a 1, ejecutaremos “Periods.VariablePeriods”. Si no hacemos la prueba unitaria donde “value” no sea 1, igual tendremos 100% de cobertura de código. De esta manera, el código nos engaña y puede ser que tengamos un defecto escondido pues realmente no lo probamos bien.

El principio es que queremos que cada línea de código realice una sola operación con el fin de verificar más fácilmente que lo entendemos y probamos correctamente. Por esto, en lo personal recomiendo que usemos un if completo, donde la comparación y las líneas que se ejecutan puedan leerse y probarse por separado.

Tener estos cuidados y análisis es lo que esperamos de un profesional, pues es consciente de que es más importante lograr un código fácil de leer que de escribir.

¿Que características del lenguaje producen código frágil?

Finalmente, sería útil identificar qué partes del lenguaje nos llevan a la fragilidad para poder evitarlas. Por ejemplo:

  • Parámetros opcionales
  • Condicionales de acuerdo a tipos (enumerados, booean oí s o cualquier indicación de tipos).
  • Códigos de error numéricos o cualquier string que identifiquen tipos. Por ejemplo, un string que signifique si un inversionista debe pagar impuestos o no.
  • Si estamos hablando de bases de datos, las columnas opcionales también indican fragilidad.
  • En lo personal, cuando escucho que un lenguaje se anuncia a su mismo como “flexible” le pongo mucho cuidado porque usualmente están hablando de algo riesgoso que lleva a un código frágil. Por ejemplo, en lenguajes dinámicos, el no tener tipos podría ayudar a escribir código más rápido, pero por causar muchos problemas pues no tenemos un compilador que encuentre errores pronto y tenemos que ejecutar la aplicación para verificar que todo funciona bien.

Al final, cada lenguaje es una herramienta, con ventajas y debilidades. Es responsabilidad de cada uno de nosotros el usarlas correctamente. Espero estas recomendaciones nos sean útiles y podamos compartir más ejemplos de otros lenguajes. ¡Me encantaría leer sus comentarios!

Responder

Introduce tus datos o haz clic en un icono para iniciar sesión:

Logo de WordPress.com

Estás comentando usando tu cuenta de WordPress.com. Cerrar sesión /  Cambiar )

Google+ photo

Estás comentando usando tu cuenta de Google+. Cerrar sesión /  Cambiar )

Imagen de Twitter

Estás comentando usando tu cuenta de Twitter. Cerrar sesión /  Cambiar )

Foto de Facebook

Estás comentando usando tu cuenta de Facebook. Cerrar sesión /  Cambiar )

Conectando a %s