Introducción a la programación actual

Empezamos esta serie de artículos dedicados a las buenas prácticas de programación. Veremos algo de teoría (necesaria para sentar conceptos fundamentales) y ejemplos prácticos, para afianzar e interiorizar la manera correcta de programar.

En los ejemplos prácticos emplearemos, principalmente, PHP. La razón de esto es que es el lenguaje preferido de la inmensa mayoría de los desarrolladores web. Sin embargo, los conceptos que aquí vamos a ver te servirán igualmente para desarrollo de aplicaciones de escritorio, aplicaciones para móviles, desarrollo de videojuegos, o cualquier otra que sea tu especialidad. Y, por supuesto, te serán igualmente útiles, sea cual sea tu lenguaje de programación preferido.

Si eres un programador «de la vieja escuela» te costará un poco cambiarte el chip. Si llevas pocos años en este sector, te resultará mucho más fácil. En todo caso, es necesario mantenerse al día, ya que, lo que aquí veamos, es fruto de la experiencia y saber hacer de miles de programadores y desarrolladores en todo el mundo. Y esto es como la evolución: o te adaptas, o te extingues.

LA PROGRAMACIÓN DE AYER

A lo largo de la historia de la programación han ido surgiendo tendencias en el trabajo que pretendían, en mayor o menor medida, facilitar el desarrollo de aplicaciones. Si, como yo, eres un programador de la vieja escuela (yo me inicié en estos avatares incluso antes de que existiera Internet tal como lo conocemos hoy) habrás conocido muchas de estas tendencias. Desde la total anarquía programática de aquellos tiempos, donde lo que importaba era exprimir lo más posible los recursos de los rudimentarios equipos con los que contábamos, hasta la paulatina pero inexorable optimización de desarrollo.

A mediados y finales de la década de los 80 del pasado siglo se tendía a reinventar la rueda cada vez que era necesario realizar una tarea, que ya habíamos realizado incontables veces. Así, un proyecto podía ser muy similar en cuanto a su uso a otro anterior y, sin embargo, llevarnos el mismo tiempo (o casi) que el original, y tropezar en los mismos escollos. Los expertos de la época consideraban esto normal, alegando que cada proyecto era único y, por lo tanto, había que desarrollarlo desde cero. Además, las herramientas de programación de que se disponía no eran, en modo alguno, proclives a solucionar esta situación.

Se tenía la mentalidad de la programación a muy bajo nivel, e incluso cuando se programaba con lenguajes de alto nivel, como GW BASIC o COBOL, se pensaba de esa manera.

La falta de un espacio común donde compartir experiencias (cómo es hoy día Internet) hacía que cada cual fuera «a su rollo». Para la mayoría de los (pocos) programadores de la época, ir «a su rollo» era hacer las cosas a «como salieran», con tal de que salieran. Aunque yo soy, para ciertas cosas, partidario del silogismo que dice que «un buen plan hoy es mejor que uno perfecto mañana», esto no puede aplicarse a la programación.

Surgió así una tendencia muy acentuada al llamado código spaguetti. Consistía en procesos que saltaban de un sitio a otro de un programa, sin ninguna estructuración. Y no pensemos en, cuando en medio de un programa, era necesario añadir o modificar algo, y había que buscar cómo se llegaba hasta ese punto, como se salía, que datos había que mantener y cuales no… Para colmo, era tendencia muy habitual desarrollar un proyecto entero en un sólo listado de código, que incluía funciones que «resolvían» infinidad de cosas.

Como el tiempo pasa y las cosas van cambiando, surgió el concepto de programación estructurada. Un intento de facilitar las cosas, poder recuperar ciertas partes del código, organizarlo mejor… No era la mejor solución, pero suponía un avance. Los objetivos empezaron a mirar a los procedimientos que los resolvían y surgió la programación procedimental. Hoy día, todavía se emplea, hasta cierto punto, en módulos muy simples, de uso muy limitado, y para casos puntuales.

LA PROGRAMACIÓN DE HOY… Y, TAL VEZ, DE MAÑANA

Sin embargo, las tendencias siguieron mejorando, y en los noventa empieza a popularizarse la POO (Programación Orientada a Objetos), u OOP, por sus siglas en inglés. Aunque ya se hablaba de este concepto en círculos muy cerrados (universitarios, gubernamentales, etc), no fue hasta el advenimiento de la popularización de Internet, en las postrimerías del pasado siglo, cuando realmente empezó a extenderse. Este paradigma de desarrollo, con sus constantes mejoras y evoluciones, se mantiene hoy día, ya que ha demostrado que cubre una serie de objetivos completamente imprescindibles en el panorama actual. En concreto, nos permite implementar una metodología de trabajo que se destaca por varios puntos esenciales.

