Является продолжением предыдущих публикаций. Не секрет, что при упоминании R в числе используемых инструментов вторым по популярности является вопрос о возможности его применения в «промышленной разработке». Пальму первенства в России неизменно держит вопрос «А что такое R?»
Попробуем разобраться в аспектах и возможности применения R в «промышленной» разработке.
Что такое промышленная разработка ПО?
Нисколько не удивляет, что под этим термином каждый понимает что-то свое. Начиная с «только C++» или «только java» и заканчивая «наличием плана продаж и роадмэпом на 10 лет вперед». Но без четкого определения термина дальше двигаться невозможно.
Поскольку однозначно устоявшегося определения нет, для ответа на поставленный вопрос соберем это понятие из совокупности внешних и внутренних присущих артефактов:
- Артефакты с точки зрения менеджмента. Промышленная разработка ПО: профессиональная занятость в сфере разработки ПО в организации или подразделении, основная цель которого – создание программного продукта. Источник
- Внутренние артефакты. Совокупность команды, управленческих методологий, используемых инструментов и соглашений по методам разработки ПО, позволяющих в заданный срок выдавать ПО с требуемыми характеристиками по функциональности, надежности, производительности и эргономичности. Это определение построено на основе трудов различных известных людей: Д.Кнут, С.Макконнелл, К.Вигерс, М.Фаулер, Э.Хант, Ф.Брукс, К.Бек, Дж.Спольски и др.
По артефактам с точки зрения менеджера все понятно, но они слабо относятся к языку программирования. Внутренних артефактов набирается великое множество, но для многих из них совершенно неважно, на каком языке ведется разработка. В частности, при применении R могут использоваться из числа любимых/привычных
- система управления требованиями (методология и инструменты);
- система ведения проекта (методология и инструменты);
- система контроля версий;
- багтрекер
- пр.
Таким образом, основные претензии можно переформулировать в контексте надежности («ага, интерактивная работа в консоли это вам не 24x7, да и писали все для академических исследований!»). Замечание в целом верное, но по слегка устаревшим данным. По факту в R в настоящее время сформирован набор пакетов и подходов, которые позволяют легко и элегантно делать приложения и в формате 24x7. Поэтому далее сфокусируемся на наиболее интересных моментах в задаче обеспечения надежности разрабатываемого ПО:
- поддержка различных парадигм программирования;
- отладка;
- статический анализ кода;
- логирование и анализ во время исполнения;
- функциональное и юнит тестирование;
- пакетирование и автоматизированная сборка;
- система генерации документации.
Отчасти эти моменты пересекаются с областью «defensive programming».
Также не следует забывать, что R — высокоуровневый язык, ориентированный на решение задач по манипуляции с данными и обладающий широким набором проверенных библиотек (пакетов). Содержательная часть решения даже весьма сложных задач может занимать всего лишь несколько сотен строчек кода. Для более крупных проектов желательно использование механизма пакетирования, позволяющего структурировать код путем формирования собственных библиотек (пакетов).
Поддержка различных парадигм программирования
Существующий набор пакетов, дополняет и расширяет возможности base-R не только в части математических алгоритмов, но и в части парадигм разработки. Вынося за скобки вселенную tidyverse, имеет смысл упомянуть два пакета, расширяющих реализацию ООП и функционального программирования:
- Система R6 классов. Детально ознакомиться с реализацией ООП на базе R6 можно по видео с конференции UseR! 2017. The R6 Class System, Video и естественно, на сайте CRAN. Там же можно почитать про сравнение характеристик по производительности по сравнению со штатной системой классов S3.
- Функциональный подход. Пакет lambda.r реализует парадигму функционального программирования.
Если говорить о функциональном подходе, то реализация отдельного набора элементов такого подхода в пакете purrr
в совокупности с pipe оператором %>%
позволяет на практике очень сильно упростить и обезопасить обработку данных с существенным сокращением требуемого для этого объема кода. В качестве старта можно ознакомиться с хорошим докладом на эту тему: Happy R Users Purrr – Tutorial
Отладка
В дополнение к классическим инструментам отладки, хорошо описанным в статье «Debugging with RStudio» я бы упомянул следующие небесполезные инструменты:
- Функция
DebugFnW()
из пакетаwrapr
для сохранения окружения в случае падений внутри функции. Ссылки на видео по этому инструменту можно найти в кратком описании к пакету. - Пакет
listviewer
для интерактивного анализа иерархических объектов. Виджет базируется на кодеjsoneditor
. - Пакет
diffobj
для визуального сравнения различных объектов.
Статический анализ кода
Тут все достаточно просто. Наиболее популярный инструмент — lintr@CRAN или lintr@github.
Lintr интегрируется с RStudio IDE, чтобы не повторяться, детали можно посмотреть в ветке Lintr integration with RStudio.
Логирование и анализ во время исполнения
Логирование
Логирование процесса исполнения ПО в логически значимых точках хоть и добавляет накладные расходы, но будучи организованным правильным образом существенным образом упрощает задачу обеспечения последующей технической поддержки разработанного ПО. С учетом высокоуровневости R и компактности кода, даже постоянно включенное логирование не является накладным по ресурсам.
Для задачи логирования наиболее удобен пакет futile.logger. Семантика log4j многим известна и не требует изучения нового. Из полезно-удобных дополнений/расширений я бы выделил следующие:
- Конфигурация логгера в форме
flog.appender(appender.tee(log_name))
позволяет включать одновременный вывод и в файл и в консоль. - Для формирования сложных строк, содержащих значения переменных, вместо
base::paste
гораздо удобнее использоватьglue::glue
. Например, строкаpaste0(" FROM", ch_db$table, "WHERE ", where_string, sep=" ")
превращается в одну форматную строкуglue(" FROM {ch_db$table} WHERE {where_string}")
. С учетом векторизации glue печать табличной выборки также превращается в одну строчку. Детали можно посмотреть в анонсе glue 1.2.0 - Для вывода сообщений, выдаваемых функциями в stdout можно использовать функцию
capture.output(fun...)
. - Для вывода времени исполнения блока команд весьма удобно использовать функции
tic()
,toc()
из пакета tictoc. При этом финализирующую функцию сразу включать в логгер в форме такой конструкции:flog.info(glue("Data query response time: {capture.output(toc())}"))
Real-time валидация
В целом, особенно в языках с динамической типизацией, правилом хорошего тона является проверка данных, поступающих на обработку в ту или иную функцию. По-хорошему, проверку следует разделить на два этапа: физическая проверка (данные требуемого типа) и логическая проверка (содержание данных правильного типа также следует установленным требованиям). Время на исполнение логической проверки может быть на порядки больше, нежели на физическую. Элементарный пример — на этапе физической проверки мы смотрим, что на вход пришел вектор чисел с плавающей точкой, а на этапе логической проверки смотрим, что все элементы вектора неотрицательны.
В R для этого все есть и даже с весьма неплохим выбором. Упомяну только самые интересные и перспективные пакеты. checkmate
для физической проверки и assertr
, validate
для логической.
Приятно, что в отличие от assertive
, реализация checkmate
изначально ориентирована на скорость и минимальный оверхед, о чем можно прочесть в публикации «checkmate: Fast Argument Checks for Defensive R Programming».
Ну и возможность написания компактных правил валидации в стиле regexp средствами qassert
очень радует, поскольку позволяет свернуть типичную функцию проверки на 2 строки до строки в несколько символов.
В части логической проверки — тут каждый может выбрать удобный ему способ. Все зависит от того, какие рода данные, идет ли обработка независимо или в конвейере (pipe), что именно нужно проверять.
В зависимости от того, что требуется по логике программы, можно либо делать проверку на соответствие условиям с получением TRUE/FALSE
и последующим ветвлением логики, либо генерировать exception (assert).
Обработка исключений
Механизм генерации исключений несомненно полезен при работе с данными, а подробный вывод сопутствующей информации как нельзя кстати при интерактивной работе в консоли. Однако при переходе к потоковому исполнению останов программы при возникновении ошибки совершенно не нужен, а разнообразие в способах формирования диагностических сообщений начинает утомлять при создании обработчиков.
Обработка исключений штатными механизмами tryCatch
хорошо описана в книге «Advanced R». Более интересным и полезным применительно к обработке данных в программном режиме является два следующих расширения:
- обработка исключений без останова конвейера (pie);
- унификация ответов от функций.
И тот и другой функционал реализован в пакете purrr
семейством функций-оберток safely
. Получение от любой функции стандартизованного списка с полями error/result позволяет не прерывать потоковую обработку, а обработать возникшие исключения и ошибки после завершения конвейера. Вовсе нет необходимости всегда бить в колокол и поднимать исключение, если в ходе обработки вектора возникло деление на 0. Достаточно пометить некорректный элемент и перейти к следующему. Такая инкапсуляция обработки исключений позволяет вместо нескольких десятков строк кода, призванных учету непредвиденных ситуаций при обработке данных свести все к одной обертке. Меньше код, меньше избыточной вариативности — стабильней результат.
Хорошо и кратко возможности по использованию safely
были описаны в блоге RStudio: purrr 0.2.0 by Hadley Wickham + документация.
Функциональное и юнит тестирование
Пакет testthat
. Изучение можно начать со статьи «testthat: Get Started with Testing», продолжить CRAN и книгами Hadley Wickham, а также книгой «Testing R Code». С учетом положений, упомянутых выше, я бы перекладывал при чтении функции из assertive
на функции из пакета checkmate
. Автотесты можно писать как для пакетов, так и для отдельных функций.
Пакетирование и автоматизированная сборка
Просто констатируем, что есть но не все об этом знают. Сборка, валидация, документирование, проверка и пр., все интегрировано RStudio IDE. Кратко охвачено в статье «Building, Testing, and Distributing Packages». Досконально все описано в отличной книге Hadley Wickham: «R packages». Полезным может оказаться применение хелпер-пакета usethis
Также есть пакет packrat
позволяющий сформировать снапшот пакетов, необходимых для работы конкретного приложения. Тем самым обеспечивается независимость среды функционирования ПО от пакетов, установленных в системе.
Система генерации документации
Просто констатируем, что есть для пакетов, но не все об этом знают. Построена на базе roxygen, интегрировано с RStudio IDE.
Досконально все описано в отличной книге Hadley Wickham: «R packages».
Заключение
Сейчас R очень активно развивается. Каждую неделю появляются новые полезные функции, пакеты, подходы или улучшаются существующие. В задачах, связанных с обработкой данных указанный в публикации набор пакетов позволяет писать на R быстрый, стабильный, компактный и прогнозируемый код. Год назад этих пакетов было меньше, что будет в конце 2018 — время покажет.
В такой ситуации делать заключения о возможности или невозможности применения языка и платформы на основании данных двух и более летней давности некорректно. Как минимум, надо ознакомиться с текущим состоянием.
Что касается скорости, то, как всегда, этот вопрос относителен. 2 дня разработки + 10 секунд на исполнение на R много меньше, чем 2 недели разработки + 0.1 секунда на исполнение, например, на Java. О скорости необходимо говорить в контексте. Для функций, требующих быстроты исполнения, есть возможность реализовать ее на C++ не выходя за границы R путем применения пакета Rcpp. С кратким обзором возможностей можно ознакомиться также в одной из статей автора: «Extending R with C++: A Brief Introduction to Rcpp».
Задач по разнообразной обработке данных (от сбора до визуализации) становится все больше. Почему бы не взглянуть в сторону R?
Предыдущая публикация — «R, Asterisk и платяной шкаф».
Автор: Илья Шутов