Recientemente leí una entrada en el blog argumentando que los comentarios son una parte importante del código, y la gente que afirma lo contrario se está perdiendo. Aunque tiendo a caer en el lado opuesto del argumento, ciertamente estaría de acuerdo en que hay un tiempo y un lugar donde los comentarios en código tienen sentido. Más interesante aún, me di cuenta de que no he visto muchas explicaciones de por qué los comentarios son subóptimos, y cuando todavía son importantes. Así que, en un esfuerzo por mejorar esa situación, aquí está mi opinión sobre los principios que hay detrás de los comentarios, y cuándo esos mismos principios están mejor servidos con diferentes herramientas.
Para entender por qué se pueden querer comentarios, es útil comprender primero el contexto en el que se crearon los comentarios. Los primeros lenguajes de programación se ocupaban principalmente de la mecánica de la computación. También eran extremadamente verbosos, con incluso operaciones simples que requerían una serie de declaraciones diferentes. Estos problemas se ven agravados por el hecho de que las declaraciones no están diseñadas para transmitir un significado a un humano, sino que son instrucciones para una máquina (sorprendentemente limitada).
Para aclarar mejor el punto, veamos un ejemplo de código ensamblador bien escrito. Específicamente, esta es una pequeña muestra del código informático de guía del Módulo de Mando del Apolo1. Este es, literalmente, un código lo suficientemente bueno para poner a los hombres en la Luna. Los ingenieros que escribieron este código sabían que un error podría terminar matando a una tripulación de Apolo, así que usaron las mejores herramientas disponibles para asegurar el éxito. Esas herramientas incluían comentarios del código, que he eliminado de la siguiente muestra.
BOTONES TC LIGHTSET CAF HI5 MÁSCARA ERESTORE EXTEND BZF +2 TCF NONAVKEY +1 CS ERESTORE EXTEND BZF ELRSKIP -1 AD SKEEP7 EXTEND BZF +2 TCF NONAVKEY +1 CA SKEEP4 TS EBANK EXTEND DCA SKEEP5 INDEX SKEEP7 DXCH 0000 CA ZERO TS ERESTORE TC STARTSUB
Sin los comentarios, el ensamblaje y su aspecto se vuelven rápidamente ininteligibles. Incluso después de que sofisticados compiladores permitieran la creación de lenguajes de programación de mayor nivel, todavía existían restricciones en el número de llamadas a funciones y en la longitud de los nombres de las variables que limitaban la capacidad de los programadores para expresarse claramente en el código.
Dadas estas limitaciones, los comentarios fueron «la peor forma de contexto, excepto por todas las demás que se han probado «2. Los comentarios seguían teniendo los mismos problemas que vemos hoy en día, pero no había una mejor solución disponible. De hecho, todavía hay algunos entornos de programación que tienen restricciones que limitan nuestra capacidad de incluir el contexto con nuestro código. Dadas esas restricciones, escribir comentarios apropiados es una habilidad importante para un desarrollador.
Contexto sin comentarios
El objetivo de los comentarios es proporcionar un contexto al prójimo que no está presente en el código. Como creo que proporcionar contexto es una de las tareas más importantes de un desarrollador de software, se podría pensar que sería un gran defensor de los comentarios. Entonces, ¿por qué, en cambio, estoy escribiendo una entrada de blog tratando de convencerte de que tal vez no necesites ningún comentario en tu código?
El mayor problema que tengo con los comentarios es que divorcian el contexto que proporcionan de la decisión real. En ambientes restringidos, pueden representar lo mejor que puedes hacer. Sin embargo, cuando puedes escribir código que incluya el contexto de por qué , muchos de los problemas que vienen de tener dos representaciones independientes de una solución desaparecen. Probablemente hay tantas maneras de intentarlo como personas que escriben código, pero dos que he encontrado muy poderosas son el código expresivo y las buenas pruebas de unidad.
Código expresivo
Una de las mayores herramientas que ayuda a integrar el contexto en el código es el aumento de las características expresivas del código. En la mayoría de los entornos, las llamadas a funciones locales están lo suficientemente cerca como para liberar que el único momento en que nos encontramos con problemas de profundidad de la pila de llamadas es cuando la recursividad va mal. El único límite efectivo en los nombres de las variables es cuánto estás dispuesto a escribir, así que no tenemos que incluir comentarios sobre lo que iLoc2 significa realmente.
Considere este ejemplo de… Tal vez, la biblioteca de la que hablamos en un post anterior:
publicstaticMaybe<T;FirstOrNone<T;(thisIEnumerable<T;self)whereT:class{returnself.FirstOrDefault().ToMaybe();}
En (efectivamente) dos líneas de código, aprendemos que FirstOrNone es un método de extensión en IEnumerable que devuelve el primer artículo de la colección o Maybe.None. Hay mucho que hacer entre bastidores para que eso funcione realmente, pero el objetivo de las abstracciones es permitirle ignorar algunas complejidades técnicas y en su lugar trabajar en generalizaciones. Y aunque este es un ejemplo extremo, he escrito mucho código que efectivamente no puede ser comentado útilmente, porque los comentarios sólo duplicarían lo que el código está haciendo realmente.
GUTs
Las buenas pruebas de unidad3 son una segunda y poderosa forma de incrustar el contexto en su código. Gran parte de ese poder proviene del código expresivo, pero el hecho de que nunca llames a tus pruebas te libera incluso de las restricciones (relativamente menores) que tendemos a poner en el código de producción. Por ejemplo, veamos algunas de las pruebas de… Tal vez:
clase públicaCuando_obtenga_el_valor_y_hay_un_valor:Especificación_de_contexto{por=()=[...resultado=quizás.Valor;Debe_devolver_el_valor=()=[...resultado. ShouldEqual("hi");staticMaybe<string;|quizás=Quizás.Some("hi");staticstringresult;}clase_públicaCuando_se_obtiene_el_valor_y_hay_un_valor:ContextSpecification{Becauseof=()=>result=Catch. Excepción(()=.Cuando_obtenga_el_valor_y_hay_no_un_valor y debe_tirar_una_excepción_de_operación_invalida no son el tipo de nombres que quiero ver en el código "normal". Pero hacen un gran trabajo de describir, en inglés, lo que se está probando, y lo que puedo esperar al llamar .Value.
El poder de las pruebas unitarias como comentarios también proviene del hecho de que pueden ser probadas por una computadora (usted está ejecutando sus pruebas unitarias de manera automatizada, ¿verdad?). Es muy fácil que los comentarios se divorcien accidentalmente de la verdad con el tiempo, ya que el código se mueve y el contexto cambia. Como profesionales, intentamos evitar que eso suceda, pero la única forma efectiva de hacerlo requiere de una vigilancia humana. Dejando que un ordenador haga el duro trabajo de comprobar que sus comentarios siguen siendo verdaderos, sólo tiene que prestar atención cuando algo cambia.
Conclusión
Los comentarios proporcionan la forma más simple y universal de incluir un contexto adicional alrededor de nuestro código. En algunas situaciones, son la única forma efectiva de proporcionar ese contexto. En muchos casos, sin embargo, hay formas de incluir ese contexto que se integran con el código real, protegiéndonos de los problemas que surgen cuando se separan el contexto y la implementación.
1 El código fuente completo de la computadora de guía de Apolo está disponible en Github. Esta muestra fue tomada de FRESH_START_AND_RESTART.agc
2 Con disculpas a Winston Churchill
3 Para una discusión mucho más profunda de lo que hace una buena prueba de unidad, recomiendo esta charla de Kevlin Henney
Categorías: technicalTags: clean code, testing