As developers we do not work in vacuums. We are constantly faced with constraints like time, budget, and resources. We are influenced by priorities outside of our teams that impact what we can work on, when, and for how long. This is particularly the case when we are trying to develop a reputation for customer support. Our ability to respond to the customer's needs in a timely fashion can be the difference between a lucrative relationship and a missed opportunity.
Oftentimes in an effort to work within these parameters, the quality of our code suffers. We make sacrifices to save time and effort and we take on technical debt with the mentality that once our deadline has been met we'll be able to revisit the code and clean up our mess. The only problem with this approach is what is looming just beyond the horizon - another deadline, and another, and another. Before we know it we've built up significant technical debt and it starts inhibiting our ability to make changes or add new features. Our progress slows and the product stagnates. The choices we were making to be successful start to become the cause of our failure.
The circumstance is familiar to many developers and given enough time and technical debt, it can leave us feeling helpless to fix things. Where do we go from here?
We Must Accept Our Reality
Delivering business value through development efforts is paramount. This means that getting buy-in from stakeholders to devote a large chunk of time to paying down technical debt is often just not an option. The return on investment for large refactoring efforts is too low when the net result is not a tangible improvement to the product.
Larger scale technical improvements can, and should be tied to business value whenever possible; but this takes a lot of coordination and sometimes is just not feasible. Even in a best case scenario these efforts are usually tied to net new features and rarely do they render existing code obsolete such that our desires for better software are met.
We must let go of the notion that we will have time to re-visit our mistakes and correct them without the external pressures that drove us to make them in the first place.
With this in mind how can we turn our code around?
A Case for Kaizen
A Kaizen approach to continual improvement highlights that changes should be small rather than radical. It suggests that ideas should come from the workers themselves with the hope that they will be easier to implement and it encourages ownership amongst the workforce to help improve motivation and team building. When it comes to improving our software these tenets apply perfectly.
Refactor as You Go
Every time you open your editor you are given a chance to perform maintenance on your codebase. It doesn't have to be glamorous or ground-breaking, but if with every change you strive to leave the code in a better state than you found it, the cumulative difference can be earth-shattering.
When it comes to improving code there are all kinds of different approaches and your success with any of them will vary depending on the code and your experience among other factors. Below are some of the techniques I employ to try and constantly improve any codebase that I am a contributor to:
Address lint errors. Lint affects code readability and if you can't read code you can't hope to understand or alter it. Once you've completed a story, why not make a single commit to remove existing lint in a file that you've edited? It takes moments to accomplish but the impact can be significant.
Look for low hanging code smells. Without blowing the scope of a given ticket you can often address some obvious issues.
- Excessive comments - When code comments describe what is happening rather than why, it is a pretty obvious sign that improvements can be made. Can the the code be written in a self documenting manner such that the comments are unnecessary?
- Long methods - Shorter methods are easier to read, edit, test and troubleshoot. That 75 liner needs your help.
- Long parameter lists - It is a safe assumption that long parameter lists lead to increased complexity. Are all of those params really necessary? Can they be combined into an object? Limit parameter length to reduce complexity.
- Duplicated code - Find ways to DRY it out. Can the duplication be extracted into a method? Is there an opportunity for a service object? Removing duplication aids readability and reduces opportunities for errors to be introduced.
- Dead code - If you come across dead code, remove it - NOW. Doing so will improve readability and reduce overall code size.
- There are plenty of other code smells that we can addressed as well. The more familiar you are with them the more capable you'll be of identifying and fixing them in your own code.
Tests are code too! Maintaining tests that are easy to read and accurately describe the desired functionality makes refactoring the code itself substantially easier. Is there a test case that can be added? Can the tests be more readable? Less brittle? Is a given test testing the right thing? Improved tests means improved code.
In general, ask yourself this question every time you make a change: What can I do to make this piece of code easier for the next person to read, edit, test, or troubleshoot?
When Do We Find the Time?
Obviously, the main challenge is the seeming lack of time to make those approaches during the regular workday. I’d suggest finding time during your normal routine, or to prioritize it during down time. For example, provided you have good test coverage you could easily perform some basic refactoring within a class while addressing the acceptance criteria for a given ticket. (It does take some practice though.)
For larger items, I find time on slow Fridays to go back to or finally address some larger issues that have bothered me. I have also chipped away at large jobs over the course of weeks, if needed.
By adopting the right mindset you will find the opportunities you need without having to sacrifice so much time that these tasks require the full treatment a regular story would get.
If you improve the code, there will be less room for unintended side-effects from new development. Furthermore, you are more well versed in the code itself, so your ability to modify it will be improved moving forward as well.
A Stitch In Time Saves Nine
The effort you put towards incremental improvement in your code compounds over time.
Through constantly refactoring you will dramatically increase your understanding of the codebase. When you understand the code well you are significantly more efficient at editing it. Furthermore, your ability to estimate the complexity related to new features is improved and you are much stronger when it comes to onboarding new developers or helping your team.
Learning when, what, and how to refactor makes you a better developer. The techniques you will employ to effectively refactor are universal and will aid you over the course of your career.
Lastly, cleaning up your codebase makes you a better team member. It is almost a certainty that other people on your team have been caught or frustrated by the same things that you are coming across. If you find a way to address someone else's pain point they will love you for it.
Building Software is Hard
Building good software on a tight schedule with limited resources is even harder, but it’s not impossible.
If we can learn to accept the reality that we live in and and focus on striving to incrementally improve it rather than writing perfect code, we are better able to maintain the agility that we desperately need to remain productive and for our products to remain competitive.
Taking an approach of continuous improvement to your daily development efforts can help you grow as a developer, improve your team's morale and ultimately increase your ability to deliver business value.
Are you a talented developer and team player? Do you want to help us maintain high quality code while solving challenging problems at Flipp? Check out our current job postings.