Cuando recién empiezas a programar, suelen decirte que comentes tu código todo lo posible. Pero al poco tiempo lees un artículo que te dice justo lo contrario. ¿Confundido? Este artículo te dará un panorama claro de cuándo conviene comentar y cuándo no.
El principio es simple: deja que el lenguaje y el runtime garanticen, lo antes posible, que lo que documentas sobre el software es cierto.

Nuestros guardianes robóticos
Voy a comenzar con una función sin documentar e ir subiendo de nivel en el orden en que normalmente implementaría las cosas. Primero, lo básico y sencillo; después, la verificación automatizada de que la documentación dice la verdad; y por último, las formas más débiles de documentación, para cuando no haya verificación automatizada disponible.
1\. Sin documentación
Empecemos con esta función simple, p!, el factorial, sacada directamente de los ejercicios básicos de programación. El ejemplo está en Python, pero esto aplica también a otros lenguajes de programación.
def fact(p):
ret = 1
if x == 0:
return 1
for i in range(1, x + 1):
ret *= i
return ret
Podrías sentirte tentado a agregar un comentario como:
""" This is the factorial """
Pero los comentarios muchas veces no se leen, y este se vuelve redundante en cuanto tienes nombres decentes.
2\. Nombres
Cambia la función por:
def factorial(p):...
Ahora la documentación está integrada en el código que se ejecuta, no pegada al lado. Quien quiera usar tu función tiene que leer este nombre. Por eso conviene que las funciones sean cortas y tengan un único propósito: así el nombre puede estar cerca del código y describirlo bien.
Cualquier otra mejora que ayude a quien lee a entender tu código (por ejemplo, usar miembros privados cuando corresponda) también es una forma decente de documentación.
Ahora, renombra el parámetro a n, para recordarles a los usuarios que es un entero.
def factorial(n):...
Después de todo, aunque el factorial solo está definido para enteros, puede extenderse a no enteros. La función Gamma es una especie de factorial extendido.
Un usuario avanzado podría preguntarse si eso se usa en nuestra implementación, y con razón: scipy.special.factorial, por ejemplo, sí da una respuesta para entradas float no enteras, usando la interpolación Gamma del factorial. Necesitamos aclarar que eso no es lo que ofrecemos aquí.

