четверг, 5 мая 2011 г.

"Version control with Git"

Эх, что-то своих мыслей давно не приходило. Ладно буду компенсировать пересказом чужих.

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

Автор книги, Jon Loelinger, контрибьютор проекта Git и старательно фокусируется на его внутреннем устройстве и логике, лежащей за тем или иным поведением. Книга весьма радикально отличается по глубине изложения от свободно доступной Pro Git и от полупиратски попавшей ко мне "Getting good with Git" (она правда на полноту и не претендует).

Так вот, начинается книга (не считая установки и "первых шагов" естественно) с обзора внутреннего устройства репозитария, его элементов и их структуры. Обзор, кстати, оказывается настолько хорош, что его не приходится дополнять на протяжении почти всей книги. Далее, описывая новую операцию, автор приводит краткий обзор того, как эта операция переводится в термины внутренних структур репозитария.

Этот подход используется повсеместно практически во всей книге и результат по-моему просто отличный. Те места, которые выглядели сложными и запутанными после других источников делаются абсолютно прозрачными и очевидными. Весь этот ад с удалёнными репозитариями, fetch vs pull, tracking branches и т.д. становится более менее понятен или по крайней мере поддаётся освоению с точечными вычитками документации.

Вообще после книжки осталось стойкое ощущение, что использование Git подразумевает изучение его самого, а не того как с ним работать. Причём подразумевает на уровне самых ранних дизайнерских решений. Кстати, интересно есть-ли инструменты (наборы скриптов?..) превращающие его во что-то вроде Subversion c локально доступной историей но при этом соответственно сниженным порогом вхождения?

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

P.S. С удивлением заметил, что в книжке слово developer заменяется местоимением she. Это что получается: разраб в английском по умолчанию - девочка?

воскресенье, 27 марта 2011 г.

"Java concurrency in practice"

Собственно прочитал, наконец сабж. Книга без сомнения отличная и информации о ней в Сети достаточно, добавить особо нечего. Запишу просто пару субъективных впечатлений.

Книга написана просто и понятно. Материал изложен абсолютно последовательно без ссылок вперёд и с минимальным расстоянием в обратных ссылках. Автор старательно избегает обращения ко всяким примитивам синхронизации, вместо этого старается ввести набор паттернов и идиом безопасной синхронизации. Только в последней главе можно увидеть подробное изложение JMM и несколько трюков напрямую с ней связанных.

Из нового для себя я открыл работу с interrupt и InterruptedException а также паттерны корректной остановки выполнения заданий. Вообще тема останова и отмены задач в Java до сих пор была для меня  не изведана. Познавательно, но местами сложновато.

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

