Как рефакторинговать цепные методы?


Начиная с этого кода:

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

Как его можно преобразовать в:

 new Person("ET", 88)

Какую последовательность рефакторингов необходимо выполнить для завершения преобразования?

Почему? Потому что их могут быть сотни, и я не хотел бы вводить ошибки, делая это вручную.

Можно ли сказать, что недостаток fluent-интерфейсов заключается в том, что их нелегко перефокусировать?

Примечание: Я хочу сделать это автоматически, не набирая вручную код.
5 3

5 ответов:

Возможно, самый простой способ рефакторинга - это изменить имя " WithAge "на" InitAge", сделать InitAge приватным, а затем вызвать его из конструктора. Затем обновите все ссылки new Person(string).WithAge(int), чтобы использовать новый конструктор.

Если WithAge является однострочным, вы можете просто переместить код в свой новый конструктор и полностью избавиться от InitAge, если только наличие дополнительного метода не обеспечивает дополнительную читаемость.

Наличие хороших модульных тестов изолирует, где вводятся ошибки, если они являются.

Предполагая, что WithAge-это метод на человеке, который возвращает человека, как насчет чего-то вроде

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

Или более обобщенно:

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

А затем, как сделать FluentMethod частным, если вы не хотите его, или сохранить его открытым, если вы хотите разрешить оба способа.

Если это C# (в идеале вы бы пометили вопрос языком), то класс Person нуждается в этом конструкторе:

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

Чтобы затем изменить весь клиентский код для вызова этого нового конструктора, где это уместно, вам нужно будет найти все вхождения шаблона:

new Person(x1).WithAge(x2)

, где x1 и x2-выражения, и заменить их на:

new Person(x1, x2)

Если есть другие методы модификатора, кроме WithAge, это может усложниться. Например:

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

Возможно, вы захотите что стать:

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

Все зависит от того, есть ли у вас IDE, которая позволяет определять такие шаблоны поиска/замены с учетом языка. Вы можете пройти долгий путь к решению с помощью простого текстового поиска и замены, в сочетании с макросом, который воспроизводит последовательность нажатий клавиш.

Вы бы сказали, недостаток с беглым языком интерфейсы - это то, что они не могут быть легко рефакторинг?

Не особенно - это больше, что рефакторинг функции в IDE либо разработаны достаточно гибко, чтобы позволить вам творчески изобретать новые рефакторинги, или же они жестко запрограммированы для некоторых распространенных случаев. Я бы предпочел, чтобы общие случаи были определены как примеры, которые я мог бы мутировать, чтобы изобрести новые.

У меня нет никакого практического опыта в таких вещах, но если бы я был в вашей ситуации, я бы искалпользовательские рефакторинги Eclipse (или эквивалент в Рефакторе! Pro для .Net, если это то, что вы используете).

В основном то, что вы хотите, - это сопоставление и замена, за исключением того, что ваши регулярные выражения должны соответствовать абстрактным синтаксическим деревьям, а не простому тексту. Вот что такое автоматический рефакторинг.

Одним из рисков такого рефакторинга является то, что целевая версия менее точна, чем оригинал. Рассмотрим:

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

Невозможно сказать, к какому из этих конструкторов прикован зов человека.WithAge следует заменить на.

Таким образом, автоматизированная поддержка для этого должна была бы проверить такие неоднозначности, прежде чем позволить вам продолжить. Если уже есть конструктор с целевыми параметрами, прервите рефакторинг.

Кроме того, что это кажется довольно простым. Дайте новому конструктору следующее содержание:

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

Тогда вы можете смело заменить исходный вызов новым.

(существует тонкий дополнительный риск, заключающийся в том, что вызов withAge внутри конструктора, то есть на частично сконструированном объекте, не совсем то же самое, что вызов его после конструктора. Разница имеет значение, если у вас есть цепочка наследования и , если withAge делает что-то нетривиальное. Но тогда для этого и нужны ваши модульные тесты...)

  1. Напишите модульные тесты для старого кода.

  2. Рефакторинг до тех пор, пока тесты не пройдут снова.