Are you taking your first steps as a software developer? Do you find this world fascinating and terrifying at the same time? Don’t worry, we’ve all been there. Software development is a fulfilling activity that requires engineering, craft, and dedication. It’s easy to feel overwhelmed by all that’s there—even more lately with the AI boom we’ve been living in for the last 2 years. Here are a few things I would have benefited from knowing in my early years as a software craftsman.
The importance of simplicity
If I had to define software development in one phrase, it would be “the craft of building solutions for complex problems in simple steps.” It’s very easy to lose track of simplicity when you start building big systems, and it’s even impossible to find simplicity when you jump into software that’s been evolving for years and years. Our responsibility as developers is to find simplicity in everything we do. That’s contradictory to how we teach and evaluate junior developers: always looking for the highest optimization possible without considering any trade-offs with simplicity.
You must have heard about the KISS principle (from the original “Keep It Simple, Stupid”, and I don’t care if anyone finds this offensive—this comes from the aeronautical and systems engineer Kelly Johnson, and that’s within the historical group of people I don’t mind being called “stupid” by). I would argue this is the most important principle to base any decisions on when you’re starting in this field. To do things serially vs concurrently? Choose the simplest. To have an asynchronous method or a blocking synchronous one? Choose the simplest. To use a Brute Force algorithm or to use a Dynamic Programming approach using Tabulation? CHOOSE THE SIMPLEST. Your job is to find THE SIMPLEST solution to a problem, considering all requirements you have. So, why don’t you start with the simplest solution and check if there are any requirements that solution can’t serve?
Some time ago, I joined a team that built and maintained internal data systems. One of those systems ran every few hours and performed a validation on a data set. The data set was HUGE, and I was eager to contribute to the team with all my knowledge, so my first thought was to implement this in a concurrent architecture where multiple processes would process different batches of data in parallel. Luckily for me, Iris Altman —who’s an incredible leader I wish everyone had the experience to work with— asked the best question possible: “Is all that complexity necessary?” The fact that the system was running every few hours was key: it was not necessary for the system to be the most performant possible; there was time for it to run longer. And focusing on simplicity allowed the system to be easier to understand by new joiners, and easier to maintain and debug. Optimization should only be a response to a NFR request (in this case, a certain performance goal). When in doubt, keep it simple.
The importance of testing
I’ve talked about this before, and I’ll continue talking about this until it’s burned into your head: Testing matters A LOT. I don’t know how it is nowadays when you are starting to learn how to work on code, but back in the day we were given barely any attention to testing. It’s funny to hear me talk about testing and TDD now, when just a few years ago I wouldn’t have been capable of explaining what a good testing strategy is, not even ask about TDD.
And this is important because the value that testing conveys is not purely in verifying what the code does. To me, testing—and specifically TDD—is a necessary strategy to reduce the mental burden working on complex codebases has on me. Having good testing not only makes it safe to change code, but also adds an abstract layer of specs I can rely on whenever I need to understand anything in my code. I don’t lie when I say that I always go to tests first when I try to understand something. Understanding a component? Unit tests. Understanding functionality? Functional tests. Understanding use cases? E2E tests. Understanding component integrations? Integration tests. I can’t think of a clearer way to have system specifications than a living, runnable set of tests.
I know that you have to be very lucky to grow from a junior position in a team that does TDD, and having mentoring around TDD is sometimes impossible. What I would recommend is to focus on the bare minimum principles and patterns around writing good tests and making an effort to incorporate those into your day-to-day work. Why not force yourself to verify all changes in your pull requests using tests? Why not try to write a spec as a test? Why not start the design of a new component with tests so you can build from the best interface possible? Why not think about tests the same way you think about your code, with simplicity and readability in mind?
I’m working on some video material about testing for less experienced developers out there. Keep an eye on my website to learn more about these.
The importance of a growth mindset
One thing that always surprises me is the fact that my head keeps trying to undermine itself—even after years of experience. No matter how much you study, how many jobs you have, how many successful projects and teams you’re part of, it’s like your confidence is always at the edge, ready to fall from an intrusive thought that hits it where it hurts the most. “I won’t be able to do it”, “It’s impossible for me to be at their level”, “There’s no way I can be good at it.” These kinds of thoughts can be really destructive for a mind that’s just starting to face these sorts of challenges and is constantly living in an anxiety-filled environment.
It’s important to develop a clear understanding of what a growth mindset is, and the actions you can take to pivot from a destructive train of thought into a more nurturing search for growth and opportunity. Of course, you don’t know anything about this new thing you’re working on, but isn’t that the best motivator to learn about it? Of course, you are not performing like a pro, but isn’t that because practice will still make you grow? Learning how to pivot away from those thoughts and transform them into a positive mindset may sound silly, but it will really help you grow faster and stronger.
Try reframing thoughts like:
-
“I’m terrible at this; I’ll never understand distributed systems.” To “This feels hard because I’m learning something complex. If I break it down and keep practicing, I’ll get better.”
-
“I should already know this by now.” To “I’m still growing — mastery takes time. Even senior engineers constantly learn new tools and patterns.”
-
“This bug makes no sense; I must be stupid.” To “If I can isolate the problem and reproduce it, I’ll learn something about the system’s behavior.”
This last point seems to be the simplest, but in my experience, it’s the one thing you’ll have to continue working on for years and years. I try to think of it as a good signal that you’re conscious of your limitations, but that you need to step between your destructive thoughts and your development and be your own ally always.
There are a lot of online groups if you need mental support in your growth journey. Always ask for help if you feel you can’t handle this by yourself.
