вторник, 1 января 2030 г.

О блоге

Более двадцати лет я занимался разработкой ПО, в основном как программист и тим-лид, а в последние пару лет как руководитель департамента разработки и внедрения ПО в компании Интервэйл (подробнее на LinkedIn). Поэтому в моем блоге много заметок о работе, в частности о программировании и компьютерах, а так же об управлении.

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

пятница, 28 ноября 2014 г.

[life.sport.darts] Осенний этап Кубка Гомеля -- 30 ноября 2014

Осенний этап Кубка Гомеля по дартс назначен на 30 ноября 2014 года. Место проведения турнира: СШ№66 г.Гомеля (ул.Макаенка, д.19).

Личное первенство. Игра в 501 single-in/double-out.

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

Победитель и призеры (1-3 места) награждаются памятными медалями.

Вся информация по турниру собрана на официальной странице.

PS. Это, вероятно, предпоследний турнир в Гомеле в 2014-м году. Далее будет еще Финал Кубка Гомеля в декабре, на который, судя по всему, будут допущены только те игроки, которые принимали участие в квалификациях и этапах Кубка Гомеля. Так что это крайняя возможность заработать право побороться за Кубок Гомеля 2014. Не пропустите! ;)

PPS. Этот пост специально будет висеть вверху блога до конца ноября 2014.

четверг, 27 ноября 2014 г.

[business] Почему G+/Facebook/VK/etc не хотят становится нормальным площадками для текстового контента?

Регулярные проблемы с G+ комментариями навели меня на вот такой простой вопрос: "Почему Facebook не позволяет писать полноценные посты?"

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

Но оставим в стороне тех, кто сидит в соцсети full-time за деньги. Возьмем простого блоггера вроде меня. С одной стороны, мне интересно, чтобы меня читало все больше и больше людей. С другой стороны, мне не интересно публиковать ссылку на очередной блог-пост в десятке разных соцсетей. Тем более, что ситуация сейчас такова, что порядка 80-90% читателей уже являются пользователями либо Facebook, либо G+ (либо и того, и другого сразу). А дальше будет еще больше. Все идет к тому, если если я есть в Facebook, то мне становиться безразлично, что кого-то из моих читателей в Facebook-е нет. Как говорили в рекламе в 90-х: "Если вас нет на желтых страницах, вас нет вообще" :)

Следовательно, если подавляющее большинство читателей уже находятся вместе со мной внутри соцсети, то зачем мне писать что-то вне этой соцсети, а затем размещать ссылку на свой материал? Почему бы мне не написать его прямо внутри Facebook? Или Google+, или VK?

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

Т.е. если бы сейчас Facebook дал бы мне возможность писать тексты настолько же удобно, насколько это позволяет Blogger, я бы спокойно полностью ушел бы в Facebook и не парился.

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

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

В конце-концов, с чего живут какие-нибудь сетевые издания? С рекламы, с проплаченных постов и т.д. Ну так пусть живут прямо внутри соцсети. Какая разница, кому платит рекламодатель: сетевому журналу "Кольчатые черви средней полосы России" или VK? Может даже VK платить выгоднее, т.к. там аудитория побольше будет.

В связи с этим я не очень понимаю Google, который со всеми своими мощностями не может решить проблему G+ комментариев. Ведь, в принципе, стартовые позиции у Google классные: крупнейший поисковик, крупнейший почтовый сервис, крупнейший видео-сервис, собственный блог-сервис, Google+ как лента новостей да еще и Google Drive. Увязать все это должным образом и получится нечто большее, чем просто социальная сеть для обмена впечатлениями об очередном бизнес-ланче.

Еще одна штука, которая меня немного удивляет, это почему Facebook не сделает свой собственный почтовый сервис. Система обмена сообщениями у них есть, работает надежно, информация о лайках и комментариях не теряется. Теперь отмасштабировать это и приделать удобный интерфейс и можно было бы получить убийцу Google Mail :) Тогда вообще, заходишь с утра в Facebook, и сидишь там весь день никуда не выходя. Не это ли цель владельцев соцсетей?

среда, 26 ноября 2014 г.

