Pensando en programar

Es muy difícil establecer entre unas actividades y otras, una analogía que encaje de forma completa. El desarrollo de software a veces se compara con la construcción y la arquitectura. Otras veces lo comparan con la ingeniería. O con la carpintería. Otros lo comparan con la jardinería, con la cocina, con escribir novelas, con construir aviones mientras vuelan, con pastorear gatos… o con otras muchas actividades variadas. Lo importante para utilizar cualquiera de estas metáforas es entender que nunca se pretende decir que sean iguales, sino que en algún aspecto concreto comparten alguna característica.

Hasta ahora nos hemos centrado en una actividad bastante personal, en el proceso de pensar, de entender el problema, de analizarlo, de imaginar soluciones, de plantearlas y llevarlas a cabo. Pero hay aspectos de todo esto, incluso de esas tareas con apariencia íntima como es “pensar”, que requieren ir un poco más allá. Requieren, en general, comunicarnos con otras personas y pensar en grupo.

Pensando en Grupo

A mi, a veces, me gusta comparar escribir código con escribir un libro. No una novela, sino más bien escribir un manual de instrucciones o un artículo periodístico informativo o algo similar. Escribir software no es, como decía, igual que escribir un libro, pero… desde ciertas perspectivas tiene algunos aspectos en común.

Cuando escribimos un manual de instrucciones de algún aparato o un artículo informativo, tenemos que alcanzar dos objetivos diferentes para que este sea un éxito. Por una parte, debemos reflejar la realidad tal como es. Es decir, el manual tiene que explicar realmente cómo funciona el aparato; no podemos inventar o escribir lo que se nos ocurra, sino que estamos restringidos a escribir ciertas cosas concretas. Debe ser técnicamente correcto.

Por otra parte, el manual está dirigido a un público y a que ese público lo entienda y consiga aprender cómo se maneja el aparato. Si no fuera así, por mucho que fuera técnicamente correcto, el manual sería perfectamente inútil. Así que ese otro objetivo es que el manual debe ser claro y fácil de entender para el usuario.

Cuando escribimos código tenemos también un poco de esta doble perspectiva. Para verlo, pensemos en el código por un momento.

Escribiendo código. Leyendo código.

Circula, desde hace tiempo, esta cita:

Programs should be written for people to read, and only incidentally for machines to execute. Abelson & Sussman - SICP

Debemos escribir nuestros programas para que otras personas los puedan leer, y de paso para que las máquinas los ejecuten.

Esta idea luego la han repetido otros con diferentes variaciones como aquello de que “Cualquiera puede escribir código que lo entienda una máquina; los buenos programadores escriben código que entienden los humanos”, que decía Martin Fowler años más tarde.

A mi me gusta ver esto desde un punto de vista un poco diferente. No mucho pero sí un poco.

Siempre, programemos en equipo o programemos solos, hay “otras” personas involucradas en el proceso. Lo más evidente es cuando programamos en equipo, claro. Sea profesionalmente o no, cuando programamos varias personas juntas, surge la necesidad de comunicarnos unas con otras. Pero incluso si estamos programando solos, nosotros mismos somos “otras” personas. Cuando hoy escribimos una pieza de código estamos en una situación muy particular, tenemos unos ciertos pensamientos sobre lo que escribimos. Pero cuando dentro de una semana, o mañana mismo, estemos escribiendo otra pieza de código diferente, que posiblemente interactúa con la que escribimos el otro día, nosotros habremos cambiado. Estaremos pensando en otras cosas diferentes, en esta otra nueva pieza, por ejemplo. De modo que aunque somos la misma persona físicamente, en cierto aspecto somos otro, tenemos otros intereses. Y esto es natural, porque no podemos estar pensando en todos los detalles de todas las partes del código todo el rato. O quizá dejamos un proyecto como terminado y tiempo después se nos ocurre que queremos ampliarlo o cambiarlo.

