Simple Made Easy: Methods vs. Functions
This blog post is a reflection on the slide titled “What’s in Your Toolkit?” from Rich Hickey’s excellent talk, Simple Made Easy. The single slide contained some of the most programming wisdom I’ve seen in my career. This is the second blog post in this series, and dives into Methods vs. Functions.
Let he or she who has not confused methods and functions cast the first stone.
I know I’ve done it. I still do it sometimes.
As programmers, two similar tools in our abstraction toolkits are methods and functions. These provide the simplest way of code reuse. Bundle up a few of these lines of code, and go to town! Forget writing a + b
, add(a, b)
is what we’re doing these days.
In Rich Hickey’s talk, Simple Made Easy, he covers why methods complect (complicate) the structure of an application, where functions allow your applications to stay simple.
This blog post will do a deep dive into the difference between methods and functions, and then explore why functions lend to simpler programs.
What is a Function? What is a Method?
In short, a method is attached to an object, whereas a function is not. StackOverflow has a great answer the topic, so we won’t belabor the point too much here.
Let’s take a look at some code samples. We’ll start off with a function:
def calculate_netpay(gross_pay, tax_rate, donation_amount)
pay_less_donations = gross_pay - donation_amount
pay_less_donations - (pay_less_donations * tax_rate)
end
Here we have a function with 3 parameters that calculates the net pay for an employee in the United States under the simplest of scenarios. It’s moderately more interesting than a simple def add(a, b); a + b; end
example.
There are a few properties about this function worth noting:
- It’s written in Ruby, so it’s floating in the global namespace. Anyone can call
calculate_netpay
from wherever they are. - It’s a pure function. It has no side effects and its return value is solely the result of its input parameters.
- It has 3 parameters, which is right on the edge of having too many.
Next up, let’s take a look at the same functionality but implemented as a method:
class Paystub
def calculate_netpay(tax_rate, donation_amount)
pay_less_donations = gross_pay - donation_amount
pay_less_donations - (pay_less_donations * tax_rate)
end
end
Here the method presumably does the same thing as the function before, but this time it’s done via a Paystub
instance.
Let’s enumerate some of the interesting properties here:
- The behavior is presumable the same as before
- We have one less parameter. It looks like we’ve gotten rid of the
gross_pay
parameter, and we are instead using a definition ofgross_pay
defined on thePaystub
instance. - Ruby is a nice language, so it means we don’t have to type as much. But these ergonomics come at a loss of specificity. We have an implied
self
and a potential method call in here. If we were to write some Crappy Ruby™, we could rewrite it as follows.
class Paystub
def calculate_netpay(tax_rate, donation_amount)
pay_less_donations = self.gross_pay() - donation_amount
pay_less_donations - (pay_less_donations * tax_rate)
end
end
Here it’s clearer where we are pulling that gross_pay
from (the instance itself). It’s also clearer that gross_pay
isn’t an attribute on Paystub
, but a method call. Goofy. (Please don’t write Ruby like this.)
And here is where methods start to get complicated. gross_pay
is now reaching somewhere on Paystub
to do it’s job. The process of calculating the net pay is no longer a pure function.
All bets are off and the doors have been thrown wide open. Is calling #gross_pay
really expensive? Does it need to be set before we call Paystub#calculate_netpay
? What happens if it’s unset?
You’ll notice that our old friend STATE has crept in.
How Do Methods Complicate?
Methods complicate programs because they allow a coupling with state.
In a previous blog post, we explored why state makes programs complex, and values make programs simpler. Based on their signatures, it’s ambiguous whether methods interact with state on an object. Methods can be pure, but there is no guarantee.
Functions on the other hand are free-wheelers. They can affect state, but likely only global state. They could also inflict side effects, but so can methods. Functions don’t have a self
or a this
.1
The complications around methods come in as soon as the language’s self
or this
starts to show up. When not using that to call a method (e.g. self.call_my_method()
) and using it to interact with an attribute, we are bringing state into the execution of our method. This permeation of state will slowly poison our program with null checks and ambiguity.
Let’s assume that the gross_pay
from the above example was an attribute on our class instance. Watch how it starts to complicate the program.
class Paystub
# (Not using attr_writer here to be explicit for the
# non-Rubyists reading.)
def gross_pay=(val)
self.gross_pay = val
end
def calculate_netpay(tax_rate, donation_amount)
if self.gross_pay
pay_less_donations = self.gross_pay - donation_amount
else
raise 'An error?'
end
pay_less_donations - (pay_less_donations * tax_rate)
end
end
Because we have this stateful gross_pay
attribute, we introduce a dangerous property into our Paystub
class: It can be half-baked. From here, one of two things will happen:
- The conditional checks for
gross_pay
will spread to callers, forcing each caller to check to make surePaystub#gross_pay
is set before callingPaystub#calculate_netpay
. All callers will have to make this check or roll the dice. - The conditional check will be pushed into the
Paystub#calculate_netpay
, causing its execution to be less simple or non-deterministic. Callers now have to worry about the possibility of an exception or some nonsense return value.
Either way, callers are burdened with the knowlege that Paystub#calculate_netpay
is not always straightforward. Do this a few hundred more times and your application will be a nice Ball of Mud.
A Way Out
So what can be done? Generally, prefer stateless functions over methods even if the methods are stateless. Methods open up the possibility of state.
Not every language makes writing stateless functions easy. One of my favorite languages, Ruby, does not have a good way of organizing functions. On larger projects, I’ll often employ the funky Pure Function as an Object (PFaaO) pattern. In JavaScript ES2015+, the import
/export
nature makes writing and organizing stateless functions much simpler.
So as our projects grow and we look to keep our applications simple, we now have a new tool in our toolkit to do so: Prefer Functions over Methods.
Happy Programming!
Epilogue
When sourcing this blog post for feedback, Matan Zruya pointed out that having only stateless functions can lead to a different type of problem entirely: Anemic Domain Models.
This is where our values become merely buckets of data with no decoration. In the example above, there may be a case for a Paystub
to be a rich value object where calculating net pay is just a derivation of its own values. This is a great point and some excellent feedback.
So I’m leaning toward presenting an order of operations for preferring functions over methods:
- Move logic from methods into stateless functions. This helps you see.
- Once you have stateless functions, ask the question if you have Anemic Domain Models.
- If so, carefully move logic back into methods to improve your domain models.
Let’s see how that code might look if we take our stateless function approach to the extreme, modeled loosely on the above example:
# Here we assume that the `paystub` parameter is just a value object
# with no methods hanging off of it. It’s just a bucket of data.
def calculate_netpay(paystub)
paystub.gross_pay -
paystub.employee_taxes -
paystub.benefits_deductions -
paystub.donations
end
So we have a different smell, which is that our parameter object should likely encapsulate the concept of net pay. When we see code shaped like this in a stateless function, here is one the times where we want to move back to a method:
class Paystub
def netpay
gross_pay -
employee_taxes -
benefits_deductions -
donations
end
end
The nice part about Ruby is that this code even looks cleaner, and remains simple.
Here we have a good sequencing of cleaning up existing code, and then finding the best home for it. It’s great to be able to see this tradeoff and walk through counterexamples.
Thanks, Matan!
Special thanks to Hector Virgen, Matan Zruya, and Justin Duke for providing feedback on early drafts of this post.
If you enjoyed this post, you may also enjoy another post in this series: State vs. Values.
-
self
orthis
might resolve to something in your language, but it will be nonsense. There’s a big object in the sky in Ruby, soself
inside of a method in Ruby will still do something. ↩