Make your C++ interfaces ‘trait’-forward

Having tried the Rust language recently, I was glad to see the lack of typical object-oriented concepts in favour of a greater commitment to parametric polymorphism (a.k.a. generic programming). Its means of doing so – traits – is subtly different to what most programmers are used to, yet some of their advantages can be implemented right now in your C++ code. Applying their principles will help you define flexible interfaces, avoid cryptic template error messages, and adapt legacy code with ease.

As OOP goes through its final death, what will the prevailing way of modelling our software look like? It certainly looks like we are converging towards functional programming, with the addition of lambdas in nearly every language but C and many modern languages supporting type inference and/or generics. Some good efforts have been made with declarative programming, the most common example in use today being SQL but C# has made a heroic effort with LINQ. But there will always be a need for algorithms that require a certain set of permitted operations on the data types they use.

Most familiar languages (Java, C#) achieve this with interfaces, which define a set of functions required to achieve some goal or property of an object. For example, IList is an interface that lets you add, get and remove objects contained within another object. The author of the type must specify the interfaces supported by that type. If the type comes from a library, for example, you can’t make that type implement an interface you have created without resorting to the Adapter pattern.

interface IList
{
    Object getObject(int index);
    void setObject(int index, Object newValue);
}

class MyFancyArray : IList
{
    Object getObject(int index) {
        // code to get an object at the given index
    }
    void setObject(int index, Object newValue) {
        // ...
    }
}

Interestingly, Go has a few features that makes this easier – anonymous fields and implicit interfaces – but you still have to define a new struct to implement the interface for an existing type, then wrap instances of that existing type in your new interface-implementing type. This may sound like a palaver, but in the absence of generics, the worst problem you can have is verbosity.

Haskell has a different concept of typeclasses. A typeclass is also a set of functions required to implement a particular concept. However, Haskell types are not ‘objects’ and do not come with ‘methods’, so typeclasses have to be defined separately from the type. Importantly, this means anyone can define a typeclass and make it support any type, even if you didn’t write it or it comes from a library.

class Eq a where
  (==), (/=)            :: a -> a -> Bool
  x /= y                =  not (x == y)

instance Eq Foo where
   (Foo x1 y1) == (Foo x2 y2) = (x1 == x2) && (y1 == y2)

Rust’s traits are very similar to typeclasses. They are the primary method of dynamic dispatch as the language, like Haskell, does not support inheritance on its record types. But the value they bring to generic functions using static dispatch can be applied to C++ using templates, without sacrificing the convention of implicitly typed interfaces. Let’s see an example.

template <typename T>
class GraphicalTrait {
public:
  GraphicalTrait(T& self_) : self (self_) {}
  int getWidth() const { return self.getWidth(); }
  void setWidth(int width) { self.setWidth(width); }
  void render() { self.render(); }
private:
  T& self;
};

This trait is an abstraction of a graphical item, defining three methods. The default implementation proxies the call to the referenced object – the aforementioned implicit interface – but by specializing the template we can implement the trait for a type that does not have the getWidth etc. methods.

template <>
class GraphicalTrait<BigBallOfMud> {
public:
  GraphicalTrait(BigBallOfMud& self_) : self (self_) {}
  int getWidth() const { return self.GetWidgetWidthEx(); }
  void setWidth(int width) { self.SetWidgetSizeNew(width, self.GetWidgetHeightEx()); }
  void render() { cout << self.WidgetTextRepresentation << endl; }
private:
  BigBallOfMud& self;
};

The general concept is more important for legacy code or adapting libraries; new code can be written that does not require a major upheaval of existing code, allowing your code base to be refactored gradually.

What benefits does this give us over typical implicit interfaces in C++? Documentation and API usage is easier, because defining the trait interface forces you to enumerate all of the operations you are expected to implement. Writing code using the trait is easier, as it makes code completion possible. And code comprehension is improved because it is easier to reason over a smaller interface (that which defines the trait) than over a larger one (a whole class).

How do we write code that uses GraphicalTrait? Using templates, of course, but the devil’s in the details:

Option 1: Accept any type as a parameter, and create an instance of GraphicalTrait in the function body. This is easiest for users of your function, as they can directly pass an instance of their type implementing the trait you are using. However, like most uses of templates in C++, the function signature does not enforce the interface the type is expected to implement.

template <typename GraphicalTraitType>
void embiggen(GraphicalTraitType& graphicalObject) {
  GraphicalTrait<GraphicalTraitType> graphicalTrait(graphicalObject);
  graphicalTrait.setWidth(graphicalTrait.getWidth() * 2);
}

The process is easier if you have a class where the trait reference is a member; in this case, you can initialise the member in the constructor and there is no more code than if it were a standard reference type.

Option 2: Accept a GraphicalTrait as a parameter, callers construct a GraphicalTrait. This makes the interface explicit but callers are forced to specify boilerplate, as C++ template deduction rules do not take into account implicit conversions.

template <typename T>
void embiggenedRender(GraphicalTrait<T> graphicalObject) {
  auto oldWidth = graphicalObject.getWidth();
  graphicalObject.setWidth(oldWidth * 2);
  graphicalObject.render();
  graphicalObject.setWidth(oldWidth);
}

embiggenedRenderer(GraphicalTrait<CBWidget> (myCBWidget));

People with an interest in the drafting of C++11 may have heard of concepts; what I just described is concept maps. It is a shame that they never made it into the language; it is a convenient way of adapting existing code, and without the syntactic sugar offered by a language like Rust, the result will always be a little clunky for the code producer or consumer. On the other hand, this clunkiness is no different to most other C++ features!

Rust in pieces: the first 15 minutes

With the Rust language nearing its 1.0 release and many (but not all) APIs starting to stabilise, now seemed like a good time for someone with little time to play around with it.

Coming from a C++ background, the promise of powerful zero-cost abstractions and compiler-enforced lifetime management is certainly appealing. In practise, is it too much effort to be useful for the average performance-sensitive application? I hope we can extrapolate from some toy examples, because that’s all I’ll be able to manage just now!

If you want a tl;dr of my thoughts so far:

  • Compiler warnings are on a par with modern compilers for other languages (gcc, clang/xcode)
  • Type inference is great and cuts down on a lot of boilerplate.

  • The borrow checker is annoyingly merciless.

  • Type inference without garbage allocation makes it really easy to incur undefined behaviour everywhere.

  • The borrow checker fixes this.

  • What one hand giveth, the other taketh away, but there is a net positive: you’re safe from use-after-free bugs and data races.

If you are interested in how I arrived at these conclusions, then read on.

First, define a problem: Read a file and print back the unique lines in that file together with the number of times each line occurred. Should be easy, right? Let’s get some sample input, fruit.txt:

apple
banana
cherry
banana
apple
apple
damson
cherry
banana

And our program should output some permutation of the following:

apple: 3 time(s)
banana: 3 time(s)
cherry: 2 time(s)
damson: 1 time(s)

Let’s see how quickly we can get such a program together in Rust.

Continue reading

Don’t put self-documenting code on a pedestal

I don’t look at comments, they’re always out-of-date. Yogi Berra Co-worker

Good coding discipline is important, and we have many rules of thumb to guide us as we design and write or programs. We should also remember that blindly following your heuristics is unwise; there is no silver bullet.

The ends usurped by the means

I’d like to draw your focus towards the notion of self-documenting code. I often hear programmers telling others how hard they try to make their code self-documenting. I think we all know at least one occasion where we or someone we know has, with much pride, got rid of several comments by careful variable naming. But rarely have I heard someone mention making an effort to produce easy to understand code.

Isn’t it the same thing, you may ask? They may be correlated, but you can have one without the other. Poorly-designed software can be full of code which is self-documenting when taken in isolation, but the way modules are combined and the choice of algorithms to accomplish each task can be utterly confusing in a macroscopic view of the program. Sometimes you need to know why the code does what it does, but the self-documenting approach only tells you how. Conversely, literate programs are mostly documentation and can be very easy to understand.

Reach for the stars, fall in the gutter

Most commonly, code is none of these things, and often ripe for improvement. During some code review, we looked at the following block of string processing code which is responsible for grouping together strings recursively by the bytes within each character until it can no longer be subdivided, then processing each group found. (I expanded the example from the original posting date to give some more context here.)

void groupStrings(pair<int, int> currentGroup, bool upperBitsCleared,
                  int currentBits) {
  vector<pair<int, int>> stringGroups;
  // code to populate stringGroups elided
  firstElement = 0;
  if (upperBitsCleared && currentBits == (MAX_BITS - 1)) {
    // this implies we have reached the end of the strings in the first list,
    // which is our base case
    finishProcessing(stringGroups[0]);
    firstElement = 1;
  }
  for (int i = firstElement; i < strings.size (); ++i) {
    groupStrings(stringGroups[i], upperBitsCleared && i == 0, i);
  }

The proponents of self-documenting code among me seized on the condition and associated comment. “Can we make this more readable?” Their suggested outcome was to extract the conditional expression into a method:

void groupStrings(pair<int, int> currentGroup, bool upperBitsCleared,
                  int currentBits) {
  vector<pair<int, int>> stringGroups;
  // code to populate stringGroups elided
  firstElement = 0;
  if (isEndOfString(upperBitsCleared, currentBits, MAX_BITS)) {
    finishProcessing(stringGroups[0]);
    firstElement = 1;
  }
  for (int i = firstElement; i < strings.size (); ++i) {
    groupStrings(stringGroups[i], upperBitsCleared && i == 0, i);
  }

However, the new name is unspecific. Only the first element of the array ‘stringGroups’ contains strings that have no more characters to process; it is not true for the entire array, or even any of the input arguments! What did we really gain from this? According to the teachings of ‘Uncle Bob’, we avoided failure. In his book Clean Code, he says,

The proper use of comments is to compensate for our failure to express yourself in code. Note that I used the word failure. I meant it. Comments are always failures.

The implication in the rest of the chapter is that anything you can do to remove that comment is automatically an improvement, with exceptions (and this isn’t one of his exceptions). However, what happens if you don’t have the ability to accurately judge whether you really expressed that comment in your code?

It’s easy to tell yourself something is obvious and doesn’t need to be explained once you’ve already gained that understanding. Of course we know ‘isTerminal’ relates to the first element, and that this code handles the base case of our recursive algorithm. But how about six months from now? After a dozen bug fixes and feature requests? When code have been added or changed, is it still going to be that obvious?

Another of Uncle Bob’s teachings is the Boy Scout rule: “Always check a module in cleaner than when you checked it out.” The point of the rule is to encourage gradual refactoring of the code into an easier-to-understand form. However, this doesn’t mean any effort is positive: if you vacuum a carpet, but wear muddy boots doing it, only the gnarliest of dust bunnies can justify the damage caused. Even if you believe you’re improving the code, you cannot claim to follow the rule if you lack the perspective to evaluate that improvement.

How odd I can have all this inside me and to you it’s just words

The paraphrase at the top of this essay was said at this code review, and is the reason why I wrote this at all. I concede that nobody forces you to update comments when the code surrounding it changes. Yet comments and identifiers have more in common than you think.

The biggest criticism of comments – that their content does not match reality – is equally true of identifiers. The compiler cannot check the syntax of language used in your identifier name. It cannot warn you when you are storing the end of the range in a variable named ‘start’, nor the width of an object in a field named ‘height’. It cannot tell you when you changed the behaviour of a function, and its name is no longer accurate.

Worse, you are further constricted by the length you can reasonably give an identifier. There’s a reason why nobody writes essays on Twitter: not every concept can be expressed in 140 characters or less.

If one of your axioms is that comments are worse than any code you could write, you are making a mistake. The only alternative to comments is using appropriate variable and function names. It is said that there are two hard problems in computer science, cache invalidation and naming things; it takes a lot of hubris to believe you can name things as well as you can describe them.

Regardless, you must try to name things as well as you can, but be careful. The capacity of a bad name to confuse and mislead the reader is boundless. A bad name unexplained is worse than no name at all.

Excuse me, I believe you have my stapler

This isn’t even considering the benefits of inlining code at its only call site rather than creating an elaborate tree of indirections throughout the source. Unfortunately this is a tough sell, as self-documenting code’s only means of describing blocks of code is through named functions. Take that away and what is left? But even to try and use functions this way is a mistake in my opinion.

Organising everything into small chunks of code works well when your code is perfectly designed and the problem it is trying to solve can be reduced to a series of independent processes. Often, you can ensure the code is reusable, or maps a set of inputs to a set of outputs in an easily-understandable way. It works because the code is simple.

In most real projects, we have to endure incomplete design or unmanaged inherent complexity in the problem domain. For these, it’s a suboptimal strategy unless you are simultaneously removing that complexity. Not all functions extracted from a block of code are fully independent from the code you extracted it from, usually because it expects its inputs to be in a particular state. Extracting that function does not remove complexity from that code. Instead it spreads it around, making it harder to understand why the function does what it does, and allowing other developers to use a function in the wrong context, potentially introducing bugs.

If your goal is to organise the code, commenting does this without removing the spatial context of the code it is complected with. Save functions for eminently reusable code. Functions imply reusability and the movement from one level of abstraction to another. Hijacking them for the purpose of code organisation is a misuse of structure. Structure is important: that’s why the GOTO wars were fought.

Here is an egregrious example from Uncle Bob, where a 10-line function does something more than its name suggests. Rather than documenting its behaviour, it is exploded into an English-language DSL that buries the unique feature of the class not described in its name – replacing a symbol at most once – three levels deep in the call stack. Local variables become class members, and since any method can alter a class member, we have increased the overall complexity of the original code. If trying to document your code makes it more complex, you are probably doing something wrong.

I hope most programmers are not as extreme as him, or at least not influenced too much. It is at least illustrative of the problem that self-documenting code is no replacement for good documentation. In the instance where you are trying to replace a comment, you are merely going from one form of documentation to another – and in a medium where it is harder to express yourself. This is not to say we shouldn’t try to make our code explain itself, but that it is only one of many means to the true goal: to write code that is easily understood and remains so for years to come.