пятница, 11 октября 2013 г.

[prog.management] Статья "Why Big Software Projects Fail: The 12 Key Questions" вызвала целый поток сознания

Статью "Why Big Software Projects Fail: The 12 Key Questions" от автора процессов PSP/TSP Уоттса Хамфри нашел пару недель назад (в процессе подготовки вот этих заметок: #1, #2). До сих пор она валялась у меня в закладках и я перечитал ее уже несколько раз.

Мне представляется, что при верно сформулированной проблеме, эта статья пытается подвести читателя к неправильному решению. Но решению, которое позволит "продать" читателю процессы PSP/TSP ;)

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

В указанной статье эта проблема обозначена правильно. Как правильно обозначено одно из важных препятствий к обеспечению management visibility: бесполезно опрашивать разработчиков о текущем состоянии их работы для того, чтобы составить общую картину. С одной стороны, разработчики сами могут точно не знать где именно они находятся и сколько времени осталось для достижения их ближайшей цели. С другой стороны, оценки -- это, зачастую, выдача желаемого за действительное. По ряду причин никто не хочет выглядеть плохо, поэтому оценки и прогнозы вольно или невольно будут оптимистичными, нежели реалистичными.

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

И вот здесь у меня складывается смутное ощущение, что что-то не так.

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

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

В-третьих, и это самое важное на моей взгляд: характер разработки ПО таков, что в произвольной точке времени T нельзя сказать, где именно находится проект. Именно так. Нельзя при внезапном вопросе председателя совета директоров "Как обстоят дела с продуктом X" заглянуть в какой-то волшебный программный пакет (будь то MS Project или GreenHopper) и ответить: "Проект выполнен на 77.98%, с вероятностью 84.5% он будет готов к дате Ч".

Если предположить, что я прав, и что в произвольный момент времени нельзя точно оценить состояние программного проекта, то тогда станет понятно, что изобретатели процессов, вроде Уоттса Хамфри, пытаются решать не ту проблему. Не нужно оценивать состояние, нужно оценивать результаты.

Если обратить внимание на начало статьи Хамфри, то на первой же странице он рассказывает о том, как им удалось поместить разработку сложного проекта под контроль. Рецепт в итерационности. Проект был разбит на серию итераций. По результатам каждой итерации цели и сроки следующих итераций могли быть скорректированными.

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

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

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

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

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

  • самый простой вариант с поддержкой только собственного формата данных, без кастомизации интерфейса, проверки орфографии, вставки изображений. На выходе версия 0.1;
  • добавление в версию 0.1 кастомизации интерфейса. На выходе версия 0.2;
  • добавление в версию 0.2 функциональности по экспорту документа в несколько других открытых форматов. На выходе версия 0.3;
  • добавление в версию 0.3 функциональности по проверке орфографии по запросу пользователя. На выходе версия 0.4;
  • добавление в версию 0.4 функциональности по внедрению в документ сторонних объектов. На выходе версия 0.5;
  • добавление в версию 0.5 функциональности по импорту документа из нескольких открытых форматов. На выходе версия 0.6;
  • добавление в версию 0.6 функциональности по он-лайн проверке орфографии...

Фокус здесь в том, что при переходе от версии к версии может потребоваться серьезная переделка того, что уже было сделано в предыдущей версии. Например, в версии 0.2 кастомизированный интерфейс может потребовать очень сильно изменить ту часть редактора, которая осуществляет взаимодействие с пользователем. А в версии 0.3 может потребоваться полностью переписать подсистемы чтения/записи документов. А потом еще раз переписать их в версии 0.5.

Такое переписывание -- это трата времени и, соответственно, денег. Если процесс разработки выстроить так, чтобы при проектировании UI редактора сразу учитывались требования кастомизации интерфейса, внедрения в текст графических объектов, он-лайн проверки орфографии... А при проектировании и разработке подсистемы чтения/записи документов сразу бы учитывались все поддерживаемые форматы... Т.е., теоритически, времени и ресурсов потребовалось бы меньше. Но зато риски были бы выше. Т.к. разработчики бы стояли перед задачей гораздо большего масштаба, разбить которую на части, воплотить все эти части в коде, сшить их затем в единое целое, протестировать и задокументировать было бы намного сложнее. И предсказуемость такой разработки была бы, на мой взгляд, намного хуже. Прозрачности бы не было практически никакой :)

