Imagine a man appearing at a formal ball. He is wearing a perfectly tailored suit, the jacket of which is bright pink with a red checkerboard pattern. His pants are green with purple vertical stripes. The suit is the creation of a renowned designer and it cost quite a bit. The man also has a gold chain around his neck and ruby rings on both hands. Behind him, however is a woman wearing a black dress and no accessories save for a pair of pearl earrings. Think about the two guests for a moment. Which of the two would you consider elegant?
The IT industry knows the first type very well. The man in the suit is the equivalent of a system that cost a company millions. The system is made up of several components from different providers that often can’t communicate properly, and more often than not, freeze just at the moment you need to deliver an urgent message to your boss.
Elegance in Tech
Unix philosophy is closer to the lady in the black dress, with a focus on using tools or writing programs that do one thing, but do it perfectly.
When we ask Google the definition of “elegance.” it offers two.
- “the quality of being graceful and stylish in appearance or manner.”
- “the quality of being pleasingly ingenious and simple; neatness.”
Wikipedia shows something similar with, “Elegance is beauty that shows unusual effectiveness and simplicity.” (My emphasis added.)
Why am I getting into elegance on a tech blog? Because it is we engineers who are to be blamed for over-complicated systems, a project going over budget, and the general ugliness (better known as “the technical debt”) that surrounds our work. Most of us know the KISS principle, but we often forget about it each time there is a need to implement just one more feature in our code.
What Hinders Elegance?
Tight time constraints are the main killers of elegant solutions. Simple does not equal “quick” or “easy.” Usually, it is the opposite. Simple design requires careful thought and analysis of all expected use cases, to come up with an idea that is clear and concise. While you can build software without a proper design and without putting much thought into the scope of possible functions, maintaining that code will be a pain. Because each time there is a bug, it will most likely be caused by your lack of proper edge-case handling. And without a clear scope of what one function should or shouldn’t do, you’ll most likely end up adding a conditional clause somewhere in the code.
But after ten such cases, your code no longer looks like it did after the initial release. It’s now a soup of obscure conditionals and nobody knows what is the desired behavior of the application. Sometimes a change in something totally unrelated at first sight causes errors in a different subsystem. Everybody then scratches their heads and quietly reverts the last commit. Time for another approach.
Why Complex Is Easier to Implement
Elegant solutions require focus and observation. They require analysis, good communication with the client, and a well-thought scope. If you don’t have time to figure out all the possible input arguments or if you just can’t figure them out because you lack a proper design and the customer is unsure about how he will use the product, you end up skipping a few steps in the process of solving the problem.
But there is also another source of complexity. And this one is much harder to overcome. It’s the abundance of ready-made components and abstractions waiting for you to use. The front-end development is especially vulnerable. Writing an appealing web application using plain HTML5, CSS and Javascript is not an easy task. That’s why we decide to trust a third-party to implement all the nitty-gritty details so we can focus on the important matters. We choose a framework, look out for a few more modules, and end up with the infamous 3GB node_modules directory. All should be quite alright as long as the abstraction layers in those modules align with our way of using them. More often than not, there is something we can’t agree on with our framework so we end up writing a terrible workaround or a special case just to make it work.
I wish I could share a viable way to deal with leaky abstractions, but I can’t. I don’t have one! But I’m aware we can’t just stop using them. This would be a huge waste of resources.
How to Keep it Simple
You write a mobile application? Instead of creating your own backend, use a Backend-as-a-Service such as Firebase.
You want to host a landing page or a blog? Go with a static site generator (like Jekyll or Gatsby) and static file hosting (like Netlify).
You want a CMS with that? Check out Contentful or DatoCMS.
Tired of keeping track of SSL certificates for your web service? Go with an automated refresher like AWS ACM, Traefik, Caddy or Zombie Nginx.
Want your code to run “on demand” instead of paying fixed price of a VPC instance? Try a Function-as-a-Service (or Serverless) solution.
Oh, by the way, unless you have a very refined taste, it’s a good idea to use a managed database service like AWS RDS/Cloud SQL/Azure SQL.
To achieve simplicity is to reduce the number of components instead of accumulating them.
The Benefits of Keeping it Simple
More moving parts often mean more errors. In a classic blog engine example, you can expect at least the following problems:
- Poor database performance.
- Poor blog engine performance (insufficient memory/CPU).
- Insufficient network throughput.
- Disk space running out.
- You overprovision for the most pessimistic use-case thus you overpay the rest of the time.
- Broken deployments.
- Database migration can run wild.
- The VPC may disappear without a trace along with backups.
I’m not here to sell you a protection racket, but if you opt for a static site generator and a static file hosting, most of these problems simply go away. You also gain a free Continuous Delivery pipeline in which each source code change results in changes visible on the page. Both simple and feature-full!
Looking for Inspiration
Some people take the KISS principle very seriously and apply it in their projects. If you do, I’m happy to hear your recommendations for keeping solutions elegant through simplicity. Feel free to use the comments section below to share some great examples of elegant projects, recommendations for simple reductions, or warnings on what to avoid.