También puede ocurrir que nosotros estemos escribiendo hoy una pieza de código pero que esté destinada a que otras personas utilicen ese código junto al suyo. Personas que, posiblemente, ni siquiera conocemos, como ocurre en el caso de estar publicando una librería o una herramienta para otros programadores, sea Open Source o no. O bien puede ocurrir que no lo sepamos, pero que lo que escribimos nosotros hoy pensando que nadie más tendrá que ver, dentro de 3 años otra persona reciba ese código y tenga que modificarlo añadiendo o cambiando alguna cosa.

Así, sea hacia otras personas distintas, de nuestro equipo, sea hacia nosotros mismos en otro momento, sea hacia personas que ni siquiera conocemos, la realidad es que de algún modo necesitamos establecer una comunicación con otras personas para compartir lo que pensamos y las razones y motivos que nos han llevado a escribir nuestro código como lo hemos hecho.

Los elementos de la comunicación

Podría ahora desviarme completamente y meter aquí un libro entero sobre la comunicación, el emisor, el receptor, el mensaje, el canal… Pero no creo que sea necesario. Todos deberíamos conocer estos términos. Y si no, bueno, en Internet hay muchas y buenas referencias sobre los Factores de la Comunicación.

Es interesante, eso sí, que nos planteemos cuáles son estos factores en nuestro caso concreto.

Como decía antes, tendremos dos receptores de nuestra comunicación. Por un lado, siempre un ordenador (o un compilador, un intérprete, etc). Su comprensión del mensaje es muy estricta y esto tiene como consecuencia que debemos ser muy claros no solo con lo que queremos decir sino con cómo lo decimos. El ordenador tratará de ejecutar nuestro programa tal como se lo pidamos, sin adivinar cuáles sean nuestras intenciones. Esta es la parte que nos lleva a escribir código que sea técnicamente correcto1).

Por otro lado, otro de los receptores es esa otra persona (otro programador del equipo, alguien que ni conocemos pero que usará nuestro código, nosotros mismos en otro momento del tiempo…). Y en este caso, la intención es diferente. Lo que queremos transmitirle a este otro receptor no es sólo lo que hace nuestro código -que ya es bastante- sino también por qué lo hace y por qué lo hace así. Y además, en este caso hay que tener en cuenta que aquí la comprensión es mucho menos estricta y mucho más interferida por el ruido, por el contexto, el canal y demás.

Más de lo que se ve

Así, no basta con que el código sea correcto para el ordenador -compilador, intérprete, etc-, sino que querremos que el mensaje sea también claro y accesible para las personas. Ya hablé un poco de esto pero es interesante insistir un poco más.

Esta idea, la de escribir código que es “más que correcto”, un código que es claro y comprensible, que incluye no solo las instrucciones técnicas sino que expresa la motivación y las ideas del proceso de pensamiento que nos ha llevado hasta él, es una de las ideas fundamentales detrás de lo que algunos llaman “código limpio”. Yo, personalmente, prefiero verlo como comunicación, como la simple necesidad de transmitir esa información adicional que va más allá de lo que se ve, porque eso de la “limpieza” no necesariamente representa una comunicación clara y exitosa.

Por todas estas cosas, creo que es interesante ver algunas de las ideas y técnicas fundamentales para pensar en grupo y para comunicar a través del código.

Un lenguaje común

Una de las principales formas que tenemos de mejorar las probabilidades de éxito de la comunicación es asegurarnos de que “todos sepamos de qué estamos hablando” y que “hablamos el mismo idioma”. Estas expresiones populares del habla común tienen detrás una idea: Que, en esos elementos de la comunicación que señalaba antes, hemos establecido correctamente el canal, el contexto y la codificación. Es decir, que, efectivamente hablamos el mismo idioma.

Pero no se trata solo del idioma (o del lenguaje de programación), sino de que si usamos una determinada palabra que puede tener diferentes significados y matices, todos entendemos el mismo significado y matiz. Si por ejemplo usamos la palabra “bote”, todos conocemos bien el contexto y sabremos que nos referimos a una pequeña embarcación, y no a un recipiente ni a un salto.

