понедельник, 23 декабря 2013 г.

Чем хорош текст

Недавно отсмотрел серию презентаций небезызвестного Брета Виктора. Кстати всем их рекомендую. Последнюю из них, "The Future Of Programming", я бы назвал вторым лучшим техническим докладом что я видел (Угадайте какой первый?).

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

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

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

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

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

У этого достоинства есть пара очень важных подпунктов. Первый - это валидация. Для текстов есть развитая теория грамматик и не менее развитая практика написания парсеров. Есть ли они для графов/картинок?

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

Думаю мненно этими преимуществами текста вызвана примерно нулевая популярность всяческих графических языков и сред разработки для них.

понедельник, 2 декабря 2013 г.

Упрощение кода с case classes - пример

Давненько я ничего не писал с приверами кода... Вот немного практики в продолжение октябрьского поста про читабельность. Несколько месяцев назад довелось написать вот такой вот класс. Ни функционал ни производительность этого кода особо не важны - затыкалась отдельно взятая дыра в дизайне, кажется успешно :) Если описать этот класс кратко - его экземпляры должны позволять дожидаться получения фиксированного количества результатов. Однако даже беглый осмотр позволяет понять что  код тяжеловато читать, для задачи такой сложности, думаю в районе третьей строчки все закрыли гист и вернулись на эту страницу.

Как мне кажется проблемы с читабельностью имеют две основные причины.
  1. 3 возможных состояния объекта закодированы в 3 аттрибутах. При этом возможны заведомо бессмысленные состояния (doneCnt != res.size() или ex != null && doneCnt == totCnt).
  2. Методы fail, add, listen судя по коду предпринимают какие-то действия  в слепую, иногда проверяя лишь часть переменных. Как следствие о корректности кода нельзя судить по отдельным фрагментам - нужно думать о поведении всех методов сразу.
Это в принципе типичные неприятности при дизайне классов в классическом джавовском стиле. Давайте посмотрим как пара простых изменений позволили их устранить.

Суть выражена в переходе от набора из нескольких аттрибутов к одному но с более сложной структурой.
Было:
private var doneCnt = 0
private var ex: Exception = null
private val res = new ArrayList[T](totCnt)
Cтало:
private var res: Either[util.ArrayList[T], Exception] = Left(new util.ArrayList[T](totCnt))
Помимо экономии пары строк мы добавили немного семантики изменяемому состоянию объектов. Теперь видно что наличие исключения и списка результатов - взаимоисключающие состояния. Но есть ли ещё какие-то скрытые значения в состоянии списка? С одной стороны можно бы это описать в комментариях, но они как известно не проверяются компилятором. Так что я попробовал сделать так чтобы она стала ясна из прочтения первого-же метода.

Было:
def add(item: T) {
  lock.lock()
  try {
    res.add(item)
    doneCnt = doneCnt + 1
    if (doneCnt == totCnt) {
      done.signalAll()
      for (lsnr <- succLsnrs)
        lsnr(res)
    }
  }
  finally {
    lock.unlock()
  }
}
Cтало:
def add(item: T) {
  lock.lock()
  try {
    res match {
      case Left(list) if list.size < totCnt => {
        list.add(item)
        if (list.size == totCnt) {
          done.signalAll()
          for (lsnr <- succLsnrs)
            lsnr(list)
        }
      }
      case _ => return
    }
  }
  finally {
    lock.unlock()
  }
}
Смотрим метод add, из него видно что добавления результатов разрешены пока количество элементов в списке меньше totCnt. Здесь уже вырисовывается точная семантика для аттрибута res.
  1. Right(exception) - сбор результатов провалился.
  2. Left(list) && list.size < totCnt - сбор результатов всё ещё в процессе.
  3. Left(list) && list.size == totCnt - сбор результатов завершён их все можно получить.
