Reflection on Maintaining a Toy C++ Project
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.
Anyway.
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.