суббота, 31 декабря 2011 г.

Итоги года

Прочитал подобные посты (подведение итогов уходящего года), и неожиданно захотелось тоже что-то подобное написать. Поехали.

Год для меня в целом был напряженный, и событий в нем был много всяких разных, но в целом  через весь год красной нитью прошло одно важное событие - мой переезд в  США. Это потребовало неожиданно много времени (в общей сложности от начала переговоров до собственно пересечения границы прошло больше года), много усилий с моей стороны и со стороны компании, пригласившей меня, и много нервов и тревог, что все это сорвется из-за какой нибудь ерунды. С другой стороны, теперь я в этой теме поднаторел и могу, наверное, давать полезные советы на эту тему желающим просветиться :)

Я помню, что на Хабре меня многие спрашивали на эту тему и просили написать статью - как нибудь или я напишу статью, или запишу подкаст. Знания должны распространяться.

В профессиональном плане у меня тоже произошел некоторый сдвиг - теперь я официально TPM (Technical Product Manager), что на практике означает, что кроме написания кода/проектирования архитектуры я теперь я трачу часть времени время на всевозможные митинги, участвую в обсуждениях требований к продукту, отвечаю на множество писем и раздаю задачи разработчикам в команде (коих пока мягко говоря немного, но надеюсь в наступающем году будет больше).

Два этих момента съели львиную долю моей энергии, и некоторые вещи, которые я начинал в этом году, я начал и оставил на полдороги. Начал например, учить Git (и выучил до некоторой степени), прежде всего чтобы форкнуть один интересный мне проект на Гитхабе и развить его. Проект этот - Antro (разработан Женей Кирпичевым, за что его огромный респект!).  Проект форкнул, пофиксил пару багов в нем, чуть допилил под нужные для себя задачи и оставил.

Начал учить некоторые редкие языки - Erlang, Lisp, Scala (не такой уж редкий, но все же не мейнстрим). Начать начал, а продвинуться в них существенно времени (а может, мотивации...) не хватило.

Довольно о былом. Чего бы я хотел в новом, наступившем уже году?

В карьерно-профессиональном плане - найти наиболее подходящий лично для меня баланс между ролью ведущего программиста/архитектора и роль PM-а. Чтобы и компания получала максимальный профит от моего вклада, и мне это приносило удовлетворение. Хотелось бы увидеть, как тот увлекательный R&D, которым я занимался тут последнее время, приносит свои плоды в реальных проектах для реальных заказчиках.

В лично-профессиональном плане хотелось бы найти время и постоянную мотивацию, чтобы добить те технические навыки и знания, которые я упомянул выше. Просто взять и добить. И взять на щит еще некоторые интересные знания/языки программирования/подходы/технологии. И использовать эти знания в каком-нибудь open source проекте.

Хотел бы наконец прочитать следующие книжки (кое-какие уже начал, их дочитать):

 -  dragon book, эпичный труд по компиляторам.
 -  Джонатана Льюса, по Oracle - Introduction to cost-based optimization или как она там точно называется. Изумительная книжка по ораклу. Дает понимания, просто сакральное знания, что же все таки такое COST запроса.  Вопрос, на который вам внятно на собеседовании не ответит 9 из 10 человек, пишущих в резюме "владею Oracle". Кстати, а как вообще можно писать в резюме "владею Oracle" (я именно такую формулировку видел, и не раз), если вы не Ларри Эллисон? Оставлю вопрос за совесте резюмеписателей.
 - Groovy in Action, 2 издание. Чтобы, так сказать, catch up все те вкусности, которые появились в последних релизах.
 - SICP. Без комментариев. Стыдно признаться, но - не читал.

Ну и еще кое-что по мелочи.

Что еще? Завести себе мотоцикл, и начать рассекать по техасским хайвеям.

Сдать на лицензию на оружие, и постреливать в тирах из разнообразного короткоствола. Хорошее развлечение, хорошо заряжает энергией.

Через год посмотрим, что из всего этого удастся осуществить. А заодно - что осуществиться из пока еще незапланированного.

Все. С новым годом всех! Желаю прежде всего, чтобы ваши цели были кристалльно ясными для вас самих, ведь это - первый шаг к их осуществлению. А когда цели ясны - пожелаю силы воли, мотивации, здоровья и удачи. Это то, чего должно хватить для достижения всего намеченного.

Cheers!


среда, 28 декабря 2011 г.

Установка Oracle под Ubuntu

Установка Oracle Enterprise Edition под Ubuntu это задача не для каждого разработкика.

Полная и точная (по словам автора - я сам пробовал многочисленные похожие инструкции, все равно полностью без ошибок поставить ни разу не удавалось) инструкция находится здесь - http://www.excession.org.uk/blog/installing-oracle-on-ubuntu-karmic-64-bit.html.

Сравните, насколько это геморройнее и сложнее, чем установка того же Oracle EE под Windows - там запустил инсталлер и следуешь его указанием. Короче, отличный пример протекающей абстракции Джоэля Спольски.

Но для Express (XE) версии есть все-же человеческое решение - оригинал по английки тут - http://www.varyonic.com/2010/01/installing-oracle-xe-on-ubuntu/.

Мой вольный перевод на русский:


Oracle XE официально доступен только под x86 системы, хотя, возможно, его можно с помощью бубна, удачи и чьей-то матери поставить и на X64. Требует это чудо природы 512Мб памяти и 1Гб свопа. Добавьте в файл /etc/apt/sources.list следующую строчку:
deb http://oss.oracle.com/debian unstable main non-free
и потом запустите под рутом (т.е. sudo ..<your command>, он спросит пароль текущего юзера и исполнит команду под рутом):
wget http://oss.oracle.com/el4/RPM-GPG-KEY-oracle -O- | sudo apt-key add - apt-get update apt-get install oracle-xe
Библиотеки libaio и bc доступны в самом репозитории, и будут подтянуты автоматически по зависимостям, ставить их вручную больше не нужно.



четверг, 22 декабря 2011 г.

