I usually blog about game design related issues, because that’s where my heart is. I like to program but only because it allows me to be creative. But this week, I’ve gotten my hackles up and I wanted to share my frustration with the readers of this blog.
The problem
In a nutshell, my problem is “side effects”. To be more specific, side effects of the implementation of code.
I’ll give you an example I’ve been facing to illustrate the point. We’re using an old code base (name redacted to protect the creators) as part of our client prototyping for our latest project. I’m trying to get some animations to play. So I look at the code and within literally hundreds of classes is the “player” class that I need to use. Well, actually, its an “AIPlayer”, which is derived from player, which in turn is derived from:
ShapeBase->GameBase->NetObject->SceneObject->SimObject->ConsoleObject
As you can probably tell, this code base is quite old school in its architecture.
Anyway, back to the problem. I’m trying to get animations to play on a character when they receive an update in state from the server (its an online game). So I find the function “setAnimationThread” which seems to do what I want. Only, it doesn’t do what I want. On an API level it *is* the correct method to call. But in terms of functionality, it has issues. As I debug, it finally dawns on me what is happening. The side effect of parts of the “Player” functionality, is that the player class controls animation state based on movement. But it does it an opaque manner within an “updateMove” method. Effectively the side effect of using the “player” class, is that actually playing animations is fraught with problems.
Let me give you another example, which of these methods do you think is responsible for selecting which animation is currently playing?
updateAnimationTree();
updateMove();
updateTransitions();
animateSubtrees();
The problem is that when you look at an interface, a method or a class, you should have a reasonable chance of knowing what it is actually going to do. In effect, what is happening when you write a piece of code, is that you are forming a contract with the user of that code that you will fulfill a specific purpose. From the example, you really can’t isolate where specific animation clips should be selected. So would it be possible to have a clearer interface?
So how do things go wrong and what can we do to fix the problems?
The main reason I see for side effects to happen in code, is where you have legacy code that has been worked on over time, with other purposes in mind. This is a reasonably common case when you have say a “core tech” team in a larger company, but can happen pretty much anywhere. Most developers do not have the luxury of starting from scratch when they start a project. But the side effects of using a legacy code base, is that often the functionality was not as planned as it should have been and ultimately you often have the case where special edge cases are dealt with in order to ship a specific title. Unpicking the side effects of those last minute bug-fix special cases can be a nightmare.
Another reason for side effects, is that sometimes you simply don’t know what the API requirements for an implementation will be until you’ve tried implementing it. I often have the situation where I will prototype a system up to learn what kind of interface I ultimately will need and just accept that refactoring is a part of improving the product. But sometimes the product ships with the prototype code.
The major reason for side effects though, is when you are trying to create an all encompassing class that deals with all sorts of functionality in a monolithic manner. A sure sign of this kind of thing, is when you cannot actually simply instantiate a class without having it require a lot of setup from other classes.
Now let’s be clear here. I’m not saying that you can always avoid side effects. Or that all interfaces should be instantiable without requiring other classes to setup. But in the most common case, you should aim for simpler class structures and favour composition over inheritance. The reason I say this, is because the main aim here is “separation of concerns”. You are trying to make sure that whatever class you write, only deals with one specific issue. So for instance, the class I should be looking at for animation, should not require any form of movement code, or any form of network code etc. Sure, you can provide it with interfaces to communicate with those systems, but functionality wise, an animation class should concern itself with animation. I should know that if ever I want to debug animation happening in the engine, that is the place to look.
So how do you fix these kind of issues?
The first approach is to design interfaces that express this notion of separation of concerns. All that means, is that when you write code, you write it to fulfill a specific purpose and ensure that you protect the interface from being co-opted to perform other functionality. So for example, you would write accessor methods that return classes that are contained within an interface to perform a specific task. So instead of having a number of animation methods on an interface, you have it contain an animation interface and provide a method to get that interface for external use. Personally I’ve taken to using a component based architecture to more effectively state this notion of composition rather than inheritance and to try and enforce the narrowing of focus, which is definitely more natural in a component architecture.
The second approach, is to use test driven development to help enforce the design. If you consider each class as a mock object, you should think about what you would expect that class to do as a mock. The reason I suggest this, is that if you can design code to work with mocks and still have it make logical sense, then chances are that the real interface has a narrowly focused purpose and is more likely to be separable in a clean way. Separable code tends to be better at not having unexpected side effects.
The main thing here is to understand that side effects tend to come about because code that “says” it will serve one purpose, is actually used to perform functions for an unrelated purpose. It seems reasonable to assume that comes about because either the purpose is unclear, or because the implementation details that are required to clearly serve that purpose are unclear. One way or another you need to address those issues.
This is an ongoing struggle for me personally, I tend to try and work on games that conceptually have quite a lot of unknowns for me, so I often tend to create interfaces that I later understand to be incorrect. Using a component based architecture for the last few years has taught me that components are a great way of narrowing concerns, but that comes at the cost of constantly looking up or cacheing interfaces.
What methods do you guys use to ensure you don’t have side effects in your own code?
I’d really love to hear what everyone else does to try and protect themselves from code with side effects. I’m sure there are plenty of ways I’m not aware of.