Seventh in a series of posts on Software Design for Unity. Read the introduction here.
I’ve mentioned statics in the first post in this series, on Singletons, but there’s a bigger discussion to be had about this concept, and – more generally – the concept of “state”.
Static Fields
Static fields exists outside any particular instance of the object that declares them, and are thus shared between all instances of said object.
For example, consider this class.
public class ThingManager { public int things; }
This is a normal class, with a normal int field in it. Every time you instantiate ThingManager – for example ThingManager tm = new ThingManager() – you create an entirely new instance of ThingManager. This has its own block of memory, and within that block is its things field.
Therefore, if you have two instances of ThingManager – tm1 and tm2 – if you change tm1.things it won’t magically change tm2.things.
Now let’s make things static.
public class ThingManager { public static int things; }
Now, you can’t access tm1.things. It no longer belongs to the instance, but the whole class – you access it via ThingsManager.things. The int is now shared between all instances of ThingManager, rather than each having a separate things in their memory blocks.
Other Statics
Properties and methods can also be static. The Unity API uses these heavily; for example Physics.Raycast() is a static method, and Time.deltaTime is a static property. Finally, classes can be static – but this just means they can’t be instantiated and must only contain static members.
A Detour into State
A familiar refrain in this series is the idea that hard bugs[1]i.e. hard to spot, hard to diagnose when spotted, often hard to fix when diagnosed generally have something to do with state. “State” refers to data that’s stored, rather than immediately forgotten – for instance, a field compared to a local variable. “Stateful” roughly refers to behaviour which depends upon state. In other words, if the state is changed, the behaviour changes too.
For example, the following class has state, a stateful method, and a stateless method.
public class Switch { // State private bool _active; // Stateful public void Toggle(){ _active = !_active; } // Stateless public void Activate(bool activate){ _active = activate; } }
The field _active is state – it doesn’t get forgotten. Toggle() is a stateful method – what it does depends on the value of _active at the time it’s called. Activate() is a stateless method – regardless of what the value of _active is, it always does the same thing.
All things being equal, the stateless method is less likely to cause bugs. This is because you’re being explicit about behaviour. You know that if you call Activate(false), _active will be set to false. To know what Toggle() will do, you have to work out what the value of _active will be at that time. And programming being the complex beast it is, you’ll often get that wrong.
Reasoning about stateful behaviour, then, requires you to have exact knowledge of all relevant state. Get any one wrong, and your logic will arrive at the wrong conclusion. So it’s easy to cause bugs.
Reasoning about stateless code only requires understanding the one bit of code you’re reading, not the context around it
Then, it can be tricky to know exactly which bit of state you got wrong. In a complex chain of events, it can take a lot of time to step through the whole thing (mentally or with a debugger) to work out which step made reality diverge from expectation. Often cause and effect are separated by multiple steps – one bit of code can incorrectly set a value, and only later is that bad value actually used. So it’s also hard to find and fix these bugs.
Back to Statics
So, hopefully you can see that statics – since they deal with global state, the worst kind – can easily go wrong. With that in mind, I want to explore various ways of working with statics to try and mitigate the danger.
Here’s what we had above.
public class ThingManager { public static int things; }
This is the most naive, and the most bug-prone, way to use statics. Anything can access this from any place, and that’s dangerous. It’s also inflexible. To address that second point, we can amend it slightly:
public class ThingManager { public static ThingManager instance; public int things; }
Now every instance of ThingManager has its own things counter, allowing a bit more flexibility internally. Then ThingManager is free to choose which instance becomes the singleton, and the rest of the code accesses that one instance via ThingManager.instance. But it’s still accessible from anywhere.
public class ThingManager { public static ThingManager instance; public int things {get; private set;} }
This is getting better. Now ThingManager controls the behaviour of things, only letting the rest of the code get it, and not set it. However, we still have an issue – the rest of the codebase also has access to instance, and can both get and set it. There’s no good reason for this, so we can abstract further:
public class ThingManager { private static ThingManager _instance; private int _things; public static int things { get { return _instance._things; } } }
Now the codebase only has access to the one thing it needs – getting the things counter. It doesn’t know, or need to know, where that int is stored or how it’s dealt with. Likewise, methods can be made static rather than requiring the codebase to access the instance and call the method from that.
This pattern is also easier to refactor in future. Imagine changing the internals of how a static function or property works. Let’s say that initially it belongs to an instance of a class. You can set it up for access via ClassName.instance.InstanceMethod(). But then what if you decide to make it entirely static? You can either a) change all references in your code from ClassName.instance.InstanceMethod() to ClassName.StaticMethod(), or b) rewrite ClassName.instance.InstanceMethod() to internally call ClassName.StaticMethod() – which is easier but ugly.
The point being, if you started off with ClassName.StaticMethod() calling ClassName.instance.InstanceMethod(), refactoring is really simple. So it’s a good way to set things up in the first place, as it allows more flexibility later, and makes it easy to encapsulate things.
Build a toolbox of simple stateless techniques to avoid state when it’s not needed
Conclusions
I’ve found that the longer I’ve been programming, the more and more I avoid statefulness and statics. But you can also go too far[2]and sometimes, I avoid it almost religiously without much of a good reason.. State and statics are used because they’re useful! There’s an attraction to functional languages – such as Haskell, F# and so on[3]no, I’m not going into functional languages here! – but in reality, having to do entirely without the concept of state makes many, many things incredibly difficult. Sure, your code might have a lot less bugs, but it’s going to take three times as long to write.
So I’m not so opposed to state that I advocate complete removal of the concept. But you learn everyday ways to make things stateless when there’s no underlying reason for statefulness. Often stateful solutions are the most intuitive, quickest and easiest. But if you build up a toolbox of simple stateless techniques, you can get into the habit of avoiding state when it’s not needed.
Footnotes
1. | ↑ | i.e. hard to spot, hard to diagnose when spotted, often hard to fix when diagnosed |
2. | ↑ | and sometimes, I avoid it almost religiously without much of a good reason. |
3. | ↑ | no, I’m not going into functional languages here! |