Загрузка двух нативных библиотек через JNI под линуксом


Недавно по работе появилась редкая для меня задача, излагаю. Скажу сразу, решения в лоб пока не нашел, а workaround-ы мне известны, но для них требуется помощь от поставщика одной из сторонних библиотек, а потому задача более чем актуальна. 
Знатоки JVM И юникса - VERY welcome..
Итак, задача. Есть две нативные либы, А  и Б, или, в нейминга линукса, это libA.so и libB.so. А зависит от Б (должна вызывать функции из нее). А - написана мной, Б - предоставлена сторонними разработчиками в виде одного жалкого .so файла, доступа к исходникам нет.
Мне нужно загрузить эти две либы в JVM через JNI и вызвать функцию из библиотеки А, которая, в свою очередь, использует функцию из Б.
Следующий код:
static {
    System.loadLibrary(B);
    System.loadLibrary(A);
}
Отлично работает в Windows (для .dll файлов, соотетственно), при условии, что обе библиотеки лежат в java.library.path (я тут кстати раньше в блоге писал, как можно менять java.library.path в рантайме).
Как это работает в Windows - загружается B.dll, дальше A.dll пробует загрузиться, видит, что надо бы загрузить B.dll, от которой она зависит, видит, что B.dll в память процесса уже загружена, и не пытается найти и загрузить B самостоятельно, ища ее в PATH.
Теперь, что, по видимому, происходит в линуксе.
Дополнительный логгинг показывает, что libB.so корректно находится в java.library.path и загружается. Затем, рантайм-линкер пытается загрузить libA.so, и замечает, что ВНЕЗАПНО, libB.so в LD_LIBRARY_PATH нет! А менять LD_LIBRARY_PATH я не хочу, не то что неспортивно, но хочется локально решать проблемы. Чтобы уточнить - обе библиотеки находятся в директории, которая входит в java.library.path, но НЕ входит в LD_LIBRARY_PATH).
У кого-нибудь есть какие-нибудь идеи?
Перепробовал немало хаков уже, включая линковку libA.so с уничтожением таблицы символов, загрузку libB.so динамически из libA.so, получение указателя на нужную функцию (манглированную) и вызов ее - не работает ничего из этого так, как мне надо.
Еще раз уточню условия - я не хочу модифицировать LD_LIBRARY_PATH, я не хочу класть свои либы в "общеизвестные" места типа /usr/lib.
Вариант добыть вместо libB.so статически либу (libB.a) и влинковать ее жестко в libA.so мне вариант кажется хорошим, но исходников нет.
Сконвертировать libB.so -> libB.a? Теоритически наверное, можно.

воскресенье, 13 ноября 2011 г.

Java String.substring() и расход памяти


Недавно в треде на Хабре (http://habrahabr.ru/blogs/java/132500) было обсуждение того, насколько накладен метод String.substring. Мол, каждый раз при вызове этого метода создается новый объект, это жеж кошмар и все такое. Однако, давайте посмотрим чуть внимательнее.

Любой Java-программист знает, как устроен внутри метод String (до определенных пределов :) - никто не знает всех деталей этого процесса, и то, насколько много вы знаете о строках в Java, и определяет вашу зрелость как Java-программиста, ха!).

Итак, у объекта класса String есть три поля. Offset, length, value. Последнее хранит массив символов, которые и представляют из себя строку, первый и второй - смещение от начала строки в массиве value[],  и длину региона в этом массиве, которые и позволяют нам "вырезать" из массива символов value[]  то, что можно назвать "срез текущей строки". Зачем мы храним в памяти эти два дополнительных свойства, смещение и длину строки? По разным причинам, одна из них - следование паттерну Flightweight, который позволяет нам использовать тот факт, что массив value[] - неизменяем, и расшаривать один общий экземпляр этого массива при вызове методов, которые возвращают нам часть исходной строки, например, substring.

Посмотрим внимательнее на метод String.substring:


public String substring(int beginIndex, int endIndex) {

 if (beginIndex < 0) {

 throw new StringIndexOutOfBoundsException(beginIndex);

 }

 if (endIndex > count) {

 throw new StringIndexOutOfBoundsException(endIndex);

 }

 if (beginIndex > endIndex) {

 throw new StringIndexOutOfBoundsException(endIndex - beginIndex);

 }

 return ((beginIndex == 0) && (endIndex == count)) ? this :

 new String(offset + beginIndex, endIndex - beginIndex, value);

 }



Смотрим теперь по коду конструктор с такой же сигнатурой, видим:

// Package private constructor which shares value array for speed.

 String(int offset, int count, char value[]) {

 this.value = value;

 this.offset = offset;

 this.count = count;

 }

Вывод — при вызове метода Substring, новый объект String конечно создается, но массив символов, используемый для хранения данных, не копируется. В новом объекте String будут просто использованы другие значения для offset/lenght, и ссылка на тот же самый массив символов, относительно которых эти offset/length и берутся.


Небольшая тонкость относительно копирования строк, о которой не все знают. Иногда об этом не знают даже те, кто пишет об оптимизации использования памяти на Хабре ;)

среда, 26 октября 2011 г.

Запуск большого количества параллельных задач в надежных песочницах

Недавно возникла одна задача, удовлетворяющего меня на 100% ответа на которую я пока не смог найти (вариантов разных придумалась масса).

Предлагаю порассуждать на тему возможных решений.

Итак, есть крупное Enterprise-решение, написанное на Java. Как часть своей функциональности, оно предлагает пользователям возможность создавать собственные скрипты, написанные на некотором языке, которые запускаются на выполнение в песочницах, и возвращают некоторый результат. По замыслу, это именно что чистые функции — принимают значения, производят вычисления над ними, возвращают результат, все. Никаких обращений к внешним ресурсам, никаких side effects. Скриптов этим может запускаться на выполнение много — сотни в минуту, примерно так. Возникает вопрос, как организовать надежную песочницу для них.



Предположим, мы будем использовать в качестве языка для этих скриптов Jython/Groovy/JRuby, какой угодно скриптовый язык под JVM, так как остальная часть системы написана на Java.

