Refactoring your code across packages/repositories

Modern IDEs allow you to do lots of refactoring regularly. It’s so easy that you can always make rather big changes to your code and IDE will just handle it for you. But that’s all true when you refactor inside single project/repository. And what you do when your project is spanned across multiple GIT repositories?

New feature for our Node JS project was going nicely thanks to Domain Driven Design approach. Also IDE plays big role in that because of all kind of refactoring actions that make all changes a breeze.

One day though that changed. Our Domain was nice but need arose to reuse it across different services. The most obvious option was to move the domain to separate npm package and import it in services that need it. And this is where we started to have trouble.

So no more fun: renaming method name and all it’s usages is no more automatic. You need to handle it some other way. But there seems no easy way to do that. PhpStorm (which is WebStorm with DB support, who needs PHP anyway?) can still find all the usages of the method, but it won’t allow you rename it. The only option you have is to do all the renames manually. And synchronize that between different packages…

Have you encountered such a situation? Do you have some advice how to deal with that?

Ideally IDE should be able to allow you to do some refactoring in such a case.

I would think about renaming a method in class like a column rename in DB table. In code it would be probably very similar:

  1. Rename a method in package, but leave the old method’s signature intact and make it only call a new one
  2. Mark old method deprecated and state which method you need to use
  3. In service that uses your package IDE should allow you to do the switch between methods with the same signature – so that you can switch all your uses to new method.
  4. Remove old method in package when nobody is using it anymore (that’s more or less easy when you are in charge on all the repositories)

That way it would be really useful and still shouldn’t be very hard to implement. What do you think?

If we go further we can add more of that kind of refactoring, i. e.:

  • moving a file/class in directory structure – again make a “symlink” from the old one to new one and later remove
  • change method signature – add an override to method which will support new and old signatures, change client code, remove

And list can go on. The key thing here is ability to switch implementations. Is that possible? It would be really nice to have in IDEs.

Please let me know what do you think about this approach or maybe there is different option that I am not aware of?

Here is an example project with all the steps.

Initial project before refactoring

Main project:

import { Hotel } from "./packageA/Hotel"

const hotel = new Hotel("Palm Hotel")

hotel.placeGuest("John Smith")

Package:

import { Accommodation } from "./Accommodation"

export class Hotel {
  private name: string

  private accommodations: Accommodation[] = []

  constructor(name: string) {
    this.name = name
  }

  public placeGuest(guestName: string): void {
    this.accommodations.push(new Accommodation(guestName))
  }

  public getGuestCount(): number {
    return this.accommodations.length
  }
}

Renaming Hotel’s placeGuest method

If you were in the same project you would just rename the method name in place and all it’s usages. But if you are in different packages you would do everything in steps.

  1. Add new method, copy the content of old one there, make a deprecated alias for the old method name:
import { Accommodation } from "./Accommodation"

export class Hotel {
  private name: string

  private accommodations: Accommodation[] = []

  constructor(name: string) {
    this.name = name
  }
  
  public accommodateGuest(guestName: string): void {
    this.accommodations.push(new Accommodation(guestName))
  }

  /**
   * Deprecated
   *
   * @param guestName
   * @deprecated Use {@link accommodateGuest} instead
   */
  public placeGuest(guestName: string): void {
    return this.accommodateGuest(guestName)
  }

  public getGuestCount(): number {
    return this.accommodations.length
  }
}

2. Replace all the usages of the method in main project:

import { Hotel } from "./packageA/Hotel"

const hotel = new Hotel("Palm Hotel")

hotel.accommodateGuest("John Smith")

3. Remove the old deprecated method:

import { Accommodation } from "./Accommodation"

export class Hotel {
  private name: string

  private accommodations: Accommodation[] = []

  constructor(name: string) {
    this.name = name
  }

  public accommodateGuest(guestName: string): void {
    this.accommodations.push(new Accommodation(guestName))
  }

  public getGuestCount(): number {
    return this.accommodations.length
  }
}

In this particular case it’s easy to replace the usage manually, but it’s of course not always like and IDE’s ability to do so would be really helpful.

Ділись:

Leave a Reply

Your email address will not be published. Required fields are marked *