При совместной работе группы людей над одним проектом часто возникает необходимость в неких общих правилах оформления кода. Целью принятия и использования таких правил является упрощение восприятия программного кода человеком, минимизация нагрузки на память и зрение при чтении.

К сожалению, для языка R нет чётких правил оформления кода. В качестве источников таких правил обычно используется исходный код ядра R, в котором, правда, наблюдается весьма разнородные варианты оформления. Дополнительными источниками являются стандарты оформления кода, заимствованные из других языков программирования.

Одним из общих правил является необходимость придерживаться одного стиля в рамках одного проекта.

Ниже приведены рекомендации по оформлению кода при использовании языка программирования R, которые представляют собой выжимку из разнообразных источников.

Названия файлов

Прежде всего, названия файлов должны быть осмысленными и понятными.

Общим требованием к именованию файлов является написание расширений файлов, включающего принадлежность к языку программирования R с большой буквы. Обратим внимание, что все UNIX-like операционные системы чувствительны к регистру в названиях файлов. Например: pulse-analyse.R, report.Rmw, presentation.Rmd.’ Бинарный формат хранения данных имеет расширение .RData, .rda или .rds.

Имена файлов содержат символы в нижнем регистре. Для замены пробелов в названиях файлов обычно используются дефисы или нижние подчёркивания.

Названия функций и переменных

Существуют различные подходы к именованию функций и переменных. Но все они содержат общие рекомендации:

  • Названия должны быть понятны и осмысленны: colMeans, read.table;
  • Названия не должны быть слишком длинными или слишком короткими, что могли бы затруднить их понимание или восприятие;
  • Не следует использовать имена, которые уже используются в R или популярных пакетах. Для проверки уже используемых имён можно использовать функции find() и apropos();

Можно выделить несколько подходов к именованию функций и переменных:

  • alllowercase (слитное написание, все символы в нижнем регистре, без разделителей). Широко используется в MATLAB. Примеры: searchpaths, srcfilecopy;
  • period.separated (все символы в нижнем регистре с разделителями в виде точек). Используется только в R. Примеры: as.numeric, read.table, data.frame;
  • underscore_separated (все символы в нижнем регистре с разделителями в виде нижних подчёркиваний). Используется для именования переменных и функций в C++, Perl, Ruby. Примеры: package_version, seq_along, file_ext;
  • lowerCamelCase (слитное написание, первый символ в нижнем регистре, начало каждого составного слова в верхнем регистре). Используется во многих языках программирования. Примеры: colMeans, suppressPackageStartupMessage;
  • UpperCamelCase (слитное написание, первый символ в верхнем регистре, начало каждого составного слова в верхнем регистре). Используется во многих языках программирования. Примеры: Vectorize, NextMethod.

В различных руководствах приводятся разные рекомендации. Как мы уже отмечали, гораздо более важно придерживаться единого стиля при написании кода. В исходном коде R преимущественно используются стили lowerCamelCase, period.separated и underscore_separated. При этом period.separated используется в основном для создания функций (методов) для обработки S3 классов.

Присвоение

Для присвоения используется «<-» или «->», но не «=», который используется только для указания значений аргументов функций.

Отступы

Для отступов могут использоваться как пробелы, так и табуляция. В R coding standards рекомендуется использовать 4 пробела для кода и 2 пробела для документации (.Rd файлы). В Google’s R Style Guide

рекомендуется использоваться 2 пробела. Важно, чтобы отступы были везде одинаковыми, т.е. только пробелы или только табуляция.

