If you work in the software field, chances are you’ve looked at someone’s code and said to yourself, “what the hell were they thinking?!” If you’ve worked in the software field long enough, you’ve probably said the same thing about your own code:
Software production and tooling has come a long way in the past few decades, but the problem of “bad code” remains omnipresent. It’s impossible to illustrate all of the causes, but some that come to mind are:
- Unrealistic deadlines
- Subpar software engineering practices
As engineers, the third cause is our direct responsibility and will be the focus of this post. Here are there are three key software engineering practices that can greatly impact the quality of your code:
- Successive Refinement
- Adherence to the Single Responsibility Principle
- Holding Your Team Accountable
But before we get into that, we need to set up some ground rules. Successful refactoring is wholly dependent on good testing. If you haven’t fully unit/integration tested your code, then you can’t be sure that the refactor is safe. Additionally, your IDE is paramount to successful refactoring as well. Taking the time to learn the ins and outs will help make renaming, moving, and rebuilding a breeze.
Robert C. Martin describes successive refinement in his book Clean Code as analogous to writing a paper for English class. “The process, they told us, was that we should write a rough draft, then a second draft, then several subsequent drafts until we had our final version.” When you first write a unit of code, it’s almost guaranteed to be unfit for production. But if you’ve appropriately tested your code you can play around with it freely, without fear of breaking the functionality. Mold your code like play-doh, until you’re completely satisfied with the finished product. When you’re reading code that is difficult to understand, refactor it until it’s clear. If you encounter a vast and confusing piece of legacy code, create a tech debt ticket to refactor it. These small successive refinements will pay off dividends in the long-run.
Let’s get into a real-world example of a Java class that our team successfully refined over time. This class was a behemoth, at nearly 1300 lines and a huge portion of our linking codebase reliant on it. I’m going to condense it to just its public API:
Links, syncs, and statuses, oh my! The first warning sign for this class is that its name includes “Helper”. If any class is named with the general “Helper” or “Utils” it’s probably trying to do too much. Just look at how many responsibilities this one class has! Which brings us to the next point.
Adherence to the Single Responsibility Principle
If there’s one common denominator I’ve noticed among code that needs refactoring, it’s that it has multiple responsibilities. With our example ExternalAccountLinkHelper class, it was trying to be everything all at once. To revisit Java’s Single Responsibility Principle or SRP: “Each class should have one responsibility, one single purpose. This means that a class will do only one job, which leads us to conclude it should have only one reason to change.” Although defined here for classes, the principle also applies more generally to functions, components and more. What I like to do when first refactoring some large production code is break it down by its responsibilities:
Method: createLink Responsibility: Creating links New Class: ExternalAccountLinkCreator
Method: getExternalAccountLinkStatus Responsibility: Getting link status views New Class: ExternalAccountLinkStatusViewProvider
Method: dedupe Responsibility: Deduplicating links New Class: ExternalAccountLinkDeduplicator
Method: getFormFactoryForVendor Responsibility: Getting a form factory for a linking vendor New Class: ExternalAccountLinkFormFactoryProvider
Methods: getInstitutionLinkingDetails, getDaysInDegradedStateForInstitution Responsibility: Providing institution statistics/details New Class: InstitutionLinkingDetailsProvider
That was a non-exhaustive list, but you can see how simple it is to objectively look at methods and convert them into separate, SRP adhering classes. This has the advantage not only of cleaner and easier-to-read code, but also in that it reduces dependencies. Because ExternalAccountLinkHelper was so ubiquitous, it was a bottleneck within our platform. It also carried a heavy load of dependencies that were often irrelevant to the limited function you needed to perform. We actually ran into performance issues because of a poorly constructed class which had numerous expensive dependencies, which brings us to the last point.
Holding Your Team Accountable
Here at Wealthfront, all code changes need to be reviewed and approved before they can be merged into production. We find this process invaluable and if you don’t have a review process for your code, we’d recommend implementing one. You can start by developing some common standards that you will all abide by within a team, and some company-wide standards as well. Code reviews can then be an effective method to enforce and maintain these consistent and clean coding practices. When our team initially developed our clean code proclivities, the average number of comments and suggested refactors on code reviews shot up. We’ve since greatly improved our clean coding practices, and the average number of comments has fallen. So do your best to perform code reviews meticulously and methodically. Small comments focused on simplifying code will make a difference in the long-run.
People are often hesitant to spend time refactoring because it doesn’t always appear to yield concrete value. On the contrary, as developers, we spend the overwhelming majority of our time reading code, not writing it. Code that you spend several minutes writing might end up being read for several hours over its lifetime. Tech debt will inevitably build up in any project, but through successive refinement and refactoring you can reduce the build up over time, instead of waiting for it to boil over. There also is concrete value directly produced by refactoring, like the dependency reduction illustrated previously. Plus, as you read through and adjust code you’ll begin to notice all of the potential optimizations to improve performance. If there’s one thing to take away from this post, it’s that the little things truly will make a big difference over time.
The information contained in this communication is provided for general informational purposes only, and should not be construed as investment or tax advice. Nothing in this communication should be construed as a solicitation or offer, or recommendation, to buy or sell any security. Any links provided to other server sites are offered as a matter of convenience and are not intended to imply that Wealthfront Advisers or its affiliates endorses, sponsors, promotes and/or is affiliated with the owners of or participants in those sites, or endorses any information contained on those sites, unless expressly stated otherwise.
All investing involves risk, including the possible loss of money you invest, and past performance does not guarantee future performance. Please see our Full Disclosure for important details.
Wealthfront offers a free software-based financial advice engine that delivers automated financial planning tools to help users achieve better outcomes. Investment management and advisory services are provided by Wealthfront Advisers LLC, an SEC registered investment adviser, and brokerage related products are provided by Wealthfront Brokerage LLC, a member of FINRA/SIPC.
Wealthfront, Wealthfront Advisers and Wealthfront Brokerage are wholly owned subsidiaries of Wealthfront Corporation.
© 2021 Wealthfront Corporation. All rights reserved.