22  ESTILO

Al hablar de estilo, se hace referencia a formatos, que, por su frecuencia de uso, han llegado a calar tan profundamente en la psique de los usuarios que estos los encuentran bellos y legibles. Las desviaciones de tales formatos o estilos, aunque no constituyen errores, hacen que los scripts sean menos legibles y agradables.

Tal y como sucede con las lenguas, los estilos suelen evolucionar con el paso del tiempo. Y, al igual que con las lenguas, aunque existen reglas gramaticales cuya violación configuraría un error, existe libertad para la expresión de diferentes estilos dentro de tales reglas. Y, de la misma manera que en las lenguas, el estilo de los escritores consagrados habrá de ser el referente de los neófitos.

Uno de los principales referentes de programación en R es la guía Google de estilo R. Por su parte, Johnson (2022) presenta una guía de estilo, a partir de una metodología que denomina arqueológica (Rchaeological), es decir, una metodología basada en la revisión de las fuentes de quienes escriben funciones de amplio reconocimiento en R, las cuales contrasta con los usos cotidianos.

A pesar de la existencia de las anteriores guías y de muchas otras, no existe un manual oficial de estilo de escritura en R1. En muchas ocasiones se encuentra que algunas de las recomendaciones de diferentes autores entran en conflicto. Asimismo, aunque existen aplicaciones que permiten formatear los scripts, los criterios empleados, y en consecuencia los formatos resultantes, pueden diferir entre sí.

A lo largo de este texto se ha mantenido un estilo consistente, el cual combina (de manera subjetiva, por supuesto) gran parte de las recomendaciones de diversos autores y las preferencias personales. A continuación, se explicitan algunos de los aspectos que conforman dicho estilo, dividiéndolos en cinco secciones: generales, espaciamiento, indentación, llaves y alineación.

¿¡Qué más da!?

Al trabajar en modo interactivo, las instrucciones que se digitan en la consola son efímeras (no se guardan), por lo que el estilo pasa a un segundo plano, bastando con escribir instrucciones sintácticamente correctas.

En contraste, cuando se escriben scripts, estos se guardan para ser leídos, ejecutados y posiblemente editados posteriormente. En este contexto es particularmente relevante manejar un estilo limpio y consistente.

¡Hay diferentes modos de ejecución!

Dejando de lado el modo interactivo (consola), debe tenerse presente que el modo de ejecución del código dentro de las funciones difiere del modo de ejecución del código aislado.

Mientras que, en las funciones se ejecuta la totalidad del código como un solo bloque, en el código aislado (por fuera de alguna función) la ejecución se realiza por bloques separados.

En consecuencia, algunos usos que son válidos dentro de una función podrían generar errores al ser evaluados aisladamente. Este es el caso de algunas prácticas concernientes al uso u omisión de las llaves (numeral 13) y al posicionamiento del controlador else (numeral 16).

22.1 Generales

  1. Se recomienda ser ‘generoso’ en el uso de comentarios; estos facilitan enormemente la lectura, el entendimiento y la depuración del código.

  2. Es recomendable ordenar las funciones por bloques de tareas o secciones, generando funciones internas cuando se trate de tareas muy elaboradas o repetitivas.

  3. El ancho de una línea no debe sobrepasar los 80 caracteres; cuando sea necesario, debe partirse la instrucción en varias líneas, respetando, desde luego, las restricciones indicadas en la sección 2.10.

    Aunque en RStudio es fácil verificar la posición del cursor, mediante el indicador que aparece en la parte inferior izquierda del editor de scripts, en el que se muestra el número de la fila (primer valor de la dupla) y el número de la columna (segundo valor), resulta más cómodo personalizar el editor de scripts para que marque la margen con una línea.

    Se ingresa por el menú Tools y se elige Global Options.... En la ventana de opciones que aparece, se elige el botón Code y luego la pestaña Display. Allí se marca la casilla de verificación correspondiente a Show margin (y de una vez, se marca también Show indent guides).

  4. Las asignaciones deben realizarse mediante el operador “<-”. El operador “=” se reserva para la definición de los argumentos de las funciones (cf. sección 2.9).

    anova <- aov(Petal.Width ~ Species, data = iris)

