New

Don’t call a new version of something ”new” when refactoring

In long-lived applications, we often want to rewrite or refactor an approach that we have previously taken to solving a problem.

Here’s a, non-obvious, naming technique I’ve used during refactoring to ensure that the improved code ends up in well-named classes and that any old implementation are clearly marked for deletion.

You might find this helps to organise your work or better communicate the intent of future changes with your teammates.

Instead of…

…littering your codebase with the word “new”:

class NewDoThingJob << ActiveJob
  # new implementation
end

or

class DoThingJobVersionTwo << ActiveJob
  # new implementation
end

Choose…

to rename or, better, namespace the existing code to mark it as deprecated. Then create the replacement version with the correct future naming.

module ToBeRemoved
  class DoThingJob < ActiveJob
    # existing implementation
  end
end
class DoThingJob < ToBeRemoved::DoThingJob

Where the non-namespace implementation is called, change it to call the implementation in the deprecated namespace. Then deploy and ensure that the application is working as expected and that the non-namespaced version of the code is not called or enqueued anywhere.

Now you can implement the new, improved approach in the correctly named location.

class DoThingJob
  # new implmentation
end

Once you’ve built, tested and changed all the calls to the new version you can delete the (now unused) namespaced version.

Why?

This is a habit that helps communicate intent.

These refactoring projects are often large in scale and it can be hard to see what’s going on in the midst of a series of changes. This methodology clearly lays out the intention to replace existing code.

If you call your new implementation NewSomething it’s not easy for other folks in your team (and future you) to understand the direction of development of the code. “Should I use the new version yet?”

Furthermore, you don’t run the risk of being left with lots of NewSomething objects littering your code when you inevitably don’t do the final renaming. :-)

Picking a single namespace for this kind of refactoring also gives you one place to look for all incomplete refactorings and unused code in your application.

This staggered renaming also lets you deploy changes in a series of smaller releases and avoids the creation of long-running refactoring branches that can be a nightmare to rebase, merge, and deploy.

Why not?

This is a personal preference. There’s no “code based” reason to do it.

Equally you shouldn’t automatically copy existing naming. Take the opportunity when naming the replacement implementation to better reflect what the new code is doing.

If you’re building an API consumed by customers/users outside your team this might not be a good solution. If you’re developing a new version, you’ll still likely to need to support the existing version. In this case a new v2 namespace is a good idea.

Anything Else?

You may want to couple this approach with adding deprecation methods to your previous implementation to communicate more fully, through your code, with other members of your team.


last updated on 02 Aug 2021 by @andycroll

photo by
Nick Fewings

Don’t miss my next post, sign up to the One Ruby Thing email and get my next post in your inbox.

Don’t miss my next post, sign up to the One Ruby Thing email and get my next post in your inbox.