Сразу проблема — штатные средства JVM/архитектура Windows/Unix не позволяют организовать песочницу в рамках родительского процесса, в котором выполняется основная программа. Ограничение доступа к частям JDK по списку разрешенных пакетом, система пермишенов на доступ к сети, файлам, и все прочее хорошо, но одно но — нет никакого нормального способа запретить скрипту выполнить new int[10000000000000] и свалить JVM с OutOfMemory, тупо потому, что весь heap расшаривается между всеми потоками процесса, и ничего с этим поделать нельзя (строго говоря, единственный теоритический способ, это перехватывать все операции выделения памяти с помощью jvm agent, и не запускать на выполнение байткоды, запрашивающие выделение памяти через new, без проверки того, сколько памяти запрашивается… это потребует написание агента, который модифицирует байткод всех загружаемых классов, и вставляет нужные проверки вокруг всех операций выделения памяти, в том числе внутри самих классов JDK… мрак).

Т.е. для каждого скрипта нам нужен отдельный процесс. Но раз так, то JVM нам сразу для этого мало подходит. Потому что она изначально не предназначена для выполнения одноразовых скриптов, она имеет большой оверхед по запуску, инициализации рантайма (даже для client jvm), она потребляет много памяти на каждый свой процесс (в том числе потому, что на каждый процесс требуется создавать свой perm gen, в который грузятся все классы… и даже class data sharing вряд ли радикально сократит потребление памяти, хотя тут я еще собираюсь поэкспериментировать).

Следующий вариант — использовать для этих скриптов что-то вроде питона, и запускать его нативные процессы (под юниксом, через форк), считая что так они будут создаваться намного быстрее… Интегрироваться с этими процессами из основной программы можно либо по TCP, либо через pipes.

Питон как язык удобен для того, чтобы пользователи писали скрипты именно на нем. Возникает вопрос, как обстоят дела с sandboxing у Питона, по аналогии с Java Security Manager.

Следующий вариант — использовать язык типа Erlang, в котором порождение процессов гораздо проще, но и сам язык гораздо более экзотический…

воскресенье, 10 июля 2011 г.

Groovy Screencasts

Недавно в рассылке groovy-users промелькнуло интересное сообщение. Один из энтузиастов Groovy поднял сайт, на котором собираются и хостятся скринкасты, посвященные Groovy - вот этот сайт,
http://groovycasts.org/.

Пока что здесь всего 7 скринкастов, но лиха беда начало. Хочу верить, что через месяц-другой их тут будет куда больше.

Вот, например, любопытный скринкаст от Hamlet D'Arcy -  http://groovycasts.org/post/7429747445/getting-started-with-spock-and-groovy , посвященный Spock - Groovy-фреймворку для тестирования.

Так что, больше интересных скринкастов!

воскресенье, 3 июля 2011 г.

Так нужен ли ORM в крупном и сложном Enterprise-проекте?

Недавно на Хабре мелькал вопрос - так нужен ли на самом деле ORM в крупном и сложном проекте? Ведь он часто медленный, громоздкий, поддерживает только некоторое подмножество SQL, не поддерживает специфический и очень удобный синтаксис, например, Oracle (тот же connect_by для иерархических запросов) и прочее и прочее.


Высказывалось мнение, что ORM в действительности нужен только в примитивных проектах, для сокращения размера кода, а в реально большом и сложном проекте лучше обойтись без него. Я скажу за большие проекты - за мелкие пусть скажут другие :) Оговорюсь, что рассуждения и примеры строю на Java / Oracle, классическая связка.


Итак.


Сам по себе ORM, именно как mapping, в крупных проектах нужен как раз очень сильно.


Представьте себе — у меня есть очень крупная система, и есть в ней таблица orders, на ней скажем, 50 колонок (на самом деле у нас 150, ну да ладно. Нормализаторы, молчать! Про нормальные формы я тоже знаю). И вот надо представьте, что вам выбрать один ордер и показать его на экране. Допустим, вы пишете селект, вопрос - дальше что делать с его результатами, в промежуточном слое, в server-side Java? Вы не же вызываете хранимую процедуру или запрос напрямую с, скажем, JSP страницы (я надеюсь), вам все равно надо получить данные и передать их как-то, в виде какой-то структуры данных.


Так что же, передавать их в виде массива, ArrayList-a, ассоциативного массива как [имя колонки : значение]? Это громоздно, неудобно, и очень легко ошибиться. А если вам надо несколько ордеров, тогда что, создавать вложенные коллекции для конвертации результатов? Это по своему вредному влиянию будет уже напоминать известный антипаттерн "Строковая типизация". Использовать коллекцию векторов или ассоциативных массивов там, где можно использовать коллекцию объектов типа Order.

Мы нашли пример случая, нам нужен объект Order, имеющий все нужные property, и, следотельно, нам нужен код, который умеет конвертировать результаты SQL запроса в объекты (коллекцию таких объектов).

Далее, очевидно, что писать руками все запросы трудно и нудно, легко ошибиться, т.к. в Java они будут представляться или в коде в виде строк (а значит, никакой статической типизации и compile-time проверок), и их надо держать либо в Java коде (если они мелкие, и все равно получается замусоривание Java кода), либо, если побольше, выносить в отдельные XML файлы.


Здесь небольшое лирическое отступление. Спор - держать ли SQL код коротких запросов прямо в Java коде, или же выносить  их в отдельные файлы в Enterprise-e идет уже долго. 
Аргумент за то, чтобы держать из внутри Java кода - вместе используемый код находится рядом, код SQL запроса легче найти и посмотреть, чем если он лежит где-то отдельно, в случае изменений в VCS надо будет не забыть изменить только один файл, а не два.
Аргумент за разделение по разным классам - многострочные SQL запросы сильно замусоривают Java код (в том числе потому, что Java не поддерживает полноценно многострочные строковые литералы в отличие от, скажем, того же Groovy) + если они определены как константы класса, то при изменении содержимого этой константы сделать java hotswap, например, уже не получится. Я сам обычно смотрю по обстоятельствам :)


