среда, 20 мая 2015 г.

Java Hot Swap почти без недостатков

Привет всем!

Давненько тут не был.  Не прошло и 2 года :)

Хороша Java платформа. Но ее фича Hot Swap врядли может войти в список того что в ней есть хорошего. Hot Swap к сожалению не поддерживает structural replace в коде и если вы меняете что-то выходящее за рамки одного метода то придется все перекомпилировать заново и что еще хуже перезапускать приложение (будь то web приложение или еще какое либо другое).

Далеко не всегда можно обойтись одними автоматизированными тестами и по минимуму перезапускать приложение "гоняя" тесты, которые во много раз быстрее отработают чем цикл "внесли change - остановили контейнер - запустились снова - посмотрели что вышло в итоге.

Стресс от того, что приходится делать перезапуск снова и снова никуда не уходит и сильно мешает держать рабочий темп продвигая разработку вперёд. Есть большая вероятность Atl-Tab-нуться на любимый велоблог и там остаться на полчаса-часик-полдня :) (Tomcat уже давно перезапустился и грустит в одиночестве).

К нашему счастью, есть проект DCEVM (http://dcevm.github.io). Кто там от кого fork-нулся уже не разобрать, но проект в актуальном состоянии и jar-ник скачанный оттуда установился успешно на моей системе.

Суть: DCEVM "патчит" ваш JDK расширяя возможности Hot Swap.

Есть возможность вернуть всё назад есть вдруг что-то не устраивает.

Я успешно пропатчил свою 1.7 update 55.  (для 1.8 тоже можно).
До этого еще уставил плагин  http://plugins.jetbrains.com/plugin/7245 для IDEA который как-то должен взаимодействовать с пропатченной VM.
Его роль для меня осталась загадкой  и есть чувство что и без него будет работать как надо.

Всё! Запускаем Tomcat (или что там у вас?) вносим правки в код  ->  SHIFT-CTRL-F9 -> "Tomcat reloaded XXX classes". Невиданные доселе числа классов, которые он перезагрузил заново. Сэкономленные на перезапуске часы работы.

UPDATE: "Hierarchy changes not implemented".
Мой класс наследовал другой, а после рефакторинга стал "implement Serializable".
Не все коту Масленица. Это я версию "full" "накатывал" на мою VM. Лайтовая, видимо, еще слабее. Но, тем не менее, число перезагрузок приложения уменьшилось на порядок.

По мотивам:
http://dcevm.github.io/
http://blog.jetbrains.com/idea/2013/07/get-true-hot-swap-in-java-with-dcevm-and-intellij-idea/

P.S. время потраченное на настройку окупится с лихвой.
P.P.S. возможны баги, конечно, так что "на ваш страх и риск".

Пока!

пятница, 24 мая 2013 г.

Отладка в Scala (Debugging in Scala)

Привет, коллеги!

Статья перед выходными :)

Всем известный факт: при работе над проектом любой сложности возникает потребность в отладке кода. Сложный, простой -- нет разницы, всегда есть вариант что-то написать не так.

Поскольку в наше время большая редкость проект состоящий из нескольких файлов, то и непосредственно компилятором scalac никто код не компилирует.

В среде Scala стандартом де-факто для сборки является SBT (Simple Build Tool) http://www.scala-sbt.org/

При ошибках компиляции (кода или тестов) или выполнения SBT пишет лог в консоль (и в массу каких-то своих внутренних файлов). Лога часто бывает недостаточно для того чтобы разобраться в том что не работает и почему (где -- еще более-менее понятно).

Самый удобный способ отладиться (если, конечно, у вас есть доступ к исходникам (хотя и без таковых, часто)) это запустить отладчик в той IDE, с которой вам работается привычнее. У меня -- это Intellij IDEA (для нее есть плагин поддерживающий Scala) и делающий кодирование на Scala если и не идеально удобным, то покрайней мере удобнее многих известных мне IDE.

Стандарт для отладки в Java (а Scala с ней неразрывно связана) -- это JPDA.
Поскольку SBT -- это всего-навсего Java-программа, то и все что надо сделать -- это запустить VM с нужными параметрами:

# -------------- Debugging ---------------------------------------
-agentlib:jdwp=transport=dt_socket,server=y,suspend=n,address=5005
# -----------------------------------------------------------------

Лучшее место куда можно положить строки выше находится в файле sbtconfig.txt

Все -- запускайте SBT (через sbt.bat или просто sbt в зависимости от вашей ОС):

sbt
Запустится коммандный режим SBT. SBT выведет на экран первой строкой
Listening for transport dt_socket at address: 5005
Это показывает, что Java машина слушает подключения (порт 5005).


Напишите к примеру: run (чтобы запустить приложение)

