By default, the goodpractice()
function and its alias
gp()
run all the checks available in the package (use
all_checks()
to show all checks implemented). In addition,
users can provide their own custom checks.
What’s happening inside of gp()
?
The gp()
function essentially carries out two types of
steps:
- First, it runs preparation steps for the checks, such as calculating test coverage or cyclomatic complexity.
- Then it carries out the checks, e.g., if cyclomatic complexity exceeds a threshold.
The results of the preparation steps and the checks are added to the return object, also referred to as the state. The print method accesses the check results and prints the advice for the failed checks - or praise if none fail.
Writing custom checks
Checks without corresponding preparation steps
Custom checks can be created with the make_check()
function. As inputs it takes a short description
of the
check, the check
itself, and the gp
advice to
be given in case the check fails. To illustrate this, let’s use a
simplified version of the check on T
and F
instead of TRUE
and FALSE
.
The check
argument is a function which takes the state
as the input and carries out the check, returning TRUE
if
the check was successful. The state includes the path to the source code
of the package and the checkTnF()
function of the
tools package can be used to check if T
or
F
was used.
library(goodpractice)
# make a simple version of the T/F check
check_simple_tf <- make_check(
description = "TRUE and FALSE is used, not T and F",
gp = "avoid 'T' and 'F', use 'TRUE' and 'FALSE' instead.",
check = function(state) {
length(tools::checkTnF(dir = state$path)) == 0
}
)
Additional checks can be used in gp()
via the
extra_checks
argument. This should be a named list of
check
objects as returned by the make_check()
function. All checks to be carried out, regardless of whether they are
provided by the goodpractice package or custom checks,
must be named in the checks
argument to
gp()
.
The check on T
/F
implemented in the package
gives more helpful advice than this simplified version and returns which
files contain T
and F
so let’s do a quick
comparison and run both versions:
# get path to example package
pkg_path <- system.file("bad1", package = "goodpractice")
gp(pkg_path, checks = c("simple_tf", "truefalse_not_tf"),
extra_checks = list(simple_tf = check_simple_tf))
#> ── GP badpackage ───────────────────────────────────────────────────────────────
#>
#> It is good practice to
#>
#> ✖ avoid 'T' and 'F', use 'TRUE' and 'FALSE' instead.
#> ✖ avoid 'T' and 'F', as they are just variables which are set to the logicals
#> 'TRUE' and 'FALSE' by default, but are not reserved words and hence can be
#> overwritten by the user. Hence, one should always use 'TRUE' and 'FALSE'
#> for the logicals.
#>
#> R/tf.R
#> R/tf.R
#> R/tf.R
#> R/tf.R
#> R/tf.R
#> ... and 4 more lines
#>
#> ────────────────────────────────────────────────────────────────────────────────
Including a preparation step
Including a preparation step is optional but can be helpful if several checks require the same preparations upfront. In the following example we check for two different fields to be present in the DESCRIPTION file, the URL field and the BugReports field. Both checks can be carried out easily building on a preparation step with the desc package for handling DESCRIPTION files.
The checks are linked to the preparation via the prep name: it
appears as the name
argument to make_prep()
,
as the preps
argument to make_check()
and
finally as the name in the list for the extra_preps
argument to gp()
.
# prep: process DESCRIPTION file
desc_fun <- function(path, quiet) {
desc::description$new(path)
}
prep_desc <- make_prep(name = "desc", func = desc_fun)
# check for an URL field
check_url <- make_check(
description = "URL field in DESCRIPTION",
preps = "desc",
gp = "have a URL field in DESCRIPTION",
check = function(state) state$desc$has_fields("URL")
)
# check for a BugReport field
check_bugreports <- make_check(
description = "BugReports in DESCRIPTION",
preps = "desc",
gp = "add a BugReports field to DESCRIPTION",
check = function(state) state$desc$has_fields('BugReports')
)
# run the two checks with their corresponding prep step
gp(pkg_path, checks = c("url", "bugreports"),
extra_preps = list("desc" = prep_desc),
extra_checks = list("url" = check_url, "bugreports" = check_bugreports))
#> ℹ Preparing: desc
#> ── GP badpackage ───────────────────────────────────────────────────────────────
#>
#> It is good practice to
#>
#> ✖ have a URL field in DESCRIPTION
#> ✖ add a BugReports field to DESCRIPTION
#> ────────────────────────────────────────────────────────────────────────────────
More examples for using custom checks can be found in the rOpenSci unconf 2017 project checkers for automated checking of best practices for research compendia.