Coding norms
Where the Software Development Guidelines are about the practice of developing software, this document is about details of implementation in all or specific environments.
General
The first rule of style in any programming environment is to start with community norms and build on those.
Comments
First, above all other rules about comments, comment your code. Get the comment down and then worry about the rest.
Comments should follow the prevaling style of the language, environment, or particular code base.
For shorter comments, appearing on a single line, informal partial sentences are typically used:
# do the thing
do_the_thing()
Longer comments typically describe a process and may cover several blocks of code. These generally use full sentences with regular capitalization and punctuation:
# The following section first gathers the flurbs but must sort them into the
# zmort before doing the thing. The zmort will hold the original flurbs and
# also the converted flarbs.
# get the flurbs
flurbs = get_the_flurbs()
...
Of course, these types of comments often indicate code can be pulled out into a dedicated function.
Python
This guide follows Python’s industry standard styling, such as those outlined in PEP 8 standards.
Pylint
Use pylint for all Python source files: every Python file in the project should contain, at minimum, the following header:
# pylint:
This line may follow the module docstring but should precede any other code.
Exemptions: Sometimes Pylint perceives a fault and we don’t agree. There are multiple places to declare an exemption:
- to declare an arbitrary block of code, such as a single line, exempt from a particular rule, it must be preceded by a directive to disable the check, and then succeeded by a directive re-enabling it
- to declare a function or method exempt, declare the exemption directly after the function declaration and docstring
- an entire module may be declared exempt by placing the exemption directly before the module docstring (typically the first line)
- an exemption may be defined for an entire project by describing it in
.pylintrc.
With proper decomposition it is usually preferable to define an exemption at the method or function level (defining an exemption for a single line or block of code is tedious and adds noise to the code comments, while a project-wide exemption is generally too broad).
Exemptions are declared in a comma-separate list, following the marker
disable=, except for in .pylintrc. Wherever defined, they must be
documented except in trivial cases. Except in .pylintrc, these explanations
immediately follow the exemption declarations.
def do_the_thing():
""" Does that thing
"""
# pylint: disable=too-many-statements,too-many-locals
# - this is a lengthy procedure and it doesn't make sense to break it up
# - the local variables are necessary for the length valdidation process
...
Single, double and triple quotes
Use single quotes for simple strings, such as single characters or string literals used as index keys.
Use double quotes for text and for complex strings where there are substitutions, format markers, or other operations.
Examples:
# index key
print(message['greeting'])
# single character
if choice == 'q':
# text
print("Goodbye")
else:
# complex string
print(f"Did not understand: {choice}")
print("I was leaving anyway")
Use triple quotes sparingly, apart from for docstrings: they break up the indentation flow of the code and slow scanning. For some use cases, such as informative user messages or SQL query strings, these are best defined as constants and located in their own section, typically at the top of the file.
Docstrings
(These norms specifically apply to the use of triple-quoted strings for in-source documentation.)
First, document all public classes, methods, functions, and structures with docstrings.
Docstrings of a single line may be expressed with the opening triple quote on the same line as the content, followed by the closing triple quote on its own line:
def do_the_thing():
""" This is is a simple function like the others.
"""
...
Docstrings of multiple lines should be expressed with the triple quotes on their own lines:
def do_the_complex_thing(arg1, arg2):
"""
This complex function takes several arguments, does a specific thing or
series of things which is or are briefly explained by this text, and then
returns a result, also explained here. How much detail is up to you.
"""
...
In both cases, the docstring is clearly demarcated from the first lines of code by the closing triple quote.
Arguments, return value and notes may also be described where useful:
def do_the_complex_thing(arg1, arg2):
"""
(...all that stuff from before...)
Args:
- arg1: something about arg1
- arg2: something about arg2
Returns: A dictionary of information you should have.
Notes:
- inspired by https://stackoverflow/article/30493094
"""
...
The level of detail required depends on visibility of the entity and of the project overall–a public class in a public package must be documented, or it should stay private; a public class in a private package should be documented as the team will need this; a private class may be considered to be sufficiently obvious.
So, consider the audience and their needs.
Nested functions
Python allows functions to be defined within other functions. These are called nested functions. We use nested functions to keep helper functions in scope of a larger function and make such a function more readable. This also avoids unnecessary clutter in the outer function’s namespace.
def outer():
"""
This is the outer function
"""
def inner():
"""
This function is defined within outer() function
and only usable within outer() function
"""
print("I'm an inner function")
inner()
We’ve established the following rules for the use of Python nested functions:
-
There can only be at most one level of function nesting. This means that inner functions cannot have another function defined within them.
-
An outer function (meaning that function is not defined within another function) can have multiple inner functions defined within it.
-
All of the inner functions must be defined at the top of the outer function.
-
A recommendation, not a rule: Inner functions should take parameters rather than directly manipulate variables of the outer scope.
Type hinting
Type hinting allows developers to declare the expected data types of variables, function parameters, and return values, transforming Python’s dynamic nature into a more predictable environment.
In terms of spacing, use space on both sides for ->. There should be no space after the double colon and on either side of the pipe (|); this maintains consistency with our rule of omitting spaces for default paramater values. When a signature is too long, use regular indentation (4 spaces) for continuation lines.
def function1(
arg1:int,
arg2:str|None=None
) -> list[str]:
...
Use the Pipe (|) for multiple allowed types. This replaces the Union and Optional used in older Python versions.
# Simple
def function1(arg1:int) -> None:
...
# Union
def function2(arg1:int|str) -> None:
...
# Optional
def function3(arg1:int|None) -> None:
...
For collections, use list, dict, and tuple directly. Capitalized versions of these types from the typing module are no longer necessary after Python 3.9. A list of collections that no longer require import from the typing module can be found here. For example, use list[int|str] if items can be either type. Use tuple[int,str,float] for fixed order of types. Use dict[str,list[int]] if keys are strings and values are lists of ints. When dealing with custom classes, simply use that class’s name as the type.
When an argument is optional and can accept multiple types, the default must match one of the union types:
def function1(
arg1:dict|list|None=None
) -> None:
...
The code below shows an example of a nested collection that contains type hinting:
variable1:list[dict[str,int|float]] = [
{"key1": 10, "key2": 0.5},
{"key3": 22, "key4": 0.8}
]
References
We generally follow Python’s PEP 8.
JavaScript
Naming conventions and variable declarations
Use PascalCase for classes and constructor functions to distinguish them from objects. For all other variables, functions, and instances, we use lowerCamelCase. When defining hard-coded constants that do not change throughout the application’s lifecycle, we use UPPER_SNAKE_CASE.
For declarations, avoid the var keyword because of its lack of block scoping, which can lead to bugs. Instead, use const for all references. Only use let when certain the variable needs to be reassigned. This signals to others that a variable is immutable unless specified otherwise.
// Bad
var name = 'Jake';
const Object_List;
// Good
const MAX_ATTEMPTS = 5;
const objectName = ClassName();
let retryCount = 0;
Spacing, braces, and semicolons
Use 2 spaces for indentation and never tabs. Place a single space before the leading brace of function or conditional block. Furthermore, use the Egyptian braces style (K&R style), where the opening brace stays on the same line as the statement, and the closing brace on its own line.
// Bad
if(condition){
const variable = 0;
function();
}
if (condition)
{
const variable = 0;
function();
}
// Good
if (condition) {
const variable = 0;
function();
}
To avoid potential bugs caused by Javascript’s Automatic Semicolon Insertion, always use semicolons at the end of statements. Also place spaces around operators and after commas for clarity.
// Bad
function func() {
const variable1 = 2
const variable2 = 20+40/variable1+23
const [variable3,variable4,variable5] = [item1,item2,item3]
}
// Good
function func() {
const variable1 = 2;
const variable2 = 20 + 40 / variable1 + 23;
const [variable3, variable4, variable5] = [item1, item2, item3];
}
Objects, arrays, and commas
When arrays and objects become complex enough to span multiple lines, break each element onto its own line and use trailing commas for all elements (including the last one). This makes version control diffs cleaner since adding a new property to an object, for example, only shows one line changed instead of two. Also use shorthand syntax for objects when the property name matches the variable name.
// Bad
const age = 18;
const object = [
name: 'Jake',
age: age,
role: 'employee'
];
// Good
const age = 18;
const object = [
name: 'Jake',
age,
role: 'employee',
];
String formatting
To maintain a consistent visual style, use single quotes (') as the default for all strings. These look cleaner and are the established standard in the Airbnb and Google style guides. Double quotes (") should be reserved for the cases where the string itself has single quotes. This avoids the need for escape characters.
// Bad
const string1 = "This string is incorrectly formatted."
// Good
const string1 = 'This string is correctly formatted.'
// Bad
const string1 = 'This string was written in John\'s computer.'
// Good
const string1 = "This string was written in John's computer."
When the string requires the injection of variables or expressions, use template literals (backticks) instead of the + operator. String concatenation can lead to spacing errors and becomes difficult to read when multiple variables are involved.
// Bad
const string1 = 'This string was written by ' + name + ' on ' + weekday + '.';
// Good
const string2 = `This string was written by ${name} on ${weekday}.`
Documentation and code comments
The goal is to ensure that code remains maintainable, searchable, and self-documenting. This is accomplished through the use of JSDoc and normal single-line/multi-line comments.
For classes, use JSDoc and place the block directly above the class keyword. Describe what the class does and its properties. Use the @class and @property tags.
/**
* Manages the connection with the database.
* @class
* @property {string} connectionString - The URI for the database.
* @property {boolean} IsConnected - Current state of the connection.
*/
class DatabaseManager {
constructor(uri) {
this.connectionString = uri;
this.isConnected = false;
}
}
JSDoc should also be used for non-trivial functions. Document its inputs, output, and potential erros by using tags such as @param for parameters, @returns for the function’s return value, and @throws for errors thrown by the function, if any.
/**
* Processes an order.
* @param {string} orderId - The ID of the order.
* @param {number} amount - The price of the order.
* @returns {number} The final calculated price.
* @throws {Error} If the orderId is not found.
*/
function processOrder(orderId, amount, description) {
...
}
JSDoc can also be used to identify the type a variable may contain. This is useful for when the type is not obvious from the assignment.
/** @type {number | string} */
let id = 101;
id = "ABC";
/** @type {?string} */
let firstName = null;
firstName = "John"
/**
* @type {{ id: number, name: string, email: string }}
*/
const userAccount = {
id: 1,
name: "John Doe",
email: "john@mail.com",
};
Comments at the end of a line are acceptable for simple definitions, such as clarifying a simple variable. For the rest of cases, place the comment above in its own separate line.
// iterate through array
var i = 0; // counter
for (const thing of things) {
// print thing
console.log(thing);
// update counter
i += 1;
}
// print counter
console.log(i);
This trivial example was shown to demonstrate where to place comments. However, with meaningful variable names, this code is self-documenting and a single comment would be used for the compound operation.
// iterate through array and print out each member, then count
var count = 0;
for (const thing of things) {
console.log(thing);
count += 1;
}
console.log(count)
Line wrapping and continuation
When dealing with long statements, we must break it down into multiples lines. It is important to indent the continuation line by one additional level (2 spaces). In addition, the line should be broken after an operator (+, -, or ||), which signals to the reader that the statement is not finished yet.
For long method chains, we place each method call on its own line, starting with the leading dot. The object initializing the chain must be on the firs line of the statement.
const variable = data
.function1()
.function2(argument1, argument2)
.function3();
When a function call has too many arguments to fit on a single line, we wrap them by placing each argument on its own line and indenting one level.
function funct(
argument1,
argument2,
argument3
) {
...
}
When dealing with lengthy expressions, we break the line after the operator. This indicates that more conditions follow. In the example below, the if statement’s lengthy logical expression is broken down after the logical operator (&& or ||).
if (
expression1 ||
expression2 &&
expression3 ||
expression4
) {
...
}
An exception to the rule is when dealing with ternary operators. In this case, we place the question mark and the colon on new lines to clearly separate the true and false results.
const variable = isTrue
? 'The condition is true'
: 'The condition is false'
References
The following style guides have informed these norms:
Line continuation
Python provides two ways to continue a line. Always choose implied continuation: wrapping code in parentheses, brackets or braces is always preferred over using the backslash character. This is because a single invisible space after the backslash will cause a syntax error.
When calling multiple methods in a row (method chaining), follow these requirements:
-
Wrap the entire chain in parentheses.
-
Place the first object on the starting line.
-
Place the dot at the beginning of the line.
variable1 = (
object1
.function1(argument1=123)
.function2()
.function3(argument2=True)
)
When a single method has many arguments, or the arguments themselves are long expressions, structure like the following:
variable1 = object1.function1(
argument1="string1",
argument2=123,
argument3=variable2,
argument4=True,
)
Always include a comma after the last argument. This makes future git diffs much cleaner when you add a new parameter.
When you have a chain where the individual methods also have many arguments, combine the two patterns:
variable1 = (
object1
.function1(
argument1="string1",
argument2="string2",
argument3=False,
)
.function2(
argument4=["string3", "string4", "string5"],
argument5=123,
)
.function3(argument6=456)
)