Reflection on Maintaining a Toy C++ Project

Posted on July 5, 2016 by Richard Goulter
Tags: ,

A few years ago, for a course on Computational Geometry, my team made a program aiming for a stain-glass effect using Voronoi Diagrams.

To quickly explain:
Voronoi Diagrams show the regions which are closest to each point. e.g. for 2 points, you’d bisect the space between them. – By analogy, each ‘point’ would be pizza-delivery, and each region would be the region served by that store.
We’d found a cool paper explaining how to create a “stained-glass effect” using Voronoi diagrams. (Roughly, since edges in a Voronoi diagram are equidistant to two points, you wanna make points which are equidistant to edges on some input image: using an edge-detection filter of the image, you can construct a probability-distribution defined as “0 for an edge or far away from an edge, but quite high for close to an edge”… etc. etc.).

But, uh, this was a school assignment.
The original assignment was in four stages; the first three iterating towards a Delaunay Algorithm, using precise big-int computations. (Delaunay is the dual of Voronoi; where Voronoi shows the regions, a Delaunay Triangulation shows which points are closest to each other. Btw, besides stained-glass effects, there’re a bunch of practical applications to this). (Another fun fact: during this assignment, one of the teammates rage-quitted to go do a whole project by himself, which he never presented on. I had all the luck for getting teammates in uni).
Part four was “do something cool”.
The trouble with this being that the assignment was initially in C++. (I think ‘cause the module was taught by the same Professor who taught ’Intro to Computer Graphics’, which used OpenGL in C++). – Fun fact, the given code wasn’t “const correct” & so wouldn’t compile with compilers not on Windows; oddly, Uni courses are one of the places where Windows is presumed. The trouble with C++ in University courses is, unless the code is monkey-see-monkey-do, or the students are really good at C++, then there’s just too much C++ lets you screw up with to be a good language to use. (I recall at the time a teammate included a hard-coded include "C:\\Users\\username\\....\\whatever.h", because how the hell are you supposed to know that’s not The Right Thing to do?).
(Besides that, it’s much more difficult to install/setup dependencies for a C++ than for other languages like Python or Java).
Only for the most basic things can a programmer get away with blindly writing C++.

Somehow, we managed to get something which worked (ish) submitted. (When I presented it to the class, I said it was “half cool”; ’cause it was a really cool project but it was only half-working).

I put it on my GitHub.
I’d tinkered with it a year ago, enough to fix some SEGFAULT I was somehow getting.
– Going through job interviews at the moment, I can see that the places I apply to will at least look at my GitHub; and this project is my only C++ project. Fortunately, no one gives enough of a shit to actually try out the project (since, as I discovered tinkering with it again recently, the code had some other SEGFAULT issue in normal use),=; unfortunately, I’d reckon even at a glance the project was in pretty bad shape. (Maybe not ‘typical undergrad C++’ bad, but still).

– To be honest, most of what made me say that was superficial: poorly arranged files, inconsistently named files, and inconsistent coding conventions. Prob’ly my favourite being halfcamelCase which annoyed the hell out of me. – In fairness, there were a number of ‘bad’ C++ things (heavy use of extern/global variables, really long methods/functions, etc. – I did write down the things I reckoned were shit; it’s theraputic).
People glancing at the repo (e.g. people I’m applying to for a job) can notice the former easily; and when the latter isn’t so egregious, it’s difficult to notice. – But that’s not really a good excuse. I feel it’s not insane to say “well-designed > testable > working > not-working”. Software is fragile, so “working” is often going to become “not working”. Testable code lets you know this quickly. And well-designed code is easy to bring under test (and/or, a good design makes it really difficult to write bad code on top of it).

Btw, “legacy code” = any code which is untested. lol, most of aforementioned codebase is “legacy”.
In fact, in the days after we presented, the others worked their arses off to get it somewhat working. – Looking at the code again, I came across comments written in Czech. Given that my teammates were Kazakhstani and Indian, I figure this means they “adapted” the code from somewhere else. (TBH, I don’t doubt that they worked hard). – Though, looking at the code we had from the parts one to three, I kinda don’t blame them. – I don’t excuse them, either; after tidying up the code to remove the egregious Bad Things (tm), the code still doesn’t work, but at least the shit is within-functions rather than at a project-level; which is testable.
(For myself, the code I wrote was the Qt & OpenGL code. The uni graphics courses are quite happy to use GLUT.. I wanted a “real GUI”. – I’d made mistakes in loading the image into OpenGL texture coordinates, so the rendering would sometimes [though not always] be wonky).

– One thing I’ll note is about ‘cognitive load’: Programmers tend to know about the immediate-term cognitive-load, where if interrupted from being “in the zone”, it takes time to get back to the task at hand. – I think the thing most undergrads miss out on by not maintaining projects over a long term is the experience of why documentation is important. Coming back to the codebase after 1-2 years without touching it, especially a not-well-designed codebase, it’s really not clear what a particular function is for. Documentation, like good names, as well as describing what a function does (and what it assumes) is surely useful. Or, rather, in its absence, it takes quite some time to read through to remember what things were for. (& that’s with the advantage of having once before carried such a cognitive load).
– As well, for non-trivial algorithms (like Delaunay Triangulation, or constructing a Voronoi Diagram using Fortune’s Algorithm), absence of high-level explanations is quite annoying.

C++ itself is pretty brutal for novice-codebases.
Unlike Rust, C++ is quite happy to let you do stupid shit (but then yell at you about it at runtime). – “NullPointerException is for wimp languages” is what C++ must think. Apparently C++ also thinks “just wing it” when a function never returns a value (when it’s supposed to return a value), which led to some deal of “wtf, it’s not even possible to construct a value like that!”, before I realised this. (No, really, wtf C++?).
Java & JVM languages happily print a stacktrace indicating where an error occurred; “where” meaning not just which line-of-code, but the function calls leading to it. – With a “pretty bad undergrad” codebase, it can be pretty shitty to find where the sinner is.
– I mean, Rust looks hard as hell (for the borrowing/lifetimes stuff), but at least it’s damned-hard to run into errors like this.

The project isn’t that bad at the moment. (The older code for Delaunay Triangulation is, but the rest ain’t). – “Why not start from scratch?” is a nice temptation, but it’s usually better to iterate towards a good solution than to start from scratch.
If I was so concerned about a blight in my GitHub profile, yeah, maybe it’d’ve been better to remove it. But as a “cool project which mostly works”, it’d’ve been a shame to lose.

Also, protip: if you go too far while climbing Ballmer’s Peak, coding becomes less interesting.

Newer post Older post