mcpField
R
r-package-development

r-package-development

github.com/ab604开发21057 次安装6 周前更新
代码仓库

关于 r-package-development

面向 R 包开发者,提供依赖选择、API 设计、输入验证、错误处理、测试、文档和发布方面的决策指南。内容也涵盖包结构与 DESCRIPTION 等文件的实践建议。

主要功能

  1. 01评估依赖与 Base R 的取舍
  2. 02设计 API 并验证输入
  3. 03规范错误处理与错误类
  4. 04组织测试和文档
  5. 05整理包结构与发布检查

适用场景

  1. 01规划新 R 包的依赖
  2. 02为用户函数设计稳定接口
  3. 03构建包的单元及集成测试
  4. 04发布前检查包和文档

安装

复制下面的命令,在终端中运行。

$npx skills add https://github.com/ab604/claude-code-r-skills --skill r-package-development

安装后在 Claude Code 中输入/r-package-development确认已加载。

详细介绍

R 包开发决策指南

R 包的依赖、API 设计、测试、文档和最佳实践

依赖策略

何时添加依赖,何时使用 Base R
# Add dependency when:
# - Significant functionality gain
# - Maintenance burden reduction
# - User experience improvement
# - Complex implementation (regex, dates, web)

# Use base R when:
# - Simple utility functions
# - Package will be widely used (minimize deps)
# - Dependency is large for small benefit
# - Base R solution is straightforward

# Example decisions:
str_detect(x, "pattern")    # Worth stringr dependency
length(x) > 0              # Don't need purrr for this
parse_dates(x)             # Worth lubridate dependency
x + 1                      # Don't need dplyr for this
tidyverse 依赖指南
# Core tidyverse (usually worth it):
dplyr     # Complex data manipulation
purrr     # Functional programming, parallel
stringr   # String manipulation
tidyr     # Data reshaping

# Specialized tidyverse (evaluate carefully):
lubridate # If heavy date manipulation
forcats   # If many categorical operations
readr     # If specific file reading needs
ggplot2   # If package creates visualizations

# Heavy dependencies (use sparingly):
tidyverse # Meta-package, very heavy
shiny     # Only for interactive apps
在 DESCRIPTION 中指定依赖
# Strong dependencies (required)
Imports:
    dplyr (>= 1.1.0),
    rlang (>= 1.0.0)

# Suggested dependencies (optional)
Suggests:
    testthat (>= 3.0.0),
    knitr,
    rmarkdown

# Enhanced functionality (optional but loaded if available)
Enhances:
    data.table

API 设计模式

函数设计策略
# Modern tidyverse API patterns

# 1. Use .by for per-operation grouping
my_summarise <- function(.data, ..., .by = NULL) {
  # Support modern grouped operations
}

# 2. Use {{ }} for user-provided columns
my_select <- function(.data, cols) {
  .data |> select({{ cols }})
}

# 3. Use ... for flexible arguments
my_mutate <- function(.data, ..., .by = NULL) {
  .data |> mutate(..., .by = {{ .by }})
}

# 4. Return consistent types (tibbles, not data.frames)
my_function <- function(.data) {
  result |> tibble::as_tibble()
}
输入验证策略
# Validation level by function type:

# User-facing functions - comprehensive validation
user_function <- function(x, threshold = 0.5) {
  # Check all inputs thoroughly
  if (!is.numeric(x)) stop("x must be numeric")
  if (!is.numeric(threshold) || length(threshold) != 1) {
    stop("threshold must be a single number")
  }
  # ... function body
}

# Internal functions - minimal validation
.internal_function <- function(x, threshold) {
  # Assume inputs are valid (document assumptions)
  # Only check critical invariants
  # ... function body
}

# Package functions with vctrs - type-stable validation
safe_function <- function(x, y) {
  x <- vec_cast(x, double())
  y <- vec_cast(y, double())
  # Automatic type checking and coercion
}

错误处理模式

# Good error messages - specific and actionable
if (length(x) == 0) {
  cli::cli_abort(
    "Input {.arg x} cannot be empty.",
    "i" = "Provide a non-empty vector."
  )
}

# Include function name in errors
validate_input <- function(x, call = caller_env()) {
  if (!is.numeric(x)) {
    cli::cli_abort("Input must be numeric", call = call)
  }
}

# Use consistent error styling
# cli package for user-friendly messages
# rlang for developer tools
错误类
# Custom error classes for programmatic handling
my_error <- function(message, ..., call = caller_env()) {
  cli::cli_abort(
    message,
    ...,
    class = "my_package_error",
    call = call
  )
}

# Specific error types
validation_error <- function(message, ..., call = caller_env()) {
  cli::cli_abort(
    message,
    ...,
    class = c("validation_error", "my_package_error"),
    call = call
  )
}

何时创建内部函数与导出函数

在以下情况下导出函数
# Export when:
# - Users will call it directly
# - Other packages might want to extend it
# - Part of the core package functionality
# - Stable API that won't change often

