Мы злоупотребляем статическими методами?

13

Пару месяцев назад я начал работать над новым проектом, и при прохождении кода меня поразило количество используемых статических методов. В collectionToCsvString(Collection<E> elements)них хранятся не только служебные методы , но и множество бизнес-логики.

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

@Service
public class CustomerReceiptCreationService {

    public CustomerReceipt createReceipt(Object... args) {
        CustomerReceipt receipt = new CustomerReceipt();
        // creation logic
        return receipt;
    }
}

Теперь парень сказал, что ему не нравится, когда Spring управляет ненужными классами, в основном потому, что он накладывает ограничение на то, что клиентские классы должны быть самими компонентами Spring. В конце концов мы имеем все, что управляется Spring, что в значительной степени заставляет нас работать с объектами без состояния процедурным способом. Более или менее то, что указано здесь https://www.javacodegeeks.com/2011/02/domain-driven-design-spring-aspectj.html

Таким образом, вместо приведенного выше кода, он имеет

public class CustomerReceiptCreator {

    public static CustomerReceipt createReceipt(Object... args) {
        CustomerReceipt receipt = new CustomerReceipt();
        // creation logic
        return receipt;
    }
}

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

new CustomerReceiptCreator().createReceipt()

Он утверждает, что статические методы имеют некоторые дополнительные преимущества. А именно:

  • Легче читать. Импортируйте статический метод, и нам нужно заботиться только о действии, а не о том, какой класс его выполняет.
  • Очевидно, это метод, свободный от вызовов БД, поэтому с точки зрения производительности он дешев; и это хорошая вещь, чтобы прояснить это, так что потенциальный клиент должен пойти в код и проверить это.
  • Проще писать тесты.

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

Итак, мой вопрос: каковы потенциальные подводные камни этого способа программирования?

user3748908
источник
4
staticМетод , который вы иллюстрирующие выше просто обычный фабричный метод. Создание фабричных методов статичными является общепринятым соглашением по ряду веских причин. Уместен ли здесь заводской метод, это другой вопрос.
Роберт Харви

Ответы:

23

Какая разница между new CustomerReceiptCreator().createReceipt()а CustomerReceiptCreator.createReceipt()? Почти нет. Единственным существенным отличием является то, что в первом случае синтаксис значительно более неловкий. Если вы следуете первому, полагая, что каким-то образом избегая статических методов, ваш код станет лучше, вы ошибаетесь. Создание объекта просто для вызова одного метода является статическим методом с тупым синтаксисом.

То, что все меняется, - это когда ты вводишь, CustomerReceiptCreatorа не newвводишь. Давайте рассмотрим пример:

class OrderProcessor {
    @Inject CustomerReceiptCreator customerReceiptCreator;

    void processOrder(Order order) {
        ...
        CustomerReceipt receipt = customerReceiptCreator.createReceipt(order);
        ...
    }
}

Давайте сравним эту версию статического метода:

void processOrder(Order order) {
    ...
    CustomerReceipt receipt = CustomerReceiptCreator.createReceipt(order);
    ...
}

Преимущество статической версии состоит в том, что я могу легко рассказать вам, как она взаимодействует с остальной частью системы. Это не так. Если я не использовал глобальные переменные, я знаю, что остальная часть системы не изменилась. Я знаю, что никакая другая часть системы не может влиять на получение здесь. Если я использовал неизменяемые объекты, я знаю, что порядок не изменился, и createReceipt является чистой функцией. В этом случае я могу свободно перемещать / удалять / и т.д. этот вызов, не беспокоясь о случайных непредсказуемых эффектах в других местах.

Я не могу сделать такие же гарантии, если я ввел CustomerReceiptCreator. Это может иметь внутреннее состояние, которое изменяется вызовом, на меня может повлиять или изменить другое состояние. Могут быть непредсказуемые отношения между операторами в моей функции, так что изменение порядка приведет к неожиданным ошибкам.

С другой стороны, что произойдет, если CustomerReceiptCreatorвдруг понадобится новая зависимость? Допустим, нужно проверить флаг функции. Если бы мы делали инъекцию, мы могли бы сделать что-то вроде:

public class CustomerReceiptCreator {
    @Injected FeatureFlags featureFlags;

    public CustomerReceipt createReceipt(Order order) {
        CustomerReceipt receipt = new CustomerReceipt();
        // creation logic
        if (featureFlags.isFlagSet(Flags::FOOBAR)) {
           ...
        }
        return receipt;
    }
}

Тогда мы закончили, потому что вызывающий код будет введен с, CustomerReceiptCreatorкоторый будет автоматически введен FeatureFlags.

Что если бы мы использовали статический метод?

public class CustomerReceiptCreator {
    public static CustomerReceipt createReceipt(Order order, FeatureFlags featureFlags) {
        CustomerReceipt receipt = new CustomerReceipt();
        // creation logic
        if (featureFlags.isFlagSet(Flags::FOOBAR)) {
           ...
        }
        return receipt;
    }
}

Но ждать! код вызова также должен быть обновлен:

void processOrder(Order order) {
    ...
    CustomerReceipt receipt = CustomerReceiptCreator.createReceipt(order, featureFlags);
    ...
}

Конечно, это все еще оставляет вопрос о том, откуда он processOrderберется FeatureFlags. Если нам повезет, тропа заканчивается здесь, если нет, то необходимость проходить через FeatureFlags продвигается дальше вверх по стеку.

Здесь есть компромисс. Статические методы, требующие явной передачи зависимостей, что приводит к увеличению объема работы. Внедренный метод сокращает объем работы, но делает зависимости неявными и, следовательно, скрытыми, что затрудняет анализ кода.

Уинстон Эверт
источник