When the team struggles to reuse a public function, it’s very common to hear this:

This public API is too hard to reuse, let's refactor it.

Before getting into why that statement is wrong, let's understand what "refactoring" means.

According to Martin Fowler, the act of refactoring is:

[…] to restructure software by applying a series of refactorings without changing its observable behavior — Martin Fowler on the "Definition Of Refactoring" written in 2004.

Now, what's "observable behavior"? Fowler also has a definition of what that means:

[…] it means that the software still does what it did before

Well… still not very useful.

Is changing the interface considered refactoring? Yes and no, it depends on the interface. If you have control of the callers (not a published interface), then it’s refactoring.

Ok, let’s look at some examples.

Imagine an existing public library to validate an Australian address in NodeJS. It receives an Object Literal as an argument named input that contains the property value of type String representing the address to validate:

The simplified running code example of an Australian address validation function. The function is named “validate Australian address” which is called with an argument named “input”. The “input” is expected to be an "Object Literal" with the property “value” of type String.

After some time, you realize the API is confusing. You decide to change it to be called with a simple String as the argument which represents the address to validate.

This is the first step to change the public interface:

The diff for the first step change. The "input" has changed from accepting an "Object Literal" to accept a "String". The internal functions still accept an "Object Literal" with the property "value".

This is the second step to rename the input argument to address for the purpose of code clarity:

The diff for the second step change. The "input" of the public facing API has been renamed to "address" and passed through to the internal functions inside of an "Object Literal" with the property "value".

Here's the final running code:

The simplified running code example for the Australian address validation function with the first two steps applied.

The first step is NOT refactoring. The API is from a public library, which means changing the interface introduces an incompatible breaking change. It will change the observable behavior for consumers of the public library.

The second step IS refactoring. You're not changing any observable behavior, just renaming the argument.

Ok, let’s look at one more example from another perspective.

After the changes from before, you notice the internal functions are still using the old contract of accepting an Object Literal and a value property. Now you decide to change the interface for the internal functions.

This is the third step applied to 1 of the 2 internal functions:

The diff for the third step change. The "input" of the internal function to validate the suburb has changed from accepting an "Object Literal" with the property "value" to accept a single "String".

Here’s the running code with the third step applied:

The simplified running code example for the Australian address validation function with the third step applied.

From the point of view of the validate Australian Address function, the interface change for has Suburb is NOT refactoring. After all, the change requires the function validate Australian Address to modify how it calls the suburb validation.

From the point of view of the public consumers of the validate Australian Address function, the API change for has Suburb IS refactoring. After all, nothing has to change on their end.

Given that, you could also define refactoring like this:

Refactoring is to apply a series of small code change [1] steps [2] affecting the structure of the code without affecting the observable behavior [3] for consumers [4].

[1]: The checkpoint to define when something has "changed" is after any command that can be used to apply a change: a commit, file save, git add , etc.

[2]: A step is small as long as it doesn't leave the application on a broken state (syntax error, failing test).

[3]: The "no change in behavior" aspect of refactoring is very important to validate the use of TDD. If the code was created using TDD, changes should make a test break, unless they’re refactoring. If a test doesn’t break when changing the code that represents behavior, then the code was not created using TDD.

Speaking of which, can you look at this JSFiddle example? You might find something interesting :)

[4]: Consumers of the code can be internal or external.

So going back to the initial statement:

This public API is too hard to reuse, let’s refactor it.

If the interface of the API is public and hard to use, then when you change it, it’s not gonna be refactoring. It’s gonna be a redesign.

This public API is too hard to reuse, let’s redesign it.

As I've written in 2016, due to Wittgenstein’s Beetle, it is possible that in a real conversation two individuals believe they are talking about the same “thing”, but in reality, they are talking about things that are totally different.

Refactoring is one example of that.

Only when you make a code change that doesn't modify behavior or the public interface between APIs that you can call refactoring. However, even if you change the interface, it may still be considered refactoring if the change is invisible.

Is it a refactoring or not?

As almost everything in software, there’s no simple response.

The answer depends on who's asking.

It just… depends.