Simple Made Easy: State vs. Values
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 blog post dives deep into one of the topics on that slide: State, Objects vs. Values.
Early in my career, I often heard more senior engineers say “State is Evil.” Rails was the way I was learning software, which encourages a certain lazy evaluation when it comes to important concepts. It was easy to build things quickly, but developing a deeper understanding of software concepts and paradigms took time.
This blog post dives into the phrase “State is Evil” and what it means for the software we write.
What is State?
It took me much longer than I’d like to admit to realize exactly what state in a program was.
Simply, state is something that sticks around. It’s a variable that’s assigned and modified. It’s data that’s sent to and stored in the database. State is what our programs leave behind, either temporarily or more permanently.
The simplest form of state is a variable:
x = 2
If we’re using Rails, how we manipulate state might look a little different:
company = Company.find(params[:id])
company.credit_in_cents += 100
company.save!
Let’s dissect that example a little further:
company = Company.find(params[:id])
# We're setting some new state on our company object, this
# has not been saved to the database. We might distinguish this
# as "local state".
company.credit_in_cents += 100
# We are asking to save the state we've accumulated so far
# in to the database. Underneath the hood, ActiveRecord knows
# that we have modified the `credit_in_cents` attribute
# so it will turn our previous state change into a SQL statement
# and execution.
#
# The SQL query will look like:
#
# UPDATE "companies"
# SET "credit_in_cents" = $1
# WHERE "companies"."id" = $2
# [["credit_in_cents", 300], ["id", 123]
#
company.save!
There are a few types of state. There is “local state” which usually means any state accumulated within the context of a method. There is “global state” which refers to variables that any part of the program can access. This will be something like the window
object in JavaScript or a variable preceeded by a $
in Ruby, e.g. $redis
. Finally there is state in the database, which allows different processes and different machines to share the same information.
Interestingly, it is possible to have methods and classes that appear stateless and pure from the outside but use state internally. This is one of the things that makes the Pure Function as an Object pattern possible.
Why Do Engineers Say State is Evil?
According to Rich Hickey, state is dangerous because it complects or complicates your program. Complex applications are difficult to maintain and difficult to safely change. They are a Ball of Mud.
Retaining state is a side effect. If your method affects change outside of its local scope, you no longer have a pure function. Pure functions are generally easier to understand, test, and change. They provide an important ingredient for great software.
State complicates code because it makes it less inspectable and often order-dependent. State usually means you are mutating or destroying data, which can make debugging more difficult. In our above example, we don’t know if company.credit_in_cents += 100
will set the value for the first time or overwrite an existing value.
In the case of Rails, you’ll often see some backpedaling in the code if you need to detect changes here through built-in Rails helpers like the *_changed?
helpers. In our example, we could call company.credit_in_cents_changed?
to see if the value has changed before saving it to the database.
These stateful approaches lead us to a back-and-forth of re-asking questions that we already know the answers to. We have to ask the company
object about something we did to it, where we have the power to know without talking to the company at all.
State can also make programs harder to understand. Situations where an ActiveRecord attributes are used to accumulate a value complect Rails applications. Let’s create a stateful example of computing how much to pay someone. The code might look like this:
paystub = Paystub.find(123)
paystub.net_pay = earnings
# ... 100 lines later ...
paystub.net_pay += bonus
# ... another 200 lines later ...
paystub.net_pay += reimbursements
# ... 50 more lines just for good measure ...
paystub.save!
By the time we get around to saving the Paystub
instance, we aren’t quite sure how we’ve computed net_pay
. Furthermore, whenever accessing net_pay
within this method or class, we need to know at what stage of net_pay
computation we’re at. Have we added the bonus
yet to the net_pay
? How about the reimbursements
?
In this case, state makes it easy to hide concepts that should have explicit names. Each step of net_pay
should actually have an explicit name, something like earnings_and_bonus
.
So How Do Values Fix This?
Values are preferable to state because they allow for easier debugging in a program. Taking a values-based approach will make it easier to write pure functions and embrace the Functional Core, Imperative Shell paradigm.
Most of the time using them will have no discernible performance impact on your application.
Let’s see how values change the original code example we’ve written:
company = Company.find(params[:id])
changeset = { credit_in_cents: company.credit_in_cents + 100 }
company.update_attributes!(changeset)
This looks like some funky Rails code, and it’s unlikely you will find yourself writing it exactly this way. Let’s annotate this to better explain what we’re doing.
# Pull the company out of the database like we did previously
company = Company.find(params[:id])
# Here we’re creating a value object to represent the
# changes we're going to make to the company. We’re
# using a Hash since that's going to make our call to
# `#update_attributes` easier, but you could just as
# easily use a different data structure
changeset = { credit_in_cents: company.credit_in_cents + 100 }
# We call `Company#update_attributes!` which will
# update the record in the database. The SQL statement
# will look the same as above:
#
# UPDATE "companies"
# SET "credit_in_cents" = $1
# WHERE "companies"."id" = $2
# [["credit_in_cents", 300], ["id", 123]
company.update_attributes!(changeset)
Notice in this example that it’s very easy to tell how we are going to change company
. We don’t have to reinspect company
or start asking *_changed?
questions. It’s very clear what type of change we will inflict on our company
.
As the method that contains this code grows, you can imagine changeset
also accumulating a series of different keys and values. At any point in time, we’ll be able to ask the question “What changes are we going to try to save to the database for company
?” It becomes much easier to see what our program is doing.
It also becomes much easier to refactor and split up this method should it grow too long. We may introduce a method that specializes in computing this credit adjustment:
# This method returns a new Hash so we are extra sure no one is
# retaining a reference to that object and mutating it.
def add_credit(changeset, company)
changeset.merge({
credit_in_cents: company.credit_in_cents + 100
})
end
By using values and avoiding modifying state on objects, our programs become much more maintainable.
Let’s also see how this drastically simplifies our net_pay
example:
paystub = Paystub.find(123)
net_pay = earnings + bonus + reimbursements
paystub.update_attributes({ net_pay: net_pay })
It’s very clear how we’re computing net_pay
and what the components of that calculation look like.
This values-based or stateless approach is what drives projects like ImmutableJS and ROM.rb.
So Should I Ever Use State?
At some point, you need to persist state to do something interesting. If you aren’t saving emails to your database, people can’t sign up for your app. You’ll want to minimize the places where you perform stateful operations. As a general rule: Most business logic should be stateless.
There are also occasions where embracing a more stateful approach can make your program more performant by not having to reallocate objects. Most web applications are not CPU/memory bound. There are likely other bottlenecks in your app that would need tuning before making the stateless-to-stateful switch for performance reasons.
Switching to a stateless way of writing code can also take some time getting used to. I do not recommend introducing this to your code base without discussing it with your team. Feel free to point them to this blog post if you’re having trouble making the case.
Generally, try to write stateless programs until you find that you need otherwise.
Conclusion
We want to look to make as much of our application stateless to keep our app simple. Simple applications are easier to maintain, change, and test. Simple applications grow with teams. Having an application with lots of local, global, and database state can make development slow to a halt.
In these examples we used simple Ruby Hash objects. As your program grows, you will likely want to introduce immutable primitives using something like the hamster gem. You will also want to avoid primitive obsession1 and introduce your own named domain objects where appropriate.
The universe has a sense of humor, though. It’s never quite possible to write a program that is stateless all the way to machine code. Eventually, we need to be allocating memory or shuffling values into registers on the CPU before calling ADD
or VFNMADDSD
(they have a lot of instructions these days).
If you haven’t yet, check out the talk that inspired this blog post: Simple Made Easy.
Happy Programming!
Special thanks to Justin Duke for providing feedback on early drafts of this post.
-
Primitive obsession is a code smell where we use undecorated values instead of richer value objects. This usually refers to passing around strings that have meaning or hashes with lots of important data. A cure for a primitive obsession in Ruby is to use a named Plain Old Ruby Object (PORO). It’s better to say
person = Person.new(name: 'Kelly')
thanperson = { name: 'Kelly' }
. By using a named domain object we imbue it with meaning and intent. ↩