Mod:Best Practices
From Roll20 Wiki
A best practice is a buzzword referring to a technique that is used as a benchmark when comparing to other techniques with the same language or system. A "best" practice can evolve over time, although changes are usually gradual.
The Roll20 API is written using JavaScript, so many of the best practices listed here may be considered best practices for JavaScript in general. When creating API scripts you are not required to use best practices, but it is absolutely recommended, especially if you want to add your script to the Script Index.
Contents |
Namespaces
Any externally-visible functions or variables should be contained within a "namespace" object. This limits the potential for name collisions between multiple scripts, as all API scripts in a campaign are running within the same context. If you choose a single namespace for your script, a collision will only occur if someone chooses the same namespace for their script. On the other hand, if you don't use a namespace and two scripts define a function named handleInput
, there will be problems.
Strictly speaking, JavaScript does not have namespacing like some other languages, but you can simply add your functions and variables to an object which will behave in a similar way. This is what using a namespace will look like:
var myNamespace = myNamespace || {}; myNamesapce.MY_CONSTANT = 5; myNamespace.myFunction = function() { /* ... */ }
The myNamespace || {}
construction ensures that you don't completely overwrite an existing copy of the namespace in memory, for example if your script is spanning multiple script tabs. This statement will create a new empty object if myNamespace
doesn't exist yet, or do nothing if it already exists.
Function and Variable Names
Use easy, short, readable function and variable names. Ideally, you should be able to glean the purpose of the object represented by the variable just by reading its name. Names like x1
, fe2
, and xbqne
are practically meaningless. Names like incrementorForMainLoopWhichSpansFromTenToTwenty
are overly verbose, and the highly descriptive name may end up wrong as you change your code over time.
On the other hand, a function named splitArgs
is easy to understand if you're familiar with programmer jargon: "args" is a common shorthand for "arguments," and multiple arguments are frequently passed into a program as a single string which needs to be split up into its constituent parts. A variable named element
or item
located within a loop iterating over an array naturally leads one to understand that it should be a reference to one of the objects within the array (in particular, the object corresponding to the current iteration of the loop).
Legibility
Keep your code legible, especially if you need to ask for help on the forums. Indent code blocks as appropriate, include spaces, etc. Compare the following:
do if(node.name.toLowerCase()==='foo')break;while(parent=node.parent);
do { if (node.name.toLowerCase() === 'foo') { break; } } while (parent = node.parent);
The latter piece of code takes up more space, but it is at least an order of magnitude easier to read. You are not competing in Code Golf. You have no need to minify your script. Obfuscation of your source code does not grant you any sort of advantage.
Also, please write your scripts in English. English is the standard around the world for programming, JavaScript's keywords and library functions are in English, and the majority of the users on the forum who will help you if you have difficulties speak English. Naturally, if you are writing a script which is intended to output text in a non-English language, you'll have strings containing non-English text, but the rest of the code should be English.
Comments
Commenting can be something of a holy war among programmers. Some say you need more, some say your code should be enough, and so on. However, understand that for a Roll20 API script, the person reading your code may not be a programmer at all, and is completely bewildered by your black magic... but they still need to make modifications in order to suit their game. At the very least, comments describing your configuration variables are helpful to everyone who installs your script, and comments describing generally what's going on in each section of code can help the layperson trying to struggle his or her way through making the tabletop experience better.
A common mantra about commenting code is that your names should describe what the code does, while your comments describe why the code does that.
Underscore
Roll20 API scripts have access to the Underscore.js library, which contains a large number of utility functions. There is nothing you can do with Underscore that you can't do without Underscore, but the utility functions can frequently make your code more legibile, often while making the code shorter as well. Here's an example of the improvement that Underscore offers:
for (var i = 0; i < jossWhedon.shows.length; i++) { if (jossWhedon.shows[i].title === 'Firefly') { var show = jossWhedon.shows[i]; } } // show = {title: "Firefly", characters: Array[2]} var characterDistribution = jossWhedon.shows.reduce(function(memo, show) { show.characters.forEach(function(character) { (!memo[character.role]) ? memo[character.role] = 1 : memo[character.role]++; }); return memo; }, {}); // characterDistribution = {doll: 1, mad scientist: 2, love interest: 2, slayer: 1, captain: 1, mechanic: 1}
var show = _.findWhere(jossWhedon.shows, {title: 'Firefly'}); // show = {title: "Firefly", characters: Array[2]} var characterDistribution = _.countBy(_.flatten(_.pluck(jossWhedon.shows, 'characters')), 'role'); // characterDistribution = {doll: 1, mad scientist: 2, love interest: 2, slayer: 1, captain: 1, mechanic: 1}
Underscore examples courtesy of Singlebrook.com
Equals vs. Strict Equals
JavaScript has two equality operators (four, if you count their inverses): ==
and ===
. The former operator will do what it can to coerce the values you're comparing to the same type before checking their equality, while the latter will leave the values you're comparing as their original type. This means that the Equals operator is able to return true
for operands of different types, while the Strict Equals operator will only return true if the operands are of the same type.
[10] === 10 // false [10] == 10 // true '10' === 10 // false '10' == 10 // true [] === 0 // false [] == 0 // true '' === false // false '' == false // true
In general, it is recommended to use strict equals over equals. See below for the specifications for x == y
and x === y
.
Equals Specification
- If Type(x) is the same as Type(y), then
- If Type(x) is Undefined, return
true
. - If Type(x) is Null, return
true
. - If Type(x) is Number, then
- If x is
NaN
, returnfalse
. - If y is
NaN
, returnfalse
. - If x is the same Number value as y, return
true
. - If x is
+0
and y is−0
, returntrue
. - If x is
−0
and y is+0
, returntrue
. - Return
false
.
- If x is
- If Type(x) is String, then return
true
if x and y are exactly the same sequence of characters (same length and same characters in corresponding positions). Otherwise, returnfalse
. - If Type(x) is Boolean, return
true
if x and y are bothtrue
or bothfalse
. Otherwise, returnfalse
. - Return
true
if x and y refer to the same object. Otherwise, returnfalse
.
- If Type(x) is Undefined, return
- If x is
null
and y isundefined
, returntrue
. - If x is
undefined
and y isnull
, returntrue
. - If Type(x) is Number and Type(y) is String, return the result of the comparison x == ToNumber(y).
- If Type(x) is String and Type(y) is Number, return the result of the comparison ToNumber(x) == y.
- If Type(x) is Boolean, return the result of the comparison ToNumber(x) == y.
- If Type(y) is Boolean, return the result of the comparison x == ToNumber(y).
- If Type(x) is either String or Number and Type(y) is Object, return the result of the comparison x == ToPrimitive(y).
- If Type(x) is Object and Type(y) is either String or Number, return the result of the comparison ToPrimitive(x) == y.
- Return
false
.
Given the above, the equality operator is not always transitive:
new String('a') == 'a' // true 'a' == new String('a') // true new String('a') == new String('a') // false
Strict Equals Specification
- If Type(x) is different from Type(y), return
false
. - If Type(x) is Undefined, return
true
. - If Type(x) is Null, return
true
. - If Type(x) is Number, then
- If x is
NaN
, returnfalse
. - If y is
NaN
, returnfalse
. - If x is the same Number value as y, return
true
. - If x is
+0
and y is−0
, returntrue
. - If x is
−0
and y is+0
, returntrue
. - Return
false
.
- If x is
- If Type(x) is String, then return
true
if x and y are exactly the same sequence of characters (same length and same characters in corresponding positions); otherwise, returnfalse
. - If Type(x) is Boolean, return
true
if x and y are bothtrue
or bothfalse
; otherwise, returnfalse
. - Return true if x and y refer to the same object. Otherwise, return
false
.
Random Numbers
JavaScript provides the function Math.random()
which produces a random number in the range [0, 1). Roll20 provides the function randomInteger(max)
which produces a random integer in the range [1, max]. A lot of work has gone into improving the randomInteger
function, and creating an even distribution of numbers from the result of random
is more complicated than it seems.
It is strongly recommended that you prefer randomInteger
as your method of generating random numbers.