В общем, ORM в больших проектах нужен для упрощения рутинной части. Без него — никуда :)

Безусловно, обойтись ТОЛЬКО ORM не получится. Есть у нас масса мест, где сложная логика написана в запросах хранимых процедурах в 500-1000 строк на PL/SQL, ибо написанная через ORM /Java она бы занимала в 10 раз больше места и работала в 2 раза медленнее (при этом, она была бы еще и менее понятна, т.к. есть такая логика, которые в терминах реляционной алгебры описывается проще, чем в терминах ООП :), и такая логика ложится на ORM со скрипом). Сколько нибудь сложные запросы с подзапросами, юнионами, хитрыми джойнами тоже писать через чистый ORM громоздко. Оптимизировать запросы, работающие с таблицами где, хотя бы, несколько сотен миллионов записей, без доступа к планам SQL оптимизатора и статистики/средствам мониторинга уровня СУБД (для Oracle это Explain Plan, tkprof, Grid Control) тоже крайне сложно. Так что без SQL тоже — никуда :)


Можно поговорить о том, как все-таки немного подсластить работу с чистыми SQL-запросами в крупном проекте.


Один из вариантов - использование макросы. Пишется свой фреймворк для написания SQL макросов (в сущности, движок для выполнения умеренно примитивных парсеров, позволяющий людей писать собственные макросы и парсеры к ним). Макросы могут помочь решать следующие задачи:
 - Вынесение реюзабельных частей запросов (обычно, подзапросов) в отдельные скажем так, именованные SQL-компоненты, и включение их затем в запросы с помощью макроса
 - Унификация некоторых надоедливых различий в синтаксисе SQL, например когда параметр запроса может быть скалярным числом, а может быть массивом, тогда вы пишете макрос, которые абстрагирует вас от отличия между t.property = x и t.property in (x1, x2)
 - Добавление предикатов в where clause в целях безопасности
 - Оборачивания всего запроса в SELECT * FROM (query body) t order by.. для унифицированной поддержки сортировки и paging-a


и многое другое.


Да, и еще. Я ни слова не сказал тут о совместимости с разными СУБД, т.к. это часто ненужное требование. Большие и сложные системы активно использует СУБД-specific фичи (расширения синтаксиса SQL, синтаксис процедурных расширений, таких как PL-SQL, T-SQL, pgplsql вообще отличается очень сильно, схемы секционирования и прочая, и прочая), и требования писать SQL-код, одновременно совместимый с Oracle/MSSQL/DB2 часто не ставится (я перечислил тут классические признанные Enterprise-level СУБД, поклонникам остальных просьба не обижаться). Все понимают, что это — огромное усложнение и удорожение системы, далеко не всегда оправданное. Подлинная независимость от серьезного вендора, такого например, как Oracle или IBM — стоит дорого, очень дорого. А часто ли она реально нужна?

пятница, 13 мая 2011 г.

Groovy - обработка текстовых файлов



Недавно на работе в рамках написания функциональных тестов на шедулеры встретилась такая задача.Есть CSV-файл с (тестовой) информацией о заказах, в нем две колонки, в первой дата, во второй - число, представляющее quantity на эту дату. 

Требуется - получить CSV файл, в котором все значения во второй колонке умножены на 3 (4, 5, какой-то множитель). Следующий скрипт на Groovy решает эту задачу (думаю, его можно еще сократить и причесать, но все же выложу как есть).

def path = "source.csv"
def pathNew = "target.csv"

def oldFile = new File(path)
def newFile = new File(pathNew)
newFile.withWriter { out ->
  oldFile.eachLine { line ->
    out << line.replaceAll(/,(\d)*/, {x, y -> ',' + (x[1..(x.size()-1)].toInteger() * 3).toString()} ) << '\n'
  }
}




суббота, 7 мая 2011 г.

addconf 2011 - долгое послевкусие

Типа вступление :)

Как многие знают, 29-30 апреля в Питере прошла конференция Application Developer Days 2011 (http://addconf.ru/add_2011). Впечатления у меня о ней остались сугубо положительные, но пока они еще немного сумбурные. Я сам выступал там с докладом по масштабированию систем управления поставками (http://addconf.ru/event.sdf/ru/add_2011/authors/MikhailAntonov/269), ходил на некоторые, наиболее мне интересные, доклады, и изрядно пообщался в кулуарах с интереснейшими людьми. Так же, очень хорошее впечатление оставила организация (и организаторы) конференции, да и Питер в целом. Но обо все по порядку. Погнали.

Мое выступление

Если вам интересны слайды, рекомендую подождать, пока Стас Фомин и Андрей Майоров выложат обработанное видео на офф. сайте конференции, там будут и слайды, и крупные планы, и выражение моего лица в ключевые моменты, и все такое.

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

Что было? Было внимание ко мне и тому, что я говорил, со стороны присутствующих в зале, не было (кажется :)) людей, сидящих в первом ряду и втыкающих  в нетбуки, айфоны и айпэды, было немало вроде бы искренне-заинтересованных вопросов после моего доклада. Было обсуждение на час-полтора в коридоре после выступления, на котором мы обсудили кучу интереснейших, но уже довольно узкоспециальных вещей (вроде оптимизации тяжелых баз данных на Oracle,  бизнеса управления поставками, data mining-a, статистического анализа, бизнес-логики на Groovy и почему это не совсем маразм, и еще кучу всего). В итоге собрали неплохую такую кучку в кулуарах, даже из JetBrains некоторые ребята подтянулись послушать и пообщаться :) Были люди, подходившие ко мне после доклада, хлопавшие по плечу и говорившие - "чувак, ты реально зажег". Были, наконец, какие-то упоминания в паре блогов и в твиттере.

Тем подозрительнее тот факт, что особенной критики в свой я (пока) не слышал. Может, просто не натыкался еще, или критики мой доклад проигнорировали, и писать отзыв им стало лениво (а жаль...). В любой случае, конструктивной критике буду рад.

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