Reutilización de código. La mayoría de los desarrollos necesitan de procesos comunes a casi todos ellos. Por ello es sumamente interesante reutilizar un fragmento de código, que ha sido diseñado para llevar a cabo una tarea específica, y que lo hace bien, de forma que, cuando necesitas esa tarea, utilizas el código que la realiza, sin tener que crearlo de nuevo.

Como consecuencia de lo anterior surge el concepto DRY (seco, en inglés). Es el acrónimo de Don’t Repeat Yourself (no te repitas, es decir, no reinventes la rueda cada vez que te haga falta). Por oposición surge el concepto WET (mojado, en ingles). Son las siglas de We Enjoy Typing (Nos Encanta Teclear) o, según a quien le preguntes, Waste Essential Time (Desperdicia un Tiempo Valioso). Es la tendencia a recrear una y otra vez piezas enteras de código, que hacen lo mismo, o prácticamente lo mismo que el anterior.

Optimización. A grandes rasgos, lo que pretendemos es crear un código que sea limpio (en directa opsición al código spaghetti); que sea transferible, es decir, que otro programador pueda ver y entender lo que hemos hecho, y seguir trabajando desde ahí; que sea mantenible, es decir, que si en el futuro necesitamos repararlo o mejorarlo, podamos entender lo que hicimos, y trabajar sobre él; que sea escalable, lo que significa que si necesitamos ampliar sus funcionalidades podamos hacerlo sin problemas. Esto significa que:

Los nombres de variables y funciones deben ser descriptivos. Es mejor llamar a una variable que almacena el nombre de un usuario nombre_de_usuario (o nombreDeUsuario, si te va el estilo CamelCase) que a o nu. Su finalidad debe ser clara, sólo con leer su nombre. Además, a fin de compartir tareas y código con otros programadores, es una buena costumbre poner los nombres de variables y funciones en inglés, que es un idioma universal en este sector. Siguiendo con el ejemplo anterior, es mejor llamar a la variable UserName que NombreDeUsuario. Así, si mañana toma tu trabajo un programador que no sea hispanoparlante, podrá entender los nombres de las variables.

Comentarios. Los necesarios… sin pasarse ni quedarse corto. Un código debe estar documentado como nos gustaría a nosotros encontrarlo si tuviéramos que trabajar en él. Un artefacto de código con 300 líneas de programa y dos líneas de comentarios seguramente haga muchas cosas sobre las que no tenemos ni idea, y tendremos que perder tiempo explorando y probando. El otro extremo también es malo. Un exceso de comentarios (sobre todo cuando describen cosas obvias), dificulta la lectura del código y el trabajo con el mismo. De nada sirve poner diez líneas de comentarios para describir que una variable se multiplica por dos, si lo vemos en la propia instrucción que lo hace. La excepción a esta regla es cuando los comentarios, y el código en sí tienen fines meramente didácticos. En ese caso, si puede ser conveniente extenderse un poco más.

Por la misma razón que en el caso de los nombres, los comentarios del código deberían estar en inglés.

Documentación. Aunque «suene» a ser lo mismo que los comentarios del código, este concepto va más allá. Describe los tipos de datos empleados, la finalidad de nuestro artefacto, los recursos que necesita, posibles bugs a depurar… En este caso, mi opinión es que, cuanto más exhaustiva sea la documentación, mejor para todos.

Desacoplamiento. No es bueno hacer un módulo que, por ejemplo, registre a un usuario en la base de datos y le envíe un correo electrónico. En su lugar, es mucho más óptimo hacer un artefacto que registre al usuario, otro artefacto que le envíe el correo electrónico, y otro más, que invoque a los dos primeros en la secuencia adecuada. De este modo, cuando necesitemos enviar un correo electrónico a un usuario, desde cualquier punto de nuestra aplicación, y por cualquier motivo, sólo tendremos que invocar al artefacto que envía los correos, pasándole los datos adecuados. Así, si mañana tenemos, por ejemplo, que cambiar los parámetros de configuración de nuestro servidor de correo, sólo tenemos que hacerlo en un sitio, no en diecisiete.

EL PRINCIPIO SOLID

