Summary of — Clean Code by Robert C Martin — Part 3: Functions
You must buy this book to understand all the below principles in detail. Get it here on Amazon.
This article is a continuation of my previous article — Summary of — Clean Code by Robert C Martin — Part 2: Meaningful Names
Below I will be explaining how you must write functions as suggested by Robert C. Martin to maintain clean code.
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. Smaller functions are mostly robust, easy to read and maintain.
Blocks and Indenting
Try not to have nested structures in your function. Functions should not be large enough to hold nested structures. Therefore indent level of function should never be greater than one or two.
Do One Thing
Functions should do one thing. They should do it well. They should do it only.
If you find it hard to make functions do only one thing, try to write a paragraph on what your functions do. All the lines can be abstracted into separate functions.
Section within Functions
A function consisting of sections such as declarations, initializations, and sieve is doing more than one task. Functions that do only one task cannot be reasonably divided into sections
One level of Abstraction per function
Mixing the level of abstractions in functions is very confusing.
Reading code from Top to Bottom: The Stepdown Rule
We want the code to read like a top-down narrative. We want every function to be followed by those at the next level of abstraction so that we can read through the program.
For eg: To create a user in the system, we must assign the parameters to the object, then we must validate the object, then we should run before saving callbacks, then we should persist the object in the database.
Switch Statements
Switch statements in most cases make a function does more than one thing. To solve this, you can bury the switch statement in the basement of the abstract factory.
Eg:
switch(employee.type)
case HOURLY:
return calculateHourlyPay(employee);
case MONTHLY:
return calculateMonthlyPay(employee);
Instead, make the switch statement return the type of employeeType. Each employee can have their own class and the switch will do only one task which is to return the castedEmployee
switch(employee.type)
case HOURLY:
return new HourlyEmployee(employee);
case MONTHLY:
return new MonthlyEmployee(employee);
Use descriptive names:
Don’t be afraid to make a name long. A long descriptive name is better than a short enigmatic name. Don’t be afraid to spend time choosing a name. Instead, you should try several different names and read the code with each in place.
Function arguments
The ideal number of arguments for a function is zero (niladic). Next comes one(monadic), followed by two (dyadic). Three arguments (triadic) should be avoided where possible. More than three(polyadic) required very special justification — and then shouldn’t be used anyway
Arguments are even harder from the testing point of view. Imagine the difficulty of writing the test case to ensure that all various combinations of arguments work properly.
Output arguments are harder to understand than input arguments. When we read a function, we are used to the idea of information going into the functions through arguments and out through the return value. We don’t usually expect information to be going out through the arguments.
Common Monadic forms
There are two very common reasons to pass a single argument into the function. You may be asking a question about that argument, as in boolean fileExist("MyFile")
. Or you may be operating on that argument, transforming it into something else and returning it. eg. InputSteam fileOpen("MyFile")
.
If a function is going to transform its input argument the transformation should appear as the return value.
Flag Arguments
Flag arguments are ugly. Passing a boolean in a function complicated the signature method, loudly proclaiming that this function does more than one thing.
Dyadic functions
A function with two arguments is harder to understand than a monadic function. There is a short pause in understanding the two inputs as compared to the monadic functions. eg: InputSteam readFile("MyFile") vs InputSteam fileOpen("MyFile", "read")
.
One more overhead is that we have to maintain a natural ordering of input arguments.
It is not an evil practice to write dyadic functions, but you must know at what cost you are writing it and is there any way to convert it into monadic.
Triads
A function that takes 3 arguments is harder to understand than the dyads. The issue of ordering, pausing, and ignoring is more than doubled.
Argument Objects
Consider example — Circle makeCircle(int x, int y, int radius)
vs Circle makeCircle(Point center, int radius)
When a group of variables is passed which are likely part of a concept, it deserves a name of its own.
Have no side effects
Side effects are the lies. Your function promises to do one thing, but it does one hidden thing. eg. a function where you check if a class is instantiated, you should not instantiate it. It should only return true and false.
Output Arguments
As discussed we should avoid the practice of output argument. An argument is ideally interpreted as an input and not output. If you want to perform an operation on input, make it as an object and create a method for that object.
eg: appendFooter(StringBuffer report)
should be made report.appendFooter()
which is more meaningful.
Practice try/catch
A function should be made robust and it should be capable of handling errors. A function should not have any further statements after the catch block.
Conclusion
Master programmers think of systems as stories to be told rather than programs to be written. Never forget that your real goal is to tell the story of the system and that the functions you write need to fit cleanly together into a clear precise language to help you with that telling.