И тут же небольшое замечание. Я старался держаться на сцене энергичным и бодрым, а не как манакен с вмонтированным устройством для озвучки слайдов, но этому препятствовал в какой-то степени микрофон, который приходилось держать в руке близко к ротовому отверстию. Реально - радиогарнитура, прикручиваемая к голове был бы куда удобнее (с другой стороны, микрофон для многих докладчиков решает извечную проблему - куда же девать руки во время рассказа:))

А так - все было классно. Не считая некоторого волнения - получил искреннее удовольствие от выступления, от ответов на интересные, по существу, вопросы в конце и от общения с аудиторией. Надеюсь, она тоже свою порцию удовольствия получила :)

Запомнившиеся доклады

Первым докладом, на которые я пошел, был доклад Максима Мазина о  LOP - Language Oriented Programming и MPS (Meta Programming System) - продукте JetBrains, позволяющим, гм, визуально, конструировать DSL языки (и надстройки над существующими полноценными языками). Если мне не изменяет память, это был тот же самый доклад, который Максим рассказывал на JavaOne в Москве за две недели до этого, но, поскольку на JavaOne я его пропустил - в этот раз сходил на него и не пожалел.

Для меня это был один из самых интересных докладов, т.к. мы тоже занимаемся DSL (точнее, начинаем заниматься) в своем проекте, и тут любые знания и любой опыт кстати. Сам доклад был живой и интересный, но конечно, за отведенные 40 минут рассказать все было нереально. Именно потому после этого доклада большая толпа народа клубилась возле стойки с расшаренными мониторами в коридоре, выпытывая у Максима детали, наблюдая, как он вживую, при нас, реализует простейшую DSL-надстройку над Java (добавляет поддержку RW-lock на уровне синтаксиса), потом допиливает ее, добавляет туда поддержку Java-вских типов и прочее. Это общение о DSL-ах вообще и MPS в частности оставило очень хорошие воспоминания. Всегда приятно пообщаться со знатоками.

До этого я слышал про MPS, но разве что краем уха. Мне понравилась сама идея JetBrains - создать платформу / инструмент для облегчения разработки DSL-ей, и понравились те возможности, которые она дает. Стало даже немного обидно, что мы используем Eclipse и сильно на него завязаны :) Ну да ладно, JetBrains в любом случае молодцы, что смело вкладываются в продуктизацию таких черномагических (для многих) вещей, как метапрограммирование и DSL. Удачи вам в деле проталкивания этого на большой рынок!

После этого доклада, как я уже сказал, я завис в кулуарах на час или полтора, и потом... потом я забрел на вторую половину доклада Ольги Павловой об интерфейсах. Что могу тут сказать - докладчица выступала ярко, живо, рассказывала об интересных, в целом, вещах, но поскольку я в эргономике, usability, дизайне и всем таком особо не разбираюсь, да и писать интерфейсные части не люблю (хотя приходится время от времени, конечно), то я понял, что этот доклад - ну просто не для меня. Все отлично, просто я - не целевая аудитория. Ушел, не дослушав до конца. Ольга - простите :)

После этого немного побродил по коридорам, поприставал к ребятам из JetBrains, связанным с Resharper-ом, и пошел на доклад Яши Сироткина - "программирования для самых маленьких". Что тут сказать - класс и уровень докладчика виден сразу, основатель российского JUG-a все ж таки. Доклад шел бодро и четко, внимание зало было все на Якове. Ну а фраза ближе к концу "АВТОБУС ! НЕ ! ПРИДЕТ !" была отретвитчена много раз и гуляла из уст в уста еще долго.
В общем, доклад скорее из серии отвлеченно-философских, при этом живой, интересный и полезным. Основная идея доклада - то, что в программировании важен не процесс как таковой (Agile ли у вас, или что угодно другое), а получение реального результата, быстро, и укладываясь в бюджет - для меня в общем-то была очевидной, но тем не менее, очень важной. Неожиданно понял, что я видел немало людей, сфокусированных на разнообразных процессах. Процессе багтрекинга, подготовки отчетов о проделанной работе, процессе Continuous Integration, процессе code review, семействе процессов Agile, и все такое - вместо того, чтобы быть сфокусированными на поставке работающего софта. Собственно, я и сам бывают, увлекаюсь процессами (т.к. верю, что правильный процесс в малых дозах способствуют достижению результата) - так что я решил для себя, что отныне буду осторожнее с процессами.

А затем был обед, где я оказался за одним столом с, никогда не угадаете, кем - все тем же Яшей Сироткиным, и еще несколькими ребятами, и мы отлично пообсуждали проблемы тех людей, кто хочет что-то реально изменить в большой компании, ориентированной на процесс, а не на результат. Сошлись на том, что если компания изначально ориентирована на процесс, то изменить ее уже не получится. Результатно-ориентированные люди там будут не к месту.

После обеда заглянул на доклад Жени Кирпичева, где он рассказывал про хитрый инструмент собственной заточки для анализа и визуализации логов, который помогает анализировать профили загрузки на кластерах (основное его применение, как я понял). Доклад был ничего так, много любопытных графиков, из которых Евгений извлекал скрытую информацию и разъяснял остальным. Я рассчитывал поймать его после доклада, т.к. знаю что он адепт функционального программирования, редактор журнала fprog.ru, хаскеллист, знаток хайлоада и вообще незаурядная личность, но он, к сожалению, быстро раскланялся и удалился. Ну ладно, как-нибудь еще пересечемся.

Доклад моего бывшего коллеги Андрея Реброва мне тоже понравился. Четко, сжато, быстро, обзорно по основным яваскрипт-фреймворкам для мобильной разработки. JQuery Mobile, Sencha Touch и иже с ними (и даже фреймворк с неприлично выглядящим для русского глаза названием XUI :)). Хороший такой доклад, и рассказан был живо и энергично. После него отметил для себя, что раз уж мы активно используем ExtJS, то и на Sencha Touch надо бы взглянуть попристальнее.