22.2 Espaciamiento

  1. No se deja espacio entre el nombre de una función y el paréntesis de apertura. Tampoco se deja espacio entre los paréntesis y su contenido.

    library(car)
    read.delim(file.choose())
  2. Cuando se usan los controladores de flujo if, for, else y else if, se deja un espacio entre estos y el paréntesis. También se dejan espacios entre operadores binarios tales como +, -, *, >, >=, <, <=, =, <- , ==, %in% y ~. Después de la coma siempre va un espacio; nunca antes de esta.

    for (f in flores)
    if (i == 7)
    binom.test(185, 200, alternative = "greater", p = 0.9)
    m2[2, ]
    m2[, 3]
  3. Al declarar una función, se deja un espacio entre la instrucción function y el paréntesis de apertura.

    statistics <- function (x)
  4. En general, se usa un solo espacio; no obstante, en ocasiones pueden usarse varios espacios, con el fin de alinear un conjunto de instrucciones.

    peso  <- 65
    talla <- 1.65
    imc   <- peso/talla^2
  5. En los comentarios, se deja un espacio entre el símbolo de apertura (#) y el primer carácter. Cuando se incluye un comentario al final de una línea con instrucciones, el símbolo # se separa del último carácter de la instrucción con doble espacio.

    edad <- c(34, 43)  # Vector tipo doble precisión
  6. Cuando las llaves de apertura de un bloque van en la misma línea del controlador de flujo (if, else, else if, while, repeat, etc.), se separan del último carácter de la línea con un espacio.

    while(epsilon > 0.001) {
  7. Al realizar la carga temporal de un paquete (cf. tip 4.1), no se deja espacio entre el nombre del paquete, el operador :: y el nombre de la función.

    data <- readxl::read_excel("Libro1.xlsx")

22.3 Indentación

En R, a diferencia de otros lenguajes de programación, la indentación cumple una función netamente estética. Consiste en desplazar una instrucción o un conjunto de instrucciones hacia la derecha, un número determinado de espacios —al estilo de lo que se logra con un tabulador, pero sin utilizar tabuladores—. Su propósito es destacar visualmente los bloques de código que forman parte de estructuras como ciclos, condicionales o funciones.

  1. Los bloques de instrucciones, es decir, el conjunto de instrucciones de un ciclo, un condicional o una función, se indentan con dos espacios a la derecha. Si se tienen bloques dentro de bloques, cada anidamiento da lugar a dos espacios adicionales.

    if (minúsculas == T)
      for (i in 1:5)
        cat("Esta es la letra que ocupa la posición", i, ":", letters[i], "\n")
    else
      cat("No se muestra ninguna letra")
    ¿¡Sí hace falta indentar!?

    Aunque la indentación hace más legible el código, no tiene ningún efecto sobre su ejecución.

22.4 Llaves

Las llaves se utilizan para demarcar bloques o conjuntos de instrucciones que forman parte de una función, un ciclo o un condicional. Cuando el bloque incluye más de una instrucción, el uso de las llaves es obligatorio.

  1. Cuando un controlador de flujo (ciclo o un condicional) da lugar a una acción que se realice a través de una única instrucción, esta se escribe en la línea siguiente, con la correspondiente indentación.

    Precaución 22.1: ¿Con llaves o sin llaves?

    Cuando la acción dentro del controlador de flujo consta de una única instrucción, es posible omitir las llaves si el código está dentro de una función.

    Cuando se evalúa un condicional en código aislado, es obligatorio encerrar la instrucción entre llaves, aunque se trate de una única instrucción.

    # Dentro de una función (es posible omitir las llaves)
    
    if (length(x) != length(y))
      stop("¡Error!, los vectores no tienen el mismo tamaño") 
    else
      print(z <- x * y)
    # Código aislado (uso obligatorio de llaves)
    
    if (length(x) != length(y)) {
      stop("¡Error!, los vectores no tienen el mismo tamaño")
    } else {
      print(z <- x * y)
    }

    Cabe destacar el caso de los bloques de instrucciones. Considérese el siguiente fragmento de código, suponiendo que está dentro de una función:

    if (minúsculas == T)
      for (i in 1:5) {
        cat("Esta es la letra que ocupa la posición", i, ": ")
        cat(letters[i], "\n")
      }
    else
      cat("No se muestra ninguna letra")

    En el código anterior, el bloque de instrucciones conformado por el controlador de flujo for, junto con todas sus intrucciones, se considera como una única instrucción. En consecuencia, sería posible omitir las llaves que le corresponderían al controlador if si este código estuviera dentro de una función.

  2. Cuando el bloque de instrucciones de un controlador de flujo va entre llaves, la llave de apertura debe ir al final de la línea en la que aparece el controlador; la de cierre debe ir sola en una línea.

    if (type == 'ee') {
      main <- c('Medias y errores estándar por nivel de ', nombre.g)
      d.g  <- sqrt(v.g)/sqrt(r.g)
    }
  3. La llave de apertura de las funciones definidas por el usuario va sola en una línea, después de la declaración de los argumentos; la llave de cierre ocupa una línea al final.

    grafBar <- function (x = 2, groups = 1, data = NULL, type = 'de')
    {
      .
      .
      . 
    }
  4. Cuando se usan estructuras de flujo if...else o if...else if en código aislado, el controlador else o else if tiene que ir en la misma línea en la que aparece la llave de cierre del controlador anterior (if o else if).

    En este caso, no se trata de un asunto estilístico, sino de una condición necesaria para la ejecución del código.

    Código 22.1: Estructura if...else en código aislado (modo script)
    if (length(x) != length(y)) {
      cat("¡Error!", "\n") 
      cat("Los vectores no tienen el mismo tamaño")
    } else {
      z <- x * y
      print(z)
    }

    El patrón mostrado en el código 22.1 puede generar disonancia visual si se viene siguiendo la convención de alinear verticalmente los controladores del mismo nivel y colocar una llave por línea. Adicionalmente, se estarían dejando de indentar las instrucciones del bloque else.

    Esta disonancia visual puede evitarse cuando el fragmento de código forma parte de una función. Para mantener la consistencia en la indentación y la estructura del código, puede ubicarse else en la línea siguiente, alineado con if, tal y como se ilustra en el código 22.2.

    Esta solución, además de ser más armónica visualmente, permite indentar el bloque de instrucciones del controlador else. Es posible verificar que muchas de las funciones base en R siguen este esquema.

    Código 22.2: Estructura if...else dentro de una función
    if (length(x) != length(y)) {
      cat("¡Error!", "\n") 
      cat("Los vectores no tienen el mismo tamaño")
    }
    else {
      z <- x * y
      print(z)
    }

    Es necesario aclarar que la recomendación que se ilustra mediante el código 22.2 es netamente estilística y que —aun dentro de funciones— el código 22.1 se ejecuta correctamente, constituyendo, por tanto, la alternativa más segura.

    Para usuarios que priorizan la legibilidad vertical

    Ubicar el controlador else en una nueva línea, alineado con if, mejora la simetría visual del bloque condicional, facilitando la lectura estructurada.

    Sin embargo, este estilo solo debe emplearse dentro de funciones, ya que en código aislado genera errores.

  5. Cuando se requiera usar varias llaves de cierre, por coincidir la terminación de varios ciclos y/o funciones, se ubica una llave por línea.

    impLetras <- function (cantidad, minus)
    {
      if (minus) {
        for (i in 1:cantidad) {
          cat("Letra minúscula que ocupa la posición", i, ": ")
          cat(letters[i], "\n")
        }
      }
      else {
        for (i in 1:cantidad) {
          cat("Letra mayúscula que ocupa la posición", i, ": ")
          cat(LETTERS[i], "\n")
        }
      }
    }
    Déjela sola…

    Compilando lo indicado en las recomendaciones 13, 14 y 16, la llave de cierre, ya sea de un ciclo un condicional o una función, va sola en una línea.

    Se hace necesario introducir una excepción a esta recomendación cuando se trabaja con estructuras if...else o if...else ifen código aislado. En estos casos, las llaves de cierre de los ciclos previos al último no van solas, sino seguidas del controlador de flujo else (o else if), tal y se ilustra en el código 22.1:

    } else {

22.5 Alineación

  1. Las llaves de cierre “}” se alinean con la primera letra del controlador de flujo o del nombre de la función.

    impLetras <- function (cantidad, minus)
    {
      if (minus) {
        for (i in 1:cantidad) {
          cat("Letra minúscula que ocupa la posición", i, ": ")
          cat(letters[i], "\n")
        }
      }
      else {
        for (i in 1:cantidad) {
          cat("Letra mayúscula que ocupa la posición", i, ": ")
          cat(LETTERS[i], "\n")
        }
      }
    }
  2. Cuando una asignación o la definición de los parámetros de una función requieran más de una línea, esta debe partirse. Los valores que vayan en la segunda línea y sucesivas se alinean con el primer carácter dentro del paréntesis.

    vector <- c('a', 'c', 'f', 'd', 'x', 'm', 'j', 'l', 'd', 'b',
                'u', 'y', 'b', 'j', 'h', 'i', 'h', 'o', 'w', 'p',
                'r', 's', 'n', 'q')
    capture.output(summary(iris), cat("\n","\n"), head(iris),
                   cat("\n","\n"), tail(iris), cat("\n","\n"),
                   summary(anova), file="Salidas.doc")

Referencias Bibliográficas

Johnson, P. E. 2022. «R Style. An Rchaeological commentary». https://cran.r-project.org/web/packages/rockchalk/vignettes/Rstyle.pdf.

  1. Como sí ocurre con otros lenguajes, como Python y su PEP 8.↩︎