DEV Community

Cover image for Full-stack simplicity

Full-stack simplicity

oztk profile image Paul Duguet ・7 min read

We can all relate to complexity as part of our day-to-day work, whether we are a dev, a designer, a PM, or a salesperson. It’s the reason we have a job: solving complex problems. Too often, I have seen complexity built in complexity, layers of entangled logic, spaghetti code, unnecessarily complex UIs... But let’s be honest, most of our brains are not fit to deal with that.

What if there was another way, driven by one main principle: simplicity? Adopting a mindset where we obsess with simplifying everything we can and keeping it that way by being strategic about how we manage complexity.

As a full-stack dev, we can see how things go wrong when we let go of simplicity, whether in code, with users, the interface or the domain. We lose control of what we deliver and it becomes painful to make any changes.

This is why I believe adopting a certain way of designing software can translate into what we build and result in a better user experience, easier maintenance, a value-focused product, and as a result creates happier and more successful users. Some appreciable side-effects may include a happier team and the opportunity for people to learn from each other more easily.

But what does simple look like and what does it mean to live by simplicity?

🧒 Being a 5-year-old

Children are often characterised by their candour, their authenticity, and an unbiased approach to trust.

This could apply to a team as well:

  • Giving trust to team-mates by default: Relying on them to help us, to be challenged, and trusting their judgment. This may very well result in the same behaviour from them in return.
  • Not assuming intention: We all have different ways of expressing our thoughts and emotions. Always clarifying when in doubt prevents many misunderstandings that can lead to unnecessary conflicts in the worst cases
  • Staying close to our authentic self. It is human to create a “professional” persona of ourselves at work. But it is also important to keep some key aspects of who we are so we can deliver our unique value to the business and the team. Knowing that people are genuine also makes it easier to communicate by reducing social fears and barriers
  • Not being afraid to look stupid: Asking questions, even the most simple ones often reveals something that can be improved (better documentation, sharing of knowledge, etc). And if that thing you said really sounded silly when you read it out loud? Well, I guess you now look more approachable to others.

This also works when adding to the product: Can I, as a 5-year-old, understand and use this software?
Of course, the answer is not always yes, but it is a good one to keep asking as we build it so we can remind ourselves when complexity is increasing so we can contain it and preserve user-friendliness. The train of thought we sometimes take to get us to a solution can be convoluted by our own view and past experiences. Approaching the same problem with a candid childish eye often brings a different kind of insights. It can sound obvious, but doing this intentionally helps to take a step back and reassessing the problem and its components.

🤝 Using empathy

We all have a different innate level of empathy. It can make communication difficult because that leads us to naturally expect the same level from others. Awareness about this fact is what allows us to use this as a tool. Empathy is a skill that can be learned and improved. Intentionally putting ourselves in someone else’s shoes has both external (users) and internal (team) benefits.

Devs, especially, are inclined to use the software they build as intended, as they understand it better than end-users. Hence it is very difficult to realise how complex is what we are delivering. Reminding ourselves to think like our users is good practice, and when reaching the limits of our own empathy, we can use others’: Asking Solutions Architects, Account Managers, Salespeople, even friends and family, often reveals complexity in devilish details that we overlooked. A question we can ask ourselves is: “As a user, how does this make me feel?”. Some random wrong answers might be: confused, frustrated, desperate, angry. Of course, we wouldn’t want that for our users 🙂

The importance of empathy within the team is related to how we can allow ourselves to communicate, and react to communication from others. Acknowledging the humanity of every team member and the uniqueness of their context at any given time allows us to both adapt how we communicate to every person, and to understand where they come from when they communicate with us. This way, we can find the best balance of directness and efficiency with each person on the one hand, and focus on what someone is trying to communicate on the other hand, making sharing information simpler and devoid of unnecessary noise. The key thing here being to be able not to be burdened by communication and relationships within a team. We have enough problems to solve already!

🧰 🧱 Break it down until it fits

As humans, this matches our natural way of solving problems.

How do we work on a problem that is too complex to fit into our head? We narrow our scope to look at part of it and understand that first. Then we step back and put everything back together. This not only allows to make manageable what fits in our brain, but can also let everyone excel at what they do best, applying their special skills to a very specific area, and maximise what they are bringing to the table.

At the product level, this step is necessary because as a developer, designer or PM, we build software for domains that we don’t always fully understand as a whole. People specialised in their field have spent years studying this domain and sometimes as much time learning by experience. If we want to have a chance to bring real value, we have to reduce the domain complexity by leveraging experts' knowledge and applying it to a scope we can understand and act upon. Practically, we may want to spend time identifying business problems that are small and can be solved independently. We can then divide and conquer them each one by one, aggregating the value brought by the solutions together for an exponential effect.

When talking about code, this is foundational. There is no other way that I know of to build maintainable software than splitting the code when the cognitive load becomes unmanageable. But we don’t have to wait to reach that point before acting. A codebase is a live entity, permanently under changes to adapt to new requirements. This is why it is important to keep things small and understandable so the next person working on that code will be able to get the picture without having to delve into dozens of files to understand a piece of the puzzle. Regular hygiene through refactoring and explicit technical debt management relieves individuals from having to deal with accumulated complexity every time they want to change something.

💇🏽 Only keeping what is necessary

Similarly to the MVP approach, we can apply a general principle of “Less is more” as part of the delivery process. This starts from the observation that the richer a functionality is (and as a consequence its code), the harder it is to reason about and the more complex everything surrounding it will be. In the initial phase of delivering new functionality this could have multiple undesirable consequences:

  • It has higher chances to produce poor UX: every bit of functionality we are adding into the product complexifies the experience for the user
  • The time invested by the team would not necessarily be well-spent and we may end up putting effort into something that is not needed
  • The code would be more complex and specific, but not necessarily final as we would still expect feedback and having to change the implementation. So the refactoring we know we will have to do later, as well as maintenance are made more difficult.

We also want to avoid having to support every possible use-case within our architecture. Being specific allows us to apply the Y.A.G.N.I principle in the first iterations, so we can later focus on bringing qualitative improvements that we know are worth the time and people’s investment.

I shamelessly say there is no greater pleasure than to remove code from a codebase, even when it was written by me, as long as it served its purpose for a time. But removing code that never was meaningfully used leaves me with a bittersweet feeling of having taken away someone’s time that they will never get back. We can stop this. If it’s not used now, let’s not build it.

Where do we even start?

Alt Text
To conclude, I will not say it is easy to stay simple in the way we do things. Keeping simplicity as a first-class citizen while not falling into simplistic solutions is difficult, and it sometimes feels like there is no way to make something simple. But keeping it at heart across the company in all disciplines makes it real: Looking through the kid’s eyes, cultivating a genuine simple relationship with teammates, scoping down and isolating the problems I understand to solve them, has helped me more than anything else in my journey as a developer, and I try every day to further my exploration in the application of those principles, borrowing from the successful people around me.

I'd love to hear about your simplicity hacks that changed the way you work for the better!


Editor guide
jimclarkuk profile image

Lot's to think about here.
I think it's absolutely right that creating something simple is a challenging but noble endeavour!

aloustaud profile image

Great read, interesting and fully aligns with the product management thinking too; I usually think in terms of MoSCoW (Must have, Should have, Could have, Won't have) when scoping a MVP.

clemgty profile image

Inspiring thoughts, even for non-devs !