2011-10-20

Adding "Invalid" value to enumeration

Please don't do that:

    enum TrafficLightColor
    {
        red,
        yellow,
        green,
        invalid
    };

You've just artificially extended domain for every function that wants to operate on TrafficLightColor. Now everywhere we need to do stupid things:

    uint32_t toRgb(TrafficLightColor c)
    {
        switch(c)
        {
        case red: return 0x00FF0000;
        case yellow: return 0x0000FF00;
        case green: return 0x000000FF;
        default:
            assert(false);
            return 0xFFFFFFFF;
        }
    }

But why would one pass "invalid" to this function? Wouldn't it be clear if red, yellow, and green are the only possible values, and thus function just never fails?

I assume that generally the less values has function domain, the better.

The first reason why enums got this stupid "invalids", "unknowns" etc. is that somewhere in the code we convert from integer to enumeration.

    TrafficLightColor fromInteger(uint32_t n)
    {
        switch (n)
        {
        case 0: return red;
        case 1: return yellow;
        case 2: return green;
        default: return invalid;
        }
    }

That is the place where you should fail loudly on incorrect integer -- in conversion function! Either with firing an assert of with throwing an exception (depends on many factors).

The second one is that there is a code that wants to know about number of elements in enumeration, and we actually want to use it in the following manner:


    enum TrafficLightColor
    {
        red,
        yellow,
        green,
        invalid,
        number_of_element = invalid + 1
    };
   
    for (TrafficLightColor c = red; c < number_of_elements; ++c)
       ...

Don't do that either. Just make up something like

    enum TrafficLightColor
    {
        red,
        yellow,
        green
    };
    
    static uint32_t const number_of_elements = green + 1;

Don't bother too much that you'll need to change this constant every time you change enum: all clients that use TrafficLightColor needed to be recompiled anyway.

You want your compiler to complain about cases in switch statements that don't cover some enumeration values. And you should fail on enum construction time, not when enum is actually used.

2011-09-20

On overtime

It is widely acknowledged these days that chronic overtime is bad: it makes your team unproductive, unmotivated and unhappy.

At the same time, never having to do any extra hours could be an indicator of unimportance of ones work. Or you have too conservative schedule -- which is both bad economically and demotivating.

So, I think that it is even useful to have an occasional bursts during development cycle. Not only can it help to meet some tight deadline, prepare some demo, polish last glitches before release, or even -- yes -- simply make up that mistake made during time estimation. But also it can actually motivate and unify developing team. It is healthy to push yourself once in a while.

For me, "occasional burst" means to work harder than my long-term pace allows for about a week or two twice a year -- otherwise it's neither "occasional" nor "burst" anymore.

2011-09-10

Templates vs. virtual functions in C++, part II

In part I I have written that templates should generally be preferred to virtual functions if dynamic polymorphism is not absolutely needed.
In this part I will deal with some common arguments in favor of using object-oriented style interfaces and critics of template.

So why C++ developers often prefer interfaces with virtual functions to solutions based on templates? Here is the list of motives I'm aware of:

- code that use OO interfaces can be hidden in .cpp/.CC files, whenever templates force to expose the whole code in the header file;
- templates will cause code bloat;
- OO interfaces are explicit, whenever requirements to template parameters are implicit and exist only in developer's head;
- heavy usage of templates hurts compilation speed.

Let's scrutinize them in order.

Code that uses OO interfaces can be hidden in .cpp/.CC files, whenever templates force to expose the whole code in the header file

Here is how usually code which works with object-oriented hierarchies looks like:

    // in header file
    void f(Base const& t);

    // in source file
    void f(Base const& t)
    {
        // ...
        // hairy implementation
        // ...
    }

So nobody sees implementation of f() guy (*).

Here is how templates usually implemented:

    // in header file
    template <class T>
    void f(T const& t)
    {
        // ...
        // hairy implementation
        // not only hurts aesthetic feelings of every visitor
        // but also couples the interface with dependencies of the implementation
        // ...
    }

Aesthetics can be amended very easy by extracting implementation to "implementation" header file.

Second problem -- exposing dependency on implementation -- is more difficult. This can be fixed, but only if you know a subset of types that your template will be instantiated with -- just provide explicit instantiation of template functions/classes with those parameters:

    // in header file
    template <class T>
    void f(T const& t);
   
    // explicit instantiation
    template
    void f<T1>(T1 const&);
   
    // another one
    template
    void f<T2>(T2 const&);
   
    // in source file
    template <class T>
    void f(T const& t)
    {
        // ...
        // hairy implementation is now hidden
        // ...
    }

