Статьи

Раздутый JavaBeans — не добавляйте «получатели» в свой API

Недавно я писал в блоге об идее, как можно расширить JavaBeans ™, чтобы уменьшить раздувание, созданное этим широко принятым соглашением в мире Java. Эта статья была перезагружена на DZone и получила здесь весьма противоречивые отзывы (как и большинство идей, которые пытаются внести некоторые свежие идеи в мир Java). Я хочу вернуться к одной из моих мыслей в этой статье, которой уделялось немного меньше внимания, а именно:

Имена геттеров и сеттеров

Почему я должен использовать эти раздутые префиксы «get» / «is» и «set» каждый раз, когда я хочу манипулировать свойствами объекта? Кроме того, изменяется регистр первой буквы свойства. Если вы хотите выполнить поиск с учетом регистра при любом использовании

Для этого вам нужно написать довольно регулярное выражение. У меня есть проблемы с пониманием, почему мы должны использовать геттеры повсюду. Методы получения / установки — это соглашение, обеспечивающее абстракцию доступа к свойству. То есть вы обычно пишете глупые вещи, подобные этой, все время:

01
02
03
04
05
06
07
08
09
10
11
public class MyBean {
    private int myProperty;
 
    public int getMyProperty() {
        return myProperty;
    }
 
    public void setMyProperty(int myProperty) {
        this.myProperty = myProperty;
    }
}

ХОРОШО. Давайте признаем, что это, кажется, наша повседневная жизнь как разработчика Java, пишущего все это, вместо использования стандартных ключевых слов или аннотаций. Я говорю о стандартах, а не о таких вещах, как Project Lombok . Приняв факты из жизни, давайте посмотрим на java.io.File для более подробной информации. Для меня это хороший пример, когда JavaBean-o-mania ™ пошла не так. Почему? Проверьте этот экстракт исходного кода:

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
public class File {
 
    // This is the only relevant internal property. It would be 'final'
    // if it wasn't set by serialisation magic in readObject()
    private String path;
 
    // Here are some arbitrary actions that you can perform on this file.
    // Usually, verbs are used as method names for actions. Good:
    public boolean delete();
    public void deleteOnExit();
    public boolean mkdir();
    public boolean renameTo(File dest);
 
    // Now the fun starts!
    // Here is the obvious 'getter' as understood by JavaBeans™
    public String getPath();
 
    // Here are some additional 'getters' that perform some transformation
    // on the underlying property, before returning it
    public String getName();
    public String getParent();
    public File getParentFile();
    public String getPath();
 
    // But some of these 'transformation-getters' use 'to', rather than
    // 'get'. Why 'toPath()' but not 'toParentFile()'? How to distinguish
    // 'toPath()' and 'getPath()'?
    public Path toPath();
    public URI toURI();
 
    // Here are some 'getters' that aren't really getters, but retrieve
    // their information from the underlying file
    public long getFreeSpace();
    public long getTotalSpace();
    public long getUsableSpace();
 
    // But some of the methods qualifying as 'not-really-getters' do not
    // feature the 'get' action keyword, duh...
    public long lastModified();
    public long length();
 
    // Now, here's something. 'Setters' that don't set properties, but
    // modify the underlying file. A.k.a. 'not-really-setters'
    public boolean setLastModified(long time);
    public boolean setReadable(boolean readable);
    public boolean setWritable(boolean writable);
 
    // Note, of course, that it gets more confusing when you look at what
    // seem to be the 'not-really-getters' for the above
    public long lastModified();
    public boolean canRead();
    public boolean canWrite();
}

Смущенный? Да. Но все мы в конечном итоге все делали так или иначе. jOOQ ничем не отличается, но будущие версии это исправят.

Как улучшить вещи

