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.