Se trata, por tanto, de nuestro proyecto tenga su propia jerga y que todos los involucrados la conozcan y la usen de forma consistente. Y esto en todos los diferentes niveles de abstracción o de detalle, que puede tener2) sus propios términos específicos. Y no solo es importante para la comunicación, claro. Por un lado también nos ayuda a razonar mejor y por otro puede incluso llegar a hacernos darnos cuenta de algún problema en el código por el simple hecho de que lo leamos y notemos que algo, un término, una expresión, no encaja bien.

Más aún, esto también nos sirve para comunicar con nosotros mismos. Definir diferentes jergas asociadas a diferentes contextos, nos puede ayudar mucho a manejar código fuente de gran volumen. Sabremos que cuando estemos hablando de cierto elemento, que este tiene sentido o se trata en cierta parte del código. Y este tipo de asociación, a nivel lingüístico, es mucho más fácil de recordar sin apenas esfuerzo por nuestro cerebro. Así que también ayuda en eso.

Nombres y nomenclaturas

Lógicamente, si queremos establecer una jerga común, un contexto lingüístico, es fundamental que sigamos algunas prácticas a la hora de elegir los nombres de las cosas.

Sobre esto hay muchas cosas ya escritas, así que me voy a limitar a ofrecer las reglas básicas que yo sigo.

  • No mentir. Cuando pongamos un nombre a algún elemento, es fundamental que ese nombre no sea engañoso respecto a la naturaleza de ese elemento. Esto parece muy obvio, pero es un error muy común. Las formas en que más frecuentemente ocurre son por indefinición y por evolución. Por indefinición porque empezamos a escribir ese elemento (una variable, una función, una clase, un módulo…) sin tener completamente claro qué es lo que va a ser. Y a veces esto es normal, porque podemos estar en una fase exploratoria en la que vamos probando una idea pero no la tenemos completamente clara. En estos casos es importante, una vez que hemos hecho esa exploración, que revisemos de nuevo todo lo que hemos escrito. Idealmente volveríamos a escribirlo, ahora sabiendo a dónde vamos, y con nombres más correctos. Si no hacemos esto, por lo menos, deberemos repasar los nombres que hayamos usado para ver si son buenos o no. Por evolución suele ocurrir que tenemos un elemento con una misión, por ejemplo, una función que comprueba un dato, y le añadimos cierta funcionalidad extra. Es frecuente que el nombre original ya no esté diciendo la verdad sobre lo que hace esa función; ahora hará más cosas, otras cosas, menos cosas… Debemos tener cuidado con esto.
  • Ser claro. Esto está asociado a esa definición del contexto y de la jerga que hablaba. Un buen nombre, no solo no mentirá sobre lo que no representa, sino que transmitirá correctamente lo que sí representa. Esto no significa que necesitemos poner nombres a las cosas que sean muy elaborados o largos, con montones de detalles. Personalmente creo que cuando el nombre de algo está formado por más de tres palabras, es que ese nombre no está bien. Me refiero a nombres como, qué sé yo, formatDatasConversationsToShow o checkIfConversationExists. Son nombres innecesariamente largos. No es eso lo que buscamos. Si hemos definido bien el contexto en el que ese nombre existe, entonces un nombre más corto, más sencillo, seguirá comunicando toda la información necesaria.
  • Ser consistente. Esta quizá es una de las más difíciles de ver al principio, pero probablemente es de las características más importantes. Se trata de ir más allá de los nombres y usar nomenclaturas. Cuando empezamos a programar algo, aún no conocemos muchos de los nombres que necesitaremos poner a las cosas. Una nomenclatura es una forma de dar nombre, es establecer unos criterios para que cuando necesitemos nombres nuevos, estos encajen bien con los que ya existen. Por ejemplo, imaginemos que queremos hacer un programa que maneja 3 elementos distintos y por cada uno de ellos la idea de una lista de dichos elementos. Una nomenclatura consistente nos dice que si hemos llamado Perro al primer elemento y Perros a la lista de esos elementos, y si ahora el siguiente elemento es Gato, lo consistente es que la lista de gatos se llame Gatos y no ListaDeGato o de alguna otra forma. Y al revés, si inicialmente usamos ListaDePerros, ahora deberíamos usar ListaDeGatos.
  • Usar niveles de detalle correctos. Esto es fundamental. He hablado varias veces ya de los niveles de detalle 3). También a la hora de poner nombres a las cosas es importante tener esta idea en cuenta. Un buen nombre tiene que ser expresivo y mantenerse al mismo nivel de detalle en el que opera. Un nombre muy genérico, como procesarDatos no contiene apenas ningún nivel de detalle; procesar puede ser cualquier tipo de operación y datos puede ser también cualquier cosa. Por tanto, un nombre así solo debería usarse en un contexto en el que estemos trabajando a ese nivel de abstracción. Por otro lado, un nombre más específico, como obtenerPermisos contiene detalles mucho más concretos: queremos obtener, leer, conseguir de algún modo, unos ciertos permisos. Lógicamente para que el nombre sea bueno, debemos estar en un contexto en el que estemos manejando permisos para algo.