Полагаю, именно потому, что способ, который действительно может дать результат (итерации с эволюционным развитием системы от простого к сложному), является долгим и дорогим, он и не популярен у менеджмента. Тут как с похудением: с очень большой вероятностью можно похудеть, если прикладывать к этому серьезные усилия (ограничения в еде, смена стиля жизни, физические упражнения и пр.) и потратить значительное время. Но ведь хочется всего и сразу, чего-нибудь такого съесть, чтобы похудеть :) Так и здесь: давайте внедрим в департаменте разработки какой-нибудь чудо-процесс и инструментарий для него, чтобы можно было в MS Project-е нажать волшебную кнопку "The Current State" и получить красивую картинку с диаграммами и заключением "Степень готовности 77.98%, вероятность поставки продукта в срок -- 84.5%".

четверг, 10 октября 2013 г.

[life.photo] Маленький совет тем, кто собирается вложиться в дорогую мануальную оптику...

...а так же тем, кто уже вложился и тем, кто собирается серьезно работать со старыми мануальными объективами на современных цифрозеркалках.

Не пожалейте порядка $120 на приобретение специального фокусировочного экрана (с клиньями или микропризмами)!

Это просто небо и земля. Не особо в это верил, пока сам не попробовал. Фокусировочные экраны современных DSLR вообще не приспособлены для наведения на резкость вручную. Причем вообще означает именно вообще :)

Сегодня поставил на свой Nikon D700 фокусировочный экран с микропризмами. Очень жалею, что не сделал этого еще полгода назад, когда приобрел цейсовский 1.4/85mm, это было просто потерянное время. Наводить на резкость в центре кадра стало проще на порядок, а может быть и на два. Я сам этого не ожидал и сомневался в целесообразности такой замены когда читал подобные советы в Интернете. Но реальность превзошла все ожидания.

Насколько я понимаю, нормальные фокусировочные экраны для Canon/Nikon/Sony/Pentax/etc сейчас можно купить у двух производителей: KatzEye Optics (США) и FocusingScreen (Тайвань). С американским KatzEye у меня что-то не срослось (уже даже не вспомню, что именно, кажется то, что они делают фокусировочные экраны только одного типа с клиньями). Поэтому заказывал у FocusingScreen.

На момент заказа для D700 у них был выбор из 6 моделей экранов. Причем, как я понял, 3 из них -- это были переделанные под Nikon экраны производства Canon. А более дорогие три модели -- это родные Nikon-овские экраны от пленочной камеры Nikon F6. Что так же сыграло в пользу FocusingScreen. В итоге я выбрал себе F6-J (экран с микропризмами). Судя по упаковке прибывшего фокусировочного экрана и по прилагавшейся к нему спецификации -- это действительно родной, оригинальный Nikon-овский экран, в качестве изготовления которого не приходится сомневаться.

После оплаты заказа карточкой прошло четыре или пять дней на его выполнение (о чем сразу предупреждали при закупке), потом заказ был отослан по почте посылкой EMS. Посылка ко мне дошла всего за 10 дней! Причем, поскольку это была EMS, доставили ее прямо на дом. В общем, я в шоке от такой скорости :) Посылки с дартс-принадлежностями из Англии идут дольше, а доставка обходится дороже.

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

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

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

[prog.c++] Зафиксирована версия SObjectizer-5.2.0

Версия 5.2.0 фреймворка SObjectizer зафиксирована в svn-репозитории на SourceForge в виде тега. В этой версии устранен ряд некрасивостей и шероховатостей, которые были обнаружены в версиях 5.0 и 5.1.

В виде дистрибутива версия 5.2.0 пока выкладываться на SourceForge не будет. Сейчас мы займемся выпуском адаптированных под 5.2 подпроектов so_5_transport, so_log_2, so_sysconf_4 и mbapi_4. А вот уже после этого будет собран один архив с согласованными между собой версиями подпроектов, который и будет помещен в раздел Files на SourceForge. В будущем мы планируем поступать именно так: раз в два-три месяца выкладывать архив с последними актуальными версиями всех подпроектов. Отдельные версии отдельных подпроектов будут доступны в виде тегов в репозитории.