La función Gamma extiende el factorial a casi todos los números reales (y complejos).
3\. Type Hints
El nombre del parámetro es una especie de comentario, y podrías escribir un comentario para indicar que es un int (como se hace en scipy.special.factorial). Pero hay una mejor manera de avisarle al lector que el parámetro es un entero: los type hints.
def factorial3(n: int) -> int:...
Las herramientas de desarrollo de Python te avisarán de la mayoría de los errores. El principio: deja que la computadora lo documente por ti y reduce al mínimo la dependencia del cerebro humano. Por ejemplo, si tu implementación no acepta el float 3.0 como si fuera un int, el type hint lo documentará de inmediato, algo que de otra forma podrías pasar por alto.
Los comentarios pueden estar desactualizados o ser directamente incorrectos. Por ejemplo, scipy.special.factorial (sin querer atacar a SciPy, ¡es una excelente librería!) dice en su comentario " n : int", pero en realidad acepta floats e incluso devuelve un valor según la función Gamma.
Tipado en tiempo de compilación
Nuestros ejemplos están en Python, pero si usas un lenguaje con tipado en tiempo de compilación como Java o C++, al declarar los tipos el compilador bloqueará muchos errores ya en tiempo de desarrollo, garantizando lo antes posible que esta "documentación" es precisa.
Aun así, las aserciones funcionan mejor para detectar restricciones más finas, como prohibir números negativos como entrada, salvo que uses un lenguaje con un sistema de tipos más avanzado.
4\. Aserciones, excepciones o pre y post condiciones
Puedes ir un paso más allá de los type hints. Asegura que el valor sea un entero y que sea no negativo. Así, ya no es solo el tooling de desarrollo guiñándote el ojo: el runtime cortará los errores en cuanto ocurran.
def factorial4(n: int) -> int:
assert isinstance(n, int), f"{n} is not an integer and so unsupported."
assert n >= 0, f"{n} is negative and so unsupported."
...
Hay distintas formas de lanzar el error.
Como no tiene sentido permitir que esos errores avancen, deja las aserciones habilitadas. Pero, como alternativa, podrías lanzar un ValueError. Si usas un lenguaje o librería que admite programación por contrato, con pre y post condiciones (una especie de aserción estructurada), también te servirá.
@icontract.require(lambda n: isinstance(n, int) and n>=0, "n must be a nonnegative integer")
def factorial_precondition(n: int) -> int:
return __iterative_factorial(n)
Llegados a este punto, la computadora realmente está revisando nuestro trabajo. Pero las aserciones u otras excepciones tienen un segundo valor: son una forma de documentación que no puede equivocarse. Quien lee sabe que lo que dice una aserción es verdad.
Volviendo a nuestra piñata, scipy.special.factorial, el comentario dice " If n < 0, the return value is 0."
Técnicamente, eso es cierto en esta implementación, pero resulta confuso para un usuario que sabe que el factorial no está definido para números negativos. Incluso la extensión Gamma del factorial, aunque está definida sobre los reales e incluso los complejos, no lo está sobre los enteros negativos. Es mucho mejor lanzar un error y dejar bien claro qué está permitido y qué no.
5\. Unit Testing
Los unit tests se parecen a las aserciones: verifican que el sistema haga lo que esperas durante el desarrollo. Como documentación, son más débiles que las aserciones porque están lejos del código, tanto en los archivos en los que viven como en el momento en que se ejecutan. Es menos probable que otros devs los lean.
Aun así, si los desarrolladores ejecutan los tests continuamente, como deberían, empezarán a ver los errores rápido, sin necesidad de meterse a leer comentarios.
Por ejemplo, ¡nuestro factorial iterativo simple está roto! Devuelve el valor 1 para valores negativos, lo cual sin duda está mal, tanto para el factorial como en la extensión Gamma. Este test mostrará que esperamos un error, pero lamentablemente obtenemos un valor de retorno.
def test_factorial():
with pytest.raises(Exception):
factorial(-2)
Incluso puedes meter unit tests en miniatura dentro de los comentarios usando doctest, una funcionalidad nativa de Python 3. Esto coloca el test lo más cerca posible del código, donde mejor sirve como documentación (aunque también puede saturar la vista).
def factorial_doctest(n) -> int:
"""
Run this with python -m doctest -v factorial.py
>>> factorial_doctest(3)
>>> factorial_doctest(0)
>>> factorial_doctest(1.5)
Traceback (most recent call last):
...
TypeError: 'float' object cannot be interpreted as an integer
"""
return __iterative_factorial(n)
De forma similar, los tests de integración están un paso más lejos del código y resultan menos adecuados para documentarlo. En cambio, documentan el comportamiento del sistema en su conjunto.
6\. Logging
Si van a pasar cosas malas y no tienes manera de prevenirlas con anticipación, al menos puedes registrarlas. Volvamos a SciPy.
Si pasas un float, obtienes esto en la salida estándar de error:
DeprecationWarning: Using factorial() with floats is deprecated
Aparentemente usaron una implementación basada en Gamma y, más adelante, al darse cuenta de que solo deberían soportarse enteros, quisieron restringir la entrada a int. Como no pudieron hacerlo por aplicaciones que dependen de eso, al menos escriben la advertencia en un lugar donde puede llegar a leerse. Los devs quizá no lean la documentación, pero cuando se topan con problemas, sí leen los logs.
7\. Comentarios
Llegamos a nuestro penúltimo recurso: el comentario. Tienen su lugar, pero, en mi experiencia, solo en estos tres casos:
a. Las APIs públicas deben documentarse con comentarios inline, seguidos de la generación de HTML con Doxygen para Python, Javadocs para Java, etc. Conviene estructurar los comentarios con tags, siguiendo el principio de que cuando una computadora puede verificar la estructura, debe hacerlo.
Un ejemplo de comentarios estructurados en Python:
:param n: A nonnegative integer
:return: The product of all integers from 1 up to and including n; or for 0, return 1.
Pero cuando la función no es una API pública, usa estos campos estructurados con moderación: estos comentarios sirven para transmitir un mensaje específico sobre algo inusual, no para documentar meticulosamente todos los aspectos de uso.
b. Los hechos sorprendentes deben documentarse. Por ejemplo, los usuarios podrían no saber que el factorial de cero está definido como 1, ya que no parece encajar con la forma en que se define el factorial como "el producto de todos los números de 1 a n". De la misma manera, los workarounds raros y los hacks feos deberían comentarse en el código.
c. Los algoritmos deben nombrarse, ya que la computadora no puede mostrar ni garantizar qué algoritmo se está usando, y quien lee tampoco siempre puede identificarlo a simple vista. Por ejemplo, el factorial puede calcularse mediante multiplicación o aproximarse con varios algoritmos para Gamma. Un usuario con aplicaciones numéricas exigentes podría querer saberlo.
def factorial(n: int) -> int:
"""
This is a simple iterative implementation of factorial.
The input must be a non-negative integer.
Note that the factorial of zero is defined as 1.
"""
8\. Documentación externa
Los comentarios eran el penúltimo recurso; la documentación externa es el último de todos. RTFM es un buen lema, pero pocos leen el manual, ya que está tan lejos del código que se desactualiza con facilidad. Aun así, si necesitas un panorama general de la funcionalidad o tutoriales, puedes ponerlo en un documento que se mantenga por separado del código.
Pero ojo: tienes que revisar y releer estos documentos continuamente si quieres que reflejen lo que hay en el código, que cambia constantemente.

Mientras más cerca del código y más automatizado, mejor; pero todo tiene su utilidad.
La documentación debe decir la verdad. El tooling de tu lenguaje y el runtime tienen muchas formas de garantizar la confiabilidad, así que aprovéchalas. Pon la documentación lo más cerca posible del código y haz que la verificación automatizada de su exactitud ocurra temprano en el proceso de desarrollo.
El código ejecutable de las distintas implementaciones está aquí, con unit tests: https://github.com/doitintl/commenting
Para mantenerte al día, síguenos en el DoiT Engineering Blog , el canal de DoiT en LinkedIn y el canal de DoiT en Twitter . Para explorar oportunidades laborales, visita https://careers.doit-intl.com .