Al margen de estas ideas básicas, como decía antes, hay muchísimo escrito ya. Pero en su mayoría son detalles más sintácticos que semánticos. Es decir, hay mil reglas más que podemos aplicar. Que ciertos nombres deben empezar con mayúscula, que otros con minúscula. Que escribamos nombres_asi o escribamos nombresAsi. Que usemos el plural o no lo usemos. Etc. Son detalles ciertamente importantes y ayudan, pero ayudan a otro nivel y generalmente son reglas bastante mecánicas y que podemos establecer dependiendo del proyecto, del equipo, de… Se deben acordar de antemano para mantener la consistencia, claro que sí, pero muchas de ellas ya vienen dadas por las convenciones típicas de cada comunidad y lenguaje o realmente no importa tanto la forma concreta que escojamos sino el hecho de que escojamos una misma forma. Es decir, que generalmente añaden una cierta consistencia superficial. Como digo es algo que ayuda, sí, pero a un nivel diferente a lo anterior.

Comentarios y documentación

Este es un tema discutido. Lo aviso de entrada porque es probable que encuentres por ahí opiniones bastante contrarias a lo que voy a decir, y algunas defendidas por gente y grupos “importantes”. Me he encontrado personas por otra parte bastante razonables que descartan por completo el uso de comentarios y/o de documentación. Creo sinceramente que están equivocados. En cualquier caso, ahí queda el aviso y tú mismo puedes juzgar y formar tu opinión.

Los lenguajes de programación son, como ya hemos dicho, bastante estrictos. Más aún, a un ordenador nosotros no le importamos. Sí, esto es así. Los ordenadores no piensan, no sienten, no nos quieren ni nos odian. Y esto tiene relevancia. De las dos comunicaciones que señalaba más arriba, cuando comunicamos con el ordenador, con el compilador, intérprete, etc, a este no le importa por qué queremos hacer las cosas. Lo único que debemos decirle es lo que queremos que haga y cómo. Le decimos cosas como “suma estas dos cosas y multiplica por 3 y pones el resultado aquí” y va y lo hace. ¿Qué son esas dos cosas que sumamos? Le da igual. ¿Qué es ese 3? ¿Por qué 3 y no 147? No le importa. ¿Para qué quiero que ponga el resultado ahí? Pfff, a él plin. De hecho, al ordenador ni siquiera le importan los nombres que demos a las cosas. Para él, tan bueno es obtenerPermisos como p o eirugbcurilgleulirunteruwhe.

