Статьи

Пустые методы как поведение черных дыр

Этот пост можно было бы озаглавить «Пустые методы считаются вредными», если бы «признанные вредными» эссе сами по себе не считались вредными . О боже

Пустые методы вездесущи в большинстве объектно-ориентированных кодовых баз. Прямое следствие изменчивого состояния или ввода-вывода где-то в вашей среде выполнения, вы можете обернуть любое поведение, которое фанаты функционального программирования назовут нечистым , которое в принципе не имеет значимого возвращаемого значения. Один общий пример — во время начальной загрузки приложения, например, в Java:

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
@Resource MyClass implements Runnable {
 
// ...
 
@PostConstruct
public void init() {
    if(this.enabled) {
        this.executorService.scheduleAtFixedRate(
            this,
            0,
            500,
            TimeOut.MILLISECONDS);
    }
}
 
// ...
}

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

Ваш public интерфейс

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

Начнем с предыдущего примера:

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
@Resource MyClass implements Runnable {
 
// ...
 
@PostConstruct
public void init() {
    if(this.enabled) {
        this.executorService.scheduleAtFixedRate(
            this,
            0,
            500,
            TimeOut.MILLISECONDS);
    }
}
 
// ...
}

Наш класс, вероятно, получает какой-то экземпляр executorService во время построения, возможно, полученный из некоторого кода для внедрения зависимостей , после чего запускается рабочий график. Вероятность того, что клиентскому коду необходимо явно вызвать init() , обычно очень мала. Это говорит о том, что наш метод @PostConstruct вероятно, должен иметь более ограниченную видимость, может быть, private или protected , и на этом все закончится.

Но так ли это на самом деле?

способность быть свидетелем в суде

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

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
// changed code from the original MyClass file:
@PostConstruct
public ScheduledFuture<T> init() {
    if(this.enabled) {
        return this.executorService.scheduleAtFixedRate(
            this,
            0,
            500,
            TimeOut.MILLISECONDS);
    }
}
 
 
public testExecutorShutdown(){
    ScheduledExecutorService service = Executors.newSingleThreadScheduledExecutor();
    MyClass c = new MyClass(service, true); // executorService, enabled
    ScheduledFuture<T> scheduled = c.init();
    executorService.shutdown();
    scheduled.get(1, TimeUnit.SECONDS); // throws exception
}

Приведенный выше тестовый код проверяет, что запланированное действие завершается в течение 1 секунды (или двух запланированных итераций) после выключения исполнителя. Такой тест основан на доступе к будущему объекту, возвращенному методом init.

Самодокументирование

именно человеческое восприятие закрыто за горизонтом их нынешнего сознания

Элия ​​Мудрая

Изменение, которое мы внесли в метод init() включило поведенческий тест, но внесло важный побочный эффект: объект ScheduledFuture теперь является частью открытого интерфейса MyClass , что означает, что теперь любой клиентский код может взаимодействовать с ним. Является ли это желаемым свойством, действительно зависит от MyClass использования, который MyClass предназначен для поддержки, и, возможно, вы хотите инкапсулировать ScheduledFuture в более дружественный класс, который, например, предоставляет только что-то вроде bool isDone() .

В любом случае, сохранение вышеупомянутого метода init недействительным всегда приведет к тому, что ваш клиентский код (или разработчик, взглянув на сигнатуру init с использованием его / ее IDE), будет не MyClass.init() что в действительности делает MyClass.init() . Просто посмотрите на разные подписи и подумайте о том, как вы кодируете каждую из них:

1
2
public void init()
public ScheduledFuture<T> init()

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

Делай одну вещь и делай это хорошо

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

Выводы

Будьте добры к себе в будущем и вообще ко всем разработчикам, использующим ваш код, и никогда не скрывайте такую ​​ценную информацию, как возвращаемое значение, в вашем публичном API, никогда больше.

Объятия и поцелуи, в.

Смотрите оригинальную статью здесь: Пустые методы как поведение черных дыр

Мнения, высказанные участниками Java Code Geeks, являются их собственными.