State pattern, tech debt fertilizer

A short post about unreasonable usage of State pattern - something I keep seeing every so often in code (regardless of language / platform).

What's a State pattern?

State pattern is a way to encapsulate state (& behaviour that depends upon it) within an object the state belongs to. It's a natural way to express physical reality (and its variability over time) in a conceptual Object-Oriented way. If you follow state pattern:

  • 1 physical world object is usually represented by 1 entity (DDD-wise) (+ tree of dependencies, etc.) with a life-cycle that corresponds to the life-cycle of physical object; for instance: Car (particular car)
  • state is the inner property of this entity; for instance: Broken, Out-of-Gas, Refueling, Riding, etc.
  • list of entity's implemented behaviours covers its capabilities for all the states; for instance: Fix for Broken, Refuel for Out-of-Gas, Stop for Riding, etc., but ...
  • ... some of the behaviours are prohibited in a given state (it's not possible to Refuel the Car that is already Riding)

Implementation-wise this can be done in several ways (pretty much depends on the language), for instance:

  1. one huge interface with all the behaviours
  2. collection of interfaces corresponding to behaviours (interface per state)
  3. generic command handler
  4. function overloading / arguments pattern matching

Why State pattern is bad for you?

Bag of Wonders

We've all seen this so many times: key entities' composition & logic, once clear & tidy, gets more & more messy over time, because it covers all views & perspectives on those entities. New stuff gets done & mingles deeply with already existing logic, creating ultra-complex, non-maintainable quasi-state-machines. That's how big ball of mud is born.

Sometimes it's not that obvious in the beginning - devs think they do good, because they have a clear, distinguished shared kernel, but this shared kernel becomes logical sum of all entities' characteristics (with time/life-cycle stage as an additional dimension!), instead of logical intersection (kind of simplification, yes, I realize). Once tangled, it very quickly becomes legacy no-one ever wants to touch anymore.

Model VS Reality

Design & model isn't about depicting reality 1:1. This would be rather a domain for chroniclers ;P Ubiquitous language & using the same terms business users use is crucial, but it doesn't mean we should do it blindly (vide: context mapping in DDD).

If entity in different states has different properties, is a subject of different actions, is accessed by different actors in different steps of different processes -> there's really no point in insisting on having the common denominator (even if it's still the very same physical item).

Run-time error VS compile-time contract

There's a very clear consequence of implementing the state pattern: for each behaviour there has to be a check whether it's allowed (/proper/suitable) in current state. I'd rather have it in compile-time (contract-restricted) than in run-time ("if"-ology, "case"-ology) as it's much harder to omit a condition unintentionally that way.

If behaviour is not dependent on the internal property (entity and particular state are bound together), there's no need for the check at all. And you won't experience things like:

throw new InvalidOperationException();  
def Foo(bar: String): String = ??? // Scala's most concise way to throw NotImplemented  
throw(:not_implemented)  
throw new IllegalStateException("OMG, can't refuel a running car!");  
if (new state) then { twist the bowels }

Run-time error handling is painful, but still feasible. However, it requires a lot of additional code to maintain. Not only tons of conditions, but also a lot of additional negative, automated tests. To be frank: very error-prone automated tests. Even such a simple operation like adding an additional state will require you to both review all the behaviours & adjust tests for them as well.

What to do instead?

Create separate entities for different states, for instance:

  • CarBroken
  • CarOutoOfGas
  • CarRefueling
  • CarRiding

This way each entity will implement only behaviours that are proper for its particular state, code will be much more concise & readable and due to reduced dependencies, it will be much easier to follow single responsibility principle.

What about switching between states?

Doesn't look like a big issue for me. Here are few samples, one in Scala:

class CarOutOfGas {  
  override def StartRefueling(): CarRefueling = {
    ... // return new object of CarRefueling class
  }
}

ore even a bit more functional (in Elixir):

defmodule CarOutOfGas do  
  defstruct ...
end

defmodule CarRefueling do  
  defstruct ...
end

defprotocol Refuelable do  
  def start_refueling(car)
end

defmodule CarOps do  
  defimpl Refuelable, for: User do
    def start_refueling(car), do: %CarRefueling{...}
  end
end  
Doesn't it break our model ...

... for instance: by making it more bloated / harder to understand?

I believe that what really makes code less readable is interweaving meaningful, business code that actually does something with heavily obfuscating, useless streaks of conditions that actually do nothing but raise run-time errors.

What about persistence?

All the entities that represent an object in different states can have a shared, persistent representation (if needed), there's no big deal about that. There are no additional, persistence-related classes of problems that could arise just because you've dropped following the state pattern, unless you decide to persist entities (different for different states) separately - in this case, consistency may require some expensive synchronization (locking).