На ближайшее будущее для SObjectizer запланированы следующие шаги:

  • дальнейшее вылизывание кода ядра SObjectizer;
  • профилирование и оптимизация SObjectizer с целью повышения производительности его работы;
  • размещение на SourceForge актуальных обучающих материалов/документации по SObjectizer на английском языке.

Тут я хочу спросить совета у своих читателей. Очень бы хотелось сделать на SObjectizer демонстрационную реализацию какой-то не очень объемной задачи. Как раз в качестве иллюстрации возможностей SO. Ну типа того, что "на Erlang-е это выглядит вот так, на Java вот так, а на SO -- вот так". Надеюсь, что кто-нибудь из читателей предложит пример такой задачи. Чтобы демонстрация была не на том, что интересно персонально мне. А на том, что интересно потенциальным пользователям SObjectizer.

Какого плана может быть эта задача? Разного, наверное :)

  • SObjectizer упрощает реализацию concurrency (нет хорошего русского термина, к сожалению): т.е. когда приложению нужно параллельно обрабатывать разные сигналы от разных источников, возможно, используя разные алгоритмы для разных сигналов.
  • Objectizer может использоваться для распараллеливания операций/действий: агенты распределяются по разным нитям, агент-координатор озадачивает каждого из них, а так же собирает результаты их работы;
  • распределенные приложения можно делать с использованием SObjectizer, когда компоненты обмениваются между собой сообщениями (этот слой реализует подпроект mbapi_4).

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


Ну а сейчас немного подробностей о том, что же было сделано в SObjectizer 5.2:

  • изменен подход к обеспечению null-safety для пересылаемых между агентами сообщений. Введено строгое деление на сообщения и сигналы, для первых отсылка происходит через deliver_message, для вторых -- deliver_signal. Подробнее об этом я рассказывал пару недель назад;
  • полностью изменен подход к информированию об ошибках. Раньше пользователь мог выбирать, сообщит ли ему SObjectizer об ошибке исключением или же вернет код ошибки. При этом, однако, вместо кода ошибки вполне можно было получить и исключение. Сейчас старого понятия throwing_strategy_t нет вообще, а обо всех ошибках сообщается посредством исключений;
  • изменен формат методов add_agent у agent_coop_t. Раньше пользователю нужно было сконструировать умную ссылку на агента (agent_ref_t) и ее передать ее в add_agent. Что во многих случаях было неудобно. Поэтому сейчас add_agent принимает либо голый указатель на агента, либо же std::unique_ptr, где T -- это любой наследник agent_t;
  • произведена небольшая оптимизация механизма диспетчирования сообщений, на одну операцию атомарного инкремента/декремента стало меньше;
  • изменен формат конструктора so_environment_t, теперь во многих случаях параметры в него нужно будет передавать с использованием функции std::move;
  • ну и еще ряд мелких изменений и улучшений в коде.

Так что SObjectizer 5.2 нарушил 100% совместимость с предыдущими версиями, но зато избавился от нескольких косяков. Так что использовать 5.2 будет проще, чем 5.1.

В заключение хочу сказать большое спасибо всем участникам The SObjectizer Project за огромную помощь в подготовке этой версии!

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

[prog.c++11] Пример преобразования кода с использованием lambda-функций

Расскажу о том, как сегодня переделывал функцию и, в итоге, задействовал ставшие доступными в C++11 лямбда-функции для борьбы с дублированием кода.

Внутри SObjectizer-5 был метод следующего вида:

void
so_environment_impl_t::run(
   so_environment_t & env,
   throwing_strategy_t throwing_strategy )
{
   m_layer_core.start();

   m_disp_core.start();

   m_timer_thread->start();

   m_agent_core.start();

   bool init_threw = false;
   std::string init_exception_reason;
   try
   {
      env.init();
   }
   catchconst std::exception & ex )
   {
      init_threw = true;
      init_exception_reason = ex.what();
      env.stop();
   }

   m_agent_core.wait_for_start_deregistration();

   m_agent_core.finish();

   m_timer_thread->finish();

   m_disp_core.finish();

   m_layer_core.finish();

   if( init_threw )
   {
      SO_5_THROW_EXCEPTION(
         rc_environment_error,
         "init() failed: " + init_exception_reason );
   }
}