Templates will cause code bloat

Usually your code will actually shrink comparing to non-template version -- because only those template functions, classes and member-functions instantiated, and with those type arguments only, that you actually use in code! (**)

    int f()
    {
        std::vector<int> v;
        v.push_back(42);
        // only constructor, destructor, push_back() 
        // and whatever they use internally will be instantiated,
        // and only for type argument int
    }

Bloating can happen though if you are not careful. Classic example is containers that hold pointers. In C implementation there will be just one container that holds void*, and a couple of macros for convenient (but very unsafe) casting of those pointers to specific types. In naive C++ implementation there will be bunch of functions generated for each pointer type:

    std::vector<int*> ints;
    std::vector<float*> floats;
    std::vector<Goblin*> goblins;
    ints.push_back(0);
    floats.push_back(0);
    goblins.push_back(0);
    // three pairs of push_back() will be generated... or not?

All decent implementations though provide a specialization for pointers:

    // primary template
    template <class T> class vector; // additional template omitted

    // specialization for void*
    template<> class vector<void*>
    {
    public:
        // here comes the real implementation
        void push_back(void* p) { ... }
    };

    // partial specialization for pointer types
    template<T> class vector<T*> : private vector<void*>
    {
    private:
        typedef vector<void*> base;
    public:
        // use version of vector<void*> with cast
        // inlined, and doesn't cause any code bloating compared with
        // version one would implement without templates
        // (also safe: user cannot screw up)
        void push_back(T* p) { return base::push_back(static_cast<void*>(v)); }
    };

OO interfaces are explicit, whenever requirements to template parameters are implicit

This one is sad but absolutely true. Remember example from part I:

    template <class T>
    void f1(T const& t)
    {
        // no requirements on T except in comments if you are lucky
        bool flag = t.do_something();
    }

    // serves as an explicit specification
    class Base
    {
    public:
        virtual bool do_something() const;
        // ...
    };

    void f2(Base const& t)
    {
        // explicit requirement is Base's class interface
        bool flag = t.do_something();
    }

Concepts would have solved this problem, but unfortunately they have been rejected from C++11. Let's hope that they appear in the next Standard.

Until then, you have two options.
  1. You can specify requirements in comments and documentation (like SGI STL documentation and C++ Standard do).
  2. Or you emulate concepts. Boost.Concept is the nice tool for that. Finally you can at least use constrains in templates implementation (***)
Heavy usage of templates hurts compilation speed

This one is also true. Compilation slows down due to following reasons:
  • "real code" is generated from templates, and that takes time. Not much can be done with this that "issue". (Alternatively you can write all code by hand, but that would take even more time);
  • templates usually implemented in header files (see the first point), and thus increase preprocessing time and introduce new dependencies that every template user depends on. Sometimes that could be mitigated with explicit instantiation requests, other times with accurate dependency management (not include what can be just forward declared etc.). Sometimes you can just live with it, and other times you can consider using other abstraction tool and not templates.
All in all, in my opinion in modern C++ templates and static polymorphism should be considered the basic design tool -- especially for libraries, and object-oriented techniques should be only considered after them, and not something you start with.

_____________________________________________________________________

(*) Unless developer wants to make it inline, which s/he usually doesn't -- if efficiency of this function was so important s/he wouldn't use virtual functions here.

 (**) Subject to some restrictions: for instance, virtual functions always instantiated, [provide second example]. For more details, read C++ Templates -- The Complete Guide book.

(***) Constrains solve another (but closely related) problem with early diagnostics of violations on type requirements. Unfortunately they are poorly suitable for documenting interface as they are specified at implementation, not in interface.

2011-09-04

Templates vs. virtual functions in C++, part I

Virtual functions and templates are two major tools for supporting polymorphism in C++.

Virtual functions are basic blocks for supporting dynamic polymorphism and thus object-oriented paradigm in C++. Templates are useful for generic programming and metaprogramming; they allow static polymorphism.