Перед этим, в своей IDE, поставьте break point на той строке, которая сможет вызваться в результате комманды выше. 

Запустите отладчик в IDE (как правило в Intellij IDEA доп. настроек не требуется).

По достижению нужного участка кода отладчик остановится на нужной строке (показывая данные о переменных и т.п.)

Конец

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

четверг, 16 мая 2013 г.

Управляющие конструкции (Control structures)

Привет!

Во многих языках программирования есть конструкции управления выполнением кода - for, while, if etc.

Где-то их больше, где-то -- меньше. Они составляют базис языка.

Scala -- не исключение и тоже содержит некоторое число встроенных управляющих конструкций (напр. if, for) но в ней возможно написать нечто свое очень похожее внешне на "control structure".

Т.е. выглядящее вот так:

  breakable {
    // your code is here
    if (someCondition) {
      break
    }       
  }

С виду очень похожее на "встроенную" возможность языка, но breakable входит в scala-library.jar как простая фунция.
Тем, кому не хватало какой-нибудь экзотической конструкции могут написать ее сами.

 

Пример ниже:

  type Resource = BufferedReader
  type ResourceUsage = (Resource => Any)

  def use(closable: => Resource)(code: => ResourceUsage) {
    /**
     * Function in function. Comfortable isn't it?
     */
    def handleEx(e: IOException) {
      sys.error(e.getStackTraceString)
    }

    try {
      val resourceToClose = closable

      // use opened resource
      code(resourceToClose)

      resourceToClose.close()
    } catch {
      case e: IOException => {
        handleEx(e)
      }
    }
  }
Эта структура, названная use избавляет нас от необходимости писать try-catch при использовании файла. 
К тому же она сама закроет reader в конце работы с ним.                               
                                                        
Так называемый call-by-name приводит к тому, что выражение:

new BufferedReader(new FileReader(new File("c:\\tmp\\1.txt")))

вызывается внутри ф-и, а не до вызова use. В Java для подобного нужно было бы делать некий callback интерфейс, который нужно было бы "дёргать" из функции use() вызывая нужный блок кода "потом".

А поскольку блок val resourceToClose = closable находится в тесном окружении блока try-catch внутри функции, то и беспокоиться за "вылет" исключения при вызове use() не нужно. Все будет обработано внутри def use().

Ф-я use() "каррированная" -- возвращает ф-ю, которая использует параметр переданный в порождающию функцию use(). Это дает возможность передать второй параметр (блок кода, который работает с открытым ридером) не в скобках, а используя "{}". 
Что делает use() ничем не отличающейся с виду от того же "while() {}".

Кстати, def handleEx(e: IOException) находится внутри ф-и use(...) что очень удобно с точки зрения логической группировки кода (к подобному удобству привыкли те, кто достаточно поработал с Javascript).

Итог

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

Возможность несомненно понравится тем, кто мигрирует с других языков и кто имеет набор понравившихся функций которые есть там, но которых не хватает в Scala.

среда, 15 мая 2013 г.

Неявные преобразования (Implicit conversions)

Привет всем!

Не один раз нам приходилось писать что-то наподобии:

val button = new JButton

button.addActionListener(new ActionListener {
  def actionPerformed(e: ActionEvent) {
    println("A lot of boiler-plate code.")
  }
})

Код выше -- не Скале, но код на Java немногим отличается (да еще и более многословен).

Все вроде нормально, но он содержит так называемый boiler plate code.
Т.е. к смысловому значению "подмешивается"  "обвязка", код, который нужен языку, а не программисту.

Смысл тут в строке println(...) и в том что мы "вешаем" обработчик на кнопку. Создание анонимного внутреннего класса (который имплементирует интерфейс ActionListener) и описание метода actionPerformed(...) это то, что приходится писать, а не то что хочется.

Использую возможность Скалы неявного преобразования типов можно свести вышеописанное к простому виду:

button.addActionListener((e: ActionEvent) => {
    println("Much better")
})

Напишите функцию в scope кода выше:

/**
* Implicit conversion function:
* from function: (ActionEvent => Any)
*
* Note: You can choose any name you want for this function
*/
implicit def ActionListenerToFunction(fn: (ActionEvent => Any)) = new ActionListener {
  def actionPerformed(e: ActionEvent) {
    fn(e)
  }
}

Эту функцию также можно подключать во всех классах где она может быть нужной (что и делается в очень многих местах стандартной библиотеки классов Scala).

Как все это работает:

Если в коде встречается функция, которая принимает ActionEvent, а возвращает тип Any, то происходит замена такого кода кодом: 

    new ActionListener {
      def actionPerformed(e: ActionEvent) {
        fn(e)
      }
    }  
