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 siquis 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 R. 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 y, por ende, los formatos generados no coinciden. 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, llaves, indentación y alineación.

Es importante tener en cuenta que la compilación de las funciones difiere de la del código aislado. 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 (o falta de uso) de las llaves y al posicionamiento del controlador else. Aunque todas las recomendaciones que aquí se presentan están dirigidas a la escritura de funciones, esto no debe ser motivo de preocupación, puesto que el principal uso de los controladores de flujo es dentro de las funciones.

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.

  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 las secciones 2.10.1 y 2.10.2. 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). También es posible (y recomendable) personalizar RStudio para que muestre la margen, ingresando por el menú “Tools” y eligiendo “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.

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

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(homals)
    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 +, -, *, >, >=, <, <=, =, <- , == 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) {

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 conjunto en cuestión consta de más de una instrucción, el uso de las llaves es obligatorio; los demás aspectos que se describen a continuación son de estilo.

  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, sin encerrarse entre 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:

    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 que conforman el controlador de flujo for se considera como una única instrucción. En consecuencia, es posible omitir las llaves que le corresponderían al controlador if.

  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. Las instrucciones else y else if, cuando forman parte de un grupo de instrucciones compuestas, van en la línea siguiente a la llave de cierre y seguidas de la lleve de apertura.

    if (length(x) != length(y)) {
      cat("¡Error!", "\n") 
      cat("Los vectores no tienen el mismo tamaño")
    }
    else {
      z <- (x * y)
      print(z)
    }
  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")
        }
      }
    }
  6. Compilando lo mencionado en las recomendaciones 12, 13, 14 y 15, la llave de cierre, ya sea de un ciclo un condicional o una función, siempre va sola en una línea.

Indentación:

  1. Los bloques de instrucciones, es decir, el conjunto de instrucciones que va dentro de llaves (por formar parte de un ciclo, un condicional o una función), van indentados con dos espacios a la derecha. Si se tienen bloques dentro de bloques, cada anidamiento da lugar a dos espacios adicionales (Ver código de ejemplo de la recomendación 15).

    Cabe mencionar que aunque es muy recomendable seguir la anterior recomendación, para hacer más legible el código, esta es netamente estética. En R, a diferencia de lo que sucede en otros lenguajes, la indentación no tiene nigún efecto sobre la ejecución del código.

Alineación:

  1. Las llaves de cierre “}” se alinean con la primera letra del controlador de flujo o del nombre de la función (Ver código de ejemplo de la recomendación 15).

  2. Aunque muchos manuales de estilo recomiendan encerrar las instrucciones else y else if entre llaves, esto entra en conflicto con la correspondiente indentación, obligando a dejar sin indentar las instrucciones contenidas dentro de estos controladores, tal y como se muestra a continuación.

    if (length(x) != length(y)) {
      cat("¡Error!", "\n") 
      cat("Los vectores no tienen el mismo tamaño")
    } else {
      z <- (x * y)
      print(z)
    }

    En lugar de ello, resulta preferible pasar las instrucciones else y else if a la línea siguiente (tal y como se indica en la recomendación 14, con lo cual se supera el conflicto de indentación. Es posible verificar que muchas de las funciones base en R siguen este esquema.

    if (length(x) != length(y)) {
      cat("¡Error!", "\n") 
      cat("Los vectores no tienen el mismo tamaño")
    }
    else {
      z <- (x * y)
      print(z)
    }
  3. 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")