Each tool has its own applications. Here is an advise from Bjarne Stroustrup from his book The C++ Programming Language, 13.8:
  1. Prefer a template over derived classes when run-time efficiency is at a premium.
  2. Prefer derived classes over a template if adding new variants without recompilation is important.
  3. Prefer a template over derived classes when no common base can be defined.
  4. Prefer a template over derived classes when built-in types and structures with compatibility constraints are important.
Note that it leaves only one case for virtual functions: when adding new variants without recompilation is important. And if you don't need this benefit, then start with templates. There are several reasons for it:

Templates are non-intrusive

Once type meets requirements imposed by function or class template, it can be used unmodified. Usually that means that type is required to provide some functions or member-functions with predefined semantics.

To pass object to function that operates via pointers/references to root of some hierarchy, type should be specially prepared (either it should be derived from this root directly on indirectly from the beginning, or it should be adapted later) and should have some member-functions with exact signature (*):

    template <class T>
    void f1(T const& t)
    {
        // no requirements on T except that it should provide
        // member-function do_something that
        // returns something convertible to bool
        // and can be called without arguments (so can have i.e default parameters)
        bool flag = t.do_something();
    }

    class Base
    {
    public:
        virtual bool do_something() const;
        // ...
    };

    void f2(Base const& t)
    {
        // first, t should be of class derived from Base
        // second, Derived::do_something() should follow 
        // the signature of Base::do_something()
        bool flag = t.do_something();
    }
Templates produce faster and smaller code

Call it "premature optimization" if you like. But if you are implementing a library you often don't know what will be on the critical path of an application using it. And if you are implementing a C++ library, you'd better make everything as fast as possible, and without using unnecessary memory.

    template <class T>
    void f1(T const& t)
    {
        // no virtual call overhead
        // t doesn't have to store a pointer to vtable
        bool flag = t.do_something();
    }

    void f2(Base const& t)
    {
        // probably virtual call overhead
        // stores pointer to vtable
        bool flag = t.do_something();

        // that can be undesirable especially for small objects
        // and trivial and/or frequently called operations

    }

Templates don't lose information about types

    template <class T>
    void f1(T const& t)
    {
        // here you know the exact type of t and can make further decisions
        // based on it (perhaps using traits or strategies)
    }

    void f2(Base const& t)
    {
        // here information about exact type erased
        // you cannot get it back without run-time type queries
        // (which is inefficient and doesn't scale on a number of derived classes)
    }

Making up hierarchy costs nothing

... but not vice versa. You can always wrap your types designed for using as template parameters with object-oriented hierarchy -- without run-time penalty (**), but once you have "virtuality" in place, its overhead with you forever!

I consider this one the most important. Because it means that, other thing being equal, you probably won't make mistake starting with templates. It is possible because of the previous benefits: performance, non-intrusiveness and keeping type information.

In part II we will deal with some arguments in favor of using dynamic polymorphism.

_____________________________________________________________________


(*) There is relaxed for covariant return types (and for exception specifications which can be stricter in derived class -- provided you consider them part of signature)


(**) Of course, compared with having hierarchy from the start.

2011-08-12

Review your commit changes with 'git add --patch'

I found that git is particularly useful for reviewing changes I'm going to commit.

When I'm ready to commit and my change is more than couple of lines of code in one file, I do

  $ git add --patch .

Then git nicely shows me every hunk of change I made in the code.

I see two benefits of working that way:
  1. Obviously it is opportunity to skim through my own code, and it leads to more focused reviews than if I just read through whole source, or even skim through full patch to be committed.
  2. It leads to atomic commits which I'm big fan of:
  • I don't forget to add things to index. This is not much important when using git, 'cause I always can amend my commits, or even rebase interactively, but it's always nice to form nice commit earlier rather than later. Of course I can do just 'git add .' or even 'git commit -a' for that which leads to next point:
  • if for some reason I made several unrelated changes, I have an opportunity to split my change into several logical changes if needed. That is where real power comes.
The workflow is as follows:

  $ <hack hack hack>
  $ git add --patch . # interactive session where I select what comes to commit
  $ git stash save --keep-index # I stash my unstaged index; needed for the next step:
  $ <compile and run tests to make sure you haven't screw with partial adding patches>
  $ git commit
  $ git stash pop # get the remainder of my changes back in working copy
  $ <repeat with git add>

If I have screw somewhere:

  $ git reset # moves staged changes from index (they are still in working copy)

If I want to review the changes one more time after adding but before commit:

  $ git diff --staged

