Показаны сообщения с ярлыком java. Показать все сообщения
Показаны сообщения с ярлыком java. Показать все сообщения

вторник, 22 октября 2013 г.

Joker

Сходил 15 октября на конференцию Joker. Было хорошо, организация на 5- (минус за грустного чувака в костюме бетменовского джокера, Леджер негодует с того света), подбор докладов на 4. Далее краткий обзор докладов на которых я присутствовал.

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

"Парадигмы ООП, основы здравого дизайна и архитектуры Java приложений" Не очень понравился. Рассказывал какой-то коуч, так что представление было бодрым. Но и рассказ согласно званию автора был про очевидные вещи вроде SOLID и DRY. Много ссылок на "авторитеты" вместо рассмотрения каки-то конкретных решений и их последствий. Много проталкивания одной позиции в давних холиварах вроде interface vs. abstract class или setters/getters vs. public field.

"Компромиссы, или Как проектируются языки программирования" Неплохо. Но в основном содержание свелось к "В Kotlin всё збс.". Я пару раз попробовал набросить на тему что хаки в компиляторе склонные выявляться в самые интересные моменты. Мне сказали что встроили аккуратно но крепко, никто ничего не заметит. Ага...

"Платформа для Видео сроком в квартал" Хороший доклад от техлида Одноклассников. Рассказ был про их хостинг видео. Всё кратко и по делу. Вопросы не отставали от доклада. Очень понравилось. Не так часто разрабы сервисов такого уровня делятся архитектурными решениями и цифрами. Яндекс вон всё больше норовит мап-редьюс фреймворк какой заопенсурсить.

"The (not so) dark art of Performance Tuning" Откровенная муть. Какие-то эмпирические правила на тему что смотреть при проблемах с производительностью и куда крутить когда что-то увидели. Мало описания смысла циферок в табличках, мало идей на тему как искать проблему когда она не на поверхности. Долгое и непонятное демо в конце.

"Разработка API в Java-проекте: как оказывать влияние на людей и не приобрести врагов" Попытка обобщить хорошие практики в дизайне API на джаве. Много очевидных вещей, но зато автор не ленился приводить альтернативы и трэйд-оффы. Подача мягко говоря хромала, очень монотонная без намёка на акценты речь. Каких-то новых для себя идей не заметил, но как обобщение пойдёт.

"JDK8: Stream style" Хороший технический доклад от ораклового инженера. Довольно кратко и по делу. После него достаточно понятно становится чего ждать от 8го релиза джавы. Были приведены кое-какие интересные детали дизайна. Докладчик опытный, подача на отл. Рекомендуется к просмотру.

"В поисках Tommy Hilfiger" Интересный рассказ про внутренности Lucene. Немного дизайна немного алгоритмов, много задачек ушло в зал, на что-то я даже ответил. Докладчик молодец что (а) выбрал техническую тему, без лирики, (б) с самого начала наладил взаимодействие с аудиторией. Если Кормен на полке покрылся пылью то рекомендуется к просмотру с паузами на вопросах в зал.

UPD: вставил ссылки на записи докладов.

четверг, 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

среда, 22 октября 2008 г.

Ещё одна странность JBoss

Почемута JDBC драйвер для PostgreSQL выложенный в папку /lib приложения подцепляется неправильно. /*Сдаётся мне проблема в извращённом класслодинге и/или том хаке, который я описывал простом раньше*/ При обращении выдаёт ошибку в духе: "неправильный драйвер для такой строки соединения". Лечение тривиально - положить жарник с драйвером в /lib сервера.

Раздельные логи приложений в JBoss

Встала задача организовать раздельную запись логов для нескольких экземпляров одного и того-же приложения, запущенных на сервере(разделение экземпяров просто переименование war-ок). При этом дополнительно хотелось получить также: конфигурирование логов отдельно от самого сервера(читай не в серверном jboss-log4j.xml, а в собственном конфиге приложения, один экземпляр log4j как на диске, так и в памяти.

Простая упаковка конфига вместе с приложением успеха, как и ожидалось, не принесла. Если верить форумам/мэйллистам причина в том, что по умолчанию JBoss подкладывает приложениям свои библиотеки в класспат, соответственно приложения используют уже инициализированный экземпляр log4j.
Вторым этапом стала попытка использовать хак со слушателем контекста и установкой в нём RepositorySelector'а взятый тут, раздел 10.3.8(что примечательно попал он мне на глаза сначала в каком-то блоге, а не в оф. доке). Работал он плохо: при переразвёртываниии приложения падала ошибка при инициализации JBoss log4j plugin. Думаю из за того, что при этом старый ClassLoader убивается и все созданные им классы вместе с ним. Заниматься дебагом сервера было немного лень, а сообщение обошибке было, мягко говоря, кратким.
В третий заход применил подход описанный там-же в разделе 10.3.6. А именно: положил в папку WEB-INF/lib log4j и commons-logging(в своём приложении на всякий случай решил использовать его, а не напряму log4j), положил в папку WEB-INF/classes свой log4j.xml а также создал файлик jboss-web.xml(до этого обходился стандартным дескриптором) с содержимым, описанным в доке чуть ниже.
Потом ещё переучил оставшийся с прошлой попытки листенер записывать в системные свойства имена приложения(точнее контекста) и конфига(log4j.xml). /*В спринге есть специальный, более одарённый листенер для этих целей, но спринга пока в проекте нет и связываться было лень.*/
Commons-logging увидел старшого брата без дополнительных манипуляций.

Результат: аккуратненький набор папочек в /log сервера с подневными логами соответствующих приложений. Надо отметить, что это, между тем, не совсем нирвана ибо экземпляр log4j на каждое приложение - и память не по делу и место на диске... в эпоху гигабайтных планок не сильно проблемно, но всё-таки. Есть мнение, что если поглубже покопать в серверный класслоадинг
(точнее хоть чуть-чуть копнуть, ибо то, что вписал в конфигурашку я понял приближённо) можно её и достичь...