По-доброму стоило бы сделать предикаты для res отвечающие в каком именно состоянии он находится, и это добавило бы коду ясности. Но я отказался от этого варианта в пользу match по двум причинам. Во-первых количество обращений к этим предикатам было бы крайне небольшим, повторное использование получалось весьма небольшим. Во-вторых case одновременно с определением состояния позволяет извлечь из него какие-то значения, что немного экономит место на экране. По-моему в коде такой сложности место на экране и необходимость прокруток являются существенными источниками проблем с читаемостью, так что было выбрано решение с матчингом. Надо заметить что есть тут и негативный эффект - выросло количество уровней вложенности.

Метод fail тоже неплохо прибавил в выразительности - теперь оба add и fail явно переводят объект из одного состояния в другое. Надо признать что новая версия ведёт себя не точно так-же как старая, новый подход сделал более строгое поведение и более лёгким. add больше не принимает результаты сверх заданного числа, а fail - не изменяет состояние завершённого объекта.

Было:
def fail(e: Exception) {
  lock.lock()
  try {
    ex = e
    done.signalAll()
    for (lsnr <- failLsnrs)
      lsnr(ex)
  }
  finally {
    lock.unlock()
  }
}
Cтало:
def fail(e: Exception) {
  lock.lock()
  try {
    res match {
      case Left(list) if list.size < totCnt => {
        res = Right(ex)
        done.signalAll()
        for (lsnr <- failLsnrs)
          lsnr(ex)
      }
      case _ => return
    }
  }
  finally {
    lock.unlock()
  }
}
Существенно улучшилась и структура метода get - теперь он сначала дожидается одного из завершённых состояний, потом предпринимает некие действия. Раньше он вынужден был выполнять две по-сути параллельные проверки во время ожидания и по-разному из него выходить.

Было:
def get: JavaCollection[T] = {
  lock.lock()
  try {
    while (doneCnt != totCnt) {
      if (ex != null)
        throw ex
      done.await()
    }

    res
  }
  finally {
    lock.unlock()
  }
}
Cтало:
def get: JavaCollection[T] = {
  lock.lock()
  try {
    while (res.left.toOption.exists(_.size < totCnt))
      done.await()

    res match {
      case Left(list) => list
      case Right(ex) => throw ex
    }
  }
  finally {
    lock.unlock()
  }
}
Метод listen в принципе изменился так же как get, так что я не буду приводить его код. Вот окончательная версия.

Я вижу следующие явные улучшения:
  1. Вместо 3х скоординированных значений - одно, содержащее результат. Состояний по-прежнему три, но способ их кодирования радикально упростился.
  2. Методы add, fail, listen изменили форму. Раньше они более а в основном менее явно предполагали некую историю изменений и обновляли состояние на основе этих предположений. Теперь методы инспектируют текущее состояние, и устанавливают новое на его основе. Практически конечный автомат.
  3. Метод get сначала неявно ожидал нужного состояния одной из двух переменных (ex и size), "произошло исключение или количество результатов сравнялось с ожидаемым". Теперь этот метод явно ожидает пока нарушится одно условие, "существует неполный список результатов". Точек принятия решения в методе изначально было две, стала одна.
Одно общее следствие: класс стало возможно анализировать и читать более мелкими шагами, по одному методу. При этом пространство возможных состояний уменьшилось лишь немного, изменилось лишь их представление и интерпретация.

вторник, 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: вставил ссылки на записи докладов.

среда, 16 октября 2013 г.

ГЭБ

Прочитал сабж. Ещё буду выборочно перечитывать, по крайней мере доказательства...

Что можно сказать?... Автор неимоверно крут. Что бы по науке, технике или искусству вы сейчас не читали - бросайте, читайте эту книгу. Всё-рано после неё захочется перечитать и передумать заново...

От прочтения есть важный бонус. Когда встречается какой-нибудь просветитель рассказывающий что мол нельзя так запросто объяснить зачем там какой-нибудь теркат нужен и что он такое из себя представляет - можно смело говорить что он бездарен и тыкать книжкой в качестве пруфа. Хотя скорее всего будет просто грустно и хотеться уйти...

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

