Table of Contents
Functions should do one thing, do it well and do it only.
Functions should be small
So how long should a function be? Four lines is OK, maybe five. Ten lines is too long. One of the reasons for this is that we will not see much indenting in a four line function (think of ifs, while loops, try/catch blocks, etc). The less indenting, the cleaner the code.
Typically a set of very small functions will be obtained by merciless refactoring into a set of nicely named private functions, with long and meaningful descriptive names.
Cleaning up a large function is not hard and it pays huge dividends within hours or days. Keeping functions large, while might lead to some short-term benefit for you, places the burden of breaking your thought process on others and to you as well when you’ll have to look at the code you wrote in a month or a year time.
Are you out of your mind?
You’re probably thinking that to get to four line functions, you’ll be drowning in a sea of functions and that writing all those tiny functions is going to take you a long time. You’re also probably thinking that the invocation of all those functions will result in a significant performance overhead.
You can get lost if you have use good names for your functions, your classes and your namespaces. Today computers are so fast and compilers so optimised that function calls take nanoseconds or maybe less. We should take advantage our our computers power to partition our software for readability first.
Where do classes go to hide?
A long function is really where classes go to hide.
A large function is a scope and that scope is divided into sections of functionality. These sections communicate with each other using variables which are global to the entire function scope.
What do you have when you have variables defined in a large scope, accessed by many different segments of functionality? You have a class. Large functions can almost always be refactored into one or more classes.
To refactor big functions into classes there are some tips one might want to consider:
- Have a test that verifies the large function works
- Move all the local variables from the function to instance variables
- Identify different responsibilities / concerns within the function and create a class for each responsibility. For example, if the big function contained code to generate prime numbers and print them, one could refactor it into two classes: one in charge of generating prime numbers, one in charge of printing them.
- After every bit of refactoring, run the test to make sure you haven’t broken anything
A function should do one thing, do it well and do it only.
What does “one thing” mean? It could mean anything.
If your function is composed of many different sections, from the point of view of the reader, it’s doing more than one thing. Also, a function should not manipulate different levels of abstraction. For example, String buffers, parsers and so on are low levels of abstraction, while business entities closely related to the domain under consideration are higher levels of abstraction. We don’t want a function to manipulate both because the reader is forced to keep switching between levels of abstraction, making understanding the code very difficult. Therefore different levels of abstraction should be separated into different functions.
In order for a function to do one thing, it must not cross levels of abstraction.
Identifying different levels of abstraction can be fuzzy and undesirable. What we really need is a deterministic and unambiguous way to tell whether a function is doing one thing.
Extract till you drop
If there’s one thing you should take away from this episode is the content in this paragraph.
How can you write functions that do one thing? You keep extracting until you can’t extract anymore. Ultimately if you can extract a function from another, it means the function was doing more than one thing.
The result of all this is that our classes are going to be composed of functions that are all about 4 lines long! We want functions so small that if statements and while loops don’t need braces. Braces should be seen as an opportunity to extract.
Switch statements should be seen an opportunity to use polymorphism.