Skip to content
Emanuel Huber edited this page Oct 8, 2019 · 3 revisions

Feel free to contact me if you have any troubles, questions, comments, wishes, etc.

[email protected]

Variables / objects

Naming:

  • myVariableName
  • variable names should be nouns
  • fPath (instead of filePath, fileName, filename, path)

Functions

  • don't repeat yourself
  • keep functions as simple as possible (generally each function does one thing)
  • each function should either returns an object, or has a side-effect
  • processing functions should return the processed object in the same format (such that the processing functions could be combined in a pipeline)
  • function arguments defining a window length (or similar): argument in time or length unit (not in number of points/pixels)

Naming

  • doThatThing()
  • function name should be a verb
  • Favour explicit, lengthy names, over short, implicit, names
  • Make sure that function families are identified by a common prefix, not a common suffix.
  • private function starts with a dot. Example: .myPrivateFunction().

S4

  • with generic method: use exactly the same arguments as the existing methods. To know the arguments of a method use args(myMethod).
  • empty slot should have length 0: length(object@mySlot) = 0. Therefore, use:
    • character(0) (instead of "", because length("") = 1),
    • matrix(ncol = 0, nrow = 0),
    • numeric(0),
    • integer(0),
    • list()

Code style

  • spacing
    average <- mean(feet/12 + inches, na.rm = TRUE)
    sqrt(x^2 + y^2)
    x <- 1:10
    base::get
    y ~ x
    tribble( 
      ~col1, ~col2,
      "a", "b"
    )
    
    # and not
    average<-mean(feet/12 + inches,na.rm=TRUE)
    sqrt(x ^ 2 + y ^ 2)
    x <- 1 : 10
    base :: get
    y~x
  • Line length: 80 characters Split file names, warning and error messages, e.g.,
    warning("This a very, very, very long",
            "warning")
  • Assignment: Use <-, not =, e.g.,
    x <- 10 + y
    # and not
    x = 10 + y
  • Indenting
    if(y < 0 && debug){
      message("y is negative")
    }
    
    if(y == 0){
      if(x > 0){
        log(x)
      }else{
        message("x is negative or zero")
      }
    }else{
      y^x
    }
    for long code lines:
    long_function_name <- function(a = "a long argument",
                                   b = "another argument",
                                   c = "another long argument") {
      # As usual code is indented by two spaces.
      y <- a * b + c
    }

Recommendation

  • use paste0() instead of paste(..., sep = "")

  • use message() inside a function instead of cat() or print()!

  • invisible(): Return a (temporarily) invisible copy of an object, used in plot function

  • missing(): check if an arguments is missing

  • Don't want to pass ...-arguments to a function? Solution: use a wrapper function, where the args after ... are the args that you don't want to have in the function. e.g.:

    lPoints <- function(..., log, axes, frame.plot, panel.first, panel.last) {
      points(...)
    }
    
  • unname()

  • Reset the plot parameters to their default values

    op <- par(no.readonly=TRUE)
    # plot...
    par(op)

Loops

  • Use seq_along(x) to protect against instances where x is empty:
    for(i in seq_along(x)){
        doThatThing(i)
    } 
  • Use apply(), lapply(), Mapply(), outer(), Reduce(), Filter(), Map(), Negate(), Position(), replicate(), etc. when possible

Function structure

  • head: first parameter = x (GPR object)
  • slot check (optional)
  • argument check
  • private function applied to the data: .fun(x@data)
  • update the slots if necessary
  • update processing history: proc(x) <- getArgs()
  • return(...)

Keeping track of the processing

The function getArgs() when put inside a method captures the method name, its argument names and the values passed to the arguments. Therefore, it is possible to keep track of the processing applied to the GPR data by storing the captured information into the @proc slot. The adopted notations is:

functionName//arg1=val1+arg2=val2+arg3=val3

The method must have as first argument an object of the class GPR. Here an example:

setMethod("dcshift", "GPR", function(x, u, FUN = mean){
    shift <- matrix(apply(x[u,],2, FUN), nrow = nrow(x), 
                    ncol=ncol(x), byrow = TRUE)
    x <-  x - shift
    proc(x) <- getArgs()
    return(x)
  } 
)

To insert "manually" an additional processing step, use the function addArg() (but normally you don't need it):

proc(x) <- getArgs( addArgs = list('arg10' = val10, 'arg11' = val11))

For example:

proc(x) <- getArgs( addArgs = list('eigenvalues' = 1:10, 'w' = 30.4))

Defensive programming

In R, the “fail fast” principle is implemented in three ways:

  • Be strict about what you accept. For example, if your function is not vectorised in its inputs, but uses functions that are, make sure to check that the inputs are scalars. You can use stopifnot(), the assertthat package, or simple if statements and stop().

  • Avoid functions that use non-standard evaluation, like subset, transform, and with. These functions save time when used interactively, but because they make assumptions to reduce typing, when they fail, they often fail with uninformative error messages. You can learn more about non-standard evaluation in non-standard evaluation.

  • Avoid functions that return different types of output depending on their input. The two biggest offenders are [ and sapply(). Whenever subsetting a data frame in a function, you should always use drop = FALSE, otherwise you will accidentally convert 1-column data frames into vectors. Similarly, never use sapply() inside a function: always use the stricter vapply() which will throw an error if the inputs are incorrect types and return the correct type of output even for zero-length inputs.

From http://adv-r.had.co.nz/Exceptions-Debugging.html