Действительно одна из центральных концепций книги - "странные петли". Системы с обратной связью, но только между всеми уровнями, без начала. Про этакие гармошки свёрнутые в бублик. Про проблемы которые можно начать изучать откуда угодно и и никогда не закончить. Про всю эту жуть которая пугает технаря со стажем, так же как пугала всех математиков ещё 100 лет назад. А может и им до сих пор страшновато?

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

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

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

понедельник, 7 октября 2013 г.

Несколько простых идей по улучшения кода на Scala

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

Самое очевидное, но и самое часто забытое - for. Альтернативой ему обычно выступают цепочки из map, filter, collect, flatMap, foreach. for имеет смысл использовать почти всегда когда нужна манипуляция с составом коллекции. Во-первых он существенно легче читается при росте длины. Цепочка из 3-4 преобразующих методов находится уже на грани понимания, при записи в for-нотации эта грань ещё далеко. Во-вторых for позволяет существенно читабельнее выписывать типы связанных переменных, а компилятор частенько от нас этого требует. Например сравните:
sessions.foreach((s: Session) => mngr.cancel(s.id))
и
for (s: Session <- sessions) mngr.cancel(s.id)

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

Однако есть и ситуация когда прямое использование методов коллекций оправдано - если у нас уже есть функция фильтрации или преобразования и у неё хорошее говорящее имя. Например names.filter(wellFormed). В таком случае скорее формируется мини-DSL вокруг коллекции в целом чем набор манипуляций с отдельными элементами.

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

def hostedOn(host: Host)(node: Node) = host.addresses.contains(node.address)

def handleFailure(failed: Host) = {
    val affectedNodes = allNodes filter hostedOn(failed)
    ...
}

Удачно сочетая методы стандартного API с именами и сигнатурами своих функций можно строить очень читабельные и производительные DSL просто на ровном месте. Там где в джаве обычно получаются циклы с телами на 200 строк.

Третья недоиспользованная возможность языка - case classes. Во-первых они позволяют с разумной перегрузкой по синтаксису создавать новые типы данных. Тип данных из 3 полей это не 50 строк кода, а одна! Их можно и нужно создавать всякий раз когда они нужны, каждый модуль на 100 строк может позволить себе иметь 10 внутренних типов с собственными именами и именами их полей и при этом быть читабельным. Во-вторых они могут быть использованы в паттерн-матчинге без дополнительных усилий.

Особенно всё описанное выше подходит для исключений, не забываем что catch - это по сути паттерн. Если наше исключение является case-классом - можно делать интересные вещи в обработке исключенией. Например вкладывать в них какие-то полезные (но не меняющие семантику данные) данные. Сравните

try {
    ...
} catch {
    case NoDataReceivedException =>
        channel.close()
        showError("No data received.")
    case PartialDataReceivedException =>
        if (confirmDataLoss(session.received()))
            channel.close()
}
и
try {
    ...
} catch {
    case TimeoutException(received) if received == 0 =>
        channel.close()
        showErrorDialog(received)
    case TimeoutException(received) =>
        if (confirmDataLossDialog(received))
            channel.close()
}

В какую сторону изменится выбрасывающий исключения код предлагаю додумать самостоятельно.
В завершение хочу поделиться вот этой вот замечательной книжкой: Scala By Example Раньше она висела на главной документации по языку, но сейчас почему-то исчезла оттуда. Если вы начали изучать Scala с какого-то мудрёного доклада на конференции или вводной на какой-то user group - скорее всего забыть всё услышанное и начать с этой книжки будет отличной идеей. Она является действительно кратким и очень целостным введением в язык, при этом просто полна идеями как с помощью самых простых средств писать очень выразительные программы на Scala.

воскресенье, 8 сентября 2013 г.

Пара слов о реальных проблемах со Scala

