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.
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 a CMS with that? Check out Contentful or DatoCMS.
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.