If you're a new developer, one term you'll hear used a lot is state. I remember I struggled with the idea at first, so I thought I'd write a brief introduction to the concept of state, and how it is applied to a few design and testing principles.
What is state?
Let's use me as an example. Here are three facts that describe my current state:
- My name is Richard.
- I'm 23 years old.
- I just ate some of my flatmate's pancakes for lunch (wohoo!).
- I'm in a pretty good mood.
In code, you might represent this as:
These fields comprise my state. Some are unlikely to change (my name, for instance) while others are much more volatile (I will be hungry again in a few hours).
So you can see state changes over time. For example, my state right now is totally different than my state after 40 hours of nonstop travel from London from New Zealand -- I arrived tired, and pretty frazzled. Both these states are different again than when I'm asleep.
State can be recorded, persisted (e.g. saved to a Person table in a RDBMS) and tracked over time. Usually, though, we're only interested in the current state.
So state is just data, right?
Well... yes and no. When persisted, state becomes data, and in an object, state represents the data portion. Remember object-oriented programming combines behaviour (methods) with data (state).
But at a higher level, state represents the overall picture of an object right now, how it will behave, and what range of actions are available. A good example of this is the State Pattern from the GoF book. In their example, a TCP network connection has different states: established, closed, or listening for an incoming connection. These states are each encapsulated into an object, assigned to a field and the TCPConnection delegates behaviour to the current state:
So you can see in examples like this, state is more than just data -- it affects behaviour too.
Unit testing state vs unit testing behaviour
This is something you might hear in the context of TDD and BDD. In short, testing on state means looking at an object's visible state after something happened, and checking that all the values are correct.
In comparison, testing on behaviour means using mocks to verify the expected methods are called.
Generally behaviour-based testing is preferred, because:
- Focusing on behavioural seams (methods on interfaces) results in looser coupling, because the internal implementation of objects should change more often than the interfaces between them.
- The internal state of an object is private, and you shouldn't be looking at it anyway -- the basis of the Tell Don't Ask principle.
An object's state effects its behaviour. For example, when I eat, I feel full. If I eat too much, I feel sick -- I can't continue eating over and over.
Services, on the other hand, should be stateless. Imagine a dice-based random number generator. Every dice roll is a clean start -- it should not retain any memory between rolls. To simplify usage and scale better, all service calls should be like this.
Another simpler example of unwanted state is services with methods like Initialize(), Start(), Connect(), Open() etc that must be called before other methods. Users of this service need to take responsibility for calling it and thus managing the lifecycle of the service -- an unreasonable burden that introduces unnecessary coupling.
Explicit state transitions
One newer technique in domain-driven design is recognizing important state transitions, and making them explicit through the use of events. A simple example of this could be when your bank account balance goes below zero, its state becomes overdrawn:
This is an important state change -- part of the business's ubiquitous language that might have further implications down the line e.g. incurring overdraft fees. Udi Dahan writes a lot on this topic -- it is a big help in decoupling your domain model, and is is the basis for event-sourcing CQRS.
So hopefully that starts to gives you an idea of what state is all about -- more than just data!