Pero en la otra comunicación, esa que queremos establecer con esos otros miembros del equipo, o con personas que no conocemos, o con nosotros mismos en otro instante temporal, ahí son precisamente los qués, los porqués y los para qués los que más interesan. En general, cualquiera de nosotros podremos seguir cualquier trozo de código que nos encontremos4). Sin embargo, es mucho más difícil, partiendo de un trozo de código desconocido, comprender por qué o para qué hace lo que hace. Justo por eso que decía en el párrafo anterior: Que cuando escribimos el código, el ordenador esos detalles no los necesita.

En una medida razonable, si hemos aplicado bien la idea de dar buenos nombres, de definir bien los contextos y las jergas, podemos llegar a transmitir bastantes de esos detalles a través del propio código. Si llamamos eirugbcurilgleulirunteruwhe a una función, quien lo lea se va a quedar igual, pero si la llamamos obtenerPermisos quien lo lea tendrá una idea bastante más clara de qué es esa función. Esto es indudable.

Sin embargo siguen quedando detalles que son muy difíciles de transmitir usando solo el propio código. No solo, por ejemplo, por qué algo se hace de una determinada forma, sino también por qué no se hace de otra forma distinta. A lo largo del tiempo me he encontrado personalmente en por lo menos una docena de ocasiones con alguien que, teniendo que modificar cierto código, decide cambiar el modo en que se hace algún detalle. Lee el código existente, no ve nada que le diga lo contrario y asume que no hay ningún motivo para no hacer lo que él pretende, y luego esto da lugar a un comportamiento erróneo y resulta que era importante que eso se hiciera de esa cierta forma por un motivo que lamentablemente el código no podía expresar.

Todos los lenguajes de programación razonables5) permiten incluir comentarios en el código. Los comentarios son una parte de la sintaxis del lenguaje. Están ahí para algo. Están ahí precisamente para esto, para cuando necesitemos anotar el código para expresar intenciones y motivos, para cuando queramos añadir detalles a esa comunicación con otros humanos6), para cuando tenemos que incluir un detalle que es importante pero no para el ordenador.

Como es lógico, los comentarios no deben repetir lo que ya está expresado en el código. Imagina que tras la línea de código que imaginaba unos párrafos más arriba, encontramos un comentario que dice /* sumamos dos cosas y multiplicamos por tres */. Esto no aporta nada. Esto ya lo decía el código por sí mismo. Pero esto no quiere decir que debamos evitar todos los comentarios de forma absoluta. Solo quiere decir que tenemos que escribir comentarios cuando estos sean útiles.

Con la documentación ocurre algo parecido. Hay quien defiende que el código debe ser su propia documentación… Eeeeh… no, no realmente. El código debe ser claro y debe ser expresivo y debe incluir toda la información que pueda. Pero la documentación es otra cosa. ¿Os acordáis cuando hablábamos de analizar el problema, de diseñar una solución? Cuando hacíamos esto lo más probable es que tomáramos bastantes notas, que evaluáramos varias soluciones alternativas, que comparáramos, que eligiéramos, que tomáramos algunas decisiones… Todo esto, entre otras cosas más, son detalles que jamás llegarán al código. Es más, el código está sometido a esas decisiones. Solo puede expresar el resultado de las decisiones, pero no el proceso que seguimos hasta llegar a ellas, las opciones que descartamos o los motivos por los que descartamos unas y elegimos otras.

Y todo esto, de nuevo, es importante para los humanos.

Mi recomendación, en ambos casos, es que es más fácil de perdonar el pecar por exceso que por defecto de información. Claro que escribir comentarios o documentación es un coste y requiere un esfuerzo adicional. Pero la información es valiosa y generalmente merece la pena invertir algo de esfuerzo en conservar toda la que sea o pueda ser relevante.


1)
como mínimo
2)
y generalmente debe tener
3)
o de abstracción
4)
suponiendo que conocemos la sintaxis básica de ese código y que entendemos cómo funciona
5)
y algunos que no lo son tanto
6)
o nosotros mismos

Discusión

Escribe el comentario. Se permite la sintaxis wiki: