Pedro Lucas Porcellis

Regarding Reliable Software

Translations available:

I spend at least 8h of my day writing code, watching a text editor and some terminals. Thinking about data structures, thinking about how to efficiently store data, and how to teach my computer (or others) to do things that I don’t want to have to do manually, because the computer is more trustworthy than me at certain tasks. It can calculate faster, remember things for a longer period of time, it can talk to people faster than I would ever be able to, and I delegate many of these tasks to it. But I can’t trust it.

We live in a time where trust in a computer is normal, and even expected. It’s expected that I utilize them to check my bank account balance, to find a ride home, or to guide me to new places. It’s expected that I share my personal data with them and trust that its being kept somewhere safe. I trust that they will deliver the internet service I have purchased, that my fire alarm will warn me in time, so on and so forth.

The most direct means by which we can influence these problems is by writing software. Having done just that every day for at least the last six years, it stuns me every time someone says that they trust software to, or even wishes that it would control or influence more of their lives. People who write the code you use can and do make mistakes, leaving doors open and letting bugs slip in. I know because I make these mistakes, and work with people that make these mistakes, every day.

The maxim that “all software breaks” is the status quo. And of course, even in environments where there’s deliberate software quality assurance, be it via automatized tests, manual testing, or internal and/or open source code review, bugs still slip into production. New vulnerabilities arise everyday in software, even in code that has been in use for 15 years. It doesn’t prevent “autonomous” cars from killing people. It doesn’t prevent massive breaches from exposing users data, be that entity public or private.

I keep these ideas in mind when thinking about my own projects. When I write code for a company, I have little decision-making power regarding these matters, which is evident in the fact that we use MongoDB as a relational database because it was hot and new at the time. But, for my own work, I make different choices. I prefer reliable software, for obvious reasons; upgradable hardware, even if it’s older; strongly typed languages which can pre-empt my mistakes; and whichever database is appropriate for the task, not the job listing. If my software works, and it works for a long period of time, I need a good goddamn reason to change it.

Change is a part of software development lifecycle, and must be managed appropriately. Most of the time, I argue that the best management is no change at all. But, change demands reason - they need to have a real provocative need, or else they just accumulate in a pile of new problems that they inveitably bring along.

One of these poor motivators, in my observation, it the desire to fit universal solutions into software that should instead address a specific domain. Complexity grows exponentially when a certain project does not have a fixed scope and does not draw a line regarding how much it can expand to and accomodate every little problem and specific need of its users.

i3, the window manager that I use, reached a mature state where it looks like it’s stagnated, but it just doesn’t want more features, so it can focus on bug fixes and performance, which will eventually give the project more robustness and stability. You can then combine with other tools to fill a specific use-case.

Another issue I have noticed, is the obsession with solving trivial problems with huge and problematic solutions. Instead of opting for the simplest path, picking the most overengineered one.

The Ruby’s minitest testing suite is a nice example of that, as it is small and simple. It does not try to perform magic, nor does it try to be smarter than the developer who uses it. It relies on the basic ruby concepts of classes, modules and methods. It only does what it must, in the most simplest and least intrusive way it can. There’s no DSL, there’s no huge set of assertation matchers.

My text editor, vim has the same predictable behaviour for over a decade, and it’s forks keep the same default behaviour, so I know I can hop into some random machine and know it will work the same way everywhere. All it has to do it’s input some text unto a screen, If I need something else, I can customize it to fit my needs.

Weechat, the IRC client I have used to talk with friends 1 for at least three years, runs smoothly while the slack app that I have to use to communicate with my work colleagues can’t spend more than a hour without crashing, presenting some form of lag or eating half of my RAM, even though its main functionlity of sending and receiving real-time text messages is a problem which was solved over 30 years ago.

So I choose reliable software: simple, auditable, performant. Even if that means need to sacrifice some features or somehow limiting the capability to connect or compose with external tools. And embedding these ideals in my life, as a developer means always looking with a critical eye for what it seems “easy”, “new and shiny” solutions for old problems and always seeking to understand the trade-offs they bring along, which usually means to kill stability and/or offloading that cost as a maintenance burden and/or complexity.

  1. I ended up migrating to use slack over wee-chat, although I long for the day I don’t need to use slack at all. ↩︎