Этот метод не обеспечивал безопасности по исключениям. Т.е. если, например, m_agent_core.start() бросал исключение, то не выполнялись откаты выполненных до этого действий (не вызывались методы finish() у m_timer_thread, m_disp_core и m_layer_core). Для исправления этой ситуации все последовательно идущие действия были преобразованы в последовательность мелких методов, каждый из которых вызывает всего один метод start(), передает управление следующему методу, а после возврата оттуда вызывает метод finish(). Получилось что-то вроде:

void
so_environment_impl_t::run(
   so_environment_t & env )
{
   try
   {
      run_layers_and_go_further( env );
   }
   catchconst so_5::exception_t & )
   {
      // Rethrow our exception because it already has all information.
      throw;
   }
   catchconst std::exception & x )
   {
      SO_5_THROW_EXCEPTION(
            rc_environment_error,
            std::string( "some unexpected error during "
                  "environment launching: " ) + x.what() );
   }
}

void
so_environment_impl_t::run_layers_and_go_further(
   so_environment_t & env )
{
   m_layer_core.start();

   try
   {
      run_dispatcher_and_go_further( env );
   }
   catchconst std::exception & x )
   {
      m_layer_core.finish();
      throw;
   }

   m_layer_core.finish();
}

void
so_environment_impl_t::run_dispatcher_and_go_further(
   so_environment_t & env )
{
   m_disp_core.start();

   try
   {
      run_timer_and_go_further( env );
   }
   catchconst std::exception & x )
   {
      m_disp_core.finish();
      throw;
   }

   m_disp_core.finish();

}
...

Таких мелких методов run_something_and_go_further набралось четыре штуки. И все одни были практически близнецами братьями, отличаясь только тремя строчками внутри. Понятно дело, что это уже плохой знак. Но еще хуже то, что приведенная выше первая реализация была не очень хорошей.

Дело в том, что метод finish, в принципе, так же может выпускать наружу исключения. Т.е. если внутри catch вызывается finish и бросает исключение, то наружу уйдет только выпущенное из finish исключение, а информация об исходном исключении потеряется. Но ее хотелось бы сохранить. Т.е. внутри catch обращение к finish нужно было бы заключить в еще один блок try...catch. Проделывать все это заново в каждом из четырех методов мне не хотелось, т.к. это прямой путь к плохой копипасте со всеми вытекающими последствиями.

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

void
so_environment_impl_t::run_layers_and_go_further(
   so_environment_t & env )
{
   do_run_stage(
         "run_layers",
         [this] { m_layer_core.start(); },
         [this] { m_layer_core.finish(); },
         [this, &env] { run_dispatcher_and_go_further( env ); } );
}

void
so_environment_impl_t::run_dispatcher_and_go_further(
   so_environment_t & env )
{
   do_run_stage(
         "run_dispatcher",
         [this] { m_disp_core.start(); },
         [this] { m_disp_core.finish(); },
         [this, &env] { run_timer_and_go_further( env ); } );
}

А сам метод do_run_stage после нескольких итераций приобрел вид более сложный, чем я первоначально предполагал:

void
so_environment_impl_t::do_run_stage(
   const std::string & stage_name,
   std::function< void() > init_fn,
   std::function< void() > deinit_fn,
   std::function< void() > next_stage )
{
   try
   {
      init_fn();
   }
   catchconst std::exception & x )
   {
      SO_5_THROW_EXCEPTION(
            rc_unexpected_error,
            stage_name + ": initialization failed, exception is: '" +
            x.what() + "'" );
   }

   try
   {
      next_stage();
   }
   catchconst std::exception & x )
   {
      try
      {
         deinit_fn();
      }
      catchconst std::exception & nested )
      {
         SO_5_THROW_EXCEPTION(
               rc_unexpected_error,
               stage_name + ": deinitialization failed during "
               "exception handling. Original exception is: '" + x.what() +
               "', deinitialization exception is: '" + nested.what() + "'" );
      }

      throw;
   }

   try
   {
      deinit_fn();
   }
   catchconst std::exception & x )
   {
      SO_5_THROW_EXCEPTION(
            rc_unexpected_error,
            stage_name + ": deinitialization failed, exception is: '" +
            x.what() + "'" );
   }
}

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

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

