Dodging State
By: . Published: . Categories: state.Soroush Khanlou gives some concrete tips for reducing the amount of state in your code. I’m going to rephrase his advice into terms that connect better with other reading I’ve been doing:
- When you recognize cohesive groups of properties, bud off a new object.
- Prefer domain types to primitive types.
- Take advantage of computed properties to express derived state; reintroduce caching only when unavoidable.
Budding Off
Sometimes you notice you’re manipulating the same bits of state together,
say properties dollars
and cents
of a Product
.
Well, viewed another way, maybe you’re actually just inlining the methods
of a latent type; dollars + cents
might be the answer to the total
message
of a not yet existent Price
class.
You don’t have to end up here by an act of deep vision; you can work you way up to it by way of doing some extract method refactorings in extant classes, then noting that you have a coherent group of methods and state, and then just bundling those up and schlepping them off to a new class. Budding complete!
Domain Types
Whatever your code is doing, it’s very likely not natural to talk about at the level of bools and ints. You might instead have something that’s on or off, or red or green; something that’s big, medium, or small, or maybe you have some money rather than just a decimal value.
Using types native to the domain of discourse that you’re modeling and automating raises the level of abstraction of your code. It also raises the profile of these ideas in your language. If you fail to name these types, you risk ending up with bits of functionality relating to them scattered across the code that needs to work with it.
Naming primitives – even if to start with it’s little more than
a wrapper around a single primitive value, like
struct Name { let fullName: String }
–
makes the the meaning of the primitive value explicit
and provides a foothold from which you can drag further functionality
into existence in an appropriate context.
Derived State
You can split program state into two classes:
- Essential state: Information that you have to have.
- Derived state: Information that can be computed from other information you have.
In theory, you should be able to get by holding onto only the essential state and computing the rest as needed via functions/methods/computed properties. In practice, you might need to throw a caching layer (like a stored property) in there more often than you’d like, but let your profiler be your guide.
(What about the stuff in your program that’s not state? For a further teardown, you should check out Mosely & Marks’ “Out of the Tar Pit”, or at least skim Adrian Colyer’s survey of it.)
Conclusion
Soroush draws these conclusions as a result of approaching code from a functional programming standpoint, but that’s an accidental path dependency. Object-oriented design principles, as elaborated for example in Practical Object-Oriented Design in Ruby, provide plenty of motivation to reduce state and better name and organize what’s left of it.
Now, go check out Soroush’s article, particularly the cool bits about state machines – then wonder how you might use discriminated unions (AKA enums) in Swift to tackle the same problem.