Almost three years ago I wrote an article entitled 5 Lessons I’ve learned as a Junior Software Engineer. At the time, I was just getting into the industry as a developer and thought I would share the lessons that I had learned up until that point.
Now with some more experience under my belt, I’ve grown at lot over the past two years, both as a person and as a developer. I still love my job and am constantly reflecting on how grateful I am that I get to work in my dream profession.
Don’t get me wrong, not every day is sunshine and rainbows. I have my moments where I’m dreading a particular meeting or am frustrated that I can’t figure something out, just like anyone else.
That being said, I have learned a lot. I’ve become more pragmatic in my approach to developing software, started developing my philosophy for writing more maintainable code, and have learned the importance of things like writing tests and good documentation.
So here are five more lessons that I learned as a now mid-level software engineer!
I cannot state just how important good planning really is, it is the backbone upon which the rest of the projects stands. When there’s good planning the project tends to run smoothly, deadlines are usually met on a consistent basis, and everyone on the team is able to work efficiently.
But when things aren’t planned well the team begins to fall apart, deadlines are missed again and again, and the project begins to feel like its on fire. If you’ve ever been on a project that’s had poor planning, you know exactly what I’m talking about.
One of the biggest negative impacts of poor planning is that members of the team can begin to resent the project as a whole, and start to not put forth their best work. This just makes the project degrade even more as a whole, further worsening the issue.
On the other hand when a project has really good planning, team members are able to chug through their work at a streamlined pace (assuming there aren’t other, external negative factors at play). The level of planning of a project tends to be a fairly good reflection of the project management itself, surprisingly. This can be due to the fact of, when everyone is happy with leadership, people tend to want to contribute more and put forth their best work.
The planning also tends to reflect the degree of communication happening within a project as well. High-functioning teams always have great communication due to high psychological safety, and as a result, also tend to have excellent planning in place. Keep in mind these aren’t hard and fast rules, and there are always exceptions, but it’s what I’ve observed.
Good planning doesn’t only cover the “happy path” as well. Edge cases need to be considered, system failures anticipated, and contingency plans set it place for when (not if) the systems does fail. You may not think that anything will go wrong, but at some point something will definitely go awry. The only thing saving you will be how prepared you are to handle these situations, how clearly you are able to communicate with others, and how gracefully you are able to respond.
A final consideration is entropy. Software systems are inherently entropic over time, becoming difficult and costly to maintain. Often times, there is a direct correlation between the level of entropy in a system to the amount of planning that has gone into it. A potentially fatal mistake that many project maintainers forget is the need to make time for making refactors, perform system maintenance, or just generally improving the overall health of the system.
All of this isn’t to say that planning is *the* defining thing that makes a project good or bad, only that it one of many of the key components to a successful project.
I’m sure you’ve probably read some other article or heard from a colleague the importance of finding a mentor. Finding a mentor isn’t just about working with someone who is in a senior position above you. To me, finding a mentor is much, much more than this.
A good mentor is your guide, that invaluable resource you’re able to come back again and again to and confide in. The person who will always gracefully help you when you’ve made a mistake, and will celebrate with you on your successes. That person who gives you those tidbits of break-through wisdom that you’re able to reference back to time and time again.
This might not just be one person either, for myself I’ve had many people that I would consider mentors, and not just in my career but in life as a whole. Some of them I’ve only had a single conversation with in where they gave me some crucial missing piece of the puzzle. Others I’ve had the pleasure to work under or alongside, in which I was able to learn and grow from observing how they dealt with challenges or responded to difficult situations.
While I consider myself a diligent person in making sure that I’m constantly growing and improving, I would be no where near where I am today if not for the many people that I’ve been fortunate to be mentored by. I’ve done my best to pay it forward by attempting to be a mentor for others, often times simply echoing the things I’ve learned from my mentors to the people asking my advice on something. But I don’t think I’ll ever be able to truly repay the incredible amount of value added to my life from my mentors.
Some people get the luck of the draw, being placed directly under a naturally great mentor at work or at school. But if you take the time to intentionally seek out the advice and mentorship from others, you will grow by leaps and bounds as a developer.
This lesson is a bit more technical than the previous two, but I think it’s an important one. I’ve paid the price when I’ve coupled some critical piece of business logic to my application logic.
If you’re a beginner to software development, you might be asking, “What is the difference between business and application logic?”
Business logic are those requirements of the system that are defined by the business/product itself. A simple example of this might be the validation requirements around the password field of a signup form. One business might say they want passwords to be a minimum of six characters, while another might want twice that!
Application logic on the other hand, is the supporting logic and configuration around the business logic. Using the same signup form example, the way in which form validation is actually ensured is an example of application logic. Often times, application logic can even be ripped out from one app and used on another app (such as the actual mechanism by which validation is enforced).
The line between the two can be blurry sometimes, and you will run into situations where some piece of logic could be considered as both business and application logic. In these situations its best to see if you can first decouple the two somehow, otherwise it should be treated as business logic.
The important part here is to keep the two separate. Why? Several reasons: it becomes easier to make changes to evolving business requirements, testing the business requirements isn’t painful, and maintainability overall is generally improved.
One of the simplest approaches I’ve found is to encapsulate business logic in pure functions where ever possible. The referential transparency of a pure function becomes invaluable when placing that component of business logic under test. If you’re working in a more strictly OOP environment in a language such as Java, ensuring that you’re avoiding sneaky side effects in POJO methods that change state also make it significantly easier to test things. Following the Single-Responsibility principle does hurt either.
The key takeaway here is to make sure you’re not jumbling business logic together with application logic or configuration. Keeping a clear separation between the two keeps your concerns separated, makes testing easier, and helps to ensure that your system is easier to maintain or make changes in.
I’m the type of engineer who loves to explore all of the different ways in which I could approach a problem that I’m currently looking at. And in software, there are often many, many different solutions to the same problem.
This is one of those key lessons I learned from a mentor: make sure that I’m balancing obscure abstract conjecture with concrete implementations and approaches. What a mouthful, let’s unpack that a bit.
Abstract conjecture to me is that process of thinking about all the different ways in which a problem could be solved. Often times using some obscure library that has a dozen stars on GitHub will create more problems than it will solve. There could be unseen edge cases you didn’t consider, testing may be difficult, or you might even be over-solving (or over-engineering) the real problem at hand.
A concrete implementation on the other hand avoids most of these problems. A simple proof-of-concept could be all it takes to reveal those hidden edge cases, ensure that your code is testable, and potentially show you if you’ve over-engineered the problem at hand. The downside to a PoC is the time sink it’s going to cost you. For larger problems, it could potentially take a couple days or even weeks to ensure that you’ve picked the right approach.
The trick is to balance the two. Abstract thinking is good because it allows you to see a problem from multiple angles, and consider all the ways in which it can be solved. A concrete implementation is either a partial or full-on commitment to a particular solution approach, allowing you to see how well a solution actually solves the problem.
When you’re a new developer, it’s really tempting to invest as much of your time as possible into what you’re working on. You can easily end up spending day after day cranking out code for what you’re working on, becoming “lost in the sauce”, so to speak.
This isn’t necessarily a bad thing, its important to have a good work ethic and be sure that you become a valued contributor to the project you’re working on. But it’s also equally important to make sure you’re finding that critical work-life balance to avoid burnout.
I’ve made this mistake myself, and learned my lesson the hard way. Ultimately I became burned out, and my productivity level took a big hit. I learned that I need to make sure I’m pacing myself and taking appropriate breaks. I was lucky enough to be working on a great team where taking breaks was actively encouraged, and I was able to bounce back relatively quickly.
Take a break. Walk outside for 15 minutes, make a cup of coffee or tea, at the very least step away from your work station for more than just bathroom breaks and for longer than 5 minutes. Allow yourself to breathe, and give yourself the space you need to make sure you’re in a good state of mind.
Taking care of your mental health is just as important as your physical health, something that has recently become even more apparent thanks to COVID-19.
In conclusion, these are just a few of the stand-out lessons I’ve learned on my journey to becoming a better developer. I’m grateful for my mentors who have taught me such much, and not just in my career but in life in general. While there are other lessons (such as the importance of testing or how to write good documentation), these were the ones that resonated the most with me that I wanted to share. Take them as you will, and good luck on your own journey in software development!