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.
1. “the quality of being graceful and stylish in appearance or manner.”
2. “the quality of being pleasingly ingenious and simple; neatness.”
Wikipedia shows something similar with,
“Elegance is beauty that shows unusual effectiveness and simplicity.”
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.
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.
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.