На рассказ Сергея Туленцова по MongoDB немного опоздал, и определенно часть недопонял / упустил, но то что я услышал мне понравилось. По сути, как я помню, они пишут приложения на Вконтакте, которое собирает от пользователей ответы на странные вопросы типа "Как вы думаете, способен ли ваш друг А выпрыгнуть из штанов ради внимания от девушки Б". Короче, приложение по своей полезности как раз под целевую аудиторию контакта. Но доклад был хороший, интересный. Я даже искренне принял докладчика за одного из коммитеров / разработчиков этой  MongoDB - что, определенно, для него большой комплимент.

Ну и последний доклад на котором я был в этот день был доклад от DZ о взаимоотношениях с заказчиками. О программировании там не было ровным счетом ничего, но всем понравилось - уверен, на 99% из-за личности / харизмы самого DZ.

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

Успел, хотя немного и не с начала, на круглый стол, организованный HeadHunter / IT-Dominanta / JetBrains, посвященный состоянию IT-индустрии в Питере. Понял вилки местных зарплат, основные схемы работы компаний, и почему JetBrains перевозит часть своих разработчиков из Питера в Мюнхен, в Германию.

После чего были опять кулуары, коридоры... Заглянул на доклад Андрея Карпова, посвященный статическому анализу кода на С / С++. Интересные примеры трудно находимых ошибок в популярных продуктах, таких, например, как Miranda. Доклад был хорош, и продукт видимо тоже - неясным лично для меня осталось только, как извлекать прибыть используя этот инструмент :) Точнее даже так - насколько часты и критичны те ошибки, которые не отлавливаются юнит-тестами, но которые отлавливаются статическим анализом, и как правильно установить процесс ревью этих ошибок, чтобы получать от этого бенефиты, не тратя слишком много времени на сам процесс (см. доклад Яши Сироткина о том, что процесс ради процесса - зло).

Дальше был отличный доклад Владислава "VladD2" Чистякова, тех. редактора журнала RSDN и по совместительству, главного нынешнего разработчика языка Nemerle. Доклад сам был хорош - единственное замечание по существу - ощущение такое, что аудитория не тянула уровень доклада, и докладчик это в общем понимал, из-за чего перескакивал со слайда на слайд, уходил в разъяснение каких-то деталей  и прочее. Но, повторюсь - лично мне было очень интересно.

Дальше был обед, и после него мой собственный доклад об управлении поставками, а после него часа на полтора обсуждения в кулуарах, из-за чего я, к сожалению, пропустил вторую часть доклада Влада о Nemerle, и успел только на окончание, в котором Денис Рысцов и Влад демонстрировали написанный на Nemerle.PEG парсер JSON-a. Было круто, но по моему, подавляющая часть аудитории не понимала до конца, что, и, главное, КАК там происходит :)

Ну и наверное, это все что мне запомнилось по докладам.

В кулуарах, люди, докладчики и организаторы

 Из докладчиков и просто людей, с кем пообщался - запомнились все тот же вездесущий Яков Сироткин, с которым я до этого оффлайн никогда не встречался..Запомнился Максим Мазин, с которым плодотворно поговорили про языки программирования, метапрограммирование, DSL-языки, грамматики, и в конце - про возможность проведения в Питере конференции по Groovy (тут спасибо Лере Андриановой, которой эта идея возможно, сразу показалось немножко бредовой, но она вроде бы восприняла ее позитивно :)). Еще запомнился Максим Цепков, которому была очень близка моя предметная область, и с кем мы ее обсуждали до и после моего доклад, VladD2, от которого я узнал много интересного о том, что происходит сейчас с Nemerle. Еще запомнились многие, но тут уже перечислять поименно не буду, извините.

Разве что, не могу не сказать про Стаса Фомина, Андрея Майорова и Владислава Орликова. Все они оба дня из кожи вон лезли, чтобы обеспечить нормальный ход конференции, успевая при этом ходить на доклады, постить воодушевляющие твиты, обсуждать технические тонкости в кулуарах. Респект. Стас даже дал мне на один вечер свой запасной ноутбук для дополнительной подготовки (спасибо!).

Организация
Ничего плохого не вспоминается, а значит, все было на самом деле хорошо. Особенно хорошо было то, что иногородние докладчики жили в той же гостинице "Карелия", где проходила конференция - т.е. не надо было вставать спозаранку и ехать до места проведения. Это может быть и мелочь, но очень важная. Про бутерброды, кофе, обеды ничего писать не буду, ибо всем это уже надоело. Скажу только - все было на уровне.


Единственное вот - может, не стоило начинать конференцию так рано, в 9-15? Все таки среди программистов много сов :)

Волшебный город на Неве
Ну и, несмотря плотное расписание конференции, я каждый вечер, включая дни приезда и отлета, гулял по ночному Питеру. Невский, Площать Восстания, Дворцовая, Аничков мост, канал Грибоедова, Фонтанка, Исаакиевский собор.. Питер есть питер. Коренные петербуржцы - цените то, что вы живете в одном из красивейших городов мира!

суббота, 23 апреля 2011 г.

Java и JNI - меняем java.library.path в runtime

В подмножестве экосистемы Java, относящейся в основном к JNI (без которого никуда не деться, если приходиться интегрироваться с каким-то legacy или просто редким и специфическим кодом, написанном на С или каком-то другом языке), есть такое понятие, как java.library.path. Вкратце, это в некотором роде аналог classpath, только не для Java классов и *.jar файлов, а для нативных библиотек - системное свойство, которое указывает JVM, где искать эти самые нативные библиотеки (.dll  в винде или .so под юниксами).

Свойство это устанавливается один раз, перед запуском JVM, через глобальные system properties, или как ключ -Dname=value для JVM, и после этого оно становится read-only. Точнее, менять-то его можно, но никакого эффекта на работу программы это не окажет, т.к. после того как вы обновите это свойство, JVM не перечитает его и не будет использовать новое значение.