т.е.неявная функция-преобразователь сделает за нас работу по имплементированию  интерфейса и вызовет fn() с параметром "е". А сама fn(...)  это --
    (e: ActionEvent) => {
       println("Much better")
    })

Техника неявного преобразования широко используется в языке Скала и является одной из возможностей, которой нет в Java.

Главное -- это использовать ее в меру, поскольку подключения массы таких функций из разных мест может серьезно осложнить чтение кода.

понедельник, 13 мая 2013 г.

Примитивный DSL на языке Scala

Привет всем начинающим (и продолжающим) программистам Scala.

Интро:

Одной из наиболее привлекательных сторон Scala (а ее название, как известно, произошло от scalable language) (и наиболее располагающей к творчеству) является возможность создания своего языка на основе Scala. Так называемого DSL (domain-specific language).

После Java это кажется очень привлекательной возможностью, поскольку независимо от того насколько продвинута очередная библиотека под Java код работающий с ней всегда выглядит наподобии:

instance.method(parameter1, parameter2, ...)
instance.method2(parameter1, parameter2, ...)

и т.п.

Но описывая тот или иной аспект системы проще выражать его в форме чем-то напонимающей предложения английского языка, вроде:

circuit voltage 220 V current 100 mA

Как видите достаточно компактно, нет лишних скобок, фигурных скобок.

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

Далее приведена попытка разработать свой небольшой DSL, задача которого описывать шаги некоего build скрипта, аналог команд записанных в bat-файле.

Естественно, что этому мини-проекту расти и расти до мало-мальского реального применения в реальной жизни, но моя задача показать что можно сделать такого на Скале out-of-the-box чего нельзя на Java (это к слову сказать весьма частый вопрос людей не знакомых с языком).

Код ниже:

package spb.ext

import sys.process._

/**
 * Builds our project
 * User: oabakumov
 * Date: 29.04.13
 * Time: 12:44
 */
object Spb extends App {

  /**
   * Build script
   *
   * @param options Map of pairs String -> String that represents build settings (path, build variables, options etc.)
   * @param body script's body to run
   * @return
   */
  def script(options: Map[String, String])(body: (Map[String, String] => Any)) {
    body(options)
  }

  /**
   * Represents a command (atomic part of the script)
   * @param label label
   * @param cmd shell command
   * @param silent verbosity of the command
   */
  case class Command(label: Option[String], cmd: String, silent: Boolean) {
    def loudly(m: String) = Command(Some(m), this.cmd, false)

    def doing(c: String) = Command(this.label, c, this.silent)
  }

  def command = new Command(None, "", true)

  def run(c: Command) {
    c match {
      case Command(_, cmd, true) => {
        // silent
        cmd.!!<
      }

      case Command(label, cmd, false) => {
        // verbose
        println(label.get)
        val r = cmd.!!<
        println(r)
      }
    }
  }

  def bunch(label: String)(commands: Command*) {
    println(label)

    commands.foreach(c => {
      run(c)
    })
  }

  val MYSCRIPT1 = script(Map("codebase" -> "c:\\my-code-is-here\\")) _
  MYSCRIPT1(
    options => {
      val cb = options("codebase")

      bunch("STAGE 1") (
        command doing "cmd /C cd " + cb + "proj\\sub-proj",
        command loudly "publish locally" doing "cmd /C sbt clean publish-local"
      )
    }
  )
}

То что вышло в результате (и то что можно назвать DSL) начинается вот тут:

val MYSCRIPT1 = script(Map("codebase" -> "c:\\my-code-is-here\\")) _
  MYSCRIPT1(
    options => {
      val cb = options("codebase")

      bunch("STAGE 1") (
        command doing "cmd /C cd " + cb + "proj\\sub-proj",
        command loudly "publish locally" doing "cmd /C sbt clean publish-local"
      )
    }
  )
Я создаю переменную (вернее -- значение - val) MYSCRIPT1 для дальнейшего использования.
Функция script - это функция которая возвращает другую функцию "зафиксировав" параметр
options: Map[String, String].

Это называется словом "карринг". Полезная в ряде случаев техника.

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

Затем я вызываю ее тут --

MYSCRIPT1(
    options => {

    ...   
    }
)

options - доступен мне как параметр - могу его использовать в блоке кода.


Вот это и есть шаги скрипта (т.е. что ему делать на каждом этапе):

bunch("STAGE 1") (
    command doing "cmd /C cd " + cb + "proj\\sub-proj",
    command loudly "publish locally" doing "cmd /C sbt clean publish-local"
      )

Т.е. написанное выше это тоже что и:

bunch(
new Command().doing("cmd /C cd " + cb + "proj\\sub-proj"),
new Command().loudly("publish locally").doing("cmd /C sbt clean publish-local")
)
Можно и "одиночными" run {command doing "cmd /C sbt clean publish-local"}