It sound like a lot of work should be done for every commit. I don't know. First of all I do it fast enough (shell history can help do it even faster). And I use this technique only if I've touched code in several places.

Advanced techniques and details of git add you can add in the documentation, and in Markus Prinz'es article.

2011-08-04

Hacking in front of a kid

I've been hacking a simple breakout/arcanoid game clone last time. It uses pygame for graphics/audio/input/windows/etc. so that I can concentrate on the game logic.

My goal is to implement something cool for my 6-year old son. But also I try to show my son what daddy can do with his text editor and terminal. The game logic itself is the next to trivial for such a simple game, so I trick myself for developing new features in front him.

I must say that that is amazing experience. We do it in 10-20 minutes sessions. During that time I'm implementing some game feature, and afterwards he plays a bit. While I'm hacking he is sitting next to me and staring at python code. And for sure if I don't produce something exciting in 5 minutes or so, he get bored. So I'm coding in real time like hell. (And using dynamic language with extremely short "hack-try-fail-fix-try-works" cycle suits really well.)

No tests, no design (except some half-minute thinking in advance), just pure hacking.

At this point of time I just want to show the kid what you can do with programming. I think you need to excite somebody with before teaching them  industrial methods. Like, you buy your children Lego, and you show them what they can build with it, and then you want them to play with it. And only later on you teach them that they should design their buildings first and that they should pass some regulatory mandated tests and follow the industrial good practices. And hacking simple python games using nice game engine is just like that -- constructing Lego buildings.

I think I'm halfway done with exciting my son with programming, because after third of fourth session he asked me what it takes to be a programmer :)

P.S. Disclaimer: I make up the code a bit while he is sleeping though, so when we start next day it doesn't look like complete mess. One day I will share it on github.

2011-06-14

Being cross-platform

Too often developers sing the praises of cross-platform code. I claim that striving to be cross-platform is not always that good.

Platform independence is an abstraction. As any abstraction it is good -- when it solves real problem. But it could as well be premature, and contribute nothing but complexity and inconvenience both for the code clients and the implementers.

Working with just one platform, you get the benefits of using native platform tools; compiling your code with native build system; using just one compiler and one standard library; using just one set of system primitives; and all that without being forced to play around infinite quirks of each individual component.

If you cannot afford luxury of development just on one platform, strive at least to work with as small subset as possible, and to as close platforms as makes sense. For example, developing network server for Linux and FreeBSD can be OK (at least you have POSIX and pretty much same compiler), but adding Windows to the box is not so fun. The same way, developing desktop game on different Windows versions makes sense, but striving to platform independence only because "one day we may want to run it on Mac" would likely add no value but definitely will increase your budget/schedule.

After all, you should stop somewhere. Like, "this application is only going to work on desktops", or "this will be a library to help with mobile development". My point is that the earlier you stop the better. The less specific and more portable standard you comply to, the less useful primitives you get. At the end you'll be left without threads and directories. Sometimes there is a reason for that.

As with any abstraction, don't try to build this one "just in case": 1) you aren't going need it: instead do on demand; 2) you will do it wrong: instead let it grow organically.

Having said that, it doesn't mean that platform-dependent primitives should proliferate through all your code. On the contrary, your higher level code should not probably depend on platform-specific low-level details. But hey, it has a little to do with "cross-platform" stuff, it is just how reasonable abstractions are built!

Of course, sometimes you can get abstraction from platform for free -- for example, when you already have good cross-platform library or tool that does what you just need. In this case there is no reason not to make use of it. Remember, platform independence is not bad on itself, but only when it implies costs that otherwise could be avoided.

2011-06-07

Enums in C++

"Should I use enumeration in this code, or could I just use plain integer/boolean type for representing set of unique integers?"

Enumerations are OK if:
  • you use the names in 'switch' statements
  • using values in template parameter and specializing on that parameter
  • semantics of some code will be changed if new value has been added
  • semantics of the code should not be changed if values of two variables in the set have been swapped
