Skip to content

Commit 0444a35

Browse files
authored
add cyclomatic complexity section (#112)
1 parent 7c7e82f commit 0444a35

File tree

1 file changed

+73
-0
lines changed

1 file changed

+73
-0
lines changed

r-code.Rmd

Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -287,6 +287,79 @@ checks.
287287
* Use the packages `r BiocStyle::Biocpkg("ExperimentHub")` and `r BiocStyle::Biocpkg("AnnotationHub")` instead of downloading external data from unsanctioned providers such as <i class="fab fa-github"></i> GitHub, <i * class="fa fa-dropbox" aria-hidden="true"></i> Dropbox, etc. In general, data utlilzed in packages should be downloaded from trusted public databases. See also section on web querying and file caching.
288288
* Use `<-` instead of `=` for assigning variables except in function arguments.
289289

290+
### Cyclomatic Complexity
291+
292+
A metric developed by Thomas J. McCabe, cyclomatic complexity describes the
293+
control-flow of a program. The higher the value, the more complex the code is
294+
said to be.
295+
296+
In the `BiocCheck` example below, we have a series of control-flow statements
297+
that check for multiple conditions and return a value if some of those
298+
conditions are true. The function `hasValueSection` is checking that the
299+
documentation contains a 'value' Rd tag i.e., a return section in the
300+
documentation.
301+
302+
```r
303+
hasValueSection <- function(manpage) {
304+
rd <- tools::parse_Rd(manpage)
305+
type <- BiocCheck:::docType(rd)
306+
if (identical(type, "data"))
307+
return(TRUE)
308+
tags <- tools:::RdTags(rd)
309+
if ("\\usage" %in% tags && (!"\\value" %in% tags))
310+
return(FALSE)
311+
value <- NULL
312+
if ("\\value" %in% tags)
313+
value <- rd[grep("\\value", tags)]
314+
if ("\\usage" %in% tags && (!"\\value" %in% tags)) {
315+
values <- paste(unlist(value), collapse='')
316+
test <- (is.list(value[[1]]) && length(value[[1]]) == 0) ||
317+
nchar(gsub(" ", "", values)) == 0
318+
if (test)
319+
return(FALSE)
320+
}
321+
TRUE
322+
}
323+
```
324+
325+
As you can see, the code here is quite complex, creating `if` statements for
326+
specific conditions. The measured cyclomatic complexity as given by the
327+
`cyclocomp` package is a value of 16.
328+
329+
```r
330+
library(cyclocomp)
331+
cyclocomp(hasValueSection)
332+
#> [1] 16
333+
```
334+
335+
Let's re-write the function to reduce the complexity and use a base character
336+
vector representation.
337+
338+
```r
339+
hasValueSection2 <- function(manpage) {
340+
rd <- tools::parse_Rd(manpage)
341+
tags <- tools:::RdTags(rd)
342+
value <- rd[grepl("\\value", tags)]
343+
value <- unlist(value, recursive = FALSE)
344+
value <- Filter(function(x) attr(x, "Rd_tag") != "COMMENT", value)
345+
values <- paste(value, collapse='')
346+
nzchar(trimws(values)) && length(value)
347+
}
348+
```
349+
350+
By internalizing a base character vector representation in the code, the
351+
successive functions will operate on that representation. This gives us code
352+
that is less complex and easier to understand and maintain with a complexity
353+
metric value of 2.
354+
355+
```r
356+
cyclocomp(hasValueSection2)
357+
#> [1] 2
358+
```
359+
360+
Note. Switches based on `docType` should be outside of the function as a filter
361+
for the `manpage` input instead.
362+
290363
#### End-User messages
291364

292365
Use the functions `message()`, `warning()` and `error()`, instead of the

0 commit comments

Comments
 (0)