Functions
Functions should be small and do one thing.
Small
The first rule of functions is that they should be small. The second rule of functions is that they should be smaller than that.
Refer to Write Short Units of Code
Do One Thing
One way to know that a function is doing more than “one thing” is if you can extract another function from it with a name that is not merely a restatement of its implementation.
See examples in the "Small" section.
One Level of Abstraction per Function
In order to make sure our functions are doing “one thing,” we need to make sure that the statements within our function are all at the same level of abstraction.
Abstraction is a fundamental concept in OOPS. It talks about hiding the “how” part and only exposing “what” to the outer world.
According to SLAP (Single Level of Abstraction Principle), source code inside each method should refer to concepts and mechanisms relevant to just one level of “operational complexity” of your application.
Apply refactoring technique "Extract Method".
See examples in the "Small" section.
Reading Code from Top to Bottom: The Stepdown Rule
We want every function to be followed by those at the next level of abstraction so that we can read code from top to bottom.
See examples in the "Small" section.
Avoid nested structures
Avoid negative conditionals
Negatives are just a bit harder to understand than positives. So, when possible, conditionals should be expressed as positives.
Switch Statements
It’s hard to make a small switch
statement. By their nature, switch
statements always do N things. Unfortunately, we can’t always avoid switch
statements, but we can make sure that each switch
statement is buried in a low-level class and is never repeated. We do this, of course, with polymorphism.
There are several problems with the switch statement.
First, it’s large, and when new enum types are added, it will grow.
Second, it very clearly does more than one thing.
Third, it violates the Single Responsibility Principle (SRP) because there is more than one reason for it to change.
Fourth, it violates the Open Closed Principle (OCP) because it must change whenever new types are added.
Use Descriptive Names
Don’t be afraid to make a name long. A long descriptive name is better than a short enigmatic name. A long descriptive name is better than a long descriptive comment.
Be consistent in your names. Use the same phrases, nouns, and verbs in the function names you choose for your modules.
beforeEach();
afterEach();
beforeAll();
afterAll();
Prefer fewer arguments
The ideal number of arguments for a function is zero (niladic). Next comes one (monadic), followed closely by two (dyadic). Three arguments (triadic) should be avoided where possible. More than three (polyadic) requires very special justification—and then shouldn’t be used anyway.
void createUser(String username, String password, Gender gender, int roleId);
Apply refactoring technique "Replace Method Parameters with Method Object": When a function seems to need more than two or three arguments, it is likely that some of those arguments ought to be wrapped into a class of their own.
Functions that take variable arguments can be monads, dyads, or even triads. But it would be a mistake to give them more arguments than that.
Arguments are even harder from a testing point of view. Imagine the difficulty of writing all the test cases to ensure that all the various combinations of arguments work properly.
Sometimes we want to pass a variable number of arguments into a function. Consider to use Argument Lists
public String format(String format, Object… args);
String.format(”%s worked %.2f hours.”, name, hours);
Read more: Keep Unit Interfaces Small
Avoid flag arguments
Function with flag argument does more than one thing. It does one thing if the flag is true and another if the flag is false!
Split the method into several independent methods that can be called from the client without the flag.
Have No Side Effects
Side effects are lies. Your function promises to do one thing, but it also does other hidden things.
The side effect is the call to update(). The get user function, by its name, says that it gets the user by email. The name does not imply that it updates the last access of the user.
Output Arguments
Anything that gets modified or mutated but is not returned by the function. An array/object passed to a function, which changes the body of the array or changes the data of the object but does not return it, then it is the output argument.
Output arguments should be avoided. If your function must change the state of something, have it change the state of its owning object.
public void appendFooter(StringBuffer report);
appendFooter(report);
report.appendFooter();
Separate command from query
Functions should either do something or answer something, but not both.
public boolean set(String attribute, String value);
if (set("username", "gpcoder")) {}
if (attributeExists("username")) {
setAttribute("username", "gpcoder");
}
Prefer exceptions to returning error codes
Returning error codes from command functions lead to deeply nested structures and maybe a violation of command query separation.
Another exmaple:
When you use exceptions rather than error codes, then new exceptions are derivatives of the exception class. They can be added without forcing any recompilation or redeployment.
It is better to extract the bodies of the try
and catch
blocks out into functions of their own.
Put all code inside a try block
If the keyword try exists in a function, it should be the very first word in the function and there should be nothing after the catch/finally blocks.
The DRY principle
Refer to DRY Principle
Structured Programming
Every block within a function should have one entry and one exit. Following these rules means that there should only be one return
statement in a function, no break
or continue
statements in a loop, and never, ever, any goto
statements.
If you keep your functions small, then the occasional multiple return
, break
, or continue
the statement does no harm and can sometimes even be more expressive than the single-entry, single-exit rule. On the other hand, goto
only makes sense in large functions, so it should be avoided.
Write, test, and refine
When I write functions, they come out long and complicated. But I also have unit tests that cover every one of those clumsy lines of code. So then I massage and refine that code, splitting out functions, changing names, eliminating duplication.
— Robert C. Martin
Last updated