views:

159

answers:

5

Starting with this code:

 new Person("ET").WithAge(88)

How can it be refactored to:

 new Person("ET", 88)

What sequence of refactorings needs to be performed to complete the transformation?

Why? Because there could be hundreds of these, and I wouldn't want to introduce errors by doing it manually.

Would you say a drawback with fluent interfaces is they can't easily be refactored?

NOTE: I want to do this automatically without hand typing the code.

A: 
  1. Write unit tests for the old code.

  2. Refactor until the tests pass again.

Chris Ballance
What I want to know is *how* to refactor it, I already know I want to refactor..
Bjorn Reppen
not very helpful answer.
Preet Sangha
+3  A: 

Assuming that WithAge is a method on Person that returns a Person, what about something like

Person(string name, int age)
{
    this.name = name;
    this.WithAge(age);
}

Or more generalized:

Person(SomeType originalParameter, FluentParamType fluentParameter)
{
    //Original constructor stuff
    this.FluentMethod(fluentParameter);
}

And then as make the FluentMethod private if you don't want it, or keep it public if you want to allow both ways.

Davy8
+4  A: 

Perhaps the simplest way to refactor this is to change the name "WithAge" to "InitAge", make InitAge private, then call it from your constructor instead. Then update all references of new Person(string).WithAge(int) to use the new constructor.

If WithAge is a one-liner, you can just move the code to your new constructor instead, and do away with InitAge altogether, unless having the additional method provides extra readability.

Having good unit tests will isolate where errors are introduced, if they are.

lc
Thanks. This is closer to what I'm looking for. But my real problem is how to update all references automatically using a combination of well-known refactorings (like Introduce parameter).
Bjorn Reppen
For this, I think you're safe to start with a global replace on "new Person(*).WithAge(" to "new Person(*," instead. I'm not sure there's a good automatic way because it's a different kind of refactoring than those typically address.
lc
And the good part is that the compiler will flag the error. Remove the one parameter constructor and substitute by the two parameter one. Then the compiler will flag all incorrect uses (old code). It will be tedious but safe.
David Rodríguez - dribeas
+1  A: 

I don't have any practical experience with that sort of thing, but if I was in your situation the place I'd go looking would be custom Eclipse refactorings (or the equivalent in Refactor! Pro for .Net if that's what you're using).

Basically what you want is a match and replace, except that your regular expressions should match abstract syntax trees rather than plain text. That's what automated refactorings are.

One risk of this refactoring is that the target version is less precise than the original. Consider:

class Person {
  public Person(String name, int age);
  public Person(String name, int numberOfChildren);
}

There is no way to tell which of these constructors the chained call to Person.WithAge should be replaced with.

So, automated support for this would have to check for such ambiguities before allowing you to proceed. If there is already a constructor with the target parameters, abort the refactoring.

Other than that it seems pretty straightforward. Give the new constructor the following content:

public Person(String name, int age) {
  this(name);
  withAge(age);
}

Then you can safely replace the original call with the new.

(There is a subtle additional risk, in that calling withAge within the constructor, i.e. on a partially constructed object, isn't quite the same as calling it after the constructor. The difference matters if you have an inheritance chain and if withAge does something non-trivial. But then that's what your unit tests are for...)

Morendil
In some languages you might also get a problem if there is an implicit conversion, e.g. an existing constructor accepts a double and some calls are already passing an int to that. Adding a new constructor that takes int will cause a closer match for those calls, changing their meaning.
Daniel Earwicker
+2  A: 

If this is C# (ideally you would tag the question with the language), the Person class needs this constructor:

public Person(string name, int age)
    : this(name) { WithAge(age); }

To then change all client code to call this new constructor where appropriate, you would need to find all occurrences of the pattern:

new Person(x1).WithAge(x2)

where x1 and x2 are expressions, and replace them with:

new Person(x1, x2)

If there are other modifier methods aside from WithAge, it might get more complicated. For example:

new Person(x1).WithHair(x2).WithAge(x3)

Perhaps you'd want that to become:

new Person(x1, x3).WithHair(x2)

It all depends on whether you have an IDE that lets you define language-aware search/replace patterns like that. You can get a long way to the solution with simple textual search and replace, combined with a macro that replays a sequence of key presses.

Would you say a drawback with fluent interfaces is they can't easily be refactored?

Not especially - it's more that refactoring features in IDEs are either designed flexibly enough to let you creatively invent new refactorings, or else they are hard-coded for certain common cases. I'd prefer the common cases to be defined as examples that I could mutate to invent new ones.

Daniel Earwicker