Como ya hemos comentado, uno de los objetivos de un desarrollo es la mantenibilidad. El mantenimiento de un proyecto consume una gran cantidad de tiempo. Y no hablemos de la escalabilidad. Prever la posibilidad de esacalar un proyecto es una tarea inherentemente compleja, porque nunca podemos saber que se le va a ocurrir a un cliente, o a un usuario, que le resulte «imprescindible» y que nos obligue a modificar el diseño. Afortunadamente, contamos con técnicas que nos facilitan enormemente estas tareas. Son estrategias de desarrollo orientadas a lograr la mayor flexibilidad y desacoplamiento posibles. Gracias a gurús del desarrollo, como Robert C. Martin, Bertrand MeyerBarbara Liskov o Martin Fowler, contamos con los principios SOLID. Esta palabra es el acrónimo de Single Responsibility, Open / Closed, Liskov Substitution, Interface Segregation y Dependency Inversion (Responsabilidad única, Abierto / Cerrado, Sustitución de Liskov, Segregación de interface e Inversión de dependencia).

SINGLE RESPONSIBILITY

Este principio se basa en diseñar cada clase para un objetivo concreto y único. Con demasiada frecuencia tendemos a incluir, en una clase, métodos que no corresponden al objetivo para el que fue concebida. Por ejemplo, podemos tener una clase cuyo objetivo sea recuperar registros de una base de datos pero «ya puestos» le añadimos un método para efectuar tal o cual gestión con los datos recuperados. El problema viene cuando debemos efectuar esa misma gestión con los datos de otra fuente. Nos vemos obligados a replicar ese método en otra clase distinta. Lo mismo puede aplicarse a funciones individuales. Ya hemos mencionado el ejemplo de la función que realiza el alta de un usuario en la página, y le envía un correo electrónico. Son dos tareas distintas, y cada una debe llevarse a cabo por una función. Dicho de otro modo. Una función debe hacer una cosa, sólo una, y hacerla bien. De este modo, lograremos un mayor desacoplamiento, y una más fácil escalabilidad de nuestro trabajo.

Cabe decir que, con frecuencia, es más fácil aplicar este principio a nivel de funciones que a nivel de clases, pero el pequeño esfuerzo que supone interiorizar esta manera de trabajar a todos los niveles se ve recompensado con creces.

OPEN / CLOSED

Aquí encontramos la razón de ser de la herencia. Una clase debe poder modificar su comportamiento, sin tener que modificar su código. Dicho de otro modo. Nuestros desarrollos deben facilitar la extensibilidad de las clases, sin que sea necesario modificar el código original. Aquí entran en juego conceptos como la sobrescritura de métodos y la implementación de interfaces.

LISKOV SUBSTITUTION

Este concepto se basa en tratar las clases derivadas como si fueran la clase base. Esto significa que los métodos heredados deben poder actuar sobre objetos de la clase derivada haciendo lo mismo que si fuera un objeto de la clase base. Evidentemente, una clase derivada tendrá nuevas prestaciones, pero no deben de dejar de funcionar las de la clase padre.

INTERFACE SEGREGATION

Este principio es similar al primero, pero referido a las interfaces, en lugar de a las clases. Cada interface que implementemos debe definir sólo los métodos abstractos que se deban emplear en cada uso de esa interface. Dicho de otro modo, es preferible implementar muchas interfaces con pocos métodos cada una, que una interface con muchos métodos de los cuales sólo vamos a usar algunos.

DEPENDENCY INVERSION

Este principio busca el máximo desacoplamiento posible entre clases. Si bien siempre es necesario un cierto nivel de acoplamiento, por el mero hecho de que un proyecto se diseña para una finalidad concreta y maneja una estructura de datos específica, un nivel de acoplamiento elevado hará practicamente imposible el mantenimiento, por no hablar de la reutilización de código. Por eso se buscan las abstracciones, de forma que las clases padre no tengan por qué «conocer» el funcionamiento de las clases derivadas. Para facilitar esto, existen patrones específicos, como la inyección de dependencias. Básicamente, esto es una inversión de control, que permite que un objeto sea pasado a una clase, en lugar de que la clase sea la que cree el objeto.

CONCLUYENDO

En este artículo hemos sentado algunos principios que deben tenerse en cuenta en el desarrollo actual de aplicaciones, en cualquier ámbito. Aunque aún es mucho lo que nos queda por comentar al respecto, así como ejemplos prácticos que ver, este artículo te ha dado una serie de líneas maestras de trabajo… si estás familiarizado con al programación orientada a objetos. Si no lo estás, no te preocupes. En los próximos artículos conoceremos los suficiente de este paradigma de programación como para poder sacarle todo el provecho posible al material que aquí tenemos. Dicho de otro modo. No te pierdas los próximos contenidos, porque todo esto debes grabártelo a fuego como desarrollador.

   

Deja un comentario