Method Structure
Table of Contents
Intro
Before we talked about how smaller methods lead to cleaner code. In brief, we should abide by the one-thing principle, where methods handle one level of abstraction rather than a single operation. Hence, they only have one reason to change.
Here we elaborate on how a method should be structured in order to handle one level of abstraction.
Arguments
Arguments can seem like a convenient way of passing objects around. Nonetheless, if we don’t pay close attention they easily become hard to read an understand. At that point they become more a liability.
For instance, when a method takes 3 or more arguments we need to remember their exact order. Passing an options hash doesn’t help since we are still coupling it to external objects. In other words, it depend on too many things to be able to do its work.
Hence we consider a method better structured if it takes 2 or less arguments. This only means that you need to create smaller methods that specialize in a task. If we notice that two or more of those tasks depend on each other, perhaps they can be extracted into their own method, class or whatever works better.
Booleans
When we pass a boolean as an argument we are actually writing a method that does two things. It does one thing for the true
case, and different one for the false
case. Instead, we should write two methods, one for each case.
Output arguments
If a method returns more than one value at a time your method doesn’t have a single responsibility. So, instead of passing an argument in order to catch the result of some process within the method simply extract that process into its own method. There’s no need for them in Ruby.
Nil
A nil
parameter, just like booleans, shows that our method is in charge of at least two things. We can either extract one responsibility, go for the NullObject pattern, or whatever seems more appropriate. Either way, if we are constantly checking for nil
s it means our unit tests aren’t preventing them from happening. We should avoid writing defensive code unless we are writing a public API that could possibly pass invalid data.
Side effects
We say that a method has side effects when it changes a variable that out lives the method call. For example, when a method changes the value of an instance variable. Side effects are important because they change the behavior of a method or some other method the next time its called, which is a persistent source of errors.
Temporal Coupling
Methods with side effects usually come in pairs: set, get, open, close, new, delete, and so on. This kind of methods have to be called in order (open before close), which makes them temporarily coupled.
Whenever we have a temporal coupling we can resolve it by passing a block. That guarantees that everything stays consistent, that methods are always executed in the right order, and that there are limited side effects.
Commands and Queries Separation
One way to manage side effects is to create a strong separation between commands and queries. Methods that return values should not change states; they are queries. Methods that change state can throw exceptions but they should not return values.
Tell don’t ask
With tell don’t ask you aim to reduce the number of queries you do to an object. An object should deal with its own problems, after all, is the object who knows it’s own state. We don’t ever want to ask an object for it’s state and then make decisions on its behalf. What that means is that public methods should be commands, as oppose to queries.
Chained method calls such as,
object.get_x.get_y.get_z.do_something
are in clear violation of tell-don’t-ask because we ask many things before we even get to do something. Instead, aim for code like: object.do_this
where object
has to figure out how to do_this
. The call to do_this
will propagate outwards until it gets to get_z
.
Law of Demeter
The Law of Demeter tell us that is a bad idea for single methods to know the entire navigation structure of a system. The main disadvantage being that it couples the method to it. Instead, we should strive for methods with very limited amount of knowledge.
The law of Demeter formalizes “tell don’t ask” with the following set of rules:
You may call methods of objects that are:
- Passed as arguments.
- Created locally.
- Instance variables.
- Globals.
You may not call methods on an object if that object was returned from a previous method call.
Internal Organization
In order to show a method’s intent we should place all public methods at the top.
On the other hand, private methods should be at the bottom since they hold implementation details.
We should strive to list all our methods in such a way that no method down below calls a method that is above. That way, it gets a bit easier to track down a method’s implementation details.
In Ruby, our methods would look like this:
class Foo
# Constants, setters and getters
# Public methods -> + Abstract (short names)
private
# Private methods -> + Detailed (long names)
end