Refactoring as a Feature Tax

Tax season is here. For many of us, that means filing a tax return to see how much money we get back after any excess income tax withholdings provided the IRS with an interest-free loan for a few months. Not a bad deal when you're collecting taxes. Income tax withholding and consumption taxes, like sales tax, provide an effective way to collect tax at the point it's accrued.

For others—small businesses, sole proprietorships, those with un-withheld income or capital gains—tax season means calculating the tax that is now owed after earnings in the past year. These tax payments have been budgeted, scheduled, and planned over the last 12 months. But, for a lot of taxpayers, this structure provides a means to under-report income and avoid paying a certain amount of taxes. In 2009, it was estimated that about 20 percent of reportable income was not properly reported, contributing to a 2009 tax gap of about $500 billion[1].

Due to this aspect of income taxes, economists and policymakers have debated whether the U.S. tax system would be better off if it were based purely on consumption tax. In 2005, Alan Greenspan gave his "cautious backing" of replacing income tax with consumption tax, due in part to the idea that "consumption tax is likely to favor saving and capital formation"[2]. A consumption tax also has the quality of being paid as one goes. It's efficiently collected at the point of sale, which leaves little room for calculated avoidance.

Much like our national debt, many software projects also have a debt to pay. Technical debt gets accrued as we add features to our codebase in a less-than-clean way that will require work to clean up later. Eventually we need to refactor our code to put it in a cleaner, better-designed state. The way in which we accomplish this refactoring can take several forms.

One strategy is to separate clearly refactoring work from feature work. This workflow typically involves identifying where a refactoring needs to take place, making the changes, submitting a pull request for just the refactoring, having team members review and approve the refactoring, then merging the changes into the codebase. Any feature that requires those changes can then be pull requested, reviewed, and merged as well, as a separate workflow. Small, concise, focused pull requests is the goal here.

Another strategy is to combine the refactoring with the feature work. Working on a feature branch? See a refactoring that would make adding the feature much easier? See some dead code or inaccurate comments? Make the necessary changes, commit, finish the feature, then pull request your combined work. One drawback and common criticism of this workflow is that the combined work is harder to review. Any reviewer has more cognitive load in assessing both the refactoring and the added feature at the same time.

But, consider the effects of the first strategy on our refactoring efforts. In order to introduce a refactoring to our project, we need to submit a separate pull request. This means we'll need to have our team approve changes to our codebase that don't add any additional functionality. It also means we're now accountable for our time spent not contributing to features that the business wants. Do we now need to create a story card, budget for, and estimate the effort for the time spent refactoring? Do the business stakeholders need to approve this effort? Are reviewers more likely to reject the changes as unnecessary when reviewed outside of the context of the feature being added?

This could be a slippery-slope argument, but nonetheless, a workflow aimed at separating feature work from refactoring work has the potential to present more barriers to paying off our technical debt. In this workflow, we disincentive refactoring, leave more room for avoidance, and end up with a refactoring gap most likely in the range of the 20 percent U.S. tax gap brought about by similar forces.

Consumption tax presents more burden at the time of purchase, but also ensures that we're collecting the necessary taxes. Expecting feature work and refactoring side by side presents more burden to the reviewer, but ensures that we're constantly refactoring and paying down our technical debt as we add features. And just as Greenspan advocated for consumption tax in hopes of encouraging saving and capital formation, we should favor a workflow that encourages quality code and paying off technical debt. Rather than viewing refactoring and feature work as two separate things, we gain more by seeing refactoring as part of the feature work. Martin Fowler put it this way:

In almost all cases, I'm opposed to setting aside time for refactoring. In my view refactoring is not an activity you set aside time to do. Refactoring is something you do all the time in little bursts. You don't decide to refactor, you refactor because you want to do something else, and refactoring helps you do that other thing. [3]

I think we can address the need for concise, incremental changes as well as the need to foster a quality codebase by keeping in mind a few principles with our workflow:

  1. Split large, complex feature stories into smaller stories.
  2. Encourage refactoring and cleaning as needed while working on these small, concise stories.
  3. Create small commits with descriptive messages to clearly show reviewers what steps were taken to deliver the feature, and to provide a smaller level of granularity that demonstrates where we refactored and where we added functionality.

Policies that differentiate and re-prioritize feature work from refactoring work disincentivize doing any more than pure feature development and encourage refactoring avoidance. Our projects then lose out on both paying off any accumulated technical debt and new investment in well-designed software. Is it possible to make it too easy to allow changes to the codebase, which introduce regressions and new bugs? Of course. Just as withholding taxes from each paycheck makes it really easy to end up with a tax refund at the end of the year after withholding too much. But bugs and regressions are reversible, short-term problems. Refactoring is a long-term solution and investment in the project. We want easy to review, easy to debug, small pull requests, but we also want long-lasting projects that avoid code decay. If we adopt smart workflow policy, I think we can have both.


[1] Cebula, Richard J., and Edgar L. Feige. "American 2009 tax gap of ~$500B. ~20% of reportable income is not properly reported unreported economy: measuring the size, growth and determinants of income tax evasion in the US." Crime, Law and Social Change 57.3 (2012): 265-285.

[2] New York Times

[3] Refactoring: Improving the Design of Existing Code

Kevin Buchanan, Software Craftsman

Kevin Buchanan is biking around Chicago looking for donuts on his way to write code

Interested in 8th Light's services? Let's talk.

Contact Us