[life] Позавтракать рано утром в Минске в выходной день

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

Раньше мы решали ее так. До 8:00 сидели в зале ожидания на железнодорожном вокзале (кстати, меня до сих пор мучает вопрос "Почему на ж/д вокзале Минска нет круглосуточного кафе/ресторана?"), потом шли в кафе "Пицца-Темпо" на Карла-Маркса 9 или в кафе той же сети, но под названием "Васильки" (на проспекте Независимости напротив задания КГБ). Эти кафешки раньше начинали работать с восьми часов утра, в том числе и в выходные дни. Но, как минимум, последние полгода они работают в другом режиме -- с девяти часов утра.

В поисках нового места для завтрака в Интернете была найдена вот эта страничка: Где в Минске позавтракать?

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

В последнюю поездку в Минск мы завтракали в кафе "Гараж" по адресу ул.Могилевская 12A. Это всего в 10-15 минутах пешком от ж/д.Вокзала (в двух шагах от станции метро "Институт Культуры"). Работает оно круглосуточно. Но в выходные дни, как оказалось, блюда из раздела "Завтраки" начинают подавать только с 8:00. Мы туда пришли около 7:00, так что пришлось делать выбор из блюд основного меню. Которые оказались вполне себе съедобными, точно не хуже, чем в Пицца-Темпо. Единственный момент, к которому я могу придраться -- это то, что нам долго пришлось ждать, пока у нас примут заказ.

В общем, если кто-то еще столкнется с проблемой завтрака в Минске ранним утром, то рекомендую кафе "Гараж" недалеко от ж/д вокзала. Это одно из нескольких кафе этой сети в Минске, информацию об остальных кафе (а так же о меню и ценах на блюда) можно найти на их сайте: www.cafegarage.by

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

[life;prog] Из непонятного: зачем высказывать свое мнение публично...

...но при этом не желать выслушивать негативные отклики? Свежий пример: заметка в ЖЖ thesz, мой комментарий, ответ thesz.

Отмазки вроде того, что "Здесь только то, что интересно мне" и "Я предпочитаю чужие посты чужим комментариям" мной воспринимаются как формальные правила, за которыми, однако, я не вижу смысла.

Ну вот серьезно, в чем смысл?

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

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


PS. Ну и таки всем любителям русского языка: если вас коробит употребление русскоязычными программистами слов "битмап", "хэндл", "мьютекс", "билд", "имплементация" и, конечно же, "роадмап", то возьмите и измените этот гребаный мир. Сделайте так, чтобы бывший СССР стал главным производителем и потребителем софта, чтобы у нас создавались новые инструменты и новые технологии, чтобы львиная доля исследований в области computer science велась здесь и чтобы к нам съезжались лучшие программисты со всего мира. Чтобы новая терминология формировалась на русском языке и заимствовалась из него в тот же английский... А трындеть в ЖЖ -- это не мешки ворочать.

[life.sport.darts] III-е место в парном разряде на 4-м этапе Кубка РБ 2013!

Впервые за последние 2.5 года результативно съездил на республиканский дартс-турнир: в паре с Игорем Роговым удалось занять III-е место на 4-м этапе Кубка РБ 2013.

За выход в финал мы боролись с парой Толкачев/Перевозчиков, но не смогли их одолеть в тяжелом и нервном матче, проиграли 2-4, хотя вели 2-0. Слабым утешением является то, что Вячеслав Толкачев и Георгий Перевозчиков в итоге выиграли парный турнир.

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

Ну и не могу не сказать, что произошло это в очень важный момент. Занимаясь дартсом уже около 4-х лет, постоянно тренируясь и пытаясь улучшить свою игру, я никак не мог достичь хоть сколько-нибудь осязаемых для себя результатов. Особенно в последние пару лет. Это очень сильно давило на психику и, честно скажу, навязчивые мысли бросить дартс ко всем чертям в последние пару месяцев посещали меня все чаще и чаще. Ну а тут такое! :) Хороший заряд бодрости :)) Думаю, еще, как минимум, на пару месяцев тренировок меня должно хватить :)))

А вообще, играйте в дартс -- это здорово!