Style Guidelines
Updated by Scott Waldron
For the most part, Dino doesn’t really care how your code is formatted. Because it requires statement terminators (;) and block boundaries ({}) – spaces, tabs, and linefeeds (collectively known as whitespace) are simply ignored when the code is parsed.
However, there is a big difference in readability between these two functions:
def foo(num, date) { var d
= date;
var a = num + 4; return d;}
def bar(num, date) {
var d = date;
var a = num + 4;
return d;
}
Both will run, and return a value, but the first will get curses called down upon you when someone else attempts to read or modify your code.
Function Style
Function names should begin with a lowercase letter so that there is less chance they will conflict with classes defined in the host framework (which almost always start with an uppercase letter). The opening block brace should be on the same line as the function declaration. Here is an example of a good function:
def goodFunc(dateStart, intNumDays) {
var dateEnd = dateStart.AddDays(intNumDays);
storeDateToDatabase(dateEnd);
return dateEnd;
}
Here are the good things about this function:
- The opening block brace is on the same line as the function declaration.
- The parameters are clearly named to indicate their type and purpose.
- Each statement is on a separate line.
- There is a single blank line before the return statement.
An additional point about the return statement is helpful here: Dino doesn’t require a function to explicitly return a value. Leaving out the return statement will simply cause Dino to return the special value null. So, when you’re returning a value from a function, it’s easier to spot with the blank line above it.
Anonymous Function Style
Anonymous functions, sometimes called “delegate functions”, are a powerful part of most modern computer languages. Their syntax and purpose are covered in other places in this document. However, they have their own set of style rules that should be followed.
var adder = (x, y) => {
return x + y;
};
The adder anonymous function (more specifically, adder is a variable that is pointing to an anonymous function, to be precise about it) uses the “block” style much like a standard function. There can be as many lines as necessary inside the block braces.
var multiplier = (x, y) => x * y;
The multiplier anonymous function shows an alternate way to define a simple anonymous. Since it’s very simple and has only one expression, it just defines the body of the function (x * y) on the same line as the function declaration. This is fine for simple anonymous functions.
var someAction = () => {
print('Hey from an anonymous function');
assert(1 + 1 == 2);
doSomethingElse();
};
The someAction anonymous function must use the “block” style because it has multiple statements.
Script Style
Functions go into a script file or record. So, there are also guidelines for the script. Here is an example of a good script.
/**********************************************************
This is a comment stating the purpose of the script.
It may span as many lines as necessary.
***********************************************************/
////////////////////////////////////////
// Imports - scripts must come first.
////////////////////////////////////////
import script '2'; // Script id 2 brings in most .NET types needed here
from Aim.Reports import NpoiHelpers;
from Csou import City, Parcel, Station, Survey;
////////////////////////////////////////
// Variables and helper functions.
////////////////////////////////////////
private const _admins = list[string]('admin', 'jenny', 'samuel');
private const _projectId = '3'; // GISKEY Bosch
/**
Gets a city from database by id.
*/
private def getCity(id) {
return City.GetRepository().Find(id);
}
/**
Gets a random number.
*/
private def getRandomNumber(intSeed) {
if intSeed > 10 {
var seed = 3 + intSeed;
return Math.Random(seed);
}
else {
var seed = 5 + intSeed;
return Math.Random(seed + seed);
}
}
///////////////////////////////////////
// Event handlers.
///////////////////////////////////////
def onInform(sender, e) {
var user = host.user().Identity.Name;
if e.Info == 'Created' and user in _admins {
sender.Description = 'This record was created by an admin user.';
}
}
def onChanged(sender, e) {
// Not implemented.
// Customer has requested that we save to a log here.
}
///////////////////////////////////////
// Command functions.
///////////////////////////////////////
/**
Adds a number of days to a given date.
*/
def cmdAddToDate(dateStart, intNumDays) {
$$
<param name="dateStart" help="The date that will be added to." />
<param name="intNumDays" help="The number of days to add." />
$$
var dateEnd = dateStart.AddDays(intNumDays);
// TODO: update a city record and save to database
return dateEnd;
}
There are a lot of things right about this script:
- It begins with a multi-line comment stating the purpose of the script.
- It’s broken down into sections:
- Imports of scripts and framework types
- Helper functions and script-level variables
- Event handlers (if any are required)
- Command functions
- Helper variables and functions are marked as private to reduce potential conflicts if this script is ever imported into another.
- Helper variables are const if possible (const values cannot be reassigned).
- Helper variables (those that are at the script level, that is, outside of any function body) start with the _ character to differentiate them from variables declared inside a function body. Variables at the script level are sometimes called “global” variables, although they are not truly global because they can be marked as private.
- The “_admins” variable is well-named to show its purpose.
- The random number function has two return statements based on an if conditional. Each return statement is preceded by a blank line.
- All variables and functions begin with a lowercase character so that they are easily differentiated from platform .NET code.
- Notice in the
getCity
function, the lineCity.GetRepository().Find(...)
. It’s easy to see that these objects and functions are platform code, not Dino code, because they begin with uppercase letters.
- Notice in the
- “Magic strings” have an explanatory comment:
-
import script '2';
- comment explaining what script '2' is -
private const _projectId = '3';
- comment indicates which project
-
- Functions have a multi-line comment explaining the function.
- Should start with /**
- End with */
- Command functions have a $$ section explaining the parameters.
- An empty function has a comment to indicate that it’s not yet complete.
- The unimplemented function has a comment stating its future purpose.
- One of the functions has a TODO: comment indicating things are not yet complete.
Variable and Function Names
“There are two hard things in computer programming: naming things, cache invalidation, and off-by-one errors” - Unknown
The first one of those “two” things is really true. Taking the time to come up with good names can save you a lot of time later in divining the purpose of a variable or function. Variables especially, because they can be in a lot of different scopes (script scope, function scope, function parameter) can be hard to differentiate.
The best practice for variables is to come up with a convention for each “place” that a variable can be defined. Here are some suggestions:
- Prefix script-scope variables with a _ (underscore).
- Example:
private var _myVar = 0;
- Example:
var _counter = 0;
- Example:
- Make const variables ALL_UPPERCASE.
- Example:
const STATION_COL = 'station';
- Example:
- Prefix parameters with something. A “p” works well. This is completely optional, but it can help with very complex functions that have a lot of privately declared variables.
- Example:
def myFunc(pDate, pNumber);
- Example:
- Function-scope variables should begin with a lowercase letter.
- Example:
var statusId = null;
- Example:
var proxy = DynamicProxy();
- Example:
For functions, use the purpose of the function as a guide.
- Event handlers should start with “on”.
- Example:
def onChanged(sender, e){...}
- Example:
def onClick(sender, e){...}
- Example:
- Private helper functions should begin with a lowercase character or underscore. Other than that, they can use any valid identifier name.
- Command-type functions (those meant to be invoked by a user) should start with “cmd”.
- Example:
def cmdDoThis(dateFrom, dateThru){...}
- Example:
Casing Recommendations
Sometimes it’s useful to differentiate private and public functions, those which are meant for commands, those which are event handlers, etc.
Types, properties and methods in the host platform usually use Pascal casing. Pascal cased names start with an uppercase character, and then put an uppercase character at each word break:
namespace MyStuff.Math
{
public class MathUtils
{
public static int AddTwoNumbers(int one, int two)
{
return one + two;
}
}
}
There are two other types of casing, either of which is recommended for Dino: camel casing and snake casing. One possibility would be to use camel casing for public functions and snake casing for private helper-type functions:
// IMPORTS
from MyStuff.Math import MathUtils;
from MyStuff.Sales import Customer;
// PRIVATE HELPERS
private def add_one(num) {
//
// Note that it's easy to identify platform code here
//
return MathUtils.AddTwoNumbers(num, 1);
}
private def subtract_one(num) {
return num – 1;
}
private def get_customers() {
return Customer.GetRepository().GetAll();
}
// EVENT HANDLERS
def onInform(sender, e) {
if e.Info == 'Saving' {
sender.Description = 'Set by business rule event handler';
}
}
// COMMAND HANDLERS
def cmdSubtract(intNum) {
return subtract_one(intNum);
}
Camel casing is distinguished by starting with a lowercase letter, then placing an uppercase letter at each word break: thisIsCamelCased. Snake casing starts with a lowercase letter and separates word breaks with an underscore: this_is_snake_cased.