Однако, возможность менять java.library.path на лету была бы очень кстати - тогда бы не пришлись много раз генерить, переписывать и перезаписывать скрипты для запуска JBoss-a, например, чтобы отразить в них все нужные пути ДО старта аппсервера.
И такая возможность, изменять эти пути, по которым JVM ищет нативные библиотеки, на самом деле есть. Подробно это объяснено тут:

http://blog.cedarsoft.com/2010/11/setting-java-library-path-programmatically/
и еще вот тут:

http://nicklothian.com/blog/2008/11/19/modify-javalibrarypath-at-runtime/

А здесь я опишу сам механизм загрузки, и почему то, что описано по ссылкам, работает. Обычно, нативные библиотеки загружаются следующим через статический инициализатор:

static {
  try { 
    System.loadLibrary("dp-integ");
  }
}

Этот вызов внутри выглядит так:

public static void loadLibrary(String libname) {
  Runtime.getRuntime().loadLibrary0(getCallerClass(), libname);
}
И далее

synchronized void loadLibrary0(Class fromClass, String libname) {
  // Проверяем, разрешено ли загружать данную конкретную библиотеку
  SecurityManager security = System.getSecurityManager();
  if (security != null) {
    security.checkLink(libname);
  }
  if (libname.indexOf((int)File.separatorChar) != -1) {
    throw new UnsatisfiedLinkError("Directory separator" +
      "should not appear in library name: " + libname);
  }
  ClassLoader.loadLibrary(fromClass, libname, false);
}

Т.е. в итоге, нативные библиотеки загружаются, так же как и обычные классы, через ClassLoader.  У класса ClassLoader есть два свойства, в которых кешируются проинициализированные пути поиска.

// The paths searched for libraries
static private String usr_paths[];
static private String sys_paths[];

Код метода ClassLoader.loadLibrary(fromClass, libname, false), довольно длинный, и загроможденный многочисленными проверками, в сокращенном виде выглядит это так.
// Invoked in the java.lang.Runtime class to implement load and loadLibrary.
static void loadLibrary(Class fromClass, String name, 
        boolean isAbsolute) {
    
  ClassLoader loader = (fromClass == null) ? null : fromClass.getClassLoader();
  if (sys_paths == null) {
    // это то, что нам нужно
    usr_paths = initializePath("java.library.path");
    
    // а это для тех библиотек, которые загружаются из классов,
    // загруженных из boot classpath.
    sys_paths = initializePath("sun.boot.library.path");
  } 

  // Дальше попытка загрузить библиотеку, и дальше,
  //  если найти ее так и не удалось, то -  

  // Oops, it failed
  throw new UnsatisfiedLinkError("no " + name + " in java.library.path");
}


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

Вы можете либо выставить в null свойство sys_paths у класслоадера, либо просто поменять его, добавив к нему нужный путь к вашим нативным библиотекам.

вторник, 19 апреля 2011 г.

ADD Conf 2011 - Масштабирование систем планирования и управления поставками

Как я писал в твиттере и как некоторые знают, я буду выступать на конференции ADD Conf 2011, Питер, 29-30 апреля сего года :).

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

Соответственно, мне интересны любые фидбеки - по содержанию, оформлению, что лучше осветить поподробнее, что лучше вовсе убрать и т.д.

Ваше мнение важно для нас (С)

четверг, 7 апреля 2011 г.

Groovy вместе (и вместо) c Java в production. Part 1

Этот пост - некоторое мое размышление о текущем проекте, может кому-то будет просто интересно. А может, у кого-то есть здесь опыт именно в таких вещах, и он прокомментирует. А может, у кого-то просто очень богатый жизненный и профессиональный опыт, и он даст "мудрый совет"  (tm) . Итак.

В последнее время стараюсь сойти с прямых и строгих рельс стальных рельс Java на языки под платформу JVM. Приложение - платформа для систем управления поставками (логистика, розничная торговля и прочее), коотрая включает в себя разные компоненты для интеграции, расчетов прогнозов (статистические и эвристические методы) , планирования заказов, трекинга перевозок и еще много чего. Написана почти вся на Java (с примесью, как водится, SQL, HTML/CSS/Javascript, + в отдельных компонентах встречается флеш и питон). Пока что мы склоняемся к тому, чтобы окончательно выбрать Groovy. Почему же?



Какие были требования к новому дополнительному языку, и в каких местах мы бы хотели его использовать?


1) Тесты. Написание тестов, особенно таких, которые работают в сервлет-контейнере и базой данных, занимает много времени. По разным причинам. 


Первая, это то что сам язык Java весьма громоздкий, и даже отличная поддержка IDE мало помогает. Данные приходится либо загружать в базу из CSV-файлов, либо создавать руками на лету. И то и то требует много дополнительного scaffolding code, писать который неприятно, а на Java, с ее отсутствием поддержки для коллекция, замыканий, Path Expressions и прочих фич так и вовсе утомляет (когда появляется множество тест-классов каждый по 700-1000 строк кода).


Вторая причина довольно специфическая.  Написание контейнерных тестов на Java и их дописывание / отладка часто требует рестартов appserver-a  (у на это JBoss), который занимает 3-4 минуты. Собственно, любое изменение кода, кроме изменения "кода внутри методов" требует рестарта JBoss-a. Поменял название поля класса? Перезапусти JBoss. Поменял тип параметра метода с float на double? Перезапусти JBoss. И так все время.


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


3) Поддержка метапрограммирования - в понятном, не слишком зубодробительном виде, но достаточная, чтобы создавать DSL-языки для таких вещей, как моделирование иерархических структур данных в БД, построение экранов с отчетами, конфигурации портлетов - в общем того, что в мире Java-энтерпрайза традиционно программируется в XML-e.


Вообще,  "программирование в XML" - это совершенно отдельная вещь. Это именно то, что Джоэль Спольски называет leaky abstraction., только в чуть расширенном понимании. До тех пор, пока вы пишете много простых, тривиальных и однотипных кусков чего-то (чего-угодно), вы на коне, и ваша продуктивность приятно радует менеджеров. Это при условии, конечно, что автор соответствующего фреймворка (для парсинга этих XML-описания и построения по ним каких-то реальных вещей) правильно предвидел наиболее типичные случаи использования, и сделал для них нормальную поддержку.


