(Source) code scalability
25/Jul 2009
Today I’d like to write about source code scalability in its most literal form, as in true source code scalability, ie - how much one would have to type if he’d want to extend existing codebase. It’s an often forgotten, bastard sibling of ‘true’ scalability, but an interesting topic nonetheless. There are certain constructions that don’t really lend to modifications that well. I’d like to list some of them here’
STL algorithms. (Mentioned it before)’ Let’s say we’d like to find all characters that aren’t dead:
Looking good so far. Now, one day our designers decide that in this situation they are not interested in characters that have less than X points of mana. In the second case all we have to add is:// find_if solution iter = find_if(objects.begin(), objects.end(), std::mem_fun_ref(&Object;::IsAlive)); // 'process *iter, then continue calling find_if to find other objects. //['] // 'Old school' solution: for (int i = 0; i < numObjects; ++i) { Object& obj = objects[i]; if (obj.IsAlive()) { // Process' // [']
For find_if’ well, there are few solutions, none of them particularly elegant or quick to implement. Admittedly, example is a little bit artificial, but hopefully you get the idea (same problem with remove_if, for_each and family, every time you’d like to extend it a bit, you’re screwed [sometimes you can get away using existing predicates and binds, but usually it ain’t pretty]).if (obj.IsAlive() && obj.GetManaPoints() > X)
Hardcoded constants. As we all know, they are BAD. As it turns out, they’re still bad when we think of amount of typing connected with them. Consider this simple snippet:
Now, every time we want to modify maximum vehicle speed, we have to do it in 2 places. More typing, more chances to forget about it and break the build, more comments needed to explain the intent. The more places we use the constant ‘> the bigger probability of forgetting about changing it. Now, sometimes it’s only present in one place, in such case it may be OK to go without named constant, but usually it saves us from commenting the code. Example:// Limit maximum vehicle speed. if (vehicleSpeed > 10.f) vehicleSpeed = 10.f;
// Vehicles explode when they exceed maximum speed. if (vehicleSpeed > 10.f) Explode(); //['] if (vehicleSpeed > MAXIMUM_ALLOWED_SPEED) // no comment needed. Explode();
Related to the previous point. Remember - comments are part of source code as well! If only I’d have a dollar every time I’ve encountered gems like this:
If you write a comment - you’re obligued to maintain it, that’s why they should be scalable as well. Otherwise, it does more harm than good. In this simple case it’s not a problem to see what the code really does, but remember - wrong comment is worse than no comments at all. In our case just write:// If object is in air - it moves twice as fast as normally. if (object.InAir()) object.m_speed *= 1.5f;
// Object moves faster while in the air. [do NOT specify how much faster, it's in the code!] // ..or [see point 2] if (object.InAir()) object.m_speed *= IN_AIR_SPEED_INCREASE_FACTOR;
Mutiple exit points. I’m not a die-hard fan of SESE rule, however, it does scale better than multiple exits. Consider:
Basically, every time you add an exit point to such function you have to remember about doing all the clean-up. It can be improved to some extent by applying RAII pattern, but it may not always be an option. My personal rule of thumb is - I allow for multiple exit points, but only in functions that do not require any epilogue operations.void Foo() { // ['] some code if (!Bar()) { CleanupA(); delete B; return; } // [..] some more code CleanupA(); delete B; }
Copy-paste/code duplication. That’s obvious, if we have N exactly same pieces of code, when we fix/modify one of them, we have to remember to do the same in N-1 other cases. As usual, the bigger N - the bigger chance of forgetting about it. Simple guideline is - as soon as you have 2 copies of code fragment - generalize. Again, it applies to comments as well. Does it ring a bell?
// Calculate width const int height = fragment->GetHeight() + base->GetHeight();
There surely are more, but those are few that springed to mind first. Source code scalability is very low-level, almost physical (better scalability = less sore fingers). However, very often, it’s directly related to getting clean, short, easy to maintain and extend codebase. Hardly a very scientific metric, more of a gut feeling than anything else, but works well in my experience. After all, if adding another enemy type requires you to recode half of your engine, it surely is a sign of bad decisions at an early design stage’