Notxor tiene un blog

Defenestrando la vida


El infierno del indentado

Estos días he estado trabajando en un tema en Python. En otros lenguajes, el indentado del código es más una cuestión estética que funcional, pero ya sabéis que ese lenguaje utiliza, precisamente el indentado, para establecer los bloques de código. Es una forma elegante de obligar al programador a escribir código legible, quiera o no, pero viendo lo que suele ocurrir con ello, se convierte en el origen de muchas frustraciones. ¿Por qué? Pues básicamente porque a cada programador le gusta una forma de hacerlo y eso implica, también, que los editores de texto lo hagan por defecto a la manera que le guste a quien lo programó, que puede coincidir o no con el gusto del usuario, y/o con el gusto del programador del lenguaje, y/o con el de los que hicieran el estándar del mismo. Esto, básicamente, es lo que yo llamo el infierno del indentado.

En emacs el indentado sigue siendo un infierno, además, no define ninguna manera por defecto y se lo deja al usuario y al programador, haciendo habitual que sea cada modo mayor el encargado de velar por la correcta estructura del código que se esté trabajando. Esto tiene sus ventajas y sus desventajas. Entre las ventajas, claro está, la capacidad de que teniendo buffers abiertos con distintos lenguajes, cada uno de ellos utilice el indentado que corresponda sin interferir en los otros buffers. Y la principal entre las desventajas es que la mayoría de programadores vienen acostumbrados de otros editores a esperar un comportamiento uniforme en ese sentido y a tener que cambiarlo cada vez que cambias de lenguaje... y a cambiarlo a su gusto, naturalmente; encontrándose que después de haber gastado tiempo en configurar los espacios y tabuladores, resulta que abren un fichero de código y directamente pasan de sus configuraciones.

Hace tiempo que dejé de pegarme con el indentado en emacs, simplemente espero que funcione correctamente, porque me tiene acostumbrado a ello: pulsas enter cuando acabas una línea y el cursor va a la posición que corresponda según los usos y costumbres del lenguaje en el que estás programando y no me preocupo de más. Hasta aquí todo correcto y si eres el único programador del proyecto en el que estés y lo haces siempre con el mismo editor y no hay interferencias de otros editores, todo funciona perfectamente. Pero en un mundo ideal, si todo funcionara según las intenciones del que actúa y no según sus acciones, nos aburriríamos demasiado.

Descubriendo incongruencias en el indentado

Como decía, había estado trabajando un poco con Python y siendo ese lenguaje tan especial con el tema de los espacios al principio de línea, me encontré con el infierno del indentado de nuevo.

Por poner un ejemplo y que se entienda, vamos a analizar el siguiente código:

def con_argumento( arg="" ):
    if arg:
                print("El argumento es: ", arg)
    else:
        print("No hay argumento")

con_argumento("Hola")
con_argumento()

Mientras esperas que la salida sea algo como:

$ python3 prueba.py 
El argumento es: Hola
No hay argumento

Resulta que lo que lanza es algo como:

$ python3 prueba.py 
  File "prueba.py", line 3
    print("El argumento es: ", arg)
                                  ^
TabError: inconsistent use of tabs and spaces in indentation

¡Mierda! ¡Ya estamos! ... A ver; vamos por partes y os lo explico todo ─al menos lo poco que yo sé─.

Preferencias personales

Cuando uno tiene que lidiar con este tema, termina haciéndose una idea de cómo le gusta o no le gusta a él el indentado. En emacs, como en otros editores se pueden establecer esos gustos por defecto en el fichero init.el. Por poner mi ejemplo, yo tengo establecidos los siguientes parámetros en la lista de custom-set-variables:

'(indent-tabs-mode nil)
'(tab-width 4)

También se podría establecer fuera de dicha lista con:

(setq indent-tabs-mode nil)
(setq tab-width 4)

La primera variable estable que no se utilicen tabuladores para el indentado y que sean reemplazados por espacios y la segunda variable, establece que cada tabulador tenga un ancho de 4. El evitar que utilice tabuladores evita que al pasar de un editor a otro, o cuando ves el código en github, por ejemplo, todo el indentado se haya ido al garete.

Como ya he dicho, luego cada modo mayor establece sus parámetros al gusto del programador del modo, que suele coincidir con lo más habitual de cada lenguaje. Pero es interesante establecer los valores por defecto, para los casos en que los modos no tratan el indentado en su código.

¿Y qué ocurre si mis gustos personales no coinciden con los que tenga quien programó el modo de emacs? Pues tienes dos opciones, te acostumbras a lo que diga el modo o te creas un gancho, del estilo:

(add-hook 'python-mode-hook
          (function
           (lambda ()
             (setq indent-tabs-mode nil)
             (setq tab-width 4))))

