<<Clean Code>> Quotes: 3. Functions
Functions are the verbs of that language, and classes are the nouns.
Never forget that your real goad is to tell the story of the system, and that the function you write need to fit clearly together into a clear and precise language to help you with that telling.
1. Small!
the first rule of functions is that they should be small.
Blocks and Indenting
This implies that the blocks within if statements, _else_ statements, _while_ statements, and so on should be one line long.
This also implies that functions should not be large enough to hold nested structures. Therefore, the indent level of a function should not be greater than one or two.
2. Do One Thing
FUNCTIONS SHOULD DO ONE THING. THEY SHOULD DO IT WELL. THEY SHOULD DO IT ONLY.
After all, the reason we write functions is to decompose ta larger concept (in other words, the name of the function) into a set of steps at the next level of abstraction.
So another 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 tis implementation.
Sections within Functions
Functions that do one thing cannot be reasonably divided into sections.
3. 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.
Mixing levels of abstraction within a function is always confusing. Readers may not be able to tell whether a particular expression is an essential concept or a detail.
Reading Code from Top to Bottom: _The Stepdown Rule_
To say this differently, we want to be able to read the program as though it were a set of _TO_ paragraphs, each of which is describing the current level of abstraction and referencing subsequent _TO_ paragraphs at the next level down.
4. Switch Statements
Example:
from:
public Mondy calculatePay(Empoyee e) throw InvalidemployeeType { switch (e.type) { case COMMISSIONED: return calculateCommissionedPay(e); case HOURLY: return calculatHourlyPay(e); case SALARIED: return calculateSlariesPay(e); default: throw new InvalidEmployeetype(e.type);}
to:
public abstract class Employee { public abstract boolean isPayday(); public abstract Money calculatePay(); public abstract void deliverPay (Money pay);}— — — — — — — -public interface EmployeeFactory { public Employee makeEmployee (EmployeeRecord r) throws InvalidEmployeeType;}— — — — — — — -public class EmployeeFactoryImpl implements EmployeeFactory { public Employee makeEmployee(EmployeeRecord r)) throws InvalidEmployeeType { switch (r.type) { case COMMISSIONED: return new CommissionedEmployee(r); case HOURLY: return new HourlyEmployee(r); case SALARIED: return new SlariesEmployee(r); default: throw new InvalidEmployeetype(r.type); } }}
One solution for big and long switch statement is to bury it in the basement of an ABSTRACT FACTORY, and never let anyone see it.
My general rule for switch statements is that they can be tolerated if they appear only once, are used to create polymorphic objects, and are hidden behind an inheritance relationship so that the rest of the system cannot see them.
5. Use Descriptive Names
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.
6. Function Arguments
The ideal number of arguments for a function is zero(nomadic), 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.
The argument is at a different level of abstraction than the function name and forces you to know a detail(in other words, StringBuff) that isn’t particularly important at that point.
Output arguments are harder to understand than input arguments. output arguments often cause us to do a double-take.
Common Monadic Forms
There are two very common reason to pass single argument into function. You may be asking a question about that argument. Or you may be operating on that argument, transforming it into something else and returning it. These two uses are what readers expect when they see a function. You should choose names that make the distinction clear, and always use the two forms in a consistent context.
A somewhat less common, but still very useful form for a single argument functions, is an Event. Inthis form there is an input argument but no output argument. the overall program is meant to interpret the function call a an event and use the argument to later the state of the system. It should be very clear to the reader that this is an event.
Flag Arguments
Flag arguments are ugly. Passing a boolean into a function is a truly terrible practice. It immediately complicates the signature of the method, loudly proclaiming that this function does more than one thing.
Dyadic Functions
Dyadic aren’t evil, and you will certainly have to write them. However, you should be aware that they come at a cost and should take advantage of what mechanism may be available to you to convert them into monads.
Triads
Functions that take three arguments are significantly harder to understand than dyads. I suggest you think very carefully before creating a triad.
Argument Objects
When a function seems to need more than two or three arguments, is is likely that some of those arguments ought to be wrapped into class of their own.
Argument Lists
Verbs and keywords
In the case of a monad, the function and argument should form a very nice verb/noun pair.
Using this form we encode the names of the arguments into the function name. This strongly mitigates the problem of having to remember the ordering of the arguments.
7. Have No Side Effects
When there is side effect, your function promises to do one thing, but it also doesn’t other hidden things.
Output Arguments
Arguments are most naturally interpreted as inputs to a function.
Anything that forces you to check the function signature is equivalent to a double-take. It’s a cognitive break and should be avoided.
In general output arguments should be avoided. If you function must change the state of something, have it change the state of its owning object.
8. Command Query Separation
Functions should either do something or answer something, but not both.
The author intended set to be a ver, but in the context of the if statement it feels like an adjective.
The real solution is to separate the commend from the query so that the ambiguity cannot occur.
9. Prefer Exceptions to Returning Error Codes
Returning error codes from command functions is a subtle violation of command query separation. It promotes commands being used as expressions in the predicates of if statements.
On the other hand, if you use exceptions instead of returned error codes, then the error processing code can be separated from the happy path code and can be simplified.
Extract Try/Catch Blocks
Try/Catch blocks are ugly in their own right. They confuse the structure of the code and mix error processing with normal processing. So it is better to extract the bodies of the try and catch blocks out into functions of their own.
Error Handling Is One Thing
Functions should do one thing. Error handling is one thing. Thus, a function that handles error should do nothing else. This implies that if the keyword try exist in a function, it should be the very first word in the function and that there should be nothing after the catch/finally blocks.
The Error.java Dependency Magnet
Returning error codes usually implies that there is some class or enum in which all the error codes are defined.
classes like this are a dependency magnet; name other classes must import and use them. thus, when the Error enum changes, all those other classes need to be recompiled and redeployed. This puts a negative pressure on the Error class.
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.
10. Don’t Repeat Yourself
The duplication is a problem because it bloats the code and will require four-fold modification should the algorithm ever have to change. It is also a four-fold opportunity for an error of omission.
11. Structured Programming
Structured Programming: every function, and every block within a function, should have one entry and one exit.
If you keep your functions small, then the occasional multiple return, break, or continue statement does no harm and can sometimes even be more expressive.
12. How Do You Write Functions Like This?
Writing software is like any other kind of writing. The first draft might be clumsy and disorganized, so you wordsmith it and restructure it and refine it until it reads the way you want it to read.
13. Conclusion
Functions are the verbs of that language, and classes are the nouns.
The real goal is to tell the story of the system, and that the functions you write need to fit cleanly together into a clear and precise language to help you with that telling.