# Example: main data processing functions
#' @export
process_data <- function(.data, ...) {
  # Comprehensive input validation
  # Full documentation required
  # Stable API contract
}
在以下情况下保留为内部函数
# Keep internal when:
# - Implementation detail that may change
# - Only used within package
# - Complex implementation helpers
# - Would clutter user-facing API

# Example: helper functions (no @export)
.validate_input <- function(x, y) {
  # Minimal documentation
  # Can change without breaking users
  # Assume inputs are pre-validated
}

# Naming convention: prefix with . for internal functions
.compute_metrics <- function(data) { ... }

测试和文档策略

测试级别
# Unit tests - individual functions
test_that("function handles edge cases", {
  expect_equal(my_func(c()), expected_empty_result)
  expect_error(my_func(NULL), class = "my_error_class")
})

# Integration tests - workflow combinations
test_that("pipeline works end-to-end", {
  result <- data |>
    step1() |>
    step2() |>
    step3()
  expect_s3_class(result, "expected_class")
})

# Property-based tests for package functions
test_that("function properties hold", {
  # Test invariants across many inputs
})
测试文件组织
tests/
  testthat/
    test-validation.R      # Input validation tests
    test-processing.R      # Core processing tests
    test-output.R          # Output format tests
    test-integration.R     # End-to-end tests
    helper-fixtures.R      # Shared test fixtures
  testthat.R              # Test runner
快照测试
# For complex outputs that are hard to specify exactly
test_that("summary output is correct", {
  expect_snapshot(summary(my_object))
})

# For error messages
test_that("errors are informative",
  expect_snapshot(my_function(bad_input), error = TRUE)
})
文档优先级
# Must document:
# - All exported functions
# - Complex algorithms or formulas
# - Non-obvious parameter interactions
# - Examples of typical usage

# Can skip documentation:
# - Simple internal helpers
# - Obvious parameter meanings
# - Functions that just call other functions
roxygen2 文档
#' Process and summarize data
#'
#' @description
#' Takes a data frame and computes summary statistics
#' for specified variables.
#'
#' @param data A data frame or tibble.
#' @param vars <[`tidy-select`][dplyr::dplyr_tidy_select]> Columns to summarize.
#' @param .by <[`data-masking`][dplyr::dplyr_data_masking]> Optional grouping variable.
#'
#' @return A tibble with summary statistics.
#'
#' @examples
#' mtcars |> process_data(mpg, .by = cyl)
#'
#' @export
process_data <- function(data, vars, .by = NULL) {
  # ...
}

包结构

推荐目录布局
mypackage/
  DESCRIPTION
  NAMESPACE
  LICENSE
  README.md
  R/
    utils.R           # Internal utilities
    validation.R      # Input validation
    core.R            # Core functionality
    methods.R         # S3/S7 methods
    zzz.R             # .onLoad, .onAttach
  man/                # Generated by roxygen2
  tests/
    testthat/
    testthat.R
  vignettes/
    getting-started.Rmd
  inst/
    extdata/          # Example data files
  data/               # Package data (lazy-loaded)
  data-raw/           # Scripts to create package data
DESCRIPTION 最佳实践
Package: mypackage
Title: What The Package Does (One Line)
Version: 0.1.0
Authors@R:
    person("First", "Last", email = "[email protected]",
           role = c("aut", "cre"))
Description: A longer description that spans multiple lines.
    Use four spaces for continuation lines.
License: MIT + file LICENSE
Encoding: UTF-8
Roxygen: list(markdown = TRUE)
RoxygenNote: 7.2.3
Imports:
    dplyr (>= 1.1.0),
    rlang (>= 1.0.0)
Suggests:
    testthat (>= 3.0.0)
Config/testthat/edition: 3

发布检查清单

# Before release:
devtools::check()         # Must pass with 0 errors, warnings, notes
devtools::test()          # All tests pass
devtools::document()      # Documentation up to date
urlchecker::url_check()   # All URLs valid
spelling::spell_check_package()  # No typos

# Update version
usethis::use_version("minor")  # or "major", "patch"

# Update NEWS.md with changes

# Final checks
devtools::check(remote = TRUE, manual = TRUE)

常见的包开发错误

# Avoid - Using library() in package code
library(dplyr)  # Never in package code!

# Good - Use namespace qualification
dplyr::filter(data, x > 0)

# Or import in NAMESPACE via roxygen2
#' @importFrom dplyr filter mutate

# Avoid - Modifying global state
options(my_option = TRUE)  # Side effect!

# Good - Restore state if you must modify
old_opts <- options(my_option = TRUE)
on.exit(options(old_opts), add = TRUE)

# Avoid - Hardcoded paths
read.csv("/home/user/data.csv")

# Good - Use system.file for package data
system.file("extdata", "data.csv", package = "mypackage")

评论

支持 Markdown