Most often, my posts are inspired by something good. But sometimes, like today, they are caused by frustration. And sometimes, I believe, people need to know about how frustrating being a programmer can be - and why we keep doing it anyway.
I am writing this while having a Terminal window open on a second screen. So far it seems like at least half of the tests - and on this project I have almost a thousand of test cases - will fail.
It all started with one new feature I planned to add to mkdev.me - a quick and simple way to manage content, minimal CMS that mkdev core team could use. I had two options:
Implement everything from scratch, which would mean writing a lot of rather boring code, mostly consisting of CRUD (Create, Read, Update, Delete) Rails Controllers and copy and pasting from existing files;
Do a quick research, check if there are any well supported, battle tested Ruby on Rails CMS gems, that I could nicely integrate into the existing code base;
I did my research, and I really liked comfortable mexican sofa or Comfy. It even had ActiveStorage integration for file uploads! Nothing against other CMS engines, be it RefineryCMS or AlchemyCMS - but I felt that Comfy had the right combination of being extremely simple, yet having enough hidden powers to not become a blocker in the future.
I've installed the gem, played around with it, added few mkdev-specific configurations and was ready to roll it out. And then I remembered that Rails 6.0 doesn't allow to override ActiveStorage services per model. ActiveStorage is a built-in solution for file uploads, that abstracts away "services" (be it AWS S3, local disk or other providers) and gives a nice API to integrate file uploads into your project. We were using ActiveStorage only to store some invoices, in a bucket dedicated only for those PDF files. I did not want to mix invoices and various images inside a single S3 bucket.
Luckily, Rails 6.1 RC was out at that point, with one of the features being to "configure attachments for service you want to store them in". Now I could set primary ActiveStorage service to point to one S3 bucket, and then override it for invoices to be another S3 bucket. Perfect!
It's not unusual for mkdev.me to jump on the latest not-yet-released Rails version, normally release candidates. It's a risk, but we mitigate this risk by huge amount of tests.
I've updated the Gemfile and, naturally, some of the gems did not support Rails 6.1. Most of them actually did support 6.1, but had too strict Gemspec, which specifies support only for 6.0. Solution is simple: you go to RubyGems.org and check if the newer version of the gem in question is released. If not, you go to the Github page of this gem and check if latest not yet released version already has the support for latest and greatest Rails. Then you update your Gemfile, run
bundle update and hope it works.
The problem begins when some of the gems really don't support new Rails version. Like a very nice routing-filter, that we use to simplify handling locale segments in URLs of the website. Unfortunately, this gem relied on very specific internal implementation of Rails routing system, which was largely re-factored in Rails 6.1, completely breaking routing-filter. The last version of routing-filter was released in 2019 and there was not much activity in the repository. So another choice I had is:
Try to fix the gem and submit a PR with a fix (and most likely just use my fork from now on);
Get rid of the gem and implement required features in our main Rails application directly;
Setting locale to be a part of the path doesn't seem to be too much of a deal, and removing gems always feels nice, so I went for the second option. Naturally, this resulted in around 5-6 hours of work, catching all the possible scenarios that were previously handled by the gem.
And of course, no matter how many tests you have, some things can still break. This re-factoring, naturally, broke some of the search functionality on mkdev.me/mentors, that we caught only a few days later. Oops.
After fixing all the possible failures and problems related to routing, I found out another issue: Rails 6.1 includes a rather big internal re-factoring of translation methods, at the same time changing the logic of how it works.
In short, Rails stores translations in YAML files and finds keys depending on where you call the
t()method. So if you invoke
app/views/home/mentors/index.html.erb, it will look for a key
en.home.mentors.index.title. This behaviour slightly changed in Rails 6.1.
This, combined with the fact that we are using cells gem for some view components, resulted in another couple of hours of hunting for the problem (it takes some time to debug what exactly and how exactly changed in Rails internals) and then fixing this problem. At this moment, I already spent over 8 hours debugging Rails internals, different gem internals, fixing application, fixing tests, screaming internally.
Among other things, there were some deprecations to fix and some interesting additions to Rails test helpers, like including certain assertions in ActiveJob helpers (
assert_nothing_raised was added, which then requires you to
config.include ActiveSupport::Testing::Assertions in your
The cherry on top was that Comfy gem depended on rails-i18n, which overwrote date I18n translations coming from russian gem, breaking translations on some of the pages. Surely enough, this resulted in another hour or so of debugging and fixing.
Sometimes you start writing code and you enjoy every second of it. You are creating something from nothing, adding new features, doing some beautiful craftsmanship that only other developers can understand. You take bad code and make it good, you take good code and make it better. Or you just add new code, and it's wonderful partially creative process, that makes you think. And then you run this code and it works and you are the happiest person in the world.
But sometimes you end up debugging the code you never wrote, fixing the bugs you never created in the first place and spending more time fixing tests than improving the actual business logic. Dependency hell, deprecated libraries, sudden clashes between seemingly unrelated things - all of this is also an essential part of the developers job.
The problem is that in both cases you are addicted. And it's not always the nice kind of addiction, when you just love your job so much, that you can't stop doing it. You can't stop doing it, because you need this code to work. And you need this code to work because you know, that once it works, you will get a boost of endorphins. Sometimes, you will hate every second of the process, but when you get to the end you will feel good, and happy, and proud - for a few seconds:
“The programmer personality is someone who has the ability to derive a tremendous sense of joy from an incredibly small moment of success.”, - Coders, Clive Thompson
The more you work as a developer, the more addicted you might get. Your brain slowly transforms to endure hours of frustration to get to the point of complete satisfaction:
This explains a lot about the mental style of those who endure in the field. “The default state of everything that you’re working on is fucking broken. Right? Everything is broken,” he says with a laugh. “The type of people who end up being programmers are self-selected by the people who can endure that agony. That’s a special kind of crazy. You’ve got to be a little nuts to do it.”, - Coders, Clive Thompson
This is, I feel, something that experienced developers should also teach their students and junior developers. Because yes, it's one of the best, most rewarding jobs on the planet. But there is also an unpleasant part, that should be mentioned:
“When you meet a coder, you’re meeting someone whose core daily experience is of unending failure and grinding frustration”, - Coders, Clive Thompson
And that moment, when I finally got a green build and that CMS gem got deployed to production? I was the happiest guy alive. For about 2 minutes.