P.S. А еще ко мне приехала новая пачка книжек, в том числе DSLs in Action и Version Control with Git, также на подлёте Programming in Scala. В первую очередь навалился на Git. Очень много новой информации относительно устройства репозитария и принципов работы разных комманд. По подробности изложения несравнимо с "Getting good with Git" и "Pro Git".

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

    Об учебных примерах

    Подготовка к продолжению семинара по Java для OSLL/АУ заставила снова глубоко озадачиться построением учебных примеров на тему программирования (кстати, для кого рассказываю и какой уровень ожидается я так и не понял, с одной стороны там рассказывают какой то реальный rocket science мужики зашкаливающей суровости, с другой стороны позвали тупо меня рассказать про тупо фичи Java). Тут ещё наложился затяжной флейм в одной занимательной рассылке. В этой связи я решил выписать важные для меня критерии качества для учебного примера на тему программирования. Для себя точку отсчёта зафиксировать, да может ещё кому интересно будет.

    1. Доступность. Пример должен быть доступен для понимания с минимумом затрат. Требования к теоретическим познаниям должны быть собраны в явные prequisities. Когда в середине, а то и в примечаниях в конце говорят "Так, а вот тут вам нужно (было) прочитать [1], [2], [3] и вот тот учебник." - материал идёт лесом.

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

    3. Простота. В примере должно так мало сущностей, как это возможно. Очень плохо, когда появляются примеры вроде: "Мы реализуем подсистему логирования с помощью аспектно-ориентированного программирования, но ещё нам потребуется IoC контейнер для включения аспектов".

    4. Изолированность. Пример должен задействовать минимальное количество (в идеале одну) фичей языка и/или техник программирования. Если речь идёт о каком-то последовательном изложении (например введении в ЯП) совершенно недопустимы ссылки вперёд. Граница с пунктом 2 не очень чёткая. Я их разделяю так: простота связана с требованиями к окружению (например "мы внутри IoC контейнера" или "у нас есть парсер JSON"), изолированность связана с инструментарием, привлекаемым непосредственно к решению задачи (например "мы используем обобщённые типы" или "мы используем механизм макросов").

    5. Реалистичность. читатель не должен проводить бессонные ночи ищя ответ на вопрос "зачем?". Знание, не подкреплённое практикой или хорошей идеей как эту практику можно устроить, быстро теряется и затирается чем нибудь более актуальным. Чисто эмпирически мне кажется, что пример должен попадать в одну из следующих категорий:
    а) Часть большего и жизненного примера (например "мы пишем графический редактор и нам нужна унифицированная обработка фигур" - "нас спасёт паттерн композит").
    б) Какое-то решение общей проблемы программирования ("нам нужно абстрагировать вычисление расстояния между точками от представления их в памяти") с которой гарантированно сталкивался каждый.
    в) Общеупотребимый алгоритмом или структура данных (например "решение комбинаторной задачи" или "представление графа в памяти").

    6. Масштабируемость. В идеале пример должен быть доведён до конца в несколько итераций, с целью показать несколько доступных уровней глубины. От простого (задача едва-едва решена) и узкого решения к более сложному (максимальному проходящему по критериям 2 и 3) и полному. Соответственно и при практическом применении рассматриваемого инструмента остаётся возможность подвигать ползунок.

    7. Содержательность. Что-то вроде возможности ответить на вопрос "зачем?" на самом высоком уровне. Пробовал сформулировать по разному, но кажется как не крути получается критерий-солянка:
    а) Не велик. Плохо когда что-то делается, а в конце говорится "на самом деле взрослые дяди это уже решили в стандартной библиотеке и лучше пользоваться их решением". Оставляет смешанное чувство собственной убогости ("действительно годное решение мне не понять?") и низкого качества материала ("то есть то же самое можно сделать лучше?"). Но это всё-равно лучше, чем когда такого примечания нет :)
    б) Связь с каким-то юзкейсом верхнего уровня (очевидно что 6. а) избавляет от этой проблемы в принципе).
    в) ..?

    Надо заметить, что это касается технических материалов для практиков. Научные статьи и обзоры по понятным причинам в эту шкалу не вписываются. А те люди которые легко переживают отсутствие таких примеров без сомнения должны бросать неблагодарное инженерное дело и релоцироваться в класс учёных. С другой стороны я недавно видел пример того, что в умелых руках даже казалось бы безнадёжно сложные и теоретизированные вопросы находят прекрасную иллюстративную основу, для сравнения: куча введений в продолжения от скалохаскелистов [1], [2], [3] и одно от питонщика.

    среда, 16 февраля 2011 г.

    "Структура и интерпретация компьютерных программ"

    Вот я наконец и дочитал эту замечательную книгу. Ну точнее постановил считать её дочитанной, так как предпоследняя глава растянулась чуть ли не на полгода, а последняя обещала и того дольше.

    Процесс и правда изрядно затянулся. Я начал её читать ещё в сентябре позапрошлого (2009) года. И вот только пара недель как закончил. Причина очень проста: книга явно требует подходящего настроения и регулярно читать не получалось хронически. Процесс и сам по себе получился не обычным. Я начал её читать с экрана, в виде английской pdf'ки. Однако, когда я был где-то на середине вышел очередной тираж русского издания и я без раздумий добыл себе бумажный экземпляр на русском.

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

    Ещё важный момент, все примеры в книге выполнены на LISPе (а точнее Scheme). Годика три назад, я наверно по этой причине сделал бы из книжки костер и сплясал бы вокруг него. Однако изучение Smalltalk и Python (а точнее отзывов их авторов о влиянии LISP) в последствии немного сгладили мое отношение к скобочкам. Нельзя не признать, что для первых глав, где демонстрируется куча разных подходов к программированию, он является пожалуй оптимальным выбором. Однако длинные куски кода на LISPе всё-таки страшно выглядят, понимание многих примеров из последних глав отнимало много времени, в основном на вычленение стандартных конструкций из мета-кодировки в скобках.

    Итак, о чём же собственно книга.

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

    Вторая глава, "Построение абстракций с помощью данных", рассказывает о том, что такое данные, как их можно унифицировать (на уровне доступа) с процедурами и почему это бывает важно. Попутно вводится понятие структуры данных и рассматриваются примеры, вроде пар, списков и т.п. Описывается свойство замкнутости для структур данных (программ?), и демонстрируется его важность в дизайне программ. Заканчивается глава по сути кратким введением в ООП, а точнее подробным рассмотрением идеи программирования в "стиле передачи сообщений". Показываются ЛИСПовые техники реализации классов, объектов, методов. Последнее без сомнения лучшее введение в ОО-программирование из всех что я видел. В основном благодаря тому, что мир объектов и сообщений строится вместе с читателем, а не приводится как данность (тут я немного забежал вперёд - достраивается он уже в следующей главе).

    Третья глава, "Модульность, объекты и состояние", вводит в стройный мир асбтрактных вычислений, выстроенный в первых главах, проблемы реального мира: присваивание и время. Примерно половина главы посвящена тому как эти явления аккуратно моделировать в языке программирования, и примерно половина конкретным техникам программирования. В этой главе особо порадовал раздел про потоки, который был прочитан с учётом возвратов из следующей главы и перелистываний на досуге раза три. Совершенно замечательный пример простого решения массы сложных проблем, настоятельно рекомендую для прочтения, даже если за всю книгу лень браться.

    Четвёртая глава, с жутким названием "Метаязыковая абстракция", посвящена созданию интерпретаторов языков программирования. В ней сначала подробно расписывается архитектура и реализация интерпретатора самой Scheme, а затем вносится в него ряд изменений, для поддержки экзотических методов программирования. Меня, в контексте аспирантуры, конечно больше всего заинтересовал логический интерпретатор (пролог в скобочном синтаксисе), который наверно единственный я более-менее до конца понял.

    Ну и на пятую главу, "Вычисления на регистровых машинах", я забил :)

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

    Вообще книга заставила много раз подряд проклясть родной универ. Всё-таки способность отечественных преподавателей сделать из даже самых интересных и полезных знаний невнятную, серую, трудноперевариваемую массу просто поражает. Вспомнился и курс функционального программирования, двухмесячный, с быстрым рассмотрением синтаксиса и стандартных функций, какого-то древнего ДОСовского ЛИСПА, увенчанный курсовиком, в котором нужно было сделать базу данных то ли студентов на кафедре, то ли телевизоров на складе. Курс, на котором понятие неизменяемой структуры данных даже не звучало, не говоря уже про то, чтобы объяснить выученному на паскале потоку как с ними работать. И курс объектно-ориентированного программирования, без единого упоминания о передаче сообщений.

    Ну да ладно, напоследок, по традиции, критическое замечание к сабжу. В последних главах очень мало диаграмм к сложным примерам кода. Хорошее именование и форматирование это конечно хорошо. Но всё-таки картинки - лучший способ доносить архитектуру до умственно не далёких читателей вроде меня.

    воскресенье, 13 февраля 2011 г.

    Из рассылки одного Open source проекта

    Q: ... What is the purpose of this group, given the existence of stackoverflow.com? Is there some advantage I am perhaps missing? ...

    A: I believe here we ask serious philosophical questions and get serious philosophical answers. At least we are trying to.

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

    четверг, 27 января 2011 г.

    Немного поупражнялся в рефакторинге

    Пару дней назад увидел пост на крайне актуальную тему в одном занимательном блоге. Суть: автор увидел кусок говнокода на Scala, переписал в цивильном виде, сделал аналог переписанного на Java и сравнил. По итогам сомнения он пришёл к выводу что разница в 30 строк кода из 100 не существенна для выбора языка, а идейной разницы никакой нет.

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

    Во-первых, утверждается, что два текста программ, приводимых в конце, одинаковы. И один имеет не лучшую надёжность, чем другой. Это просто не верно. Дело в том, что несмотря на очень похожую структуру Scala-код имеет на несколько порядков большую "защиту от дурака". Для обеспечения сравнимого уровня пассивной защиты от ошибок на Java требовалось-бы написать как-то так:
    @rest.Method(httpMethod = Array(POST)) 
    public void login(final rest.Request request, 
          @rest.Param(name = "login") @Nullable final String login, 
          @rest.Param(name = "password") @Nullable final String password) 
    { 
       debug("accessing login... " + login); 
    
       if( login == null || password == null ) 
          respond(UNAUTHORIZED, "You should provide login and password"); 
       else 
          doLogin(request, login, password); 
    } 
    
    private void doLogin(@NotNull final rest.Request request, @NotNull final String login, @NotNull final String password) 
    { 
       final UserInfo user = AccountsStorage.find(login); 
       if( user == null ) 
          handleUnknownUserLogin(); 
       else 
          (new KnownUserLoginHandler(request, login, password, user)).handle(); 
    } 
    
    И прикрутить сверху проверку статическим анализатором кода. Говорить о читабельности кода после такого преобразования не приходится.

    Во-вторых приводится аргумент, что разработчик, с учётом возможностей современных IDE будет руками писать кода Java ничуть не больше(Справедливости ради стоит заметить, что скорее всего даже меньше). Аргумент не очень хороший, в контексте того что код ведь обычно не столько пишут, сколько читают. У меня на строку написанного кода приходится минимум сотня прочитанного, и с пять сотен бегло просмотренного. Во время работы каждый программист возвращается к ранее сделанному много раз и шум вроде повторения списка аттрибутов класс четыре раза в разных обрамлениях мягко говоря не помогает понять свою или чужую мысль.

    В-третьих, в примере используется внешняя библиотека (для работы с http судя по всему) совершенно не приспособленная к языку и не использующая его возможностей. Как на образец удачной, основанной на полноценном использовании языка библиотеки можно посмотреть например на Circumflex Web Framework.

    Ну и в-четвёртых собственно выразительные возможности скалы используется крайне скупо и внутри самого решения. Естественно, что если писать в точности как на Java то и никаких отличий не обнаружится. В доказательство этого утверждения я попробую решить эту-же задачу в немного более скальном стиле.

    Итак, что мне не нравится в предложенном решении и что я собираюсь улучшить:
    1. Сквозная связанность всего кода авторизации с классами внешней библиотеки. Не то чтобы переезды целых приложений с одного фреймворка на другой являются обычным делом, но когда они всё-таки происходят...
    2. Весь алгоритм по сути выполняет простой выбор: авторизовать пользователя или выдать сообщение о том, почему в авторизации отказано. В предложенном решении для того чтобы это понять требуется отсмотреть всю сотню строк кода, а исключить другие (ошибочные) варианты реакции весьма затруднительно.
    3. Сам алгоритм авторизации имеет преимущественно линейную структуру:
      • проверить, что логин и пароль предоставлены;
      • найти соответствующие учётные данные в БД;
      • проверить, что пользователь имеет право на вход;
      • аутентифицировать по паролю.
      Несмотря на это код с самого начала начинает ветвиться, полностью скрывая основную идею.

    Приступим. Вся процедура авторизации имеет своим результатом или учётные данные авторизованного пользователя, или сообщение об ошибке. Так и запишем:
    type LoginResult = Either[UserInfo, String]
    def Success[T, E] = Left[T, E] _
    def Failure[T, E] = Right[T, E] _
    

    Сама процедура логина получает возможно предоставленные данные, и возвращает описанный выше результат:
    def login(login: Option[String], password: Option[String]): LoginResult = ...

    Теперь можно отвлечься от деталей процедуры логина и заняться обработкой результатов
    @rest.Method(httpMethod = Array(POST))
    def login(request: rest.Request,
        @rest.Param(name = "login")
        loginParam: String,
        @rest.Param(name = "password")
        passwordParam: String): Unit =
      login(Option(loginParam), Option(passwordParam)) match {
        case Left(user) =>
          request.session("user") = user
        case Right(message) =>
          respond(UNAUTHORIZED, message)
      }
    
    Здесь важно во-первых что количество вариантов внешней реакции системы явно ограничивается. Во вторых, что на этом уровне заканчиваются все связи с фреймворком, включая возможные null'ы в параметрах вызова (именно по этому я не стал делать процедуры, аналогичной setupSuccessfulAuthResult - она просто не создавала бы никакой полезной абстракции).

    Дальше хочется заняться собственно процедурой логина, но стоит снова оставить её в стороне и описать все шаги авторизации. Очевидно, что каждый из шагов будет иметь то-же результат, что и процедура логина в целом, однако будет требовать разных данных на вход. Можно записать каждый шаг в виде отдельной функции:
    def findUser(login: String): LoginResult =
      AccountsStorage.find(login).toLeft( "User not found" )
    
    def checkUser(user: UserInfo): LoginResult =
      if (user.inactive) Failure("Account is inactive")
      else Success(user)
    
    def doLogin(user: UserInfo, login: String, password: String): LoginResult =
      if (user.authScheme == "PETRIVKA")
        handlePetrivkaAuthSchemeLogin(user, password)
      else
        handleUsualAuthSchemeLogin(user, login, password)
    
    def handlePetrivkaAuthSchemeLogin(user: UserInfo, password: String): LoginResult =
      if( user.passwordMatches(password) ) Success(user)
      else Failure("Authentication failed")
    
    def handleUsualAuthSchemeLogin(user: UserInfo, login: String, password: String) =
      AccessStorage.access.auth_configs.find(_.key == user.authScheme) match {
        case Some(scheme) =>
          log.debug("authenticating with " + scheme.command)
          val exec = Runtime.getRuntime.exec(
              scheme.command replace("{login}", login) replace("{password}", password))
          if( exec.waitFor == 0 )
            Success(user)
          else
            Failure("Authentication within " + scheme + " failed")
        case None => Failure("Unknown authentication scheme: " + user.authScheme)
      }
    
    Если немного помедитировать на handleUsualAuthSchemeLogin, то наверняка можно её сократить и упростить, но это мало повлияет на основную идею решения.

    Теперь осталось самое простое - собрать все шаги вместе. Совершенно случайно в Scala завалялась подходящая конструкция :)
    def login(login: Option[String], password: Option[String]): LoginResult =
      for (login <- login.toLeft( "You should provide login" ).left;
           password <- password.toLeft( "You should provide password" ).left;
           user <- findUser(login).left;
           checkedUser <- checkUser(user).left;
           loggedUser <- doLogin(checkedUser, login, password).left
      ) yield loggedUser
    
    Не вдаваясь в детали, скажу что выражение for делает очередной шаг и проверяет результат: если он успешен то продолжает цепочку, если неуспешен то прерывает цепочку, возвращая неудачу. То какой вариант сейчас считается успешным мы сообщаем в конце каждого выражения, я соответственно всегда считаю успешным левый. Метод toLeft у класса Option преобразует его в Either, говоря куда помещать существующее значение и чем заменять несуществующее.

    Вот и всё. Данный пример исправляет отмеченные ранее недостатки, при этом имеет более простую (7 элементов против 9) и близкую к задаче структуру. Также стоит добавть, что он имеет и существенный недостаток: двойную терминологию. В одних местах используется пара Success-Failure, в других Left-Right. Однако это имеет и положительный эффект - интерпретация результатов всегда отличима от их создания.

    Вот полный исходник с кое-какими моками для компилябельности.
    class LoginDemo {
    
      type LoginResult = Either[UserInfo, String]
      def Success[T, E] = Left[T, E] _
      def Failure[T, E] = Right[T, E] _
    
      //@rest.Method(httpMethod = Array(POST))
      def login(request: rest.Request,
          //@rest.Param(name = "login")
          loginParam: String,
          //@rest.Param(name = "password")
          passwordParam: String): Unit =
        login(Option(loginParam), Option(passwordParam)) match {
          case Left(user) =>
            request.session("user") = user
          case Right(message) =>
            respond(UNAUTHORIZED, message)
        }
    
      def login(login: Option[String], password: Option[String]): LoginResult =
        for (login <- login.toLeft( "You should provide login" ).left;
             password <- password.toLeft( "You should provide password" ).left;
             user <- findUser(login).left;
             checkedUser <- checkUser(user).left;
             loggedUser <- doLogin(checkedUser, login, password).left
        ) yield loggedUser
    
      def findUser(login: String): LoginResult =
        AccountsStorage.find(login).toLeft( "User not found" )
    
      def checkUser(user: UserInfo): LoginResult =
        if (user.inactive) Failure("Account is inactive")
        else Success(user)
    
      def doLogin(user: UserInfo, login: String, password: String): LoginResult =
        if (user.authScheme == "PETRIVKA")
          handlePetrivkaAuthSchemeLogin(user, password)
        else
          handleUsualAuthSchemeLogin(user, login, password)
    
      def handlePetrivkaAuthSchemeLogin(user: UserInfo, password: String): LoginResult =
        if( user.passwordMatches(password) ) Success(user)
        else Failure("Authentication failed")
    
      def handleUsualAuthSchemeLogin(user: UserInfo, login: String, password: String) =
        AccessStorage.access.auth_configs.find(_.key == user.authScheme) match {
          case Some(scheme) =>
            //log.debug("authenticating with " + scheme.command)
            val exec = Runtime.getRuntime.exec(
                scheme.command replace("{login}", login) replace("{password}", password))
            if( exec.waitFor == 0 )
              Success(user)
            else
              Failure("Authentication within " + scheme + " failed")
          case None => Failure("Unknown authentication scheme: " + user.authScheme)
        }
    
      def respond(code: Int, message:String = "") = {}
    
      val UNAUTHORIZED = 401
    }
    
    class UserInfo {
      val inactive = false
      val authScheme = ""
    
      def passwordMatches(pwd: String) = true
    }
    
    object AccountsStorage {
      def find(login: String): Option[UserInfo] = None
    }
    
    object AccessStorage {
      object access {
        object auth_configs {
          def find(pred: {val key: String} => Boolean): Option[{val command: String}] = None
        }
      }
    }
    
    package rest {
      class Request {
        val session = scala.collection.mutable.Map[String, Any]()
      }
    }
    

    четверг, 13 января 2011 г.

    Провёл семинар по Java

    В начале декабря проводил семинар для пары студентов из OSLL про Java. Так как народ в основном ковыряет железки больше что-то не нашлось кому рассказать у них про Java. Вот я и вызвался. Ораторские неспособности потренировать, да для себя какой-нибудь забавный фактик отрыть.

    Идея провести семинар (точнее взяться наконец и провести, ибо тема семинара уже давно в их виш-листе висела) пришла ко мне давно. Соответственно кое какие мысленные подготовительные этапы я прошёл. Хотелось поменьше рассказать про язык (до него благо хоть от ++ хоть от # полшага сделать) и по больше про виртуальную машину, библиотеки, то что Sun при жизни именовал экосистемой.

    Наученный горьким опытом недавней подготовки лекции про RDF для семинаров лаборатории (там была масса "ощущений" и последующих выводов, надо бы тоже отписаться) я начал готовиться за 3 недели. Сел, бодро накидал план в 4 пункта:

    1. Устройство платформы. Ну понятно VM, байт-код, компилятор, библиотеки.
    2. Пример простой программы. Тут помимо куска кода хотелось ещё скомпилировать и потом рассмотреть декомпилятором что получилось. Основная цель - изгнать из сознания слушателей мысли о волшебстве в байт-коде и работе JVM.
    3. Обзор языка. Быстро, чтобы только понять о чём речь. Класы, интерфейсы, методы и аттрибуты, примитивные типы.
    4. Собственно обзор "мира". Библиотеки, фреймворки, ну и моё маленькое увлечение - другие языки.

    Первый и второй пункты написал быстро, вечера за три. Дальше стало резко хуже. Когда что-то используешь особо не задумываешься. Но когда надо заключить это в текстом и потом рассказать свежему человеку понимаешь насколько там на самом дохрена всего. Чуть больше года назад я писал небольшую вводную статья о Scala на хабр, там было и правда легко - язык я знал чуть, "особых случаев" там поменьше, да и в глаза они особо не бросались. А в Java ведь так и не знаешь с какого конца браться. "Пишем в класса public static void main" - какой класс? Откуда он взялся? Откуда не начни приходишь к класслоадерам и мониторам. Ладно, кое как разложил, упорядочил, докинул в начальный план дженериков, а то как-то совсем убого выглядела старушка.

    Ещё был неприятный вопрос с примерами. С одной стороны рассказывать о конструкциях языка махая в воздухе руками как-то странно. С другой стороны пространные синтаксические конструкции не располагают к выписыванию и запоминанию. Кроме того привод пример программы надо как-то объяснять что она делает. В общем покрутив и так и сяк план, понял, что стоит вернуться к классической схеме. А именно сначала описание языка и синтаксических конструкций с кусочками кода (как ни старался не получается их делать содержательными). Потом пример, декомпиляция. В итоге 2 и 3 пункт поменялись местами.

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

    Последним кусочком был обзор мира, начальный план был обширен:

    1. Бибилиотеки/фреймворки
      1. JEE
      2. Spring
      3. Apache-commons
      4. OSGi
    2. Инструменты
      1. Ant
      2. Maven
      3. IDEs (Eclipse, IDEA, Neteans)
    3. Языки
      1. Groovy
      2. Scala
      3. Clojure

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

    Ну затем и пришёл день события. Из человек 6 отписавшихся о желании слушать по факту пришло 2 :) Я про себя от души посмеялся, но всё-таки количество народу полностью развязало мне руки в плане скорости изложения и объёма. Надо заметить, что оба имели хорошую вводную по Java и кажется уже изрядно программировали на ++. Так что я как мог навалился на рассказ про архитектуру платформы. Язык прошёл легко, большая часть вещей была народу знкома, действуя по ситуации, я пару раз заворачивал вглубь: немного сказал про ограничения в дженериках(<T extends Iterable>) и анонимные классы. Явный промах был только в части обзоров. Уже в процессе я понял, что идея рассказывать про библиотеки без юзкейсов и примеров кода - порочна по определению. Однако даже этот кусочек не совсем пропал - идея Inversion of control кажется всё-таки нашла понимание в массах. Ну и естестенно я как мог с запалом рассказал про Groovy и Scala, перечислил длинный список плюшек. По Groovy набросал примеров на поиски и выборки из коллекций. Из Scala привёл пример quicksort а-ля Haskell (поставил я тут, кстати, эксперимент на сортировку 1 000 000 интов - всего в три раза медленнее нативного на массивах, кажется в Швейцарских университетах практикуют чёрную магию). Ну и вычисление ряда Фибоначи на скобочках естественно =)

    Ну и напоследок я спросил нужно ли продолжение и ответ был положительным. Так что постараюсь по весне сделать либо что-то поглубже про Java (это если Блох приедет и успею прочитать) либо (что привлекает больше и будет иметь больше практического фундамента к тому времени) краткое введение в Groovy и Scala. Ибо как говорит один английский профессор - "лучший способ разобраться в чём-нибудь - прочиать по этому курс".

    PS вот моя недопрезентация к семинару, в основном веслые картинки: http://dl.dropbox.com/u/1776995/pres.pdf