How Best To Present Your Code
This document is intended to help provide a consistent coding style across Electric Imp libraries and example code, as well as guide developers towards some best practices.
Not all applications have the same requirements, and this document should only be adhered to when it makes sense. If you are writing public code or examples that stray from the style guide, you should add comments indicating why (as you’ll inevitably be asked that question anyway).
However, code submitted to Electric Imp for inclusion among our libraries should adhere to the standards presented below.
Always use //
comments and always have one space between //
and the start of your comment.
Comment text should start with a capital letter, and you should aim to write in proper sentences.
Place comments on their own line, above the relevant code.
// Number of times the device has booted
local count = 5;
local count = 5; // Number of times the device has booted
/*
Bad
*/
Always use semicolons to denote the end of a statement.
This code:
x <- 3;
-3 == x ? server.log("1: " + x) : server.log("2: " + x);
is interpreted differently that this code:
x <- 3
-3 == x ? server.log("1: " + x) : server.log("2: " + x)
Semicolons should not be included at the end of block statements (if
, for
, function
, class
etc) unless it is part of an assignment.
local someFunction = function() {
// Function code...
};
function someFunction() {
// Function code...
};
foreach (item in list) {
// Loop code...
};
Indentations should never be tabs (\t
) and should always be four spaces.
Parentheses following a keyword (if
, for
, foreach
etc) should be separated by a single space.
if (err != null) {
// Process error
}
foreach(key in table) {
// Loop code...
}
Always includes spaces for extra clarity in code:
for (local i = 0 ; i < myArray.len() ; i++) {
// Loop code...
}
for (local i=0;i< myArray.len();i++) {
// Loop code...
}
Always use curly braces, and never start curly braces on new lines.
if ("key" in table) {
data = table.key;
}
if (x == y) {
server.log("It's a match");
} else {
server.log("It's not a match");
}
// No curly braces...
if (x == y)
server.log("it's a match");
else
server.log("It's not a match");
// Curly braces start on new line...
if (x == y)
{
server.log("It's a match");
}
Curly braces may be omitted for single line if
statements when both the condition and the statement are short and simple (though using curly braces is still preferred).
if (x == null) server.log("X is null");
Global variables should be declared with the <-
operator.
x <- 5;
Constants should be declared at the top of the code file (below #require
s), all caps, with underscores between words.
const API_KEY = "abc123";
const apiKey = "abc123";
Variable and function names should always be lowerCamelCase and be self-descriptive.
Auxiliary variables (such as loop counters) may be named with single-letter names.
// i is a loop variable
for (local i = 0 ; i < devices.len() ; i++) {
// Do something with devices[i]...
}
// r is a bad variable name
local r = http.get(url).sendsync();
Anonymous functions should be avoided whenever possible in order to increase testability of code.
function httpRequestHandler(request, response) {
response.send(200, "OK");
}
http.onrequest(httpRequestHandler);
http.onrequest(function(request, response) {
response.send(200, "OK");
});
When closures are required, they should be created via a factory method.
function responseHandlerFactory(x, y, z) {
return function(response) {
// Use x, y, z...
};
}
http.get(url).sendasync(responseHandlerFactory(x, y, z));
When constructing a table, you should use JSON syntax, ie. { "key" : value }
not { key = value }
.
local data = {
"temp" : 32.5
};
local data = {
temp = 32.5
};
Hardware objects (pin, i2c, spi, etc) should be assigned to variables before use.
button <- hardware.pin1;
button.configure(DIGITAL_IN, buttonCallback);
hardware.pin1.configure(DIGITAL_IN, btnCB);
Digital and Analog Out pins should always be initialized using the optional second parameter of the configure method.
led <- hardware.pin1;
led.configure(DIGITAL_OUT, 0);
led <- hardware.pin1;
led.configure(DIGITAL_OUT);
led.write(0);
All HTTP requests should be made asynchronously.
All methods that wrap web services should take an optional callback that will be executed upon completion of the request. The callback must take at least two parameters: err and response.
null
if there was no error).The err parameter should be populated with a string describing the error when the request returns a non-2xx HTTP status code, or when there is an error in the response body.
If the callback method requires additional parameters, they must come after err and response (additional parameters could include pre-parsed data, etc).
Calls to decode data must always be wrapped in a try...catch
block.
Methods external to a class should never be inside a try...catch
block.
Class names should be UpperCamelCase and self-descriptive.
If the class wraps a hardware component or web service, its name should be the component or service.
// Wraps the Twitter REST API
class Twitter {
// ...
}
// Wraps the TI TMP102 and TMP112
class TMP1x2 {
// ...
}
A class’ constructor should only be used to set properties and configure GPIOs. All other initialization code should take place in a public init() and/or reset() method.
class DeviceName {
// Commands
static REG_RESET = "\x02";
_i2c = null;
_addr = null;
_intPin = null;
// Default address is 0xDE
constructor(i2c, interruptPin, address = "\xDE") {
_i2c = i2c;
_addr = address;
_intPin = interruptPin;
_intPin.configure(DIGITAL_IN, _handleInterrupt.bindenv(this));
init();
}
function init() {
_i2c.write(_addr, "", REG_RESET);
}
function _handleInterrupt() {
// Interrupt handling code
}
}
I²C and SPI buses should be configured before being passed into the constructor. These are multi-use buses, so your class should be able to co-operate with other classes that make use of the same bus.
GPIO pins should be configured in the constructor.
Public methods and properties should the lowerCamelCase.
function someFunction() {
// Public function code...
}
Methods intended to be private should have an underscore prepended to the name.
function _privateFunction() {
// Private function code...
}
Classes should never encourage developers to directly access internal properties. Instead it should create get and set methods where required. Additionally, all non-static properties should be initialized to null
and have default values set in the constructor.
class MyClass {
_property = null;
constructor(property) {
_property = property;
}
function setProperty(p) {
_property = p;
}
function getProperty() {
return _property;
}
}
Static properties must always be explicitly declared static
.
class MyClass {
// Good
static _queue = [];
// ...
}
Immutable values, such as a register address and a URL, should be static
and named in all uppercase as if they were constants.
class MyWebservice {
// Good
static API_BASE = "https://example.com/api/";
}
class MyDriver {
// Good
static REG_TEMP = 0x02;
}
Libraries should never use constants or enums as they are both stored in the global constants table.
Properties should appear before the constructor with static properties above non-static properties.
The constructor should always be the first method in a class.
Public methods should appear before private methods in the class declaration.
The beginning of the private methods section should have the following line.
// -------------------- PRIVATE METHODS -------------------- //
Callbacks in libraries should be invoked with imp.wakeup().
imp.wakeup(0, function() {
callback();
});
Libraries must always expose a way for developers to handle unexpected errors. They must never silently trap and throw away errors.
Whenever possible you should avoid throw
-ing errors.
function _handleResponse(response, callback) {
try {
data = http.jsondecode(response.body);
if ("error" in data) {
imp.wakeup(0, function() {
callback(data.error, response);
});
return;
}
} catch (error) {
callback(error, response);
return;
}
callback(null, response);
}
// Developer doesn't get a chance to handle error
function _handleResponse(response, callback) {
try {
data = http.jsondecode(response.body);
if ("error" in data) {
server.error(data.error);
return;
}
} catch (error) {
server.error(error);
return;
}
callback(null, response);
}
Error strings in classes should never be declared inline. Rather, they should be treated as an immutable value.
class MyClass {
static BAD_PARAM_ERROR = "Bad Parameter";;
function doSomething(param, callback) {
if (param < 0) {
local err = BAD_PARAM_ERROR;
imp.wakeup(0, function() {
callback(err, null);
});
return;
}
imp.wakeup(0, function() {
callback(null, param);
});
}
}
class MyClass {
function doSomething(param, callback) {
if (param < 0) {
imp.wakeup(0, function() {
callback("Bad Parameter", null);
});
return;
}
imp.wakeup(0, function() {
callback(null, param);
});
}
}