Не все библиотеки и API имеют недостатки в этом смысле. Java прошла долгий путь и была написана многими людьми с разными взглядами на эту тему. Кроме того, Java чрезвычайно обратно совместима, поэтому я не думаю, что JDK, если он будет написан с нуля, все равно будет страдать от «JavaBean-o-mania ™» в той же степени. Итак, вот пара правил, которым можно следовать в новых API, чтобы немного разобраться:

  1. Прежде всего, решите, будет ли ваш API использоваться в основном в среде Spring или Heavy JSP / JSF, или в любой другой среде, в которой используются языки выражений на основе JavaBeans ™, где вы действительно хотите следовать стандартному соглашению. В этом случае, однако, СТРОГО следуйте соглашению и не называйте какой-либо метод поиска информации, подобный этому: «File.length ()». Если вы придерживаетесь этой парадигмы, ВСЕ ваши методы должны начинаться с глагола, а не с существительного / прилагательного
  2. Вышеприведенное относится к нескольким библиотекам, поэтому вам, вероятно, НИКОГДА не следует использовать «get», если вы хотите получить доступ к объекту, который не является свойством. Просто используйте имя свойства (существительное, прилагательное). Это будет выглядеть намного проще на сайте вызовов, особенно если ваша библиотека используется на таких языках, как Scala. Таким образом, «File.length ()» был хорошим выбором, так же как «Enum.values ​​()», а не «File.getLength ()» или «Enum.getValues ​​()».
  3. Вероятно, вам НЕ следует использовать «get» / «set», если вы хотите получить доступ к свойствам. Java может легко отделить пространство имен для имен свойств / методов. Просто используйте имя свойства в получателе / ​​установщике, например так:
    01
    02
    03
    04
    05
    06
    07
    08
    09
    10
    11
    public class MyBean {
        private int myProperty;
     
        public int myProperty() {
            return myProperty;
        }
     
        public void myProperty(int myProperty) {
            this.myProperty = myProperty;
        }
    }

    Впрочем, еще раз подумайте о первом правиле. Если вы хотите настроить свой компонент с помощью Spring, у вас может не быть выбора. Но если вам не нужен Spring, у приведенного выше есть следующие преимущества:

    • Ваши геттеры, сеттеры и свойства имеют одно и то же имя (и регистр начальной буквы). Текстовый поиск по базе кода намного проще
    • Получатель выглядит так же, как само свойство в таких языках, как Scala, где это эквивалентные выражения благодаря сахарному синтаксису языка: «myBean.myProperty ()» и «myBean.myProperty».
    • Получатель и установщик находятся рядом друг с другом в лексикографическом порядке (например, в представлении Outline вашей IDE). Это имеет смысл, так как само свойство более интересно, чем бездействие «получения» и «установки»
    • Вам никогда не придется беспокоиться о том, выбрать ли «получить» или «есть». Кроме того, есть пара свойств, где «get» / «is» в любом случае неуместны, например, когда задействовано «has» -> «getHasChildren ()» или «isHasChildren ()»? Мех, назовите его «hasChildren ()» !! «SetHasChildren (true)»? Нет, «hasChildren (правда)» !!
    • Вы можете следовать простым правилам именования: использовать глаголы в императивной форме для выполнения действий. Используйте существительные, прилагательные или глаголы в форме от третьего лица для доступа к объектам / свойствам. Это правило уже доказывает, что стандартное соглашение ошибочно. «Получить» является императивной формой, тогда как «есть» является формой третьего лица.
  4. Подумайте о том, чтобы вернуть «это» в установщик. Некоторые люди просто любят цепочку методов:
    1
    2
    3
    4
    5
    6
    7
    public MyBean myProperty(int myProperty) {
        this.myProperty = myProperty;
        return this;
    }
     
    // The above allows for things like
    myBean.myProperty(1).myOtherProperty(2).andThen(3);

    В качестве альтернативы, верните предыдущее значение, например:

    1
    2
    3
    4
    5
    6
    7
    8
    public int myProperty(int myProperty) {
        try {
            return this.myProperty;
        }
        finally {
            this.myProperty = myProperty;
        }
    }

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

    В любом случае, иметь «void» в качестве возвращаемого типа — пустая трата области действия API. В частности, рассмотрим лямбда-синтаксис Java 8 для методов с / без возвращаемого значения (взятых из состояния лямбда-презентаций Брайана Гетца ):

    01
    02
    03
    04
    05
    06
    07
    08
    09
    10
    // Aaaah, Callables without curly braces nor semi-colons
    blocks.filter(b -> b.getColor() == BLUE);
     
    // Yuck! Blocks with curly braces and an extra semi-colon!
    blocks.forEach(b -> { b.setColor(RED); });
     
    // In other words, following the above rules, you probably
    // prefer to write:
    blocks.filter(b -> b.color() == BLUE)
          .forEach(b -> b.color(RED));

    Размышление об этом сейчас может стать решающим преимуществом вашего API по сравнению с вашими конкурентами, как только Java 8 будет запущена (для тех из нас, кто поддерживает публичный API).

  5. Наконец, используйте «get» и «set» там, где вы действительно хотите подчеркнуть семантику ДЕЙСТВИЙ, называемых «получение» и «установка». Это включает в себя получение и настройку объектов для таких типов, как:
    • Списки
    • Карты
    • использованная литература
    • ThreadLocals
    • фьючерсы
    • и т.д…

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

Резюме

Будьте изобретательны при разработке API. Не строго следуйте скучным правилам JavaBeans ™ и Spring для всей отрасли. Более поздние API JDK, а также хорошо известные API от Google / Apache практически не используют «get» и «set» при доступе к объектам / свойствам. Java — это статический, безопасный для типов язык. Языки выражений и конфигурация с помощью инъекций являются исключением в нашей повседневной работе. Следовательно, мы должны оптимизировать наш API для тех случаев использования, с которыми мы имеем дело чаще всего. Лучше, если Spring адаптирует свое мышление к красивым, скудным, красивым и забавным API, а не заставляет мир Java наполнять свои API скучными вещами, такими как геттеры и сеттеры!

Ссылка: раздутые JavaBeans — не добавляйте геттеры в свой API от нашего партнера по JCG Лукаса Эдера из блога JAVA, SQL и AND JOOQ .