Today brings the finale to this epic series of two posts on the many connections between Italian eating habits and creating games. Whereas last week I discussed how lasagne influences game design in a good way, today I will talk about all kinds of Italian food. I will explain why programmers should not only fear the dreaded spaghetti, but also the less-known evil of lasagne. And how about pizza and ravioli? What can they learn us about the art of code?
Note that I realise that the food metaphors might be slightly lame, but the point of this post is that I think it is important to think about the implications of various structures and methods of programming, and this blogpost is basically just an analysis of some pitfalls and approaches.
Most programmers know the term spaghetti code. It refers to a larger code base that is so intertwined, that is it impossible to know how the rest of the code reacts when you change something somewhere. Every line of code might be referencing almost any variable anywhere and maintaining the code becomes all but impossible, because the risk of introducing bugs elsewhere is too great.
The common solution to spaghetti code is also well known: object oriented programming. By using classes and making them as self-contained as possible, you can make sure that as long as the class behaves the same to the outside world, it doesn't matter if you change how it works internally. Each class is a world of its own. Of course, in practice this is more an ideal than reality, because during development classes often also need to change how they behave towards the outside world. Still, object oriented programming can help a lot to separate large code bases into smaller, more maintainable chunks.
However, there is also the other end of the spectrum. Object oriented programming can be taken too far. Let's have a look at an example from a real part of Awesomenauts. In Awesomenauts, when the player dies, the music fades out and decreases in pitch. The real situation is a bit more complex, but to keep this post readable, I'm simplifying it a bit. An intern was to implement the logic for the music. So he came up with something like this set of classes:
However, due to his internship ending, he didn't finish this set of code and I had to complete it. Problem is, that these are so many classes for such a simple task, that I almost got lost in all all the class management code and could hardly make any sense of what was working and what was not. Because the problem was so simple, I decided to just throw away his code altogether and write one simple class instead. It looks like this (again, simplified for clarity):
As you can see, this is actually really simple and understandable. No need for all these classes here. It became a little bit more complicated when shuffle and killing spree music were added, but still, the MusicManager class is short and understandable.
So what the intern did, is an example of lasagne coding: too many layers. Especially programmers who are relatively new to object oriented programming often overreact to its benefits by creating way too many classes. I belive there is something like an ideal class size. This varies per class, but I think in general classes above 500 lines are almost always too long, causing spaghetti within the class, while classes beneath 50 lines are quite often too small and thus part of lasagne. Not always of course: some classes are perfectly fine for doing one clear thing in a small number of lines.
Lasagne coding is of course a common term, but I recently discovered that few programmers are aware of this name. Most coders I meet do know that having too many classes is a bad thing, of course, but I think it helps communication and thinking to also have terms for good and bad practices.
To finish this post, I'd like to mention two more food metaphors. Pizza code and ravioli code. Unlike spaghetti code and lasagne code, these are terms I just made up on the spot because I like to talk about food. The concepts are real, though.
When programming large projects, interrelations between classes often become more and more complex. Awesomenauts has around 1,200 classes, which to me is quite a lot. Even if all of those classes are proper self-contained units, adding or changing things can still become very complex, as any number of classes might be using any number of other classes. If care is not taken, the class diagram starts to look like a pizza: a big plate of dough with all kinds of stuff randomly thrown on it and no idea what goes where or why. All the classes just happen to be there.
So to make the class diagram more manageable, I think it is better to make it resemble ravioli. Ravioli are little dough wraps with good stuff in them. They are closed off and you put the entire thing in your mouth at once and it tastes good. I'd like my code to be like that. Groups of classes work together, but can be represented to the rest of the code base as a whole. Not all of them need to even be usable outside the group at all.
The nicest example of this in Awesomenauts is the input system. This consists of some two dozen classes that together handle input from devices like WiiMotes, keyboards, mice and Xbox 360 controllers. It also handles this for five different platforms (Playstation 3, Xbox 360, Wii, PC, Mac). To the outside, however, only one of the twenty classes is accessible: the InputManager class. All other code that wants to know whether a button is pressed on some device just asks this to simple functions like InputManager::getIsMouseButtonDown(). InputManager is the dough and the two dozen classes that actually handle the hardware on all the various platforms are hidden inside the ravioli ball. If all those 1,200 classes in Awesomenauts were this clearly grouped, you would get a much more manageable 50 groups to understand the relations between, instead of 1,200 separate classes.
This concludes my food series for now. Of course, none of the concepts above are revolutionary, but I think it is really important for any programmer to think about what he thinks is good programming and what is not. In my opinion programmers who never stop coding to take some time to think about whether what they used was a good or a bad method, will never become really good. This post just outlines some of my own thoughts on the matter, and I hope they help you define Good Code (tm) for yourself.