Functions
Functions are a fundamental block of any software. You can get by without Classes but you can not create anything more than a script without functions. This is why its important to know how to write clean and readable functions.
Make functions Small
- Functions should be very small, ideally less than 20 lines.
- Each function should tell a story, or better part of a story.
- Short functions are easier to read and understand.
- Keep the blocks inside control structures small. Aim for 1 line, and ideally a function call.
Do one thing
- And do it well.
- One thing is one level of abstraction.
- Decompile large concept into set of steps of next level of abstraction.
- Conceptual template for creating functions is: To (function name), we (verb) and/or (variables).
- Function should be simple enough that it cannot be divided further.
Bad:
function emailClients(clients) {
clients.forEach(client => {
const clientRecord = database.lookup(client);
if (clientRecord.isActive()) {
email(client);
}
});
}
Good:
function emailActiveClients(clients) {
clients.filter(isActiveClient).forEach(email);
}
function isActiveClient(client) {
const clientRecord = database.lookup(client);
return clientRecord.isActive();
}
One level of abstraction
Mixing levels of abstraction makes it hard for the reader to understand if an expression is an essential concept or detail.
Bad:
function parseBetterJSAlternative(code) {
const REGEXES = [
// ...
];
const statements = code.split(" ");
const tokens = [];
REGEXES.forEach(REGEX => {
statements.forEach(statement => {
// ...
});
});
const ast = [];
tokens.forEach(token => {
// lex...
});
ast.forEach(node => {
// parse...
});
}
Good:
function parseBetterJSAlternative(code) {
const tokens = tokenize(code);
const syntaxTree = parse(tokens);
syntaxTree.forEach(node => {
// parse...
});
}
function tokenize(code) {
const REGEXES = [
// ...
];
const statements = code.split(" ");
const tokens = [];
REGEXES.forEach(REGEX => {
statements.forEach(statement => {
tokens.push(/* ... */);
});
});
return tokens;
}
function parse(tokens) {
const syntaxTree = [];
tokens.forEach(token => {
syntaxTree.push(/* ... */);
});
return syntaxTree;
}
The step down rule
- Code must be read top down, like a book.
- Every function is followed by lower level of abstraction functions.
- Read program as a set of "To" paragraphs each describing the current level of abstraction and referencing subsequent "To" paragraphs.
Switch Statements
- Hide switch statements in a factory pattern or behind inheritance relationship.
- Switch statements are usually problematic because they don't do one thing.
Descriptive names
- Long names are ok.
- Prefer long names over long comments.
- Name should explain what a function does.
- Be consistent with which phrases, nouns and verbs you use.
Bad:
function addToDate(date, month) {
// ...
}
const date = new Date();
// It's hard to tell from the function name what is added
addToDate(date, 1);
Good:
function addMonthToDate(month, date) {
// ...
}
const date = new Date();
addMonthToDate(1, date);
Arguments
- The less arguments the better. Ideally no arguments.
- Arguments take conceptual power, and readers attention.
- The more arguments a function takes the more difficult writing tests for it becomes.
Monadic forms
- Single argument functions
- Question:
boolean fileExists("MyFile");
- Transformation:
InputStream fileOpen("MyFile");
- The distinction between a transformation and a question should be clear and consistent.
- And finally, there is also and Event, where a function takes input but does not return a value
void passwordFailed(times);
Bad:
function createMenu(title, body, buttonText, cancellable) {
// ...
}
createMenu("Foo", "Bar", "Baz", true);
Good:
function createMenu({ title, body, buttonText, cancellable }) {
// ...
}
createMenu({
title: "Foo",
body: "Bar",
buttonText: "Baz",
cancellable: true
});
Flag Arguments
- Hard to read, bloats the function.
- Better split into two functions.
Dyadic and Triad functions
-
Complicate code.
-
No natural order of arguments.
-
Can be converted into monads.
- Make function member of class instead.
- Abstract arguments into object.
Argument objects
When function needs more than one or two arguments, they should be wrapped into a class.
Verbs and keywords
- Functions should be written as verb noun pairs
write(name);
- Can include keywords
assertArrayContains(object);
Nave no side effects
- Functions must never behave in nonintuitive manner.
- Avoid making unexpected modification to state, class fields, or global variables.
- Readers will miss the side effects and be baffled by the unexpected behaviour of the code.
Command query seperation
- Functions should de something of answer something.
- Always separate command from query as distinct functions.
Don not return error codes
- Prefer throwing exceptions instead.
- Exceptions instead of error codes, makes the handling of errors much cleaner.
Do not repeat yourself
- This will result in bloated code.
- And will require more maintenance.
Conclusion
- Just like any writing, fist you get your thoughts down, then you massage it until it reads well.
- At first your functions are messy but then you refine the code, splitting functions, changing names, eliminating duplication, shrinking, and reordering.
- Functions are verbs and Classed are nouns in the language programmers describe a system.