Статьи

Временная связь между вызовами методов

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

class Foo {
  public List<String> names() {
    List<String> list = new LinkedList();
    Foo.append(list, "Jeff");
    Foo.append(list, "Walter");
    return list;
  }
  private static void append(
    List<String> list, String item) {
    list.add(item.toLowerCase());
  }
}

Что ты об этом думаешь? Я считаю, что понятно, что names()делает — создание списка имен. Чтобы избежать дублирования, существует дополнительная процедура , append()которая преобразует элемент в нижний регистр и добавляет его в список.

Это плохой дизайн.

Это процедурный дизайн, и в методе есть временная связь между строками names().

Позвольте мне сначала показать вам лучший (хотя и не лучший!) Дизайн:

class Foo {
  public List<String> names() {
    return Foo.with(
      Foo.with(
        new LinkedList(),
        "Jeff"
      ),
      "Walter"
    );
  }
  private static List<String> with(
    List<String> list, String item) {
    list.add(item.toLowerCase());
    return list;
  }
}

Идеальная схема для метода with()создаст новый экземпляр List, заполнит его addAll(list), затем перейдет add(item)к нему и, наконец, вернет. Это было бы совершенно неизменным , но медленно.

Итак, что не так с этим:

List<String> list = new LinkedList();
Foo.append(list, "Jeff");
Foo.append(list, "Walter");
return list;

Выглядит идеально чисто, не так ли? Создайте список, добавьте к нему два элемента и верните его. Да, это чисто — пока. Потому что мы помним, что append()делаем. Через несколько месяцев мы вернемся к этому коду, и он будет выглядеть так:

List<String> list = new LinkedList();
// 10 more lines here
Foo.append(list, "Jeff");
Foo.append(list, "Walter");
// 10 more lines here
return list;

Это так ясно сейчас, что append()на самом деле добавляет "Jeff"к list? Что будет, если я уберу эту строку? Повлияет ли это на результат, возвращаемый в последней строке? Я не знаю. Мне нужно проверить тело метода, append()чтобы убедиться.

А как насчет того, чтобы вернуться listсначала и позвонить append()потом? Вот что возможный «рефакторинг» может сделать с нашим кодом:

List<String> list = new LinkedList();
if (/* something */) {
  return list;
}
// 10 more lines here
Foo.append(list, "Walter");
Foo.append(list, "Jeff");
// 10 more lines here
return list;

Прежде всего, мы возвращаемся listслишком рано, когда он еще не готов. Но кто-нибудь сказал мне, что эти два вызова append()должны произойти раньше return list? Во-вторых, мы изменили порядок append()звонков. Опять же, кто-нибудь сказал мне, что важно называть их именно в этом порядке?

Никто. Нигде. Это называется временной связью .

Наши линии связаны между собой. Они должны оставаться в этом конкретном порядке, но знания об этом порядке скрыты. Разрушить порядок легко, и наш компилятор не сможет нас поймать.

Наоборот, у этого дизайна нет никакого «заказа»:

return Foo.with(
  Foo.with(
    new LinkedList(),
    "Jeff"
  ),
  "Walter"
);

Он просто возвращает список, который создается несколькими вызовами with()метода. Это одна строка вместо четырех.

Как обсуждалось ранее , идеальный метод в ООП должен иметь только одно утверждение, и это утверждение есть return.

То же самое относится и к валидации. Например, этот код плохой:

list.add("Jeff");
Foo.checkIfListStillHasSpace(list);
list.add("Walter");

Хотя этот намного лучше

list.add("Jeff");
Foo.withEnoughSpace(list).add("Walter");

Увидеть разницу?

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