У київському офісі DataArt пройшов тренінг «Groovy і Gradle для Java-розробників». Все було чудово організовано, і нам захотілося поділитися враженнями.
В першу чергу, спасибі прекрасному тренеру Євгену Борисову, який на два дні повністю відсунув всі наші життєві питання і проблеми. Тренінг був суперський, позитивний, динамічний і дуже захоплюючий.
Євген почав виступ з невеликого екскурсу в історію появи мови Groovy, що почалася в далекому 2003 році. Чому цей лаконічний і потужна мова розробки до сих пір не зайняв лідируючу позицію - окрема, трохи сумна історія, пов'язана, крім іншого, ще й з підтримкою IDE.
У перший день тренінгу аудиторія осягала Groovy, не всі були з ним знайомі, та й багато хто з тих, хто вже був знайомий, дізналися купу всіляких цікавих особливостей мови. Зокрема, розглянули взаємодію Groovy і Java, встановили досить цікавий факт - відсутність суперництва між цими потужними мовами, вони, скоріше, доповнюють один одного.
Багатьох Java-розробників може насторожити черговий JVM-мову - мовляв, «я і так вже знаю один і він мене влаштовує, навіщо мені переходити на інший? А як же все ті фреймворки, ліби, якими я навчився користуватися? Як же мій дорогоцінний досвід? ». Але Groovy прийшов не витіснити Java - він прийшов їй допомогти. Згадайте, скільки потрібно написати Java-коду, щоб прочитати рядки з текстового файлу і вивести їх на екран, прочитати XML- або JSON-файл і спарсіровать їх вміст в об'єктний граф. Скільки потрібно try- і catch-блоків в яких, дуже часто не зовсім зрозуміло, що потрібно робити і, в кращому випадку, ексепшн логіруется, а в гіршому - просто ігнорується (і потім спробуй знайди, де помилка сталася). Скільки потрібно POJO написати, щоб промепіть на них спарсірованние дані з JSON і XML? А в Groovy все простіше: checked exceptions не підтримуються - немає необхідності писати код, який буде їх обробляти. І є спеціальні класи, які дають можливість в парі рядків коду отримати об'єктний граф з запитаного веб сервісу або файлу в форматі JSON / XML. І ми наведемо приклад, як це робиться.
Далі ми розглянули варіанти запуску програм на Groovy. Потім перейшли до лексичним особливостям мови - closure, більш повною версією лямбда-виразів, що з'явилися в Java 1.8.
До речі, в зв'язку з тим, що в Groovy правлять бал closures, необхідність у внутрішніх класах автоматично відпала, і вони просто не підтримуються. Closure - свого роду покажчик на метод. Наприклад, цей код всередині Groovy скрипта виведе "hello" на консоль:
def clos = {println "hello!" } Clos () // надрукує "hello!"Як бачимо, код не повинен обов'язково бути всередині класу, точки з запитом в кінці стейтменті необов'язкові, дужки в кінці методу опційні, якщо він приймає хоча б один параметр, тип змінної вказувати не обов'язково (ключове слово def - якщо не вказано, правда, до цієї можливості варто вдаватися, коли дійсно тип наперед невідомий). А ось так closure буде виглядати з параметрами:
def printSum = {a, b -> println a + b} printSum (5, 7) // надрукує "12"Як бачимо, параметри в closure вказуються перед ->, а якщо -> відсутня, closure отримує один неявний параметр "it". Ось приклад його використання:
def stringList = [ "java", "perl", "python", "ruby", "c #", "cobol", "groovy"]; stringList.each () {print "$ {it}"}; // виведе: java perl python ruby c # cobol groovyІ все - це весь скриптова файл! Тут у нас квадратними скобочки створюється ArrayList і Groovy дає нам можливість працювати з ним, як з масивом. Вірніше, як з крутим масивом з купою різних корисних перевантажених операторів і методів доданих до класу за допомогою GDK (Groovy Development Kit). Загалом, Groovy дає можливість сидіти на двох стільцях одночасно - користуватися багатим лаконічним синтаксисом, використовуючи списки. Ніяких імпорт пакета, який вже став всюдисущим в Java - java.util. * (Автоматично в Groovy теж імпортуються: groovy.lang. *
groovy.util. * java.lang. * java.util. * java.net. * java.io. * java.math.BigInteger java.math.BigDecimal). Метод each () пробігається по кожному елементу списку і робить з ним ту дію, яку вказано в closure. В даному випадку виводитися значення елемента. Для цього використовується чудова Groovy рядок GString (починається і закінчується подвійними лапками, звичайна рядок починається і закінчується одинарними лапками.) Всякий раз, коли в GString зустрічається $ {...}, вираз всередині обчислюється і перетворюється в рядок. Дуже компактно і зручно.
А ось приклад роботи з картою:
Тут у нас створюється карта (за замовчуванням HashMap) і відразу ж заповнюється записами (в кожного запису - ключі до двокрапки і значення після двокрапки), а метод each () пробігається по кожного запису карти і робить з нею, що зазначено в переданому closure. Зокрема, виводить на консоль рядок виду "ключ == значення". Ще одна з численних відмінних речей в Groovy - оператор безпечної навігації. Він дає можливість позбутися від обтяжливого, рутинного коду який необхідний в Java, щоб не натрапити на NPE. Ось він в дії:
def user = User.find ( 'admin') // тут user може виявитися null якщо 'admin' не існує def streetName = user? .address? .street // а тут streetName стане null якщо user або user.address виявилися null. І ніякого NPE) Ми розглянули роботу з text-, XML- і JSON-файлами, пощупали колекції, оцінили зручність і потужність GString (тільки не треба гуглити картинки. Результат пошуку раптовий і не відповідає). Краєм ока подивилися на різні анотації (наприклад, прекрасна анотація @Canonical - до анотований класу автоматично створюються геттери / сеттери, генерується toString (), всілякі конструктори, hashcode і equals - в загальному, весь той базовий набір, який ми використовуємо майже кожен раз при створенні нового класу).
Пильно подивилися і спробували всілякі цікаві штуки, типу «а не додати нам до бібліотечного класу свій метод?» - навіщо, скажіть будь ласка, обмежувати себе класом? Можна ж підлаштовувати і один конкретний об'єкт цього класу!
Хоч і написано в дусі «подивилися - побачили», насправді краще читати це як «спробували». Тому що за допомогою Groovy робити це було просто, тим більше, з докладними коментарями тренера.
Мабуть, найбільш примітним прикладом буде задачка, яку ми робили: є клас Money, з полями amount і currency (сума і валюта - відповідно). Необхідно написати перевизначити один метод класу так, щоб при звичайному математичному складення двох об'єктів класу результатом був третій об'єкт цього ж класу, де буде загальна сума коштів і валюта першого доданка. З конвертацією за поточним курсом (витягає як властивість JSON з певного URL). Задумайтесь, скільки рядків коду це займе на Java? Скільки всього треба буде написати? Скільки бібліотек підключити? А на Groovy це все зайняло цілих ... 7. Ну ладно, гарненько відформатованих - вісім. Правда, показово? Давайте подивимося на код:
@ToString class Money {final BigDecimal amount final String currency Money (BigDecimal amount, String currency) {this.amount = amount this.currency = currency} Money plus (Money money) {if (money.currency == this.currency) { new Money (amount + money.amount, this.currency)} else {def rate = new JsonSlurper (). parse (new URL ( " http://rate-exchange.appspot.com/currency?from=${money.currency}&to=${this.currency} ")). Rate new Money (amount + (money.amount * rate as BigDecimal), this.currency)}}} def bucks = new Money (10, 'USD') def eurs = new Money (4, 'EUR' ) println "dollars = $ {bucks + eurs}" // у нас вийшло 15.44 доларів)Як видно, основна частина коду, яка відповідає за бізнес-логіку, міститься в методі plus (). Його перевизначення перевантажує оператор + (аналогічним чином в Groovy перевантажуються і інші оператори). Якщо валюта другого доданка Money відрізняється від валюти першого, вона конвертується в валюту першого. Метод parse JsonSlurper парсірует отриманий від веб-сервісу відповідь в ледачу карту (LazyMap), де можемо достукатися до значень її записів за назвами їх ключів (в нашому випадку це rate), як ніби є такі атрибути. Загалом, кредо Groovy - «геть церемоніальність».
Так що частина тренінгу, присвячена Groovy, залишила просто незабутнє враження. Але на цьому справа не закінчилася, і на другий день ми, допілівая практичні завдання і озброївшись прекрасним кавою / чаєм, почали активно вгризатися в твердий граніт Gradle!
Для початку уточнили різницю між декларативним інструментом побудови та імперативним. Ну, будемо говорити відверто: всім нам приємніше сказати, ЩО ми хочемо, і отримати результат, ніж докладно пояснювати, ЯК домогтися потрібного нам результату. Чудовим прикладом будуть слова Євгена: «зроби мені чай» - декларативний підхід, «відкрий ці двері, пройди по коридору, візьми стаканчик, постав чайник, візьми пакетик чаю, який лежить ... і т. Д.» - імперативний. Звичайно, ніхто в групі не взявся спростовувати, що іноді, коли нам необхідно щось більш унікальне, сильніше відповідає нашим очікуванням, другий підхід повністю себе виправдовує. Але в більшості випадків багато речей - звичайна рутина, і пояснювати в 100500-й раз, що я просто хочу чай, - утомливо. І тут уважний читач скаже: «Ну так є ж Maven! Він же підходить і все робить так, як треба! »І буде в чомусь абсолютно прав, крім двох малесеньких і непомітних речей: XML - не найзручніший мову для написання скрипта. <Build> ... </ build> -секція - така інтуїтивно зрозуміла і проста, що іноді плакати хочеться.
Чим же так виділяється Gradle? Так, в общем-то, всім ... Для початку, скрипти, написані на Gradle, по суті, Groovy. Але з можливістю складання, найрізноманітнішими плагінами і іншими подібними вкусняшками. Основна одиниця в Gradle-скрипті - task, який може бути легко розширений за допомогою наслідування, створення свого підкласу, взаємозалежності tasks.
Правда, перше враження було злегка двояким: найпростіший task, все, що робить, - виводить час. Збирається близько семи секунд. ЩО ?! Сім секунд, щоб час вивести ?! Це була перша реакція на роботу збирача. Насправді, Євген дуже швидко пояснив причину такої «швидкої» роботи - кожен раз при запуску скрипта запускалася віртуальна машина, на якій він виконувався. А потім закривалася. Завдяки використанню такої корисної фішки, як Gradle daemon, яка описується в файлі налаштувань для проекту. До речі, робота з файлом налаштувань (gradle.properties) - суцільне задоволення, звернення до нього просте (Groovy ж!), Проблем-ніяких, ну а настройки - завжди корисні.
Після чого ми більш детально подивилися, як влаштовані tasks всередині, що можна в них передати, як користуватися залежностями і визначати настройки всередині самого task - наприклад, при використанні методу onlyif можна налаштувати розклад виконання. Так само пройшлися по темі створення власних плагінів для Gradle-скрипта. Хорошійпоказатель - плагін, який при невеликому шаманизме може відправляти e-mail, наприклад, якщо білд не виконав по якимось причинам. Розглянули варіант, як з ланцюжка tasks можна видалити один або цілий ланцюжок. Ну, і куди ж без цього - пощупали можливість усунення конфліктів залежностей.
Мабуть, найцікавіше і приємне, що було в другій день тренінгу, - приклад налаштування сумісності з такими популярними, але не завжди зручними збирачами, як Maven і Ant. Сумісність не просто є, а прекрасно підтримується.
Підводячи підсумки, можна не сказати, що все пройшло просто чудово. Євген постійно заряджав ентузіазмом (особливо це було потрібно вранці), аудиторія активно обговорювала те, що відбувається, і судячи з облич і спілкуванню, задоволення отримали всі. Але головне, що всі отримали найцінніший досвід по роботі з двома прекрасними інструментами - мовою програмування і збирачем. У багатьох з'явилися думки, що можливо, щось ми в Java втрачаємо. Навіть не так ... не використовуємо на 100% весь потенціал.
Багатьох Java-розробників може насторожити черговий JVM-мову - мовляв, «я і так вже знаю один і він мене влаштовує, навіщо мені переходити на інший?А як же все ті фреймворки, ліби, якими я навчився користуватися?
Як же мій дорогоцінний досвід?
Скільки потрібно POJO написати, щоб промепіть на них спарсірованние дані з JSON і XML?
Find ( 'admin') // тут user може виявитися null якщо 'admin' не існує def streetName = user?
Address?
Пильно подивилися і спробували всілякі цікаві штуки, типу «а не додати нам до бібліотечного класу свій метод?
» - навіщо, скажіть будь ласка, обмежувати себе класом?
Задумайтесь, скільки рядків коду це займе на Java?
Скільки всього треба буде написати?