From Over-Complexity to Clarity: A Developer’s Path to Readable Code

Deric Atienza
Mighty Bear Games
Published in
4 min readMar 6, 2024
Photo by Pankaj Patel on Unsplash

In the world of software development there’s a prevalent belief that projects should commence with elaborate and comprehensive design patterns. Especially in my university years and early years as a professional software engineer, I tended to overthink how to implement the simplest features. Only recently did I learn that code need only be as complex as it needs to be.

Early Focus on Detailed Design

As mentioned, my coding career began with a strong conviction in the importance of intricate design patterns and extensive frameworks from the outset of a project. This mindset led me to spend countless hours searching for the perfect project template, one that was pre-equipped with all the popular packages and structured according to the best practices. This strategy seemed ideal, aiming to ensure that the projects were robustly founded on tried-and-tested architectural norms.

However, this approach soon revealed its limitations in the face of real-world constraints. Tight deadlines, the necessity for quick adaptability, and the ever-changing nature of project requirements often clashed with the overhead of maintaining these design patterns that have yet to prove their effectiveness in maintaining the project.

I still remember when I had to implement a simple crypto token and amount control in a React project. I immediately wanted it to be a component where you can simply set the currency and amount according to a main currency even though at that point we were only supporting $ETH. If I had gotten my way, I would have needed to implement:

  • A currency object with names and symbols
  • A matrix of exchange rates between these currencies
  • Auto-conversion of the amount based on the given display currency

Implementing all these would have easily taken a full day. Fortunately, my technical lead at the time convinced me that all we needed was a simple paragraph HTML element with the $ETH symbol hardcoded, turning the task into a 1-hour task at most.

This realization gradually led me to reassess my approach towards a more flexible and readability-centric coding practice.

Shift Towards Practicality and Readability

Let’s say we need to write a function that deals damage to an enemy and if the enemy has the “weakened” status, the damage should be double. However, if the enemy has the “fortified” status, the damage inflicted is halved. One way to write a function that works would be:

def damage(enemy, amount):
if enemy.has_status("weakened"):
enemy.hp -= amount * 2
elif enemy.has_status("fortified"):
enemy.hp -= amount / 2
else:
enemy.hp -= amount

This would work and is considerably readable because the function and requirements are simple enough. What I don’t like about the function as it is now, is that there are three different exit points of the function — one for each status the enemy has and another for the lack of any status. One way to “flatten” the logic and control flow would be to write it another way:

def damage(enemy, amount):
final_amount = amount

if enemy.has_status("weakened"):
final_amount *= 2

if enemy.has_status("fortified"):
final_amount /= 2

enemy.hp -= final_amount

This way, there is only one exit point and the code flows as the requirements say. The damage amount is modified before being applied to the enemy’s hit points. This may seem like an unnecessary rewrite given how short and simple the function is but I have seen functions with nested if-else statements that got out of control and became unreadable and a nightmare for developers other than the author to understand. This way, each block inside the function does one thing only and doesn’t take control of the exit point of the function.

The transition to a more practical approach was marked by an appreciation for simplicity and the readability of code. I discovered that what often impedes the understanding of code isn’t its lack of sophistication or design patterns, but rather its overall readability. Complex control flows, for instance, tend to obscure the underlying logic, making maintenance and collaboration with other developers challenging.

This led to an evolution in my coding style, where I started to prioritize clear and easily understandable code over elaborate structures. I began to embrace practices such as breaking down large, convoluted functions into smaller, more manageable segments, each responsible for a single aspect of the functionality. This not only made the code more legible but also simplified debugging and updating processes.

Emphasizing Code Simplicity

In practice, this approach meant starting projects with the basic necessities and gradually incorporating more complex patterns as and when they were warranted. This step-by-step methodology aligns more closely with the natural progression of most projects, where needs and challenges become more apparent over time. For instance, in a scenario where a feature’s requirements are still being defined, starting with a simple implementation allows for greater flexibility and quicker iterations.

Of course, like any other “best practice” or design pattern, simplicity itself has its overhead. Let’s say we're starting development on an online fighting game. Fighting games nowadays work seamlessly online. Given the fast-paced nature of fighting games where one frame could be the difference between winning and losing, we cannot afford to disregard this requirement when starting the project. The solution would be to write the gameplay code with rollback netcode from the start as the overhead of migrating from delay-based to rollback netcode somewhere down the line after multiple complaints from your players could be overwhelming.

Some of these ideas are described in even more detail and with more demonstrations in some of my favorite programming YouTube channels NoBoilerplate and CodeAesthetic.

The journey from a heavily engineered mindset to one that values simplicity and readability has been enlightening. It has highlighted the understanding that while advanced design patterns hold their value, they should not dominate the fundamental goals of creating clear, maintainable, and efficient code. Achieving this balance is key to sustainable and successful software development.

--

--