Chapter 3 of “Clean Code” focuses on functions and provides guidelines for writing clean, readable and effective functions in source code.
- Functions should be small.
- Lines should not exceed 150 characters in length
- Functions should not be 100 lines long, even better, no more than 20 lines.
- Blocks within
if/else/while
statements should be one line long, probably a function call - Functions should not be large enough to hold nested structures, level of nesting should not exceed 2.
- Functions should do one thing, should do it well and should do it only.
- A function should do only those steps that are one level below the stated name of the function.
- If your function can be divided into sections, you’re probably doing more than one thing.
- Make sure that the statements in the function are all at the same level of abstraction.
Code must be readable from top to bottom in a top-down narrative manner. We should be able to read it as though it were a set of TO paragraphs e.g
TO
RenderPageWithSetupsAndTeardowns
, we check to see whether the page is a test page and if so, we include the setups and teardowns. In either case we render the page in HTML.
- Functions should not have any side effects; they should do one thing, and do that only. A function that checks the validity of a username/password combo should not initialise a session unless that is what it is meant to do. In that case, the function name should make this clear. This violates the ‘function should do one thing only’ rule.
Function arguments
Ideally functions should have zero arguments (niladic)
One argument — monadic
Two arguments — dyadic.
Three arguments — triadic
3+ — polyadic -> This requires special justification and should be avoided.
- functions with arguments are harder to test than functions without
- avoid using flags arguments. If a function does one thing when a flag is true and another when the flag is false, it is doing more than one thing.
- When a function needs more than two or three arguments, consider wrapping some of the arguments into a class or object.
- Function names should be descriptive. Better to have a long descriptive name than to use short enigmatic name. In the case of monadic functions, names should be a verb/noun pair. For example
write(name)
,writeField(name)
,assertEquals(expected, actual)
can be written asassertExpectedEqualsActual(expected, actual)
. Here the name of the function encodes the argument names into the function name, making it easier to remember the ordering of the arguments.
Monadic functions
- Functions that take a single argument should be in two forms;
1) Function that asks a question about the argument e.g.fileExists("filepath")
. Alternatively you may be operating on the argument, transforming it to something else e.g.toDict(List)
2) Events. In this form, there is an input argument but no output. The function uses the argument to alter the state of the system. e.g.void passwordAttemptsFailedNtimes(int attempts)
. - If a function is going to transform its input argument, the transformation should appear in the return value, not in the argument.
Command Query Separation
- Functions should either do something or answer something but not both.
- Your function should either change the state of an object or return some information about it.
Example:
public boolean set(String attribute, String value);
if (set("username", "unclebob"))...
The code above isn’t very readable. Is set being used as a verb or an adjective i.e is it setting the username or asking if the username is set? The author intended for it to be used as a verb but it isn’t clear from the code.
A solution would be to rename it to setAndCheckIfExists
but the if statement would still be unreadable. Better solution is to separate the command from the query:
if (attributeExists("username")) {
setAttribute("username", "unclebob");
...
}
In this case, the functions do exactly one thing. One queries an object and returns information about it and does nothing else. The second function sets an attribute.
This code is more readable.
Prefer exceptions to error codes
- Exceptions allow you to separate error handling from the code.
- Using error codes makes you have to deal with the error immediately after getting the error.
BAD
if (deletePage(page) == E_OK) {
if (registry.deleteReference(page.name) == E_OK) {
logger.log("page deleted");
} else {
logger.log("deleteReference from registry failed");
}
} else {
logger.log("delete failed");
return E_ERROR;
}
GOOD
try {
deletePage(page);
registry.deleteReference(page.name);
configKeys.deleteKey(page.name.makeKey());
}
catch (Exception e) {
logger.log(e.getMessage());
}
Conclusion
To summarise the chapter in a breadth, I’ll say that functions should tell a story, be readable, well named and short.