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.

No comments:

Post a Comment