portable code is called a package. A package is a more or less a combination of: code data documentation tests You can put other content, but we won't cover it today. Colin Fay, ThinkR - http://thinkr.fr 7 / 35
: how your package interacts with R and with other packages R/ : the code man/ : the documentation inst/ : content that will be put in your package folder after installation data/ : data data-raw/ : a folder with content that will be ignored on build tests : the tests vignettes : the vignettes .Rbuildignore : a description of what will be ignored when the package is built ... Colin Fay, ThinkR - http://thinkr.fr 8 / 35
what to ignore when building the package. The name of the content to ignore can be written in full, or match a regex. For example: ^.*\.Rproj$ ^\.Rproj\.user$ ^README\.Rmd$ ^README-.*\.png$ .travis.yml ^CONDUCT\.md$ ^data-raw$ ^cran-comments\.md$ paper\..* ^revdep$ ^docs$ Colin Fay, ThinkR - http://thinkr.fr 9 / 35
Make a package you will be able to develop Make a package you will be able to maintain The UX of a package: taking a user first approach Make a package people will use Make a package people will effectively use Colin Fay, ThinkR - http://thinkr.fr 10 / 35
everything you can automate: on the long run, it will prevent mistakes. Don't lose your breathe on what can be automated. Make a package you will be able to maintain Create a package you can come back to in two years without having to start everything over. Prevent your package from failing when it is released. Colin Fay, ThinkR - http://thinkr.fr 11 / 35
it should be as easy as possible to start using your package. The simpler and clearer your package is for the user, the better. Make a package people will e ectively use Use meaningful package and function names. Create useful, easy to understand, and complete documentation. That will prevent from "issues overload". Colin Fay, ThinkR - http://thinkr.fr 13 / 35
: If Open Source, prefer a name that is easy to find on Google. Find a name that is unique, and that describes well what the package does: for example, {testthat} allows to "test that X", and with {usetthis} we "use this X". Placing an r on a word can make a good package name: for example {stringr} allows to manipulate strings in R. The name can only contain letters, numbers, and dots The name must begin with a letter, and must not end with a period. Good practices : Avoid capitalisation, to facilitate memorisation and typing Avoid dots, to prevent confusion with S3 methods Colin Fay, ThinkR - http://thinkr.fr 17 / 35
automated. Do as little as possible by hand". H. Wickham, "R Packages" Package developers automate not out of laziness, but out of security. => Once an automated process works, it will always work (well, in theory). => If you lose your computer in a train, you should be able to redo everything that you did. Colin Fay, ThinkR - http://thinkr.fr 19 / 35
will create a basic package skeleton. You can create a list of options in options("devtools.desc") to prefill your DESCRIPTION. You can also use the {desc} package, that we will see in a few slides. Colin Fay, ThinkR - http://thinkr.fr 21 / 35
data-raw library(usethis) use_data_raw() -> Creates a "data-raw" folder, and everything in there will be ignored at build. file.create("data-raw/devstuffs.R") -> Create a "devstuffs.R" (you can use any other name) to keep everything you did during the engineering process. Do this for reproducibility Colin Fay, ThinkR - http://thinkr.fr 22 / 35
configure your DESCRIPTION files # Remove default DESC unlink("DESCRIPTION") # Create a new description object my_desc <- desc::description$new("!new") # Set your package name my_desc$set("Package", "dockerfiler") #Set your name my_desc$set("[email protected]", "person('Colin', 'Fay', email = '[email protected]', role = c('cre', 'aut'))") # Remove some author fields my_desc$del("Maintainer") Colin Fay, ThinkR - http://thinkr.fr 23 / 35
your package my_desc$set(Title = "Easy Dockerfile Creation from R") # The description of your package my_desc$set(Description = "Create a Dockerfile.") # The urls my_desc$set("URL", "https://github.com/ColinFay/dockerfiler") my_desc$set("BugReports", "https://github.com/ColinFay/dockerfiler/issues") # Save everyting my_desc$write(file = "DESCRIPTION") Colin Fay, ThinkR - http://thinkr.fr 24 / 35
reads major.minor.patch, where major is a major release, minor a minor, and patch a bug fix. Good practice Until the first stable version of the package is released, the version number should be 0.0.0.9000. This allows to increment 0001 at each new stage of the project with ease, without getting stuck, and also to clearly notify that the package is still in the development phase. Colin Fay, ThinkR - http://thinkr.fr 26 / 35
NEWS file The data-raw folder The use of external data More about {usethis} {usethis} is a package designed to automate the implementation of package elements. For example : In the console, you will find messages with the following nomenclature : ✅: {usethis} did all the work : some tasks remain to be done Colin Fay, ThinkR - http://thinkr.fr 27 / 35
allow you to use a template and/or place the right thing in the right place. use_build_ignore(file) : create a regular expression from a file name and add it to .Rbuildignore use_data and use_data_raw : the first transforms a data set into a .Rdata, then places it in the data/ folder. The second creates the data-raw folder. use_description : creates the DESCRIPTION file (used only if you don't use the RStudio package creation interface, the {desc} package or the devtools::create function). use_package : adds the package as Imports in the DESCRIPTION, use_dev_package adds a dependency in the Remote field. use_git, use_github, use_github_labels, use_github_links, use_git_hook, use_git_ignore : interaction with Git and Github. Colin Fay, ThinkR - http://thinkr.fr 28 / 35
: if your package uses Rcpp. use_testthat : creates the testthat folder. use_vignette : creates a Vignette template. use_revdep : creates documents for reverse dependencies. use_appveyor, use_travis: for continuous integration. use_coverage: for test coverage Colin Fay, ThinkR - http://thinkr.fr 29 / 35
standard order and sorts the dependencies in alphabetical order. use_tidy_eval: functions for tidyeval. use_tidy_versions: adds to all dependencies the restriction to at least the version installed on the machine. use_apl2_license, use_cc0_license, use_gpl3_license, use_mit_license: licenses. Colin Fay, ThinkR - http://thinkr.fr 30 / 35
use_cran_badge, use_depsy_badge: create a CRAN badge with http://www.r- pkg.org, a Depsy badge with http://depsy.org use_cran_comments: create a comments file before submitting to CRAN use_lifecycle_badge : allows to indicate in the README the "state of development" of the package : Experimental, Maturing, Dormant, Stable, Questioning, Retired, Archived. use_news_md : create a NEWS file use_pkgdown : create a pkgdown use_readme_rmd and use_readme_md : create the README file in the corresponding format Colin Fay, ThinkR - http://thinkr.fr 31 / 35
use a character vector") use_data_raw() file.create(glue("data-raw/{name}.R")) file.edit("data-raw/devstuffs.R") } init_docs <- function(name = "Colin FAY"){ stop_if_not(name, is.character, "Please use a character vector") use_mit_license(name) use_readme_rmd() use_news_md() use_testthat() } Colin Fay, ThinkR - http://thinkr.fr 33 / 35
we can call "nice" functions, i.e. functions that : Do not use library() or require(): dependencies are managed in the NAMESPACE file. Do not change user options() or par(). Do not use source() to call code. Do not play with setwd(). Do not silently write in any other place than a temp file. Colin Fay, ThinkR - http://thinkr.fr 2 / 32
as many .R files as you want. There is no perfect number, but consider that each .R contains one "big" function (and its methods if necessary) or one family of functions. You can your .R in family, with an accronyme at the beginning (family-*.R). Group the "utility" functions in a utils.R file. Beware of capital letters and non alphanumeric characters in file name, which can give surprising results if you change operating system. Note that the R/ folder must not contain subfolders. Colin Fay, ThinkR - http://thinkr.fr 3 / 32
essential: both for your "future you", and for other people who will use your package. The documentation is what you will read in the Help tab once the package is installed, or via ?function (or by pressing F1). Colin Fay, ThinkR - http://thinkr.fr 10 / 32
check "Generate documentation with roxygen" and all the boxes in the options window. The documentation is managed by the .rd files contained in the man/ folder, generated in LaTeX, and are available in HTLM, text or pdf. Thanks to {roxygen2}, you dont have to type the LaTeX code by hand. Using {roxygen2} has several advantages. Notably: You don't have to write LaTeX The documentation is in the same place as the object The documentation adapts to the type of object (function, data...) Colin Fay, ThinkR - http://thinkr.fr 11 / 32
open with @, then the name of the filed to fill. RStudio offers autocompletion after the @. If they are no @, the first two lines are understood as the title and description. Then, the most common fields are: @details : details on the function @param x a function param, with a description @return : what the function returns @examples : examples of how to use the function @export : needed if you want the function to be accessible outside the package @import and @importFrom : functions or packages to import @source and @references : references on the function @section my_section : add a custom section to the documentation To know the list of parameters: browseVignettes("roxygen2"). Colin Fay, ThinkR - http://thinkr.fr 14 / 32
packages: @seealso : points to other resources, on the web or in the package @family : makes the function belong to a family of functions @aliases : gives "nicknames" to the function, to allow it to be found with ? my_alias. Colin Fay, ThinkR - http://thinkr.fr 15 / 32
package. It can be called by the user by doing ? pkgname. #' What the package does #' #' the description of the package, with details #' #' @name pkgname-package #' @aliases pkgname-package pkgname #' @docType package #' @author colin <[email protected]@thinkr.fr> NULL Colin Fay, ThinkR - http://thinkr.fr 16 / 32
functions "at once". Document a function, and comment with @rdname. Then, each new function with the same documentation must contain the same @rdname. This method generates a single .rd document for all functions with the same @rdname. use @describeIn : in a new function, we indicate where the documentation is located. inherit parameters with @inheritParams : in this case, you "only" inherit the specified parameters, not all the function documentation. Colin Fay, ThinkR - http://thinkr.fr 17 / 32
an example in \dontrun{}, it won't be evaluated when the package is built. Document two parameters #' @param x,y a numerical vector Generate the doc Once the documentation has been written, launch the roxygenise(). roxygen2::roxygenise() Colin Fay, ThinkR - http://thinkr.fr 18 / 32
that once your package is stable, you'll need people to (be able) use it. "Be able" means: documentation should easily guide them. Consider: "An R Package using the R6 paradigm to create an object oriented API designed to interactively and programmatically write Docker setup files inside an R session or script." VS "Easy Dockerfile Creation with R6" The truth is 90% of your end users don't care about technical stu s. Colin Fay, ThinkR - http://thinkr.fr 20 / 32
file, perfect for GitHub and for any other sharing platform. This files should include: A quick explanation of what the package does How to install it Basic use of the package Where to fill issues / ask questions Knit it to create a GitHub markdown Tips: a well written README can easily be used as an introduction Vignette. Colin Fay, ThinkR - http://thinkr.fr 21 / 32
package and is more developed than help pages, as the form is free (you can include images, tables, links, html...). usethis::use_vignette("mypackage") ...creates a "mypackage.Rmd" file in a vignettes/ folder at the root of the package, and adds the appropriate dependency in your DESCRIPTION. The vignette is a simple RMarkdown page. Colin Fay, ThinkR - http://thinkr.fr 22 / 32
rmarkdown::html_vignette thumbnail: > %\VignetteIndexEntry{Vignette Title} % ThumbnailEngine{knitr::rmarkdown} \usepackage[utf8]{inputenc} --- Describes your vignette We replace the title, the author, and the "VignetteIndexEntry". Then, you can write the vignette as a classic Markdown file. To preview the vignette rendering, press the knit button, or Ctrl/Cmd + Shift + K. Vignettes Colin Fay, ThinkR - http://thinkr.fr 23 / 32
question, that depends of the size of your package and of what you have implemented. Build Vignettes Use devtools::build_vignettes() to render the vignette (in /inst/doc). The end user can see the vignettes with the following instruction: browseVignettes("myPackage") Colin Fay, ThinkR - http://thinkr.fr 24 / 32
package, you can use the {pkgdown} package. devtools::install_github("hadley/pkgdown") # or install.packages("pkgdown") Then pkgdown::build_site() This command will add a docs/ folder, in which you will find all the necessary elements to a mini-site built from README, your vignettes, and the documentation of your functions. You can customize this site as a classic site (CSS...). The home of the site is on index.html. Colin Fay, ThinkR - http://thinkr.fr 25 / 32
any folder: pkgdown::build_site(path ="inst/site") Reminder: all the elements contained in inst/ are moved at the root of the package folder once installed. It can be accessed with: path <- system.file("index.html",package ="mypkg") browseURL(path) We can therefore create a function: launch_help <- function() { browseURL(system.file("index.html",package ="mypkg")) } Colin Fay, ThinkR - http://thinkr.fr 27 / 32
We find it with citation("package") citation("purrr") #> #> To cite package 'purrr' in publications use: #> #> Lionel Henry and Hadley Wickham (2017). purrr: Functional #> Programming Tools. R package version 0.2.4. #> https://CRAN.R-project.org/package=purrr #> #> A BibTeX entry for LaTeX users is #> #> @Manual{, #> title = {purrr: Functional Programming Tools}, #> author = {Lionel Henry and Hadley Wickham}, #> year = {2017}, #> note = {R package version 0.2.4}, Colin Fay, ThinkR - http://thinkr.fr 29 / 32
function. In this case, we place a CITATION in the inst/ folder, which will have this form: citHeader("Pour citer le lubrifiant dans les publications, utiliser :") citEntry(entrée = "Article", title = "Dates and Times Made Easy with {lubridate}", author = personList(as.person("Garrett Grolemund"), as.person("Hadley Wickham")), journal = "Journal of Statistical Software", année = "2011", volume = "40", nombre = "3", pages = "1--25", url = "http://www.jstatsoft.org/v40/i03/", textVersion = textVersion paste("Garrett Grolemund, Hadley Wickham (2011).".", "Dates and Times Made Easy with lubridate.", "Journal of Statistical Software, 40(3), 1-25.", Colin Fay, ThinkR - http://thinkr.fr 30 / 32
most important files of your package. It's also the one you should not edit by hand. This file describes how your package interacts with R, and with other packages. This is where, among other things, the dependencies are managed. This file also lists the functions that are exported. The namespace allows the package to work. This file is managed by {roxygen2}, via the tags @export, @import and @importFrom. Colin Fay, ThinkR - http://thinkr.fr 3 / 15
go up the searchPath until it finds this object (keep in mind that a function is an object). Here is an example of a search path: search() #> [1] ".GlobalEnv" "package:attempt" "package:forcats" #> [4] "package:stringr" "package:dplyr" "package:purrr" #> [7] "package:readr" "package:tidyr" "package:tibble" #> [10] "package:ggplot2" "package:tidyverse" "package:rhub" #> [13] "package:testthat" "package:bindrcpp" "tools:rstudio" #> [16] "package:stats" "package:graphics" "package:grDevices" #> [19] "package:utils" "package:datasets" "package:methods" #> [22] "Autoloads" "package:base" Colin Fay, ThinkR - http://thinkr.fr 4 / 15
function(x) x + 1 rnorm(1) #> [1] 2 rnorm #> function(x) x + 1 You can specify the namespace of the function with :: stats::rnorm(1) #> [1] -2.449438 stats::rnorm #> function (n, mean = 0, sd = 1) #> .Call(C_rnorm, n, mean, sd) #> <bytecode: 0x102e9c4a0> #> <environment: namespace:stats> Understanding searchPath The notation namespace:stats indicates in which namespace the function is located. Colin Fay, ThinkR - http://thinkr.fr 6 / 15
that have the same name as those in other packages. And most of the time, you will need functions from other packages. This is what the NAMESPACE is used for: to manage the interconnection between the different packages in the environment. Colin Fay, ThinkR - http://thinkr.fr 7 / 15
functions, i.e. contained in other packages. R has three types of dependencies that will be contained in the DESCRIPTION : Depends & Imports: the packages that will be attach() or load() respectively. In practice, always list in Imports : attach means that the package is attached to the search path (and remember that a good package should not touch to the user's environment). Suggests : suggests packages to use in addition to your package. Will not be attached or loaded. These elements are filled automatically thanks to {usethis}. Colin Fay, ThinkR - http://thinkr.fr 10 / 15
and easy way to specify it is the instruction: usethis::use_package("attempt") ... adds {attempt} in the DESCRIPTION. That's not enough: we will need to use a roxygen comment on each function of our package to specify the dependencies used. To do that, we will use @import (a whole package) and @importFrom (a specific function). Colin Fay, ThinkR - http://thinkr.fr 11 / 15
stats na.omit moyenne <- function(x){ x <- x %>% na.omit() sum(x)/length(x) } You can use import or importFrom. The better is to use importFrom, for preventing namespace conflict. Add to EACH function. It will take a lot of time, but it's better on the long run. Colin Fay, ThinkR - http://thinkr.fr 12 / 15
some letters at the beginning of each functions (as does {stringr}, for example) You can't search the whole CRAN for function names, but you can start with your own computer: ??my_fun ??bitShiftL ... to help UX Name the function after what it does: if the function is used to get X, call it get_x. Colin Fay, ThinkR - http://thinkr.fr 13 / 15
depending on another package means that : you will potentially have to recode your package if some breaking change happen in your dependencies. If one of your dependencies is removed from CRAN, you will be removed too. In other words, beware the {clipr} effect. Colin Fay, ThinkR - http://thinkr.fr 14 / 15
pi round(res, 2) } b <- function(num) { res <- num * 20 / pi round(res, 2) } c <- function(num) { res <- num * 30 / pi round(res, 2) } rt <- function(n, m, d = 2) { round( (n * m / pi), d ) } a <- function(num) { rt(num, 10) } b <- function(num) { rt(num, 20) } c <- function(num) { rt(num, 30) } Developer oriented optimisation The biggest rule to remember: If you need to copy and paste something, write a function. Colin Fay, ThinkR - http://thinkr.fr 3 / 24
is not reknown for its clear error messages. The end user will, at some point, try to run your function with weird arguments. So basically, you should expect a user to use a weird input, to get back a cryptic message, and to: open a Stackoverflow question (in the best case scenario) open an issue on your GitHub simply stop using your package because "it doesn't work" Colin Fay, ThinkR - http://thinkr.fr 7 / 24
unexpected problems in your code. Wickham H., Advanced R Not all errors are unexpected. To prevent errors, adopt a "defensive programming" strategy: anticipate errors and/or unexpected behaviors, in order to manage them upstream and to inform the user. Three types of alerts exist: stop : an error, stops the execution of the program. warning : an alert, informs of a potential error, does not however prevent the program from working. message : a message printed on the console, for information purposes. Colin Fay, ThinkR - http://thinkr.fr 8 / 24
my_sqrt <- function(num) { stop_if_not(.x = num, .p = is.numeric, msg = "You should enter a number") sqrt(num) } my_sqrt(1) #> [1] 1 my_sqrt("1") #> Error: You should enter a number Colin Fay, ThinkR - http://thinkr.fr 9 / 24
spend a week optimising for 10 microseconds. You can always do more, you need to know when to stop. Unless really needed, do not spend too much time optimising for speed. Colin Fay, ThinkR - http://thinkr.fr 11 / 24
is slow? => BENCHMARK! You can do this in base R with system.time: system.time({ Sys.sleep(1) }) #> user system elapsed #> 0.007 0.005 1.002 But there are more automated ways to do this. Colin Fay, ThinkR - http://thinkr.fr 12 / 24
automates benchmark. More efficient : allows the comparison to be repeated many times, and displays the maximum, minimum, average and median of the elapsed time as a result. We always start by making sure the two results are all.equal. all.equal(sum(1,2), 1+2) #> [1] TRUE microbenchmark::microbenchmark(sum(1,2), 1 + 2, times = 10000) #> Unit: nanoseconds #> expr min lq mean median uq max neval cld #> sum(1, 2) 168 181 226.7462 201 243 13262 10000 b #> 1 + 2 61 74 101.0710 92 99 40196 10000 a Colin Fay, ThinkR - http://thinkr.fr 13 / 24
you don't have to test for equality of results before running the benchmark. More human readable results. Only on GitHub for now. Read more : http://bench.r-lib.org/ Colin Fay, ThinkR - http://thinkr.fr 15 / 24
possible If a test stops the execution, do it first: compute_first <- function(a){ d <- log(a) * 10 + log(a) * 100 if( a == 1 ) return(0) return(d) } if_first <- function(a){ if( a == 1 ) return(0) d <- log(a) * 10 + log(a) * 100 return(d) } Colin Fay, ThinkR - http://thinkr.fr 20 / 24
your coworkers Transfer the project Guarantee the stability on the long run Thanks to {testthat}, we can automate the tests. -> Allows bugs to be detected before they happen, and guarantees the validity of the code. install.packages("testthat") usethis::use_test("myfunction") Creates a test/testthat folder, adds {testthat} to the Suggests of the DESCRIPTION, and creates test/testthat.R (do not touch it). Colin Fay, ThinkR - http://thinkr.fr 3 / 26
Don't trust yourself in 6 months. Be sure to send in production a package with minimum bugs. Good news: you're already writing test, you just didn't know that before. Write tests before it's too late Colin Fay, ThinkR - http://thinkr.fr 4 / 26
and a testthat folder. In this folder, you'll find .R files of the following form: test-my_function.R One test file per (big) function, with general contexts. Break down the tests in this file by type. These tests will be performed during devtools::check() (which also performs other tests), or with devtools::test() (Ctr/Cmd + Shift + T). Colin Fay, ThinkR - http://thinkr.fr 6 / 26
two elements: the first is the actual result, the second is the expected result. If the test is not passed, the function returns an error. If the test passes, the function returns nothing. library(testthat) expect_equal(10, 10) a <- sample(1:10, 1) b <- sample(1:10, 1) expect_equal(a+b, 200) Erreur : a + b not equal to 200. 1/1 mismatches [1] 11 - 200 == -189 Colin Fay, ThinkR - http://thinkr.fr 8 / 26
(if the code depends on a web connection, an API, etc...), use the skip_if_not() function. library(httr) url <- "http://numbersapi.com/42" test_that("API test", { skip_if_not(curl::has_internet(), "No internet connection") res < content(GET(url)) expect_is(url, "character") }) testthat:::skip_if_not_installed() skips a test if a package is not installed. There are other functions to skip a test under particular conditions, such as skip_on_os(), to prevent from testing on specific operating systems. Colin Fay, ThinkR - http://thinkr.fr 10 / 26
the command line (i.e. in the terminal): R CMD check. Or simply the devtools::check() function in your R session. More tests are performed with check than with devtools::test(), which "only" performs the tests in the test folder. This command runs around 50 different tests. Is performed when you click the "Check" button on the Build tab of RStudio. Colin Fay, ThinkR - http://thinkr.fr 14 / 26
with Travis (we'll see Travis in the next chapterr), and allows to know the amount of code covered by the tests online. The {usethis} function creates the appropriate yaml, and inserts in your clipboard some code to paste in your travis yaml. Colin Fay, ThinkR - http://thinkr.fr 23 / 26
the package is tested regularly Have a shareable test report Guarantee the stability of the project Travis in my project To use Travis: usethis::use_travis() Notes : The package must first be connected to Github The use of Travis is free for Open Source projects (i.e. that are on Github) With a "pro" plan you can use Travis on private rests Colin Fay, ThinkR - http://thinkr.fr 2 / 8
will perform a check whenever you commit on GitHub. Configuration The {usethis} command automatically generates the travis configuration file : Colin Fay, ThinkR - http://thinkr.fr 3 / 8
test on several versions of R by specifying : r: oldrel release devel You can request the installation of specific elements, like Pandoc versions, or apt packages : addons: apt: packages: libxml2-dev Yaml configuration If the DESCRIPTION file contains all the necessary information, the yaml can be as simple as : language: r Colin Fay, ThinkR - http://thinkr.fr 5 / 8
plan "only" allows to cover Open Source projects. AppVeyor usethis::use_appveyor() AppVeyor is a continuous integration service that integrates with a larger number of services: Colin Fay, ThinkR - http://thinkr.fr 6 / 8
Packages": http://r-pkgs.had.co.nz/ "Créer un package R en quelques minutes": https://thinkr.fr/creer-package-r- quelques-minutes "R package primer" : http://kbroman.org/pkg_primer/ "Preparing your package for a CRAN submission" : https://github.com/ThinkR- open/prepare-for-cran Colin Fay, ThinkR - http://thinkr.fr 2 / 6
(Package writing in RStudio)": https://www.rstudio.com/resources/webinars/rstudio-essentials-webinar-series- programming-part-3/ "You can make a package in 20 minutes – Jim Hester": http://www.rstudio.com/resources/videos/you-can-make-a-package-in-20-minutes/ Colin Fay, ThinkR - http://thinkr.fr 3 / 6
skeletons populated with information scraped from the function script": https://github.com/metrumresearchgroup/sinew prefixer - "Prefix function with their namespace" : https://github.com/dreamRs/prefixer remedy - "RStudio Addins to Simplify Markdown Writing" : https://github.com/ThinkR-open/remedy revdepcheck - "R package reverse dependency checking": https://github.com/r- lib/revdepcheck Colin Fay, ThinkR - http://thinkr.fr 4 / 6
it looks for the user, the better Think user first, from function names to documentation Help yourself Automate as much as you can Document as much as you can your development process Prevent bug at an early stage Keep testing Colin Fay, ThinkR - http://thinkr.fr 5 / 6