[prog.flame] Вынесу из комментариев про ООП

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

ООП -- это сложная тема. На базе принципов она более-менее понятна, а вот когда опускаешься к деталям, обнаруживается большое количество белых пятен. Ситуация усугубляется еще и тем, что из широкоизвестных ОО языков чисто ОО языками являются всего два -- Java и Eiffel. Все остальные из тех, что приходят на ум, гибридные. Даже SmallTalk и Ruby несут в себе следы функциональщины (что не удивительно, т.к. Алан Кей сам признавался, что Lisp на него очень повлиял во время работы над SmallTalk-ом). Поэтому, скажем, при работе на C++ или Ruby можно не сталкиваться с ОО-граблями потому, что в этих языках они обходятся совсем другими способами. Тогда как в Java их, что называется, не обойти и не объехать.

Тут выше в комментариях дали ссылку на фрагмент книги о Haskell-е. Там хороший пример приведен. Когда класс требует наличия operator<, а тип, который имеет такой метод, автоматически становится принадлежащим этому классу. В C++ такая штука присутствует в чистом виде при работе с шаблонами. В Ruby/Python/SmallTalk -- благодаря динамической утиной типизации.

А вот статически-типизированных ОО-языках как это выражать? Там ведь очень редко где поддерживается структурная эквивалентность (т.е. тот же duck typing, но статический). Простейший вариант, который и был реализован в Java -- это наследование: либо реализации (поэтому в базовом Object-е собрано столько всякого хлама), либо интерфесов. Но при этом интерфейсы не могут иметь реализаций методов. Т.е. в Java (по крайней мере той, с которой я когда-то работал) нельзя сделать интерфейс Comparable с одним абстрактным методом lessThan и неабстрактными equal, lessEqual, greaterThan, greaterEqual, notEqual, которые были бы реализованы на основе единственного абстрактного lessThan. Вот поэтому Java, хоть и является чистым ОО языком, но это ублюдочно убогий ОО язык.

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

К сожалению, в массы пошел язык Java. Поэтому огромное число программистов знает об ООП из Java. Т.е. не о нормальном ООП, а о сильно кастрированном. Отсюда и наезды, потому что проблем не меньше, чем достоинств.

Однако, стоит только поработать с более-менее приличной реализацией ООП (скажем в Eiffel) или с ООП в гибридном языке (скажем в C++ или Ruby), так ситуация меняется: да ООП не всегда ложиться на предметную область, ну и не страшно, в языке есть другие инструменты.

вторник, 25 ноября 2014 г.

[prog.flame] Когда же АлгТД круче ООП и наоборот? ;)

Фактически послесловие к закрытой дискуссии на eax.me. Прям сейчас возник пример ситуации, в которой алгебраические типы данных (АлгТД) удобнее объектно-ориентированного подхода.

Допустим, я делаю приложение, которое в одном рабочем потоке выгребает запросы из какого-то MQ-сервиса (не важно, будет это MQTT, AMQP или DDS), после чего обрабатывает их в этом же потоке. Прочитанные из MQ сообщения могут приводить к одному или нескольким прикладным событиям в приложении, в том числе к отложенным на какое-то время или повторяющимся с каким-то интервалом событиям. Например, поступила команда запустить электродвигатель и разогнать его до определенной скорости, нужно записать специальную команду в специальный I/O-порт, затем периодически опрашивать другие I/O-порты дабы проверять скорость вращения и, при необходимости, выдавать дополнительные команды. И так до тех пор, пока из MQ не придет команда на выключение двигателя.

В общем, на верхнем уровне в рабочем потоке будет что-то вроде вот такого цикла обработки событий (с прицелом на MQTT и libmosquittopp):

mosqpp::mosquittopp mosq(...);
auto so_env = so_5::launch_single_threaded_env(...);
...
while(true)
{
   // Process any MQTT-related events.
   auto r = mosq.loop(
      // Wait no more time than next timer event.
      to_milliseconds( so_env.timeout_before_next_event( millisecons(1000) ) ) );
   if( r != MOSQ_ERR_SUCCESS )
      ... // Some error handling.
   else
      // Application events can be processed.
      so_env.process_events();
}

