Guide for writing R code in qcthat. Use when writing new functions, designing APIs, or reviewing/modifying existing R code.
This skill covers how to design and write R functions in this package —
including naming conventions, signatures, API conventions, error handling, and
common pitfalls. For documenting functions, use the document skill. For
tests, use the tdd-workflow skill.
Functions use PascalCase (UpperCamelCase):
FetchRepoIssues() # exported
CompileIssueTestMatrix() # exported
EnframeIssues() # internal helper, same convention
Parameters use Hungarian-style prefixes to indicate type:
| Prefix | Type | Examples |
|---|---|---|
str* | Single string (length-1 character) | strTitle, strOwner, strRepo |
chr* | Character vector | chrLabels, chrTests, chrMilestones |
int* | Integer | intIssue, intPageMax, intLineStart |
lgl* | Logical | lglUpdate, lglWarn, lglOverwrite |
df* | Data frame | dfITM, dfRepoIssues, dfTestResults |
l* | List | lTestResults, lCommentsRaw, lGHEventPayload |
env* | Environment | envCall, envErrorMessage |
dttm* | Datetime/POSIXct | dttmTimestamp |
fct* | Factor | fctDisposition |
obj* | Object (other) | objShape |
FetchRepoIssues <- function(
strOwner = GetGHOwner(), # length-1 character
strRepo = GetGHRepo(),
strGHToken = gh::gh_token(),
strState = c("all", "open", "closed"),
lglUpdate = FALSE, # length-1 logical
intPageMax = 100L, # length-1 integer
envCall = rlang::caller_env()
) { ... }
Files are named after the exported function they define:
R/FetchRepoIssues.R for FetchRepoIssues().
Enum-like arguments — declare choices as the default vector; resolve with
rlang::arg_match() at the top of the function:
MyFunction <- function(x, mode = c("fast", "safe")) {
mode <- rlang::arg_match(mode)
# mode is now guaranteed to be "fast" or "safe"
}
NULL as "not provided" — use NULL as the default for optional
arguments where there is no sensible scalar fallback; check with is.null():
MyFunction <- function(x, strNamesTo = NULL) {
if (!is.null(strNamesTo)) { ... }
}
S3 object construction — build as a named list or tibble, set class explicitly with a dedicated constructor helper:
# Constructor assigns class
AsMyObject <- function(x) {
class(x) <- c("qcthat_MyObject", class(x))
x
}
envCall propagation in internal validators — helpers that validate
arguments and may throw errors should accept and forward envCall:
CheckSomething <- function(x, envCall = rlang::caller_env()) {
if (bad(x)) qcthatAbort("...", "bad_input", envCall = envCall)
}
MyExportedFn <- function(x, envCall = rlang::caller_env()) {
CheckSomething(x, envCall)
}
Return tibbles, not plain data frames. Typed subclasses (e.g.,
qcthat_Issues) are tibbles with an additional class set by a constructor
like AsIssuesDF().
Export a function when:
Keep a function internal when:
Internal helpers use PascalCase like exported ones, but with @keywords internal and no @export.
Use qcthatAbort() (defined in R/aaa-conditions.R) rather than calling
cli::cli_abort() directly. This ensures consistent error class formatting:
qcthatAbort(
"Input {.arg strOwner} cannot be empty.",
"bad_input",
envCall = envCall
)
qcthatAbort() generates error classes of the form:
qcthat-error-{subclass}, qcthat-error, qcthat-condition.
Always pass envCall = envCall (or envCall = rlang::caller_env()) so errors
point to the user's call frame, not an internal helper.
# Never use library() inside package code
library(dplyr) # Wrong