You'd better stick with plain integers if:
  • you routinely iterate through the values in 'for' loop
  • you perform arithmetics on the values
  • semantics of the code is not changed if new value has been added
  •  
    For values that represent strict binary choice -- yes/no, forward/backward, good/bad -- it is almost always great idea to have an enumeration instead of boolean type: it communicates an idea of variable semantics much cleaner.

    To sum up, you should use enumerations only if you are interested in the names, and not interested in the values (with an exception for serialization maybe), and if the set of integers is bound.

    2011-06-03

    SCM: atomic commits and merges

    (Note: this post is more relevant to centralized world of SCM)

    There are two types of merges that we face in everyday work: merging from more stable branch to less stable branch (release to trunk, trunk to feature, central to local etc.), and merging from less stable branch to more stable one (vice versa, though I hope you never integrate trunk to release branch).

    The first one is for getting last (and hopefully somewhat stable) changes.
    The second one is for delivering your work to the world.

    Typically, when you want to integrate, two branches have diverged for a more than one commit.

    How to reconcile the idea of atomic commits with merging? I'll show it on example of integrating back and forth between the trunk and some feature branch, but the same reasoning is applied on any kind of integration/merging (just substitute "trunk" with "more stable" and "feature branch" with "less stable").

    When merging changes from the trunk to the feature branch, you should merge change by change, not a bulk of changes at hand. First of all, it is obviously easier to merge this way. More important reason is that that allows to test and review each change made at trunk in isolation. It is a common mistake to merge in one lump change: a lump change means a lump diff, and a log message like "merge from trunk" -- which definitely doesn't tell you much.

    As every single commit to the trunk supposed to be an atomic, integrating those changes to the feature branch should also remain atomic.



    When merging changes from the feature branch to the trunk, you cannot and don't want to integrate change by change: by definition, you have created your feature branch because you didn't want to commit the changes to the trunk. So you can only integrate back to the trunk when the policy of the feature branch meets the policy of the trunk.


    (What is "a policy of the branch"? Think about a policy as an invariant of a codeline. A policy of the trunk could be "the code is compilable and passes unit-tests for whole system", whether a policy of some experimental branch could say "commit whatever you want". Clearly you want to integrate your experimental branch back only when it is also compilable and passes unit-tests.)

    Special care is needed for keeping such lump commits atomic: your feature branch should have a single purpose. In other words branch should be feature-oriented, not component-oriented. Don't mix space-fixing, refactorings, bug-fixing, features in one feature branch. For example, if you are working in the GIS domain and optimize memory consumption of the routing component, you should have "optimize-routing-memory" branch, and having a "routing" branch for a years is plain stupid (to whom it might concern: any coincidences are not).




    For distributed SCMs all that is much simpler, as such systems save whole graph (not trying to convert it to linear history).

    Some articles and books that I recommend to read:
    High-level Best Practices in SCM from Perforce site: Perforce Software, and especially Laura Wingerd (their VP of Product Technology) are known for evangelizing good practices in (centralized) SCM usage -- not specific to their product only. This article is about... well, high-level best practices in SCM.

    Streamed Lines: Branching Patterns for Parallel Software Development: everything about branching, mostly in "pattern language format": what, how, when, when not, etc.

    2011-05-26

    SCM: you should make atomic commits

    As with functions/modules/classes, tools, and almost everything else in software development, a change that you are going to commit should have a single purpose, and should accomplish this purpose. I will refer to such changeset as an atomic commit.
    The most important side effect of an atomic commit is that it produces diff that is easy to read and understand. In turn, having diff that is easy to read and understand makes you happy because:
    - it simplifies your debugging by localizing changes;
    - it simplifies code review by localizing changes.

    Atomic commit doesn't mix refactoring, bug fixing, development of new feature, and style changing. Neither mixes it several refactorings, or bug fixings or whatever.

    Atomic commit is self-contained, and accumulates all changes that serve its purpose.

    Atomic commit has a short and up-to-the point log message, which usually doesn't contain 'and'-word and lists.

    "But I don't have time to fix that minor issues, like capitalizing and spaces, separately: I want to do that along working on my primary task at hand!"

    If those are minor issues, why bother spending time on them? It's not "fixing" then, it's polishing. You should polish your product, not your code (unless it happens that your code is the product). Otherwise, just get a piece of paper or text file and add notes about what should be done after you have finished your task.

    "But often while working on a task I notice some TODOs or small things that I will forget if I haven't fix them right now!"

    TODOs are easy to grep, aren't they: why don't you just get good habit to elaborate them periodically. Small things could be transformed in TODOs so you don't forget. Otherwise, just get a notebook or text file and add notes about what should be done after you have finished your task.

    "But dumping small things to the piece of paper kills my flow!"

    And fixing those things while working on bigger one doesn't? Then forget about small things.

    Even better, switch to the modern SCM. This days modern means distributed, and most often that means git or Mercurial. For those who use one of such tool, there are no excuses at all to not produce atomic commits. Because in your local repository you can commit absolutely freestyle, and then slice the meat you've just produced into nice atomic cuts before upstreaming. (For git users, interactive rebases and partial commits are primary tools for that.)

    "But merging changes from one codeline to another means that corresponding commit doesn't have single purpose!"

    Wrong. The single purpose of merge should be delivering change to another codeline. That's why you should carefully choose the changes you want to merge in one commit. Once again, that is much easier to do with distributed tool, but it is also simple to do right with svn or perforce. (I will post more on branching/merging in some of subsequent posts.)

    Example: this is just awful:

    $ git diff
    diff --git a/my.cpp b/my.cpp
    index 6223d3c..4246210 100644
    --- a/my.cpp
    +++ b/my.cpp
    @@ -1,14 +1,17 @@
    -int fancy_stuff(int arg)
    +int fancyStuff(int n)
     {
    -    // do a lot of stuff
    -    return arg * 2;
    +    // Do a lot of stuff.
    +    return n * 2;
     }

    -int contrived(int arg)
    +int contrivedFunction(int n)
     {
    -    if (arg > 0) {
    -        return fancy_stuff(arg*2);
    -    } else {
    -        throw std::runtime_error("arg is negative!");
    +    if (n >= 0)
    +    {
    +        return fancyStuff(n * 2);
    +    }
    +    else
    +    {
    +        throw std::runtime_error("n is negative!");
         }
     }


    ... if all you wanted to say was:

     int contrived(int arg)
     {
    -    if (arg > 0) {
    +    if (arg >= 0) {
             return fancy_stuff(arg*2);
         } else {
             throw std::runtime_error("arg is negative!");

    2011-05-22

    Specifications, part IV

    In the previous post I talked about how to write specifications for virtual functions. This post is about second C++ mechanism which uses new code as a tuning for the old one: templates.

    Templates are probably the most important C++ abstraction-building tool: they allow constructing amazingly powerful abstractions without incurring run-time and memory penalties, and don't lose type information along the way. I think they should be preferred over virtual functions whenever run-time dispatching is not necessary (choosing one or another is the topic of separate blog post in the future though).

    Specify template parameters

    Often class and function templates (and member-function templates, and member-functions of class templates) expect their template parameters to have some properties. As usual, it is better to be explicit in what is expected.

    Consider following template:

        /// prints its argument
        template <class T>
        void f(T t)
        {
            t.print();
        }

    The code doesn't make sense if type T has no member-function template print which can be called without arguments. So it's better to put it in the documentation of function template f:

        /// prints its argument
        /// \pre T has a member-function print which can be called without arguments
        template <class T>
        void f(T t)
        ...

    (Note that it is wise specify absolute minimum -- I'm talking about function print that can be called without arguments, not about print that has no arguments.)

    This example is somewhat contrived: if client programmer misuses f compilation error will occur.
    Sometimes though deciphering compilation error get tricky as instantiation that causes error is deep burried in the call stack. With that kind of things template parameters constraints help: you can have your types checked by type system, and as close to source of violation as possible. Read this Bjarne Stroustrup's faq for that.

    And what is more important, compiler cannot verify everything: it verifies syntax and basic things (like presence of print() with compatible signature). But it will not help you with semantics that is not expressed in C++ type system. Examples of these ones are complexity, exception guarantees, specific side effects, commutativity/associativity of operation etc.

    Consider std::vector<>. It requires that type of elements it holds is CopyConstructible, and CopyConstructible means specific semantics besides mere presence of publicly available copy-constructor. If you violate this requirement while instantiating std::vector with std::auto_ptr<SomeType>, you don't get any compilation error. Instead, you get undefined behavior (which in this case can be crash at runtime).

    Another example of "semantic requirements" is usual requirement imposed on any clean-up function, and on user-defined swap operation to not throw: otherwise generic transactional code is impossible to implement correctly.

    Third example is user-defined template that operates on container and promises O(N) run-time in terms of number of elements in the container:

        /// \pre Container is STL-like container
        /// \pre Op is the class that provides context-aware operator() with parameters
        ///      of types convertible to Container and to the type held by Container
        /// \post run-time complexity is O(c.size())
        template <class Container, class Op>
        do_something(Container& c, Op f)
        {
            for (c::const_iterator it = c.begin(); it != c.end(); ++it)
               f(c, *it);
        }

    Note, that for keeping its promise, it should in general require that Op::operator() itself has the run-time complexity of O(1)! If all you have is do_something declaration and comments, you should specify it as well.

    As with virtual functions, specification of template is only one part for ensuring correctness. Another one is of course providing type parameters that satisfies the specification.

    2011-04-30

    Specifications, part III

    Sometimes we write a code to be extensible, or to be tuned by code implemented by fellow developer. In C++ it is achieved most often by using virtual functions and templates. Using these features implies that some extra care should be taken: we must specify how exactly virtual function could be overridden, or what restrictions on type arguments should be met.

    Implementing and overriding virtual functions

    It is responsibility of class where virtual function was first declared ("most base" class) to specify how it should be implemented (if was pure), or how it could be overridden (if it wasn't). Consequently, it is implementor's/overrider's responsibility to obey these specifications. (For the sake of consiseness, I will use term "override" to describe both implementing and real overriding.)

    For base class' programmer that means specifying carefully what pre-/postconditions and other special restrictions should implementation code meet and ensuring that client's code (one that uses base class) is correct when these requirements are indeed met.

    For derived classes' programmer that means careful implementing the specification. Roughly speaking, you've implemented/overridden virtual member-function correctly, if it "requires no more, provides no less" than what was prescribed by base class. In particular that means:
    • preconditions on overriden version could not be stricter than base class specification;
    • correspondingly, postconditions should not be weaker than specification said they could be;
    • overridden version should obey exception guarantee (if any) promised by base class, and should not throw exceptions other than what is permitted by base class;
    • runtime/space complexity of overridden version should not exceed what specification claims.

    Please note, that if base class has chosen to provide default implementation, it should itself play these rules. It sounds trivial, but way too often we see "error: not implemented" crap inside default version, or  default version being empty even if specification says that member-function should make some useful job instead.

    Following these rules means that your code satisfies Liskov Substitution Principle. This principle is the single most important tool for ensuring correctness of "object-oriented code" -- i.e. the code that uses runtime polymorphism. If you have violated, it, you'd open the Pandora box of bugs, because there it is not possible for base class to satisfy its promises for clients without proper derived class support:

        struct Base
        {
            /// \pre param0 is greater than 42
            /// \returns some even number
            /// \throws runtime_error
            virtual int calculate_something(int param0) = 0;
        
            virtual ~Base() {}
        };
       
        struct BadGuy : Base
        {
            /// \pre param0 is greater than 100 <-- (1)
            /// \returns some number <-- (2)
            /// \throws bad_alloc for some weird reason <-- (3)
            int calculate_something(int param0);
        };
        
        struct GoodGuy : Base
        {
            /// \pre param0 is greater than 0
            /// \returns number that is multiple of 4
            /// \throws never
            int calculate_something(int param0);
        };

    BadGuy is bad because it is mere "subcontractor" of Base, but it breaches its contract on every step:
    1. clients of Base know nothing about more strict requirement that BadGuy states on its argument;
    2. they expect even result, not just some result;
    3. probably they are not prepared to bad_alloc.
    On the contrast GoodGuy::calculate_something() satisfies specification of Base's version. It even provides more than Base promises with weaken preconditions; though clients usually cannot directly take advantage from that -- as in good OO-code clients should rarely know about implementors of interfaces they are working with.

    Simple and robust techniques to actually ensure that implementors behave well (at least that they met pre-/postconditions), is to use Template Method pattern, or even better its extreme version -- NVI. In this case you can put nice assert in non-virtual thunk, and at least test something.

        struct Base
        {
            /// \pre param0 is greater than 42
            /// \returns some even number
            /// \throws runtime_error
            int calculate_something(int param0)
            {
                assert(param0 > 42);
                do_calculate_something(param0);
            }
        private:
            /// \pre param0 is greater than 42

            /// \returns some even number

            /// \throws runtime_error

            virtual int do_calculate_something(int param0) = 0;
        };

    Obviously it doesn't work for "advanced" stuff (complexity, exceptions, etc.), but it is still better to at least check what you can. (Though sometimes you cannot or don't want to use that techniques)

    Next time I will write about templates and type requirements.

    2011-03-25

    Specifications, part II

    How can you specify your functions? Preferably in the code, and sometimes in the documentation.


    Specify in the code when you can

    When feasible, it is more expressive, ensures correct usage, and doesn't get out of sync.

    • use narrower types (reference vs pointer, unsigned vs signed, enumeration vs integer, Derived* vs Base*):

        /// \pre color is one of color_red, color_yellow, color_green
        /// \pre p != 0
        void f(int color, MyClass* p);

        // better
        void f(Color c, MyClass& r);


    • capture implicit preconditions in classes -- especially if they form meaningful domain abstraction, and can be reused:
        /// \throws InvalidDate if y, m and d don't make valid date
        void process(int y, int m, int d);

        // better
        void process(const Date& d);


    • choose good, idiomatic names:
        // all these guys are self-specifing
        std::ostream& operator<<(const A&, std::ostream&);
        Point::Point(int x, int y);
        bool MyPredicate::operator()(const C& lhs, const C& rhs) const;
        virtual BasePtr Base::clone() const = 0;

        void SomeValue::set_field(const S&);
        size_t Container::size() const; // needs comment if it is not constant time!


    Specify in the documentation if you have to

    Unfortunately not everything can be expressed in such a straightforward way.
    In that case, please check my list and provide meaningful description of how the code should be used and what does it do in different cases.

    Without it your client is left alone with its implementation, and s/he never knows what part of it relates to the specification, what is just accidental detail of implementation, and what is simply a bug.

    My favorite beast is std::lower_bound:

        template <class ForwardIterator, class T, class Compare>
        ForwardIterator lower_bound(ForwardIterator start,
                                    ForwardIterator finish,
                                    const T& value,
                                    Compare comp);
     
    How many things about this function can you name?
    1. ForwardIterator should model ForwardIterator concept;
    2. start and finish should be from the same sequence;
    3. that sequence should be sorted;
    4. sorting criterion should be the same as specified by comp;
    5. Compare should be a predicate;
    6. it returns an iterator to the first element which is "greater" that value according to comp;
    7. its complexity is logarithmic: performs at most log(finish-start) + 1 comparisons.
    Nice to have it documented somewhere, huh?

    Or, do you know what sqrt() does when negative argument passed in? Go and check its specification!

    As an example of extremely good quality documentation, I strongly recommend to read through the C++ Standard sections describing Standard Library. Other good examples are POSIX and Boost libraries.

    2011-03-11

    Specifications, part I

    Great software is often being developed by several people over a long period of time. So developers use a code written by colleagues, and write a code to be used by colleagues.

    There are few rules following which can help you be friends with the fellow using your code. Let's start with one important thing which is often neglected:

    Your code should have specification

    What is the specification of a code? It is the statement about what does it do, and how to use it. For a function, it consists from the signature, well chosen argument names, and the documentation. And in really-really trivial cases it can be the implementation itself.

    Here is the list of things I want to know about your function:
    • What does it require to operate properly? (preconditions)
    • Does it throw exceptions? Which and when?
    • What is run-time and space complexity of the code (where applicable)?
    • What does it return?
    • What visible side effects does this function have?
    • For your function (or member-function) template, what are constraints on template parameters?
    • For virtual member-function, how should it be overriden?
    • Is it something special I could benefit from knowing about?
    (I am living mostly in C++ Land, and not everything here is applicable to every programming language.)

    There are numbers of benefits of having clear specification:
    • Your code becomes more flexible -- now your clients depend on the specified contract, not on accidental details of the implementation;
    • Client code becomes more correct -- now they know what to do and what to expect back;
    • If becomes clear what can you silently change in the implementation, and what requires reviewing of all your clients;
    • Thinking about this stuff up-front will improve your design and make your code more testable;
    • You will improve your karma, and reduce overall entropy in your project, our industry and as the consequence, in the whole universe.
    Function's implementation should always follow its specification. If there is a discrepancy, your clients would not know where is a bug -- in your specification, in your implementation, or in their implementation, -- and they would trust neither, and they would be sad. It's not fun to work with the code you cannot trust.

    No, that doesn't mean every function in every project should get its page-long doxygen-comment. Come soon to read next part on that topic.