(Source) code scalability

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’

  1. STL algorithms. (Mentioned it before)’ Let’s say we’d like to find all characters that aren’t dead:

    // 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'
            // [']
    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:
    if (obj.IsAlive() && obj.GetManaPoints() > X)
    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]).

  2. 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:

    // Limit maximum vehicle speed.
    if (vehicleSpeed > 10.f)
        vehicleSpeed = 10.f;
    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:
    // Vehicles explode when they exceed maximum speed.
    if (vehicleSpeed > 10.f)
        Explode();
    //[']
    if (vehicleSpeed > MAXIMUM_ALLOWED_SPEED) // no comment needed.
        Explode();

  3. 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 object is in air - it moves twice as fast as normally.
    if (object.InAir())
        object.m_speed *= 1.5f;
    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:
    // 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;

  4. Mutiple exit points. I’m not a die-hard fan of SESE rule, however, it does scale better than multiple exits. Consider:

    void Foo()
    {
        // ['] some code
        if (!Bar())
        {
            CleanupA();
            delete B;
            return;
        }
        // [..] some more code 
        CleanupA();
        delete B;
    }
    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.

  5. 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’

Old comments

More Reading
Newer// Flying Wild Hog