Lifestyle experts want to bring simplicity to people’s lives through the magic of tidying up. People all over the world claim this method has brought happiness, and organization, to their cluttered lives. If only there was a Life-Changing Magic of Tidying Up for developers!
Whether you built or inherited it, most engineers have owned disorganized code at some point in their career. This messy code can result in bugs, prolonged development time, or increased difficulty with onboarding new engineers.
Tidying is the solution.
Tidying does not mean a huge clean, spending hours upon hours working on a big mess all at once. Tidying means finding small areas of disorganization you improve by cleaning things up bit by bit. Tidying, in the context of code, results in purely structural changes to a confusing and messy section of code.
The main motivation for tidying is a human one.
As humans, we like things to be organized. We like carefully named functions and helper methods to break down complex problems into smaller, digestible pieces. Well organized code can be especially beneficial to engineers jumping in to a new codebase or in preparation for a bigger refactor.
At Gusto, our resident tidying expert, Kent Beck, has been encouraging engineers to start spending small amounts of time tidying up old code. I decided to give it a try, so I paired with Kent to tidy up our revenue share process. He walked me through some key strategies he takes while tidying up code; here they are, along with examples of how they changed our Gusto code.
Tidying means structural (not behavioral) changes
When tidying it is important to differentiate between structural and behavioral changes. Behavioral change means changing the way the code operates. Changing the return value of a function is an example of a behavioral change. Structural change, on the other hand, does not change how the code operates. Structural change rearranges and/or reorganizes the code. Separating functions into helper methods is an example of a structural change.
As we encountered during our pairing session, it can get a little tricky identifying behavioral vs structural changes. We looked at a function that creates
PartnerRevenueSharePayouts from an invoice. Upon investigation, we found the return value of the method -
partner_id - was not used anywhere except for tests. Not a good reason for a return value! We removed the call to
partner_id at the end of this function.
At first glance, this update could be categorized as a behavioral change, however it was not an observable behavioral change. Therefore, it is a structural change.
Blank spaces are usually a good indicator for helper methods
When tidying, we want to separate our code out into the smallest methods possible.
Divide your program into methods that perform one identifiable task. Keep all of the operations in a method at the same level of abstraction. This will naturally result in programs with many small methods, each a few lines long. - Kent Beck
We add blank spaces between blocks of code to separate sections that are accomplishing different tasks. Blank spaces are a great way to identify areas of code that can be separated into helper methods.
Helper methods also give us the opportunity to communicate! Developers can define the purpose of their code through carefully named methods. Take the untidied code on the left. At first glance, you may be able to translate some of the code’s purpose - a payout is created if the partner is related to the given
partner_id. With the tidied helper method solution on the right, that context is given for free. We are building partner revenue share payouts. The partner revenue share payout is created unless the revenue share is owned by the partner.
Helper methods not only keep things tidy, they help us communicate as well!
Always keep tests green
Tests should pass at all times when making changes. What does this actually mean for us as we code? Keep changes small. Re-run tests with every change. If you make a change and it does not work, do not keep pushing forward. Revert your change and try again.
Committing frequently, and only on green builds, will help keep your work organized. Purposefully named commits help keep track of changes. If you make a mistake, you can always revert to the last commit and find all green tests.
Failing tests are stressful. Remembering what you changed between the failing tests and the last passing tests is stressful (and difficult). The goal is to make things as stress-free and easy as possible. Small changes, frequent tests.
If you cannot simplify, go for symmetry
Sometimes it is not possible to pare things down with purely structural changes. For example, say you have two methods that accomplish similar tasks but use different records. It is not possible to refactor these methods into one without changing the behavior; however, you know there is potential to refactor in the future. When you encounter this situation, the goal is to make these similar methods as symmetrical as possible. The more identical the methods, the easier it will be to refactor into one method later.
At first glance, the untidied code above appears to be creating revenue share payouts twice. Upon further investigation, Kent and I determined we are actually creating two different types of revenue share, payouts for accounting firms and payouts for software partners. This code is difficult to refactor. The
BuildRevSharePayoutsService is a complex service with many different outcomes. Instead of attempting to refactor something this complex, we can break up these two completely different functions into purposefully named helper methods.
Now it is clear that we are creating two different types of revenue share payouts. By making these methods as symmetrical as possible, we make things easier to refactor for future engineers.
This is common programming advice, but I still find myself needing the reminder. Kent and I were stuck on a difficult problem for about 20 minutes while tidying together. After a 5 minute break, we regrouped and figured out the solution almost immediately. Do not waste time banging your head against the wall. Tidying up is tiring work and a little reset can clear things up.
Go forth and tidy!
Tidying is an easy way to tackle messy code. Before embarking on a large refactor, onboarding new engineers, or simply for your own sanity, tidying up your code can help bring organization back to your working hours. Small steps towards a tidy codebase really add up.
Hopefully these tips and tricks I learned from Kent were helpful to you. Good luck on your tidying endeavours!