About four years ago (well, closer to 4½ now, I’ve been meaning to write this for a while), I left my job as a Systems Administrator for, well, various reasons. Honestly, one of them was that a job landed in my lap: a pal worked at a company who were looking for a Ruby developer. In fact, they had been for a while. There apparently aren’t many in the central belt—the running joke is that they all work for FreeAgent.
Anyway, while I had never written Ruby for money before (or written any software at all for money, except a few small projects at the Students’ Association while I was “at” university), I had been involved in SparkSeat, which was mostly written in Ruby. Anyone who was around at the time would be able to tell you that I really didn’t do much of the actual writing-code parts of that project, but nevertheless, I had a rough idea what was going on with Rails (which SparkSeat used), and a pretty good knowledge of the Ruby language.
I still think it was a bit of a gamble, in hindsight, for my current employer to hire me based on this alone. However, without tooting my own trumpet too much, it does appear to have paid off for both parties! I got to leave the job that was literally causing my hair to fall out, and the company found someone who has hopefully solved some of their software-related problems. (And in this day and age, almost anything you can think of can be reduced to a software problem if you try hard enough?)
Things which I have learned
I have learned a tonne about the industry I now work in (telecoms). Childhood me would be very proud/envious, and would’ve loved poking around telephone exchanges about as much as adult me does.
Relatedly, my record of “building or maintaining some kind of phone system at every job” remains unbroken. I don’t know how this keeps happening!
I’ve also learned a fair bit about maintaining “legacy” software (or, if you prefer, software that already works). A big part of this is resisting the urge to just rewrite everything. Many thousands of words have been written, including a famous essay and various rebuttals on whether or not that is a good idea, but on a team of one (now two) it just isn’t practical.
I am not an especially pragmatic person in general, but I think trying to establish some sense of order and structure on a 10+ year old codebase has trained me in the art of the compromise.
Now. Let’s talk about tests.
Everyone says you should have tests, and they’re right, but they’ve gotta be good tests. Bad tests are just more code you have to live with, right?
They tell you having tests means you can safely refactor. That they let you make changes without unintentionally breaking things.
What they don’t tell you is that you’ll feel like an all-powerful wizard when you do it. You can just… change things and be reasonably assured that you haven’t broken anything. It’s positively intoxicating.
Finished in 32.52 seconds (files took 6.8 seconds to load) 203 examples, 0 failures
Ahem. Anyway. Tests are good, from a business value perspective too, but everyone knows that bit already, at least in theory.
Continuous deployment is the natural extension of this, and I always get a kick out of watching it in action.
Ingredients for success
Being the only software developer in a non-software organisation—and an inexperienced one at that—has meant I’ve been less able to anticipate some kinds of issues that a more experienced hand may have spotted.
One of the main kinds of issues is what I imaginatively call “building the wrong thing.” This means either building what somebody asked for, but not what they needed, or building something that somebody asked for, but the wrong person asked.
All of the most successful projects have had:
- an easily-stated purpose
- a suitably-detailed specification
- stakeholders with
- a good understanding of their requirements,
- the ability to articulate them, and
- the time commitment to stay engaged throughout the process
Make code that’s easy to read, yes, but make code that’s easy to delete too
This can mean many things, including splitting into modules, extracting things into separate applications, defining clear interfaces with searchable names, whatever. Just make things easy to delete.
Even if you never delete it, it makes it easier to move things around later. It’s like a perforation line in your codebase that you can snap it off at later, should you need to.
I recently removed an ill-fated component of our main application, about 3,500 LOC:
$ git show --stat 305a7242c3a66126aaffa2935171e9d53d980be3 -- engines gems 275 files changed, 3644 deletions(-)
Removing the component from “the rest of the application”—all areas where it integrated, took around 15 minutes.
I searched the codebase for the component name and removed references to it, sometimes collapsing a now-redundant
$ git show --stat 305a7242c3a66126aaffa2935171e9d53d980be3 -- app config 14 files changed, 1 insertion(+), 204 deletions(-)
That was it. Gone.
See also: Sacrificial Architecture
Defer decisions until later
This does not mean not specifying anything.
It just means wait as long as possible before deciding how to approach something. Knowing how long “as possible” is is the tricky part of course. The idea is that more information may have arrived by the time you eventually get around to making the decision.
In my experience, you basically never have all the information before you start. Often you don’t even have enough information, but still you must proceed.
When you can’t put the decision off any longer, try and leave a way back. Give the next guy (probably also you) options down the road, if you can.
For heaven’s sake, write things down
Specifically, write down why you made decisions. Lots of architectural decisions are made during a chat, possibly with a coffee.
Write. Them. Down.
I cannot overstate how important it is.
One of my most frequently-uttered phrases at work, surely, is “let me just check my notes…“
It lets you remember why you did something years ago (in order to avoid the same mistake you avoided before, or to provide context).
It lets you articulate decisions to people in a persistent way. Hopefully, this means everyone has the same understanding of decisions that were made. This requires a bit more effort in expressing things clearly, however.
It lets you onboard new people into your codebase more easily, and avoids having to remember everything you might have to explain to them.
Mostly, it just saves loads of time in the long run.
See also: Architecture Decision Records
There is certainly more to write. There’s more that I’ve learned along the way, sometimes hard-won. However, sometimes it’s better to ship (post) an MVP (unfinished blog post) than wait for everything (the other 1300 words) to be written.
(One thing I have not particularly written about is everything except “things which I have learned”, which, arguably is the least interesting piece of information.)
The very short version is that writing software is
Particularly when there’s a good amount of variety and what you’re doing is interesting (to you. Goodness me the way people’s eyes glaze over when I explain what I do).