Расстановка пробелов

  1. Пробелы ставятся до и после математических символов и бинарных или логических операторов:
    • математические символы: «=», «+», «-», «*», «/»;
    • операторы присвоения: «<-», «->»;
    • бинарные операторы: «%in%», «%%», «%/%»;
    • логические операторы: «==», «!=», «&», «|», «<», «>», «<=», «>=»;
    • оператор присвоения значений аргументов: «=».
  2. Пробелы ставятся после:
    • запятых;
    • управляющих конструкций: if, for, while;
    • открывающейся фигурной скобкой - «{»;
    • открывающейся круглой скобкой - «(», за исключением объявления или вызова функции.
  3. Пробелы не ставятся между круглыми скобками: if ((x == 1) & (y == 2)).

Длинна строки

Слишком длинные строки затрудняют их восприятие кода, поэтому рекомендуется ограничить длину строки 80 символами.

Автоматическое форматирование кода (пакет formatR)

Иногда возникает необходимость отформатировать уже написанный код, при этом объём его может быть весьма существенным, чтобы делать это вручную. Пакет formatR содержит ряд функций для решения этой задачи:

  • tidy_source(): переформатирование R-кода (удаление пустых строк, приведение кавычек к единому стилю и т.д.);
  • tidy_dir(): R-скриптов в директории (есть поддержка рекурсивного сканирования);
  • tidy_eval(): выполнение R-кода и маскировка вывода с помощью префикса.

Функция tidy_source()

Функция tidy_source() позволяет отформатировать исходный код согласно заранее определённым правилам (аргументам). Функция поддерживает ввод из буфера обмена (source = "clipboard"), файла (source = "filename"), переменной (text = varname), являющейся вектором строк (character), или строковый вектор (text = "some code"). Вывод результатов работы функции может быть перенаправлен в консоль (output = TRUE) или файл (file = "filename").

В качестве настроек форматирования можно указать:

  • сохранять комментарии (аргумент comment, по умолчанию TRUE);
  • сохранять пустые строки (аргумент blank, по умолчанию TRUE);
  • заменять оператор присвоения с = на <- (аргумент arrow, по умолчанию FALSE);
  • ставить открывающуюся фигурную скобку ({) на новую строку (аргумент brace.newline, по умолчанию FALSE);
  • количество пробелов, использующихся для отступов (аргумент indent. по умолчанию 4 пробела);
  • количество символов, при достижении которого текст переносится на новую строку (аргумент width.cutoff, по умолчанию равен опции width).

В пакете formatR также предусмотрена возможность задать глобальные настройки:

options("formatR.indent" = 2)
options("formatR.arrow" = FALSE)

Для демонстрации работы функции tidy_source() возьмём файл messy.R, который поставляется в составе пакета formatR. Вот его содержимое:

messy <- file.path(system.file("format", "messy.R", package = "formatR"))
cat(readLines(messy), sep = "\n")
#>     # a single line of comments is preserved
#> 1+1
#> 
#> if(TRUE){
#> x=1  # inline comments
#> }else{
#> x=2;print('Oh no... ask the right bracket to go away!')}
#> 1*3 # one space before this comment will become two!
#> 2+2+2    # 'short comments'
#> 
#> # only 'single quotes' are allowed in comments
#> df=data.frame(y=rnorm(100),x1=rnorm(100),x2=rnorm(100))
#> lm(y~x1+x2, data=df)
#> 1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1  ## comments after a long line
#> 
#> ## here is a long long long long long long long long long long long long long long long long long long long long comment

Результат работы функции:

library(formatR)
options(width = 80)
tidy_source(messy)
#> # a single line of comments is preserved
#> 1 + 1
#> 
#> if (TRUE) {
#>     x = 1  # inline comments
#> } else {
#>     x = 2
#>     print("Oh no... ask the right bracket to go away!")
#> }
#> 1 * 3  # one space before this comment will become two!
#> 2 + 2 + 2  # 'short comments'
#> 
#> # only 'single quotes' are allowed in comments
#> df = data.frame(y = rnorm(100), x1 = rnorm(100), x2 = rnorm(100))
#> lm(y ~ x1 + x2, data = df)
#> 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 
#>     1 + 1  ## comments after a long line
#> 
#> ## here is a long long long long long long long long long long long long long long
#> ## long long long long long long comment

Функция tidy_dir()

Функция tidy_dir() позволяет обработать множество R-скриптов в указанной директории. Эта функция вызывает уже известную нам tidy_source(), поэтому поддерживает всё её аргументы и глобальные опции. В примере ниже мы скопируем демонстрационные файлы из пакета base во временную директорию и проведём реформатирование:

path = tempdir()
file.copy(system.file("demo", package = "base"), path, recursive = TRUE)
#> [1] TRUE
tidy_dir(path, recursive = TRUE)
#> tidying /tmp/Rtmp753OLC/demo/error.catching.R
#> tidying /tmp/Rtmp753OLC/demo/is.things.R
#> tidying /tmp/Rtmp753OLC/demo/recursion.R
#> tidying /tmp/Rtmp753OLC/demo/scoping.R

Функция tidy_eval()

Функция tidy_eval() выполняет переданные ей в качестве аргументов выражения или код. Варианты ввода аналогичны форматам ввода функции tidy_source(). tidy_eval() возвращает отформатированный вариант введённого кода, а также результат выполнения этого кода с префиксом, который задаётся с помощью соответствующего аргумента.

Пример работы функции tidy_eval():

tidy_eval(text = c("a<-1+1;a", "matrix(rnorm(10),5)"))
#> a <- 1 + 1
#> a
#> ## [1] 2
#> 
#> matrix(rnorm(10), 5)
#> ##         [,1]    [,2]
#> ## [1,] -0.9924 -0.3333
#> ## [2,]  0.2655  0.0724
#> ## [3,] -1.1650 -0.6102
#> ## [4,] -1.3365 -1.4659
#> ## [5,]  1.4964 -1.6908

Данная функция крайне полезна при формировании кода для воспроизведения ошибки или проблемы, которые обычно требуется при составлении баг-репортов или вопросов на Stack Overflow.

Shiny-приложение для форматирования кода

Пакет formatR также включает shiny-приложение на основе функции tidy_source() для удобного форматирования кода в отдельном окне или в браузере. Чтобы запустить приложение, введите в консоли R tidy_app().