В Data Geekery мы любим Java. И так как мы действительно входим в свободный API jOOQ и запросы DSL , мы абсолютно взволнованы тем, что Java 8 принесет в нашу экосистему.
Ява 8 Пятница
Каждую пятницу мы показываем вам пару замечательных новых функций Java 8 в виде учебника, в которых используются лямбда-выражения, методы расширения и другие замечательные вещи. Вы найдете исходный код на GitHub .
Большинство внутренних DSL устарели
Это довольно убедительное заявление одного из самых продвинутых внутренних DSL на рынке . Позволь мне объяснить:
Языки сложны
Изучать новый язык (или API) сложно. Вы должны понимать все ключевые слова, конструкции, операторы и типы выражений и т. Д. Это верно как для внешних DSL, так и для внутренних DSL и «обычных» API, которые, по сути, являются внутренними DSL с меньшей беглостью.
При использовании JUnit люди привыкли использовать спички подколенного сухожилия . Тот факт, что они доступны на шести языках (Java, Python, Ruby, Objective-C, PHP, Erlang) делает их в некотором смысле разумным выбором. В качестве предметно-ориентированного языка они создали идиомы, которые легко читать, например,
|
1
2
3
|
assertThat(theBiscuit, equalTo(myBiscuit));assertThat(theBiscuit, is(equalTo(myBiscuit)));assertThat(theBiscuit, is(myBiscuit)); |
Когда вы прочитаете этот код, вы сразу «поймете», что утверждается, потому что API выглядит как проза. Но научиться писать код в этом API сложнее. Вы должны будете понять:
- Откуда берутся все эти методы
- Какие существуют методы
- Кто мог бы расширить подголовник с помощью пользовательских Matchers
- Каковы лучшие практики при расширении DSL
Например, в приведенном выше примере, в чем именно разница между тремя? Когда я должен использовать один, а когда другой? Проверяет ли is() идентичность объекта? equalTo() проверяет равенство объектов?
Урок Hamcrest продолжается такими примерами:
|
1
2
3
|
public void testSquareRootOfMinusOneIsNotANumber() { assertThat(Math.sqrt(-1), is(notANumber()));} |
Вы можете видеть, что notANumber() видимому, является пользовательским сопоставителем, реализованным в некотором месте в утилите:
|
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
|
public class IsNotANumberextends TypeSafeMatcher<Double> { @Override public boolean matchesSafely(Double number) { return number.isNaN(); } public void describeTo(Description description) { description.appendText("not a number"); } @Factory public static <T> Matcher<Double> notANumber() { return new IsNotANumber(); }} |
Несмотря на то, что этот вид DSL очень прост в создании и, возможно, немного забавен, начинать копировать и улучшать пользовательские DSL опасно по простой причине. Они ничем не лучше, чем их универсальные, функциональные аналоги, но их сложнее поддерживать. Рассмотрим приведенные выше примеры в Java 8:
Замена DSL с функциями
Предположим, у нас очень простой API тестирования:
|
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
|
static <T> void assertThat( T actual, Predicate<T> expected) { assertThat(actual, expected, "Test failed");}static <T> void assertThat( T actual, Predicate<T> expected, String message) { assertThat(() -> actual, expected, message);}static <T> void assertThat( Supplier<T> actual, Predicate<T> expected) { assertThat(actual, expected, "Test failed");}static <T> void assertThat( Supplier<T> actual, Predicate<T> expected, String message) { if (!expected.test(actual.get())) throw new AssertionError(message);} |
Теперь сравните выражения соответствия Hamcrest с их функциональными эквивалентами:
|
01
02
03
04
05
06
07
08
09
10
11
12
|
// BEFORE// ---------------------------------------------assertThat(theBiscuit, equalTo(myBiscuit));assertThat(theBiscuit, is(equalTo(myBiscuit)));assertThat(theBiscuit, is(myBiscuit));assertThat(Math.sqrt(-1), is(notANumber()));// AFTER// ---------------------------------------------assertThat(theBiscuit, b -> b == myBiscuit);assertThat(Math.sqrt(-1), n -> Double.isNaN(n)); |
С лямбда-выражениями и хорошо разработанным API assertThat() я уверен, что вы больше не будете искать правильный способ выражения своих утверждений с помощью сопоставлений.
Обратите внимание, что, к сожалению, мы не можем использовать Double::isNaN метод Double::isNaN , так как это не совместимо с Predicate<Double> . Для этого нам нужно было бы применить магию примитивных типов в API утверждений, например
|
1
2
3
4
|
static void assertThat( double actual, DoublePredicate expected) { ... } |
Который затем можно использовать как таковой:
|
1
|
assertThat(Math.sqrt(-1), Double::isNaN); |
Да, но…
… Вы можете услышать, как вы говорите: «но мы можем сочетать спички с лямбдами и ручьями». Да, конечно, мы можем. Я только что сделал это сейчас в интеграционных тестах jOOQ . Я хочу пропустить интеграционные тесты для всех диалектов SQL, которых нет в списке диалектов, предоставляемых в качестве системного свойства:
|
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
|
String dialectString = System.getProperty("org.jooq.test-dialects");// The string must not be "empty"assumeThat(dialectString, not(isOneOf("", null)));// And we check if the current dialect() is// contained in a comma or semi-colon separated// list of allowed dialects, trimmed and lowercasedassumeThat( dialect().name().toLowerCase(), // Another matcher here isOneOf(stream(dialectString.split("[,;]")) .map(String::trim) .map(String::toLowerCase) .toArray(String[]::new))); |
… и это тоже здорово, правда?
Но почему бы мне просто не написать:
|
1
2
3
4
5
6
7
8
9
|
// Using Apache Commons, hereassumeThat(dialectString, StringUtils::isNotEmpty);assumeThat( dialect().name().toLowerCase(), d -> stream(dialectString.split("[,;]")) .map(String::trim) .map(String::toLowerCase()) .anyMatch(d::equals)); |
Хамкрест не нужен, просто старые лямбды и ручьи!
Конечно, читаемость — это дело вкуса. Но приведенный выше пример ясно показывает, что больше нет необходимости в устройствах сравнения Hamcrest и в DSL Hamcrest. Учитывая, что в течение следующих 2-3 лет большинство всех разработчиков Java будут очень привыкать к использованию Streams API в повседневной работе, но не очень привыкли к использованию Hamcrest API, я призываю вас, сопровождающих JUnit, отказаться от использование Hamcrest в пользу API Java 8.
Хамкрест теперь считается плохим?
Что ж, в прошлом оно служило своей цели, и люди к этому привыкли. Но, как мы уже указывали в предыдущем посте о сопоставлении исключений Java 8 и JUnit , да, мы верим, что мы, Java, за последние 10 лет поряли неправильное дерево.
Отсутствие лямбда-выражений привело к появлению множества полностью раздутых, а теперь и немного бесполезных библиотек . Многие внутренние DSL или маги-аннотации также затрагиваются. Не потому, что они больше не решают проблемы, к которым они привыкли, а потому, что они не готовы к Java-8. Тип Matcher Hamcrest не является функциональным интерфейсом, хотя было бы довольно легко преобразовать его в один. Фактически, логика CustomMatcher CustomMatcher должна быть перенесена в интерфейс Matcher, в методы по умолчанию.
Вещи не улучшаются с альтернативами, такими как AssertJ , которые создают альтернативный DSL, который теперь представляется устаревшим (с точки зрения многословия кода сайта вызова) через лямбды и Streams API.
Если вы настаиваете на использовании DSL для тестирования, тогда, вероятно, Спок был бы гораздо лучшим выбором.
Другие примеры
Hamcrest — только один пример такого DSL. В этой статье показано, как его можно почти полностью удалить из стека с помощью стандартных конструкций JDK 8 и нескольких служебных методов, которые, возможно, появятся в JUnit в ближайшее время.
Java 8 принесет много нового в дискуссии о DSL в последнее десятилетие, а также Streams API значительно улучшит наш взгляд на преобразование или построение данных. Но многие современные DSL не готовы к Java 8 и не были разработаны функционально. У них слишком много ключевых слов для вещей и понятий, которые трудно выучить, и это было бы лучше смоделировать с помощью функций.
Исключением из этого правила являются DSL, такие как jOOQ или jRTF , которые моделируют фактические ранее существующие внешние DSL в формате 1: 1, наследуя все существующие ключевые слова и элементы синтаксиса, что в первую очередь облегчает их изучение.
Что вы берете?
Как вы относитесь к вышеизложенным предположениям? Какой ваш любимый внутренний DSL, который может исчезнуть или полностью трансформироваться в течение следующих пяти лет, потому что он устарел в Java 8?
| Ссылка: | Java 8, пятница: большинство внутренних DSL устарели от нашего партнера по JCG Лукаса Эдера из блога JAVA, SQL и JOOQ . |