I really ❤️ R package development
Volunteer editor for rOpenSci Software Peer Review.
At work, maintenance of rOpenSci dev guide.
Created the R-hub blog.
Worked on the HTTP testing in R book.
Contributed to
pkgdown 2.0.0 (to produce documentation websites for packages)
fledge 0.1.0 (Smoother change tracking and versioning for R packages)
glitter 0.1.0 (a SPARQL domain-specific language)
The only way to write good code is to write tons of shitty code first. Feeling shame about bad code stops you from getting to good code
If you’re an experienced programmer and you’re tempted to code-shame someone, try thinking back to your own million lines of bad code. Imagine someone had mocked you then (…). Would you have continued asking for help? Would you have ever gotten through your million lines?
David Robinson’s blog post “A Million Lines of Bad Code”
I’ll present a collection of very useful things I’ve learnt over the past few years.
After each section I’ll summarize and ask you to comment.
Then you pick up one thing to improve in your package.
Get to know the cli package
variable <- 42
cli::cli_alert_info("Set {.field parameter} to {.val {variable}}")
#> ℹ Set parameter to 42
How to control verbosity? How to shup your package up?
argument in each function 😩
cli_alert_info <- function(...) {
if (!getOption("usethis.quiet", default = FALSE)) {
cli::cli_alert_info(...)
}
}
Further reading: https://ropensci.org/blog/2024/02/06/verbosity-control-packages/
🧰 Are there messages in your package you could improve?
Tips on content in the tidyverse style guide with examples.
Interface with cli::cli_abort()
🧰 Go through your package’s error messages (look for stop()
and equivalents). Could some of them be improved by applying the tidyverse guidance?
Document argument type, default.
Check arguments. rlang::arg_match()
for instance.
Further reading: Checking the inputs of your R functions by Hugo Gruson , Sam Abbott , Carl Pearson.
🧰 Does your package document and validate arguments? Improve this in one function.
stop()
🎤Please post in the chat
Does this dependency spark joy? 😉
Further reading: Dependencies: Mindset and Background in the R Packages book by Hadley Wickham and Jenny Bryan.
curl, httr2, httr. Not RCurl.
jsonlite. Not rjson nor RJSONIO.
xml2. Not XML
sf, spatial suites developed by the r-spatial and rspatial communities. Not sp, rgdal, maptools, rgeos.
🧰 Are there dependencies you could add, replace or remove in your package?
Feature creep: “excessive ongoing expansion or addition of new features in a product” https://en.wikipedia.org/wiki/Feature_creep
Okay to split the package.
Okay to say no to feature requests. Example
🧰 Are there feature requests you’d like to say no to? Save answer as GitHub reply?
stop()
🎤Please post in the chat
👀
✨
do_this <- function() {
if (!is_that_present()) {
return(NULL)
}
# more code
blip
}
switch()
👀
switch()
✨
Further reading: Code Smells and Feels by Jenny Bryan
🧰 Look at logic in one or a few functions. Could you simplify it with early returns, helper functions?
Some of it only relevant if you see code.
Align argument in function definitions.
More vertical alignment? I am not sensitive to it. 😇
One paragraph = one idea (works for writing prose too!).
Vertical space is costly (what fits on the screen?)
head <- collect_metadata(website)
head_string <- stringify(head)
body <- create_content(website)
body_string <- stringify(body)
At least in RStudio IDE, outline on the right. In any case good to indicate high-level structure within a script.
# Header level 1 ----
more code
## Header level 2 ----
more code
🧰 Open one or a few scripts, can you improve the aesthetics?
Comments are like little alerts. Don’t create fatigue!
Comments that repeat the code get out of date.
# use only non empty strings
if (!is.na(x) && nzchar(x)) {
use_string(x)
}
is_non_empty_string <- function(x) {
!is.na(x) && nzchar(x)
}
if (is_non_empty_string(x)) {
use_string(x)
}
Further reading: https://blog.r-hub.io/2023/01/26/code-comments-self-explaining-code/
🧰 Are there opportunities for less comments (or more comments!) in some of your scripts?
stop()
🎤Please post in the chat
DAMP: descriptive and meaningful.
DRY: don’t repeat yourself.
A trade-off!
Code is covered by test code so we can take more risks!
Self-contained.
Can be run interactively. testthat::test_path()
.
No leak. {withr}. withr::local_options()
, withr::local_tempdir()
…
Let’s explore https://github.com/maelle/swamp
🧰 Do some of your tests have top-level code? Can you create helper files and helper functions, and repeat object creation in each test?
My code
is_internet_down <- function() {
!curl::has_internet()
}
my_complicated_code <- function() {
if (is_internet_down()) {
message("No internet! Le sigh")
}
# blablablabla
}
How to test for the message?
In the test,
test_that("my_complicated_code() notes the absence of internet", {
local_mocked_bindings(is_internet_down = function(...) TRUE)
expect_message(my_complicated_code(), "No internet")
})
Further reading: https://www.tidyverse.org/blog/2023/10/testthat-3-2-0/#mocking
🧰 do you have such a situation to test?
stop()
🎤Please post in the chat
…with your own package! In breakout rooms.
We’ll gather in XX minutes as a group to discuss.
Comments? Questions?
See you in the #package-maintenance
channel? 😉