Как то давно я накатал изрядную стену текста на тему что хорошо в Scala и почему  люди рассказывающие что на ней невозможно писать не вызывают у меня доверия: http://it-talk.org/post82738.html#p82738

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

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

Существенно лучше показывает себя sbt и волшебная ~. Однако тул это не простой, когда я следил за его развитием совместимость ломалась чуть не каждый релиз. А если надо развивать проект с историей, то смену системы сборки в серьёз никто не рассматривает. В итоге сейчас я вижу скорость работы компилятора главной проблемой scala.

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

Во-первых это понимания почему собственно этот язык. Если люди уверены что new ArrayList/for/add лучше map, он и правда будет лучше!

Во-вторых базового навыка программирование в функциональном стиле. Ничего космического, но надо ясно видеть в коде простые паттерны роде map/filter. Не заводить переменные без повода. Понимать что функции можно параметризовать не только данными но и операциями. Уметь создавать простые АТД по мере необходимости (а не мега бины со сложной символикой null'ов в разных полях). Вообще этерпрайзие мозга сводит на нет потуги эффективно применить языки моложе 20 лет :)

По большому счёту это все серьёзные проблемы, но есть еще пара неприятностей.

Не совсем уравновешенное сообщество. В том смысле что нельзя смотреть лежащий на гитхабе код и стабильно учиться. Не устоялся стиль, не сформировались практики. Нет общепринятой границы хорошо-плохо (что на самом деле хорошо, но не в самом начале). Кто-то пишет на самом деле на хаскеле, у кого-то джава с замыканиями. Нужно очень критическое восприятие для того чтобы отбирать решения и техники для своего проекта. Пруф: http://ru-scala.livejournal.com/39341.html.

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

Противоречивый, сложный фреймворк Akka. Который позиционируется как аналог OTP выполненный на JVM, но на самом деле принципиально от него отличается.

Вот вроде и всё. Ваши мысли по сабжу?

Линкопост

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

Basics of the Unix Philosophy - в статье рассказывается история "философии Юникс", в форме примеров её формулировок разными людьми в разное время. Очень интересно показывается развитие идей в области дизайна программ.

The TTY demystified - подробно рассматривается архитектура TTY, то как особенности железа разных эпох влияли и продолжают влиять на устройство современных (уже исключительно виртуальных) терминалов. Очень интересная история, красивый и пугающий пример врмирования legacy.

Code's Worst Enemy - древний пост в ныне уже мёртвом блоге. Однако в связи со спецификой нынешней работы я прочувствовал его в полную силу только сейчас. В статье рассматриваются динамика и последствия раздувания кодовой базы. Очень помогает критически посмотреть на цену добавления "ещё одной фичи".

Как эффективно обучать инженеров? - изыскания небезызвестного Валкина на тему. Две очень полезные ссылки внутри.

VS Naipaul’s Seven Rules for Beginners - В продолжение темы, 7 простых правил написания текстов. Подкупает именно простотой. Вот, практикуюсь...

Jepsen - большая серия статей в которой рассматриваются проблемы поведения распределённых систем при сегментации. Интересна тем что автор предлагает простой тест, выражающих в конкретных цифрах утерянных данных и применяет его к ряду очень модных "scalable, fault-tolerant, NoSQL" баз данных. Серия ценна подробным анализом и объяснениями результатов тестов. Must read для всех практикующих модерновые БД в полях.

Java's Atomic and volatile, under the hood on x86 - подробный разбор особенностей реализации атомиков в Java. Содержит целый ряд заслуживающих внимания ссылок на смежные темы.

What is RCU, Fundamentally? - рассматривает семейство неблокирующих алгоритмов типа Read-Copy-Update и то как они поддерживаются Linux Kernel. Содержит массу примеров того как легко получить некорректный алгоритм такого типа, упустив из виду нюансы моделей памяти. Попутно даётся и обзор моделей памяти современных процессоров. Весь код на С, но не очень суровом, типичному Java-программисту было понятно.