An Introduction to cleanr

Andreas Dominik Cullmann

2017-01-05, 11:43:15

The Intro to the Intro

Coding is an art, but most of the code we write is more or less dreadful. Many tried to teach us how to write code less dreadful, be it implicitly as Kernighan and Ritchie (1988) did, be it explicitly as Martin (2008) did.

If you are concerned about your coding style, you will probably want to lint your codes with lintr or even reformat it using formatR.

This package tries to help you with your code`s layout: it checks our code for files too long or wide, functions with too many lines, too wide lines, too many arguments or too many levels of nesting. Note that it’s not a static code analyser like lintr.

A first example

You can use cleanr to check the layout of a file:

path <- system.file("tests", "runit", "wrappers.r", package = "cleanr")
print(cleanr::check_file_layout(path))
## [1] TRUE

Okay, looks good. Let’s make it look worse.

Options

cleanr is controlled by a list of options named “cleanr”. You can retrieve it either via options("cleanr") or via a convenience function:

cleanr::get_cleanr_options(flatten_list = FALSE)
## $max_file_width
## [1] 80
## 
## $max_file_length
## [1] 300
## 
## $max_lines
## [1] 65
## 
## $max_lines_of_code
## [1] 50
## 
## $max_num_arguments
## [1] 5
## 
## $max_nesting_depth
## [1] 3
## 
## $max_line_width
## [1] 80
## 
## $check_return
## [1] TRUE

Arguments to Check Functions

You can change any of those options on the fly (leaving the options set untouched). The real life

cleanr::check_file_layout(path, max_file_width = 79)

produces conditions that would stop this vignette from getting compiled, this is why we here assert these conditions:

print(tools::assertCondition(cleanr::check_file_layout(path, 
                                                     max_file_width = 79), 
                             c("cleanr", "error",  "condition"))
)
## [[1]]
## <cleanr in cleanr::check_file_layout(path, max_file_width = 79): /tmp/RtmpOud4w9/Rinst53421800c9de/cleanr/tests/runit/wrappers.r: line 6 counts 80 characters.>
## 
## [[2]]
## <cleanr in cleanr::check_file_layout(path, max_file_width = 79): /tmp/RtmpOud4w9/Rinst53421800c9de/cleanr/tests/runit/wrappers.r: line 6 counts 80 characters.>

Setting Options

You can set options using the convenience function:

cleanr::set_cleanr_options(max_file_width = 75, 
                           max_file_length = 20)
print(tools::assertCondition(cleanr::check_file_layout(path), 
                             c("cleanr", "error",  "condition"))
)
## [[1]]
## <cleanr in cleanr::check_file_layout(path): /tmp/RtmpOud4w9/Rinst53421800c9de/cleanr/tests/runit/wrappers.r: line 6 counts 80 characters.
## /tmp/RtmpOud4w9/Rinst53421800c9de/cleanr/tests/runit/wrappers.r: line 49 counts 77 characters.
## /tmp/RtmpOud4w9/Rinst53421800c9de/cleanr/tests/runit/wrappers.r: 55 lines in file.>
## 
## [[2]]
## <cleanr in cleanr::check_file_layout(path): /tmp/RtmpOud4w9/Rinst53421800c9de/cleanr/tests/runit/wrappers.r: line 6 counts 80 characters.
## /tmp/RtmpOud4w9/Rinst53421800c9de/cleanr/tests/runit/wrappers.r: line 49 counts 77 characters.
## /tmp/RtmpOud4w9/Rinst53421800c9de/cleanr/tests/runit/wrappers.r: 55 lines in file.>

Checking for File Layout

Files too long or too wide are hard to read. You can check for a file’s layout, i.e. for its lines’ width and for the number of its lines. You have already seen how to check for file layout:

cleanr::set_cleanr_options(reset = TRUE)
path <- system.file("source", "R", "checks.R", package = "cleanr")
print(cleanr::check_file_layout(path)) 
## [1] TRUE

We override some option by argument:

print(tools::assertCondition(cleanr::check_file_layout(path, 
                                                       max_file_length = 100), 
                             c("cleanr", "error",  "condition"))
)
## [[1]]
## <cleanr in cleanr::check_file_layout(path, max_file_length = 100): /tmp/RtmpOud4w9/Rinst53421800c9de/cleanr/source/R/checks.R: 257 lines in file.>
## 
## [[2]]
## <cleanr in cleanr::check_file_layout(path, max_file_length = 100): /tmp/RtmpOud4w9/Rinst53421800c9de/cleanr/source/R/checks.R: 257 lines in file.>

Checking for Function Layout

Functions should

You can check all functions defined in a file:

print(suppressWarnings(cleanr::check_functions_in_file(path)))
## [1] TRUE

And again we override some option by argument:

print(tools::assertCondition(suppressWarnings(cleanr::check_functions_in_file(path,
                                                                        max_num_arguments = 1)), 
                       c("cleanr", "error",  "condition"))
)
## [[1]]
## <cleanr in cleanr::check_functions_in_file(path, max_num_arguments = 1): /tmp/RtmpOud4w9/Rinst53421800c9de/cleanr/source/R/checks.R  check_file_length  found 2 arguments, max_num_arguments was 1
## /tmp/RtmpOud4w9/Rinst53421800c9de/cleanr/source/R/checks.R  check_file_width  found 2 arguments, max_num_arguments was 1
## /tmp/RtmpOud4w9/Rinst53421800c9de/cleanr/source/R/checks.R  check_line_width  found 2 arguments, max_num_arguments was 1
## /tmp/RtmpOud4w9/Rinst53421800c9de/cleanr/source/R/checks.R  check_nesting_depth  found 2 arguments, max_num_arguments was 1
## /tmp/RtmpOud4w9/Rinst53421800c9de/cleanr/source/R/checks.R  check_num_arguments  found 2 arguments, max_num_arguments was 1
## /tmp/RtmpOud4w9/Rinst53421800c9de/cleanr/source/R/checks.R  check_num_lines  found 2 arguments, max_num_arguments was 1
## /tmp/RtmpOud4w9/Rinst53421800c9de/cleanr/source/R/checks.R  check_num_lines_of_code  found 2 arguments, max_num_arguments was 1
## /tmp/RtmpOud4w9/Rinst53421800c9de/cleanr/source/R/checks.R  check_return  found 2 arguments, max_num_arguments was 1>
## 
## [[2]]
## <cleanr in cleanr::check_functions_in_file(path, max_num_arguments = 1): /tmp/RtmpOud4w9/Rinst53421800c9de/cleanr/source/R/checks.R  check_file_length  found 2 arguments, max_num_arguments was 1
## /tmp/RtmpOud4w9/Rinst53421800c9de/cleanr/source/R/checks.R  check_file_width  found 2 arguments, max_num_arguments was 1
## /tmp/RtmpOud4w9/Rinst53421800c9de/cleanr/source/R/checks.R  check_line_width  found 2 arguments, max_num_arguments was 1
## /tmp/RtmpOud4w9/Rinst53421800c9de/cleanr/source/R/checks.R  check_nesting_depth  found 2 arguments, max_num_arguments was 1
## /tmp/RtmpOud4w9/Rinst53421800c9de/cleanr/source/R/checks.R  check_num_arguments  found 2 arguments, max_num_arguments was 1
## /tmp/RtmpOud4w9/Rinst53421800c9de/cleanr/source/R/checks.R  check_num_lines  found 2 arguments, max_num_arguments was 1
## /tmp/RtmpOud4w9/Rinst53421800c9de/cleanr/source/R/checks.R  check_num_lines_of_code  found 2 arguments, max_num_arguments was 1
## /tmp/RtmpOud4w9/Rinst53421800c9de/cleanr/source/R/checks.R  check_return  found 2 arguments, max_num_arguments was 1>

Checking for File and Function Layout

cleanr comes with wrappers to check for file and function layout in one go: check_file runs the file and function layout checks on a file and check_directory runs them on all files (matching a given pattern) in a directory.

print(suppressWarnings(cleanr::check_file(path)))
## [1] TRUE
path <- system.file("tests", "runit", package = "cleanr")
print(suppressWarnings(cleanr::check_directory(path,
                                               check_return = FALSE)))
## [1] TRUE

To run cleanr on its own codes, we need to load its internal functions first:

path <- system.file("source", "R", package = "cleanr")
cleanr::load_internal_functions("cleanr")
print(tools::assertCondition(suppressWarnings(cleanr::check_directory(path)), 
                       c("cleanr", "error",  "condition"))
)
## [[1]]
## <cleanr in cleanr::check_directory(path): /tmp/RtmpOud4w9/Rinst53421800c9de/cleanr/source/R   /tmp/RtmpOud4w9/Rinst53421800c9de/cleanr/source/R/internals.R  throw  found no return() statement at all.
## /tmp/RtmpOud4w9/Rinst53421800c9de/cleanr/source/R   /tmp/RtmpOud4w9/Rinst53421800c9de/cleanr/source/R/wrappers.R  check_function_layout  found 8 arguments, max_num_arguments was 5>
## 
## [[2]]
## <cleanr in cleanr::check_directory(path): /tmp/RtmpOud4w9/Rinst53421800c9de/cleanr/source/R   /tmp/RtmpOud4w9/Rinst53421800c9de/cleanr/source/R/internals.R  throw  found no return() statement at all.
## /tmp/RtmpOud4w9/Rinst53421800c9de/cleanr/source/R   /tmp/RtmpOud4w9/Rinst53421800c9de/cleanr/source/R/wrappers.R  check_function_layout  found 8 arguments, max_num_arguments was 5>

Disabling checks

To disable any check, set the corresponding option or argument to NULL or FALSE.

cleanr::set_cleanr_options(reset = TRUE)
cleanr::load_internal_functions("cleanr")
path <- system.file("source", "R", "wrappers.R", package = "cleanr")

# will produce a condition:
r <- tools::assertCondition(cleanr::check_functions_in_file(path, 
                                                            check_return = FALSE),
                            c("cleanr", "error",  "condition"))
print(r)
## [[1]]
## <cleanr in cleanr::check_functions_in_file(path, check_return = FALSE): /tmp/RtmpOud4w9/Rinst53421800c9de/cleanr/source/R/wrappers.R  check_function_layout  found 8 arguments, max_num_arguments was 5>
## 
## [[2]]
## <cleanr in cleanr::check_functions_in_file(path, check_return = FALSE): /tmp/RtmpOud4w9/Rinst53421800c9de/cleanr/source/R/wrappers.R  check_function_layout  found 8 arguments, max_num_arguments was 5>
# set argument to disable the check causing the condition:
r <- cleanr::check_functions_in_file(path, check_return = FALSE, 
                                     max_num_arguments = FALSE)
print(r)
## [1] TRUE

To completely disable all checks use:

co <- get_cleanr_options(flatten_list = FALSE)
co <- lapply(co, function(x) x == FALSE)
options("cleanr" = list(cleanr = co))
get_cleanr_options() 
##    cleanr.max_file_width   cleanr.max_file_length         cleanr.max_lines 
##                    FALSE                    FALSE                    FALSE 
## cleanr.max_lines_of_code cleanr.max_num_arguments cleanr.max_nesting_depth 
##                    FALSE                    FALSE                    FALSE 
##    cleanr.max_line_width      cleanr.check_return 
##                    FALSE                    FALSE

Now run on any directory, check_directory will always return TRUE:

path <- system.file("source", "R", package = "cleanr")
cleanr::load_internal_functions("cleanr")
print(cleanr::check_directory(path)) 
## [1] TRUE

References

Kernighan, B. W., and D. M. Ritchie. 1988. The C Programming Language, Second Edition. Englewood Cliffs, New Jersey: Prentice-Hall.

Martin, R.C. 2008. Clean Code: A Handbook of Agile Software Craftsmanship. Pearson Education.