Portability

The past month has seen me think a lot about portability in development teams and across environments. The first real development project I worked on last year made use of a utility script which provided a unified development environment interface for all of the developers, a go script. This was something of a revelation, as it really helped with my onboarding and environment setup, and allowed me to be a productive contributor right away.

The project that I’ve been working on for the last few months has been a bit trickier with regards to getting anything close to a unified development environment set up across the team. There are a few different reasons for this. I think that people in a position of technical leadership over a development team should take steps to ensure that a team is using the same set of tooling on a billable project, which makes no concessions to individual preferences. If this is not being done, you are handicapped from the get-go when trying to achieve consistency across a team.

As this current project is a greenfield project (and quite a transpilation-heavy one at that) and nobody on the team was familiar with creating utility scripts to ensure a unified development environment interface, developers’ interactions with their development environment were fragmented from the very beginning, meaning that as the project grew in complexity, reliably recreating issues across different machines was impossible and significantly impacted confidence in the codebase.

I don’t like Ruby. It is one of those languages that just does not click with me. So when I decided to take on the task of writing a go script, I was quite dismayed to find that it all hinged on Ruby and Rake. As the development team was already highly fragmented in terms of their environment setups, the idea of trying to get people to set up a common version of Ruby just to be able to run some wrappers around some groups of commands that they were already getting by running manually seemed like a fool’s errand.

Taking the go script from a previous project as an example, I saw that the Bash script that was being used to expose the go commands was essentially a big case statement (and some other stuff) which then went on to run the relevant part of the corresponding Rakefile.

I had been writing a few ad-hoc Bash scripts to deal with daily annoyances in the project, and the thought occurred to me that I should try and integrate them into a go script. No Ruby. No Rake. Just point the relevant case to the corresponding Bash script. As time went on, I integrated more and more Bash scripts, initially all as functions in the same go.sh file; as you might imagine, this soon became quite confusing and difficult to add to.

The next step was to separate all of the functions in the go.sh file into separate files and store them in a .scripts folder. Now the go.sh file was quite literally just a case statement and a usage blurb, and new commands could be added in a very modular way by putting a new file in the .scripts folder and running it in the case statement corresponding to a given go command. I took this a step further by adding both the .scripts folder and the folder containing the go.sh file itself to the PATH of the project using direnv, which allows for project-specific environment variables without messing up your global shell rc file.

I can think of few things in the development world as universal as Bash. The syntax seems a bit arcane and daunting at first, but if you spend half an hour figuring out how to do the basics it becomes very intuitive very quickly. Having fewer dependencies for something like a unified development environment interface is a good thing, not least because it results in being able to onboard new developers to a project that much more quickly and seamlessly.

Finally, a particularly nice bonus of having go commands stored in discrete Bash scripts has been that it is trivial to make a go command (particularly cleanup commands) run as a stage or a pipeline in our CD server, because it doesn’t require anything of our agents besides the ability to run a simple Bash script. If you use something like direnv which allows you to make liberal use of project-specific environment variables in your Bash scripts that can be mirrored one-to-one in your CD server, this task becomes even more trivial still.

In short, the portability of Bash and the flexibility it can afford teams has had a profound impact on my approach to development.