Our code is rarely as neat as we’d like it to be. And sometimes we’d like to make it do something, and we’re not sure how. Depending on… …a lot of things, there are two ways around the problem:
- Go with the code. Work out the problem bottom up and wire up everything as best as we can.
- Bend (and break) the code. Work out the problem top down, lay out a neat solution and (re)wire up everything else according to how we think is best.
The main argument for going with the code is that it won’t break anything. Honest, it’s true. I’ve seen it done with extreme care and it works. But… It doesn’t make it easier to make the next change, and the next change, and the next change.
Working top down will break the code, and should be done with care too. Sometimes breaking and repairing code is better than just going along with it. But just because the code hasn’t been written to solve the particular, new problem we’re looking at needn’t mean it’s bad code.
It was an ordinary day. A colleague had been working for a couple of months on a client side bridging component. We wanted the same functionality on the server side and I just thought it would be neat to share the code. So I suggested we do just that and he told me the component couldn’t be extracted from the client. I said it could, and he asked: how?
Separation is rarely down to our software’s effective logic. For practical purposes, anything can be separated from anything else. So I just said:
“Use the Sword of Faith”
A couple of days later, the bridging component was all neatly… modularized. The code was cleaner and more readable (not my opinion – I didn’t even look). Unsurprisingly (and however unfortunate) the guy in charge of our server refused to use this code, and moved on to write their own. Whatever project this related to was postponed, and we moved on…
For all the medieval hype surrounding it, the sword of faith is a well named design tool. Ideally it should be as sharp as a Katana, and operated with surgical precision (that and lightning fast coding skill, right?). Why a sword, then. Well there are other tools you might consider working with when extending and fixing up your code:
- The mace. You can maim and mangle large sections of your code in the name of a good cause, leaving behind you many bugs for others (or more likely, yourself) to fix later.
- Biological weapons. You can add a couple of lines of code here and there to get your new feature to work. This code won’t make sense in context and will likely contribute the overall sense of rot and decay emanating from your codebase.
- The Hydrogen bomb. You can delete whole classes, even packages. At this point extending our software isn’t a problem. The problem is we’ll have to write the code all over again.
In contrast, there are several techniques founding our belief into the sword of faith – namely, that code can be improved locally and that local improvements over time can significantly reduce our overall sense of helplessness looking at (what looks less like) the ambient mess.
- Define a ‘good solution’ for the problem at hand. Inasmuch as possible, the solution should hold good considered in isolation.
- Retrofit the ideal solution onto existing code, pass (existing code fits the solution) or break (existing code must be modified)
- Rewrite/Refactor as little and as much code as necessary.
- Be comprehensive, not exhaustive. If you’re unsure whether something needs/to be changed or fixed, you (or a compiler, or a unit test, or a tester…) will find out later. Trying to be exhaustive may result in changing too much code, getting a migraine, breaking more code and losing hair.
In similar situations, many programmers will either offer to ‘entirely refactor (meaning, rewrite) the code’ or ‘just make it work’ (aka muddle through). These attitudes underly dubious beliefs, explicit and implicit.
- The explicit belief that Order and Chaos cannot coexist, (let alone interact) within our codebase.
- The implicit (and somewhat Luciferian) belief that this time we’ll get everything right.
Don’t buy into this s**t. Use the sword.
Back to my code, here is what I wanted to do today. I am rewriting the ‘go back to main menu’ feature in Antistar. Yes, I could muddle through, but I won’t. Here goes.
1. Destroying the game session is (almost) all we (should need) to do.
I have a class named GameSession. This class is instantiated by a command (GameStart) and is meant to contain the game session (everything that goes on while playing) as opposed to the game container (the menus and other bits and pieces that frame the gaming experience).
So I’m guessing deallocating the game session object is a good step towards going home.
While many objects reference the session, most (all) of these are back-references from aggregates. Say we just called [session release], would [session dealloc] be called? Sadly no. It took a couple of calls to release to get dealloc to kick in. Not being the local reference counting expert, I use a little trick to get me out of trouble in these situations. We can set a breakpoint in an object’s [ retain] method, overriding it as thus:
return [super retain]; // set a breakpoint here
I find it convenient, although it is crude. I don’t keep this code after I find what I’m looking for.
2. Everything must go
It isn’t enough that dealloc be called onto the game session object. We want the whole hierarchy of objects owned by the session to ‘collapse’. I don’t have a clean implementation of [GameSession dealloc], so I’m guessing now is a good time.
The idea is simple. All objects owned by the game session should get deallocated, unless proven otherwise — recursively.
So I spent about three hours writing miscellaneous implementations of [dealloc] (I’ll explain later why I don’t do this upfront). Amusingly, after I finished I found the code crashing exactly as it had three hours before:
- The display link dispatches to my EAGLView
- My overlay view ‘ticks’
- Talkback display attempts using a zombie instance of the… …game session.
So why is my EAGLView not being destroyed?
- Retained as a child of the main window.
- Retained by display link using EAGLView’s startAnimation method.
- Retained (and most likely, released again later) by a couple of system level thingies.
Let’s ignore (3) for now. So practically this says we should remove the 3D view from the main window and unregister the display link callback. While it may seem obvious, it is altogether pleasant and reassuring to find about it this way, right?
There we go:
- EAGLView provides a (void)stopAnimation method (this is code provided by the big guns).
- We have removeFromSuperView, or whatever it’s called.
After adding this code to my MainMenuAction, I had a slightly more worrying occurrence as an auto-release pool attempted to deallocate the view a second time. This reminded me a couple of useful points:
- When using NSZombieEnabled, we can find out the type of the zombie being accessed by checking the console, if anywhere.
- self.myRetainedProperty = foo; will retain myRetainedProperty. myRetainedProperty = foo will not.
After fixing this up, I could observe the following behavior when pressing the menu button:
- The screen goes blank
- The overlay view (ui controls) is still visible. Not much of a surprise. For better (and especially for worse), I use a UI view from a nib file for my controls. As it is, this view exists independently from the game session. It isn’t clean, but it isn’t really useful to ever get rid of it either. Notwithstanding I can press the menu button over again, causing an exception and demonstrating that, if we’re keeping a view after the game session has been discarded, we need to do some cleanup.
- The game menu isn’t showing. Not a surprise either. The game menu shouldn’t be part of the view hierarchy while we’re playing, so I guess we’ll have to put it back.
3. The final touch?
I made a trivial interface, GameSessionListener:
@protocol GameSessionListener <NSObject>
This lets me pass an instance of the game container to the game session, so after the game is over the container can put up the menu again.
While this works, starting and exiting over and over causes bugs on the game menu. It looks like the view hierarchy interacts in unpredictable ways, as eventually the menu screen goes blank.
But hey, that’s what the sword is for, right? :)
Needless to say I went back to my code a few minutes later. UIViews don’t just go blank, do they? and that view had to interact with something. So I looked up whatever views (not many) are somehow shared between the game container and the game session. Then I found the view root (a kind of custom thing, not the absolute view hierarchy root) was being over-released.
What have we gained?
In the previous version of Antistar, the game exit/re-entry process is fairly robust. Meanwhile, it involves a lot of cookery for no special reason other than me shying away from just saying ‘let’s tear down everything’. In theory, tearing everything down should be easy. In practice, if it’s not then we probably have an unsolved problem that’s not good being left unsolved.
The ‘cookery’ occasioned hard to fix bugs. Not tearing everything down means having dirty objects lingering, leading to functional bugs. I haven’t run the new code through instruments and I’m bound to find leaks. Good point here. Leaks, unlike functional bugs, can be detected automatically.
Not tearing everything down also defeats one of the useful applications of a so called game container. Running not just one, but several games under the same hat.
So I started with a clear goal and solved the problem as I intended. I wanted to clear the game session by deallocating it, and this is what it does. What I did not intend to get over and done with today (yesterday, rather) is memory management. Yes, I feel I have a head start on this, but that wasn’t quite the issue here and I’ll look into it another time.
- I didn’t go around analyzing this and that, eventually deciding that I should rewrite the whole whatchamacallit (don’t use the hydrogen bomb)
- I haven’t hacked up a quick patch (no biological weapons)
- I added code that’s useful, concise and clear and contributes functionality to my app (use the sword)
- I haven’t fixed or otherwise ‘improved’ anything that didn’t need fixing (don’t use the mace)
- I spent about 7 hours on this f***ing thing (have faith).