Simpler than you think


Hello! In this post, I want to share my thoughts on how my approach to creating software has changed, and continues to change. I’ll talk about how I used to dislike any solution that wasn’t “perfectly” made, and how I always wanted to use the newest and, in my opinion, most suitable code for every task. But over time, I’ve learned the importance of effectively analyzing current project goals to focus our efforts where they truly count – prioritizing business-critical features with meticulous care, while treating less essential elements as “good enough” or leveraging off-the-shelf solutions to save time and resources.

When I started my career as a programmer at a company, my goal was to write the best possible code: the fastest, most optimized, cleanest, and fully compliant with all the rules and principles I’d learned. I wanted no duplicates and avoided them (DRY). I wrote small, simple methods with a single responsibility (SRP). I still remember when my boss suggested using a Command in Command while using CQRS. I really didn’t like it because, after all, the rules said otherwise and it “shouldn’t be done.” I admit, I used to over-engineer, optimize my code prematurely, and use the latest language features, design patterns, or just make my code more complicated than it needed to be. I also didn’t really like YAGNI (You Aren’t Gonna Need It), sometimes writing extra helpers or extensions, thinking someone would surely use them and it would make their life easier.

How do I look at all this today? And how has it changed with more years of experience? Humility and working with other programmers played a big part here, I’m not the only one building the whole project. A key shift for me has been learning to conduct a thorough analysis of the project’s current goals right from the start. This means identifying what’s truly business-critical: those features that need to be presented flawlessly, prepared for long-term maintenance, or will require ongoing work. For these,  I immerse myself in my work with extraordinary attention to details – ensuring reliability, scalability, and alignment with business needs through careful planning and implementation.

On the flip side, for elements that aren’t core to the project’s success – those we can afford to be “good enough” or even skip for now I opt for simplicity. If something can be implemented quickly using ready-made libraries, APIs, or third-party services, I do that and defer deeper customization until it’s proven necessary. This approach not only accelerates delivery but also reduces technical debt by avoiding unnecessary complexity. Why build a custom authentication system from scratch when AWS Cognito can handle it securely and scalably out of the box? By prioritizing like this, we free up time to focus on what drives value, rather than spreading ourselves thin across every detail.

Regarding code duplicates, I’ve learned that not all similar looking classes or methods need to be removed or have their common parts extracted. Code changes over time, and these methods don’t always refer to the same thing. Often, trying too hard to reduce duplication leads to a monster made half of “if” statements. But tying this back to goal analysis: if duplicates appear in non-critical areas, it’s often fine to leave them as-is for now, especially if extracting them would complicate maintenance without clear benefits.

For small and simple methods with one responsibility, well, it’s a good approach, but as with everything in programming, “it depends.” Today, I wouldn’t say that many small methods are always more readable than one larger one. If we don’t have to, I personally wouldn’t split things up just for the sake of it. It’s a bit of a preference, but I find it easier to read and debug when I have the whole context in front of me instead of jumping between several parts. The key is implementing everything in the clearest, simplest way possible code that’s easy to delete, modify, or replace without domino effect. This mindset aligns perfectly with effective goal analysis in business important spots, that simplicity comes from thoughtful design elsewhere, it’s about not overcomplicating things that might change or become obsolete soon.

As for the “Command in Command” solution from my boss, well, it’s true it’s not the recommended approach. But again, as it often happens in programming, “it depends.” No one will punish us for it later, except the code itself. We need to weigh everything. If we’re able to accept the future problems of a decision in exchange for clear benefits right now, sometimes it’s worth making that trade. In the example I described, implementing a solution that avoided the “Command in Command” approach would have been very costly and not at all easier to maintain. I knew then that it was an anti-pattern that broke the rules of separate responsibilities, but in the end, it was a conscious compromise decision, with the understanding that we could handle its costs. Today, I’d frame this through the lens of project goals. If it’s in a low-priority area, a quick “good enough” fix like this is acceptable but for core features, I’d push for a more principled solution to ensure long-term viability.

I’ve moved away from the “ideal” solution approach and now do many things that simply satisfy me and are “good enough.” If the situation doesn’t require it, I use the simplest methods and commonly known language features, so no one has to wonder why something was used or how to handle it. I didn’t think this approach would bring such flexibility to my code. Especially since our project is changing a lot, this approach helps me extend my code in many directions without having to rewrite the whole solution, just adapting it to new needs. It also makes the code easier to remove or refactor when priorities shift avoiding over-engineering means less sunk cost when something needs to go.

I once made a bunch of helper methods for our OpenSearch solution to make it easier to manage indexes and test everything. And what happened to them? I deleted them. Bigger changes came that forced me to modify them, time was tight, and I’d have to keep eyes on them. Sounds like they were badly designed? They were! When I made them, I thought there’d be free time when we could focus on improving overall quality, but as life shows, I was wrong. That’s why I’ll never again create methods that “MIGHT” be useful. Instead, I analyze the current goals and, if they are not essential at the moment, I use a ready-made tool or put them aside completely.

To sum up, I’ve shared some of my experiences and thoughts on software development, which will surely continue to change with more years of work. This post isn’t meant to be advice, everyone develops their own approach, and you might agree or disagree with some of my points. However, I believe a good conclusion is to always approach the principles we learn more consciously, while rigorously analyzing project goals to focus on what matters most. This not only leads to better software but also more efficient teams and happier developers.

Leave a Reply