В 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 IsNotANumber extends 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 lowercased assumeThat( 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, here assumeThat(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 . |