Вроде бы все просто, но возникает маленький вопросик: а что, если нужно ограничить длительность работы process_events?

Сходу придумываются три типа ограничений: выполнение не более N событий (не важно сколько времени это займет), выполнение событий в течении M миллисекунд (не важно сколько именно событий будет выполнено), комбинированный -- либо N событий, либо не менее M миллисекунд.

Ok, как эти ограничения передать в метод process_events? Не делая при этом перегрузки process_events для разных списков параметров.

В случае АлгТД решение лежит на поверхности (в Scala-синтаксисе):

abstract class EventsConstraints
case class NoMoreThan(limit: Intextends EventsConstraints
case class NoLongerThan(limit: Durationextends EventsConstraints
case class NoMoreNoLonger(maxEvents: Int, maxTime: Durationextends EventsConstraints

Соответственно, вызов so_env.process_events мог бы быть записан, например, как process_events(NoMoreThan(100)) или process_events(NoMoreNoLonger(100,10ms)).

Ну теперь представьте, во что бы все это вылилось, если бы пришлось обходиться чистым, да еще и убогим, как в Java, ООП :)

Кстати, почему нельзя создавать разные варианты process_events? Потому, что это в данном примере process_events такой простой и получает всего один аргумент. В реальной жизни это может быть метод с несколькими параметрами. Например: process_events(exception_handling_policy, logging_parameters, events_constraints). Соответственно, у каждого параметра могут быть свои варианты. И на каждую комбинацию значений аргументов свой вариант process_events не напишешь.

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

Но этот же пример показывает ситуацию, в которой ООП выгоднее АлгТД. Например, насколько удобно посредством алгебраических типов представлять тип объекта so_env? Что это вообще будет за тип, какие у него внутренности, как именно он работает?

На начальных стадиях проектирования об этом мало что можно сказать. Можно разве что определить его интерфейс, вроде вот такого:

class single_threaded_environment
{
public :
   virtual ~single_threaded_environment();

   virtual void
   process_events() = 0;

   virtual std::chrono::steady_clock::duration
   timeout_before_next_event(
      const std::chrono::steady_clock::duration & default_timeout ) = 0;
};

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

Кстати, если у кого-то уже появилось желание написать в коментариях про то, что наследование реализации (а не интерфейса) -- это зло и что за это нужно отрывать руки/гнать ссанными тряпками/читать блог вот того крутого чувака (нужное подчеркнуть), то крайне рекомендую сначала прочитать "Повторное использование против сжатия". Это глава из книги Ричарда Гэбриеля, одного из авторитетнейших Lisp-еров, которого нельзя заподозрить в любви к ООП. Лучше чем он вы вряд ли сможете высказаться на эту тему.

Кстати, в упомянутом обсуждении на eax.me некоторые альтернативно одаренные личности ставили в вину ООП одно из его главных достоинств -- сокрытие данных, т.е. инкапсуляцию, по сути. Что, на мой взгляд, уже вообще далеко за гранью здравого смысла. Ну глупо же инструменту, главная черта которого инкапсуляция, ставить в вину именно инкапсуляцию.

[prog.c++] Версия 1.1.0 библиотеки timertt

Библиотека timertt обновилась до версии 1.1.0.

В двух словах, timertt -- это:

Header-only библиотека без внешних зависимостей, базирующаяся на возможностях стандартной библиотеки C++11. Реализует таймеры на основе тайм-аутов, т.е. таймеры, которые должны сработать через сколько-то миллисекунд (секунд, минут и т.д.) после момента активации таймера. wallclock-таймеры не поддерживаются.

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

Библиотека поддерживает три таймерных механизма: timer_wheel, timer_heap и timer_list, у каждого из которых есть свои преимущества и недостатки. Может поддерживаться большое количество таймеров (десятки и сотни миллионов) и обеспечивается высокая скорость обработки таймеров (до нескольких миллионов в секунду).

Кросс-платформенная, проверялась посредством MSVS2013 (Windows), GCC 4.9 (Windows, Linux), Clang 3.5 (Linux).

Распространяется под 3-х секционной BSD-лицензией, может свободно использоваться как в открытых, так и в закрытых коммерческих проектах.

Версия 1.1.0 добавляет новый тип сущностей -- timer_manager. В отличии от timer_thread, timer_manager не создает отдельной рабочей нити для отслеживания времени срабатывания таймеров и запуска обработчиков таймерных событий. Все эти действия должен инициировать пользователь, вызывая методы timer_manager в своем собственном цикле обработки событий. Что позволяет пользователю самому выбирать рабочий поток на котором будут осуществляться операции с таймерами.

Вот так может выглядеть работа с таймерами посредством timer_manager:

using namespace std;
using namespace chrono;
using namespace timertt;

timer_heap_manager_template< thread_safety::unsafe > tm;

tm.activate( milliseconds(100), []{ cout << "hello!" << endl; } );
tm.activate( milliseconds(200), []{ cout << "hello!" << endl; } );

auto stop_point = system_clock::now() + milliseconds(300);
while( stop_point > system_clock::now() )
{
  tm.process_expired_timers();
  this_thread::sleep_for(
        tm.timeout_before_nearest_timer( milliseconds(10) ) );
}

А вот, для сравнения, аналогичный код, использующий timer_thread:

using namespace std;
using namespace chrono;
using namespace timertt;

timer_heap_thread_t tt;

tt.start();

tt.activate( milliseconds(100), []{ cout << "hello!" << endl; } );
tt.activate( milliseconds(200), []{ cout << "hello!" << endl; } );

this_thread::sleep_for( milliseconds(300) );

tt.shutdown_and_join();

Новые timer_manager-ы могут иметь или не иметь средств защиты от многопоточности. Не защищенные от многопоточности timer_manager-ы предназначены для использования в однопоточных приложениях, где вообще все операции выполняются на контексте одной нити. Тогда как защищенные от многопоточности thread_manager-ы позволяют создавать и удалять таймеры из разных нитей. Но обработка таймеров (т.е. вызовы методов process_expired_timers и nearest_time_point/timeout_before_nearest_timer) должна выполняться всего одной нитью.

Новые timer_manager-ы предназначены для случаев, когда обработку таймеров нужно объединить с обработкой других событий в приложении, без выделения под обработку таймеров отдельной нити. Например, если приложение является MQTT-клиентом, то его цикл обработки событий, при использовании библиотеки libmosquittopp, схематично может выглядеть следующим образом:

timer_heap_manager_template< thread_safety::unsafe > tm(...);
app_event_queue eq(...);
mosqpp::mosquittopp mosq(...);
...
while(true)
{
  // Process application events while they exist...
  while( !eq.empty() )
  {
    process_app_event( eq.pop() );
    // All expired timers can be handled.
    tm.process_expired_timers();
  }

  // Process any MQTT-related events.
  auto r = mosq.loop(
    // Wait no more time than next timer event.
    to_milliseconds( tm.timeout_before_nearest_timer( millisecons(1000) ) ) );
  if( r != MOSQ_ERR_SUCCESS )
    ... // Some error handling.
}

Библиотека разрабатывалась для замены ACE в проекте SObjectizer, поэтому все, что связано с timertt, находится на SourceForge:

  • архивы с исходными текстами доступны в секции Files. Архив timertt-1.1.0-headeronly.7z содержит только основной заголовочный файл со всей функциональностью timertt. Архив timertt-1.1.0-full.7z содержит так же тесты, примеры и сгенерированный посредством Doxygen API Reference Manual;
  • основная документация для проекта собрана в Wiki;
  • исходники лежат в Subversion-репозитории на SourceForge. Релизные версии в tags/timertt, находящиеся в разработке версии в branches/timertt.

понедельник, 24 ноября 2014 г.

[prog] На каких англоязычных ресурсах можно делать анонсы C++ных велосипедов?

Обращаюсь за помощью к своим читателям. У меня сейчас версия 1.1 библиотеки timertt на подходе. До сих пор я ее анонсировал только в Рунете. А сейчас думаю и на каких-нибудь англоязычных ресурсах этот релиз засветить.

Только вот не знаю, где это принято/уместно делать.

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