Refactoring

Should I Refactor This?

This post is split into two parts of understanding the question of when to refactor, the business question and the technical question.

Business Question

The first question with refactoring, as with any business decision, is “Should we do this at all?”

While software written “the right way” feels good to read, it doesn’t necessarily always fit business objectives. An application that is long lived, undergoes frequent updates, or provides critical business functionality is in need of much more refactoring than code that is to be soon thrown away, never changed, or provides little business value. In refactoring, the long lived application saves development time during maintenance. The frequently updated application saves time adapting to the market. Finally, the critical business application improves reliability. But in each of these cases there are costs, in development time as well as complexity of the solution and end user outcomes.

When evaluating these factors remember that we should be aware of bias that leads us to underestimate each of them. Code can live a very long time, often longer than your length of employment at the same company, frequently longer than the company itself, and sometimes longer than a lifetime. There a number of antiquated systems running in programming languages not taught in schools anymore, that generate enormous wealth for corporations.

In conjunction with our ineptitude in judging the lifespan of an application, we often misjudge how it will change and how often. This can swing either way however, so it is often good practice to refactor a little more flexibility into an application with each change, rather than trying to augur all the ways it will change on the first attempt. This way the areas which most frequently change will naturally accrue the most flexibility, while keeping those areas which do not change that much simpler.

The final factor of business value is misaligned by the availability heuristic. As a programmer we are often disconnected from the users of our work, which make us unaware of the value that same work is actually providing. While a strict definition of refactoring might entail no changes to the functionality of a system, in reality small tweaks to configurability and usage occur as we try to increase flexibility and reduce complexity of a system during refactoring.

However, a feature that seems useless or overly complex to us, may be saving days worth of work or generating large amounts of value for a few users. A more direct line of communication to the users of an app should let you discover more clearly what functionality might be more important to the business. That direct line of communication could take the form of face to face conversations or feedback surveys. However, as a developer you have unique opportunity to also incorporate telemetry, audit, and log capture into your application as well. When possible back your expectations regarding refactoring needs with hard data on what features users are accessing and how much they are using it, and what changes when they do.

Technical Question

Now, given an example where we are reasonably certain that the work will pay for itself in future development savings, here are 10 common situations where you should be thinking about refactoring:

  1. There are no tests, or the tests are poor quality e.g. the unit tests don’t mock the dependencies.
  2. Inputs or other variables subject to change are hard coded.
  3. Single methods contain more logic than will fit on the screen at a time.
  4. Code has been copy pasted multiple times with only minor changes, particularly if those changes are all in the same places in each copy.
  5. Code that is no longer used and is not planned to be reused is still in the project.
  6. The code contains hard coded magic numbers that are not self documented in appropriately named constants, properties, or methods.
  7. There are comments explaining each paragraph of code in a method.
  8. Inputs or variables are typed to the concrete implementations, not the abstract class or interfaces.
  9. The code is using lower level constructs than is required for the code e.g. for loops when a foreach or LINQ statement is appropriate.
  10. You can’t tell what the application is doing on the first (or second) reading.

All of these could be considered code smells. There are a multitude of similar code smells documented in books and blogs about good code. They don’t necessarily always apply i.e. just because something smells, doesn’t mean it is bad. But it usually could be better. The basic concepts that many smells are lingering on is that we would like code to be readable, easily adaptable, and no more complex than is necessary. As a secondary goal that supports those primary goals, we like code that is structured, testable, and obviously correct. Sometimes those goals run at cross purposes. Sometimes they are context dependent on the reader of the code e.g. a person familiar with Async code will find it much more readable than someone who has never used it. There are languages that focus on providing more implicit vs. explicit logic for certain types of programming problems, and others than prefer the reverse.

It will take time for you to develop a nose for these things, which will be mostly done by reading a lot of code, preferably your own and others, and by writing code, preferably sharing it with others to get their feedback as well.

I’d like to get your feedback on this post. Please take a moment to post a comment below to let me know your thoughts on the business and technical sides to the question of when to refactor. Any code smells you think should be introduced to a beginner or junior developer ASAP? Or is there a facet to the business question I’ve overlooked? I would be very happy to hear about either.

Also, I putting together a very focused mailing list on the topics that I regularly cover on this blog. If this post was useful to you, please sign up here to get future emails from me on this and related subjects. Thanks!