Ese código se ejecutará después de establecerse el modo Python, por ejemplo, y ajustará los valores deseados a como tú los quieras. Pero es muy cansino hacerlo para todos y cada uno de los modos que puedes utilizar.

Visualizar los espacios en emacs

Todo esto, está muy bien: ya tenemos controlada ─un poco─ la fiera del indentado... ¿Qué pasa cuando nos llega código de otros, escrito con otros editores o con otros ajustes o gustos? Primero de todo es identificar dónde está el problema visualizándolo de manera directa. Si estás acostumbrado a programar en Python y a utilizar editores diseñados para la programación en dicho lenguaje, estarás más que harto de ver cómo el editor te muestra los saltos de tabulación y los espacios para delimitar los bloques de código. Eso también se puede visualizar de forma sencilla en emacs con el modo menor whitespace-mode.

En este caso, no hay que instalar nada, pues es uno de los modos que se incluyen en la base de emacs, sólo hay que activarlo llamando a whitespace-mode (o desactivarlo, con el mismo comando). Al activarse mostrará ws en la línea de estado.

Si lo activas y no lo has utilizado nunca, ni lo has configurado de otra manera, verás los espacios marcados por un carácter ·, los saltos de línea con $, los tabuladores con ». Todo eso se puede configurar, no sólo los colores que utilizará, sino también los caracteres por otros más a tu gusto o costumbre... En mi caso en su día utilicé el carácter | para el tabulador y el para el final de línea, pero quité los ajustes en una remodelación de mi init.el y ya me he acostumbrado a los valores por defecto.

Todo esto al final también es una cuestión de gustos, o más que gustos, de costumbre... pero que sepáis que poder se puede.

Cambiar el indentado sin morir en el intento

Hemos activado el modo y descubierto que nuestro código luce más o menos así:

def·con_argumento(·arg=""·):$
····if·arg:$
»   ····print("El·argumento·es:·",·arg)$
····else:$
········print("No·hay·argumento")····$
····$
con_argumento("Hola")$
con_argumento()$

Además lo marca con colores y vemos que tenemos algunas líneas que tienen al final espacios en blanco que sobran y que la tercera línea está mezclando caracteres » y ·, lo cual indica que los primeros cuatro espacios son un tabulador y los siguientes cuatro son espacios en blanco. ¿Cómo se quita? Muy fácil, con un sencillo par de pasos:

  1. Marcamos las líneas que queremos modificar.
  2. Llamamos al comando untabify, ─si queremos utilizar espacios en blanco─, o al comando tabify, ─si queremos utilizar tabuladores─, ya sabéis: va en gustos.

Cuando utilizamos tabify (o untabify) es posible que no veamos que emacs hace algo, porque el alineamiento puede estar mostrando por pantalla las cosas de manera normal: como hemos visto en el ejemplo, si el tabulador está fijado a 4 y el código está escrito para ajustarse a esos cuatro espacios, visualmente no se apreciará el cambio, a no ser que tengamos activado el modo whitespace. Algo más habitual de lo que se cree, porque los errores cuando no coinciden el ancho de tabuladores y los espacios, saltan a la vista; pero cuando coinciden y no se ven a simple vista, suele ser cuando más molestos de encontrar son.

Y ya está explicado todo lo que sé sobre el infierno del indentado y cómo enfrentarse a él desde emacs.

Conclusión

Es algo que hace que me rechinen los dientes cuando tropiezo en ello. Cada vez que me encuentro con un problema de estos me da vueltas el higadillo y la bilis lo salpica todo. Tengo que respirar hondo un par de veces y luego ya, activo whitespace-mode para visualizar los errores. Como ya habéis visto, es fácil corregirlos e inviertes sólo un rato, pero un rato que deberías estar dedicando a otra cosa importante: como comer, dormir, etc.

También quiero destacaros que no os dejéis llevar por las prisas y marquéis todo el buffer con C-x h y llaméis al comando (un)tabify para cambiar todo de golpe. A ver, poder se puede, pero esos comandos no distinguen si los espacios están o no al principio de las líneas y puedes estar modificando también espacios o tabuladores puestos para hacer una tabla en los comentarios u otros formateos de texto, como alinear las entradas de variables para visualizarlas mejor, etc. Dicho de otro modo: «Usadlo con cuidado».


Comentarios

Debido a algunos ataques mailintencionados a través de la herramienta de comentarios, he decidido no proporcionar dicha opción en el Blog. Si alguien quiere comentar algo, me puede encontrar en Mastodon y en Diaspora con el nick de Notxor.

También se ha abierto un grupo en Telegram, para usuarios de esa red.

Disculpen las molestias.