Но как только вам надо сделать что-то такое, что автор соответствующего фреймворка не предусмотрел - ждите проблем - появления множества кода с тегами // HACK, // REVISE LATER, // FOLLOWUP, и долгих часов обсуждений, а можно ли это поддержать внутри фреймворка, а сколько на это понадобится времени, чтобы это реализовать, а сколько это будет стоить, а достаточно ли это нужная и важная фича, чтобы ее поддерживать на уровне фреймворка, и прочее, и прочее. А Тикет в джире все это время висит и висит c тегом "Being investigated".
Пока это все с причинами. Теперь, так какой у нас есть выбор?

1) Очевидно, хотелось бы что-то компилирующееся в Java-байткод. Лучше конечно, чтобы была полная двусторонняя интеграция с Java на уровне байткода JVM.
2) Предлагаюших писать на Lisp или Haskell в команде не нашлось.
3) Можно брать языки, портированные под JVM - JRuby, Jython. Jython мы используем кое-где, но только для скриптинга и работы с файлами в основном. Имело бы смысл, если бы кто-то из нас хорошо знал Ruby или Python. Таковые есть, но мало. Не айс.
4) Наконец, можно писать на языках, специально написанных под JVM, с синтаксисом, более похожим на Java, с явным упором на отличную интеграцию с ней, но перенявших фишки языков из предыдущих пунктов. Таких нашлось два - Groovy и Scala (есть еще Clojure, и недавно появившийся Morah, но это пока слишком экзотично).

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

Как итог, пока что медленно и осторожно пробуем интегрировать код на Groovy в намеченных областях, и кое-какие выводы уже можно сделать.


Плюсы:
 - Возможность перекомпилировать и перезагружать любые изменения в коде классов под JBoss, в отличие от Java hotswap, который умеет перезагружать "на лету" только изменения кода внутри методов класса. Мелочь, а дух захватывает.
 - Поддержка на уровне языка на списков, мэпов, регулярных выражений, замыканий, GPath-итерации по сложным структурым..метаобъектный протокол, билдеры иерархий, и еще много всего, что есть в Python-e и Ruby  и что делает код короче в разы и приятнее в чтении.


Минусы:
 - Динамическая (опциональная) типизация. Иногда удобно, но ненавижу эти no such property, no method found applicable for the following arguments:... и прочие обратные стороны медали под назвнанием "Rapid Software Development".
 - Редакторы кода и  поддержка IDE. Есть, и неплохая - но намного, НАМНОГО хуже чем для Java в современных IDE. Понятно, что язык динамический и многого не накрутишь..но все же (JetBrains - я в вас верю!!).


Часто упоминаемая на разных форумах и в блогах "низкая скорость выполнения" - не знаю, не заметил. Если вы часто вычисляете ряды фибоначчи (вместо того, чтобы раз посчитать и хранить), вычисляете фракталы Мандельбродта или перемножаете матрицы размером 10000 х 10000 - да, у вас вероятно будет много проблем. Но в типичном Enterprise Java приложении, я так думаю, не менее 60-80% кода можно переписать на Groovy без заметной потери производительности.


Да - мои мысли в этом посте немного связаны с этими.

воскресенье, 3 апреля 2011 г.

Мемоизация рекурсивных замыканий в Groovy - FIXED

Я и забыл написать, чем кончилась вся так история с мемоизацией рекурсивных замыканий - все там оказалось в итоге довольно просто, и бага, который я подозревал, там нет. После 3 минут общения с Jochen-ом (это техлид проекта Groovy), все как-то встало нормально на свои места.

Итак, рассмотрим, как работает мой вариант, тот который я предлагал, и как следует его изменить, чтобы он работал так, как мы ожидаем.

def fact {it -> sleep(100)it ?  it call(it 1g1g}
fact fact.memoize()
println fact(70)
println fact(71)

Что же происходит в этом случае "за сценой"? Если скомлировать этот код, и декомпилировать получившийся байткод в Java-код, для простоты понимания,  то это будет выглядеть (схематично) примерно так:

class Closure1 extends MemoizedClosure {
   call (param{
       bla-bla;
       return param * this.call(param-1); // this тут значит Closure1
   }
 } 
}

Т.е. можно видеть, что вызов call() в теле замыкания всегда обращается к тому объекту замыкания, внутри которого (в лексическом контексте которого), он вызывается. Т.е. в нашем случае это всегда Closure1, а не тот враппер, умеющий делать мемоизацию, который возвращает вызов fact.memoize() (да, метод memoize() всегда возвращает новый объъект замыкания).

А как следует правильно записать это мемоизируемо-рекурсивное замыкание? А вот так:

def fact
fact {it -> sleep(100)it ?  it * fact(it 1g1g}
fact fact.memoize()
println fact(70)
println fact(71)

Обратите внимание на выделанное. Мы определяем переменную fact, и присваиваем ей ссылку на создаваемый объект замыкания, после чего создаем враппер для нашего замыкания и присваиваем ссылку на этот враппер переменной fact. И, поскольку внутри тела нашего  замыкания мы не вызываем его метод call() напрямую, а вызываем fact(), то у нас возникает возможность направить рекурсию по правильному пути - т.е. все рекурсивные вызовы теперь будет проходить через наш fact (т.е. через враппер с поддержкой мемоизации). 

В Java-коде это будет (схематично) выглядеть так:

class Closure1 extends MemoizedClosure {
  // эта ссылка будет указывать на fact, т.е. на closure wrapper,
  // который поддерживает мемоизацию.
  private Closure reference 

   call (param{
       bla-bla;
       return param reference.call(param-1);
   }
 } 
}
Желающие могут запустить исправленный код на http://groovyconsole.appspot.com/ и убедиться в его правильности :).