The JASP Guide to Writing Software for Humans ============================================= Software is not just for statisticians. Humans use software too. This guide is intended highlight some of the differences between writing for statisticians (i.e. what is generally considered 'good enough' in R packages), and writing software for humans. These boil down to the three areas - Good error messages - Thorough testing - Automatically check assumptions ### Good error messages In many R packages, quite cryptic error message can be thrown. For example, passing an Infinity into a t-test gives the following error message: > t.test(c(2,7,3,Inf)) Error in if (stderr < 10 * .Machine$double.eps * abs(mx)) stop("data are essentially constant") : missing value where TRUE/FALSE needed Of course, the user should not have an infinity in their model, but most people acquainted with murphy's law know that this probably still happens all the time. Good error messages prevent: - people being confused - them complaining / asking on forums / emailing you - you having to respond to said complaint / email In summary, good error messages make JASP awesome and save you time in the long run. Hence, in developing an analysis, it is good to think through each and every sort of mistake the user could make, and test for each of these mistakes at the start of the analysis (More about this in testing below). Error messages can either be placed over the top of the results table, or put in the footnotes. In general, an error which makes the whole analysis meaningless, should be placed over the top of the results table. A t-test where the user has specified an independent variable with three levels would be one such example. In contrast, if the error only affects one or a handful of values in the table, then an NaN should be placed in that cell, and a footnote marker added. ### Writing for internationalization Internationalization (i18n) is the process of preparing the source code such as message and other user-visible information in various human languages. An inspiring news is that JASP is moving towards i18n. Therefore, it is necessary to pay attention to the readability and flexibility of the message during writing. Passing your messages with [gettext() or gettextf()](https://www.gnu.org/software/gettext/manual/gettext.html), JASP will add automatically the strings in a .po file, that will be exposed in [WebLate](https://jasp-stats.org/translation-guidelines) so that people can translate them in their own language. Most general messages can be internationalized with a simple `gettext()` calls, like this: ```r gettextf("Number of factor levels is %s", "{{factorLevels.amount}}") ``` **Specialized Messages** If there are some arguments to be substituted into the message, you can provide them inside `gettextf()` as `$1`, `$2`, and `$3` and so on. `gettextf()` should be used in place of gettext when arguments are needed, or when special characters are used in the string (non latin characters like 'β', or the '%' character which is used for placeholder).
Some examples:
```r
# Bad writing
01. gettextf("Number of factor levels is %s in %s", "{{factorLevels.amount}}", "{{variables}}") # same %s
02. gettextf("%s Of the observations, %1.f complete cases were used. ","str",numbers) # amixed %s,%f,%d...mixed in a message
# Good writing
01. gettextf("Number of factor levels is %1$s in %2$s", "{{factorLevels.amount}}", "{{variables}}")
02. gettextf("%1$s Of the observations, %2$1.f complete cases were used. ","str",numbers) # using
Some examples:
```r
# Bad writing
gettextf("File %s is %s protected", filename, rw ? "write" : "read");
# Good writing
gettextf (rw ? "File %s is write protected" : "File %s is read protected", filename);
```
Some examples:
```r
# Bad
cat(ngettext(length(miss), "variable", "variables"),
paste(sQuote(miss), collapse = ", "),
ngettext(length(miss), "contains", "contain"), "missing values\n")
# Good
cat(sprintf(ngettext(length(miss),
"variable %s contains missing values\n",
"variables %s contain missing values\n"),
paste(sQuote(miss), collapse = ", ")))
```