суббота, 17 сентября 2011 г.

[life.work] Уволился Максим Кузьмич

Вчера свой последний день в Интервэйле отработал еще один из “старичков, с которых все начиналось” – Максим Кузьмич. Пришел к нам в числе первых, еще студентом. Как раз тот редкий случай, когда студента можно было не учить программировать, он сразу начал работать. В последствии создал и возглавил отдел контроля качества – самый большой, самый организованный и боеспособный в гомельском филиале.

Грустно. Понятно умом, что ничто не может длится вечно и все мы в конце-концов разбежимся. Но все равно грустно.

PS. Компания, которая упустит сейчас шанс заполучить Макса к себе потеряет очень и очень многое.

пятница, 16 сентября 2011 г.

[prog.c++] Попробовал было C++11 для проблемки работы с опциональными значениями

Рассказывал на днях своей команде о маленьком трюке, который может использоваться для работы с опциональными значениями. Поясню суть проблемы на примере. Допустим, у нас есть структура A. Экземпляр этой структуры должен входить в какую-то другую структуру, скажем, в config_t:

struct config_t
   {
      A m_a;
      // ... какой-то набор других атрибутов ...
   };

Но в качестве “опционального” значения. Т.е., если, скажем, в конфигурационном файле определены параметры для A, то поле config_t::m_a можно использовать. А если не определены, то работать с config_t::m_a нельзя.

В лоб, без привлечения внешних библиотек, эта проблема решается, например, добавлением еще одного булевского атрибута:

struct config_t
   {
      A m_a;
      bool m_a_defined;
      // ... какой-то набор других атрибутов ...
   };

И работа затем будет вестись следующим образом:

config_t cfg = ...;
if( cfg.m_a_defined )
   handle_A( cfg.m_a );

Проблема здесь в том, что необязательное значение m_a и признак этой опциональности разнесены на разные атрибуты. При сопровождении проекта вполне может произойти ситуация, когда новый разработчик не заметит наличие m_a_defined и начнет использовать m_a без дополнительной проверки m_a_defined. Или же выставит значение m_a, но забудет изменить m_a_defined.

Я придерживаюсь мнения о том, что опциональность m_a в config_t нужно выражать на уровне типа атрибута m_a, а не дополнительных атрибутов. Например, сделать типом m_a тип std::auto_ptr<A>. Хотя с указателями есть свои заморочки – если забудешь его проверить на NULL, то получишь крах программы, а не простое C++ное исключение, которое будет содержать в себе полезную информацию. Плюс к тому у std::auto_ptr специфическая семантика передачи владения и для config_t наверняка потребуется определять собственные конструктор и оператор копирования. Ну и может просто оказаться накладным размещать A в динамической памяти.

Поэтому я в таких случаях предпочел бы простенький класс вида:

class opt_A_t
   {
   public :
      opt_A_t() : m_defined( false ) {}
      opt_A_t( const A & v ) : m_value( v ), m_defined( true ) {}

      bool is_defined() const { return m_defined; }

      const A & value() const
         {
            if( !m_defined )
               throw std::runtime_error( "opt_A has no value" );
            return m_value;
         }

   private :
      A m_value;
      bool m_defined;
   };

И поле config_t::m_a объявлял бы как имеющее тип opt_A_t, а не A.

Примечание. Если религия позволяет использовать Boost, то вместо самописного opt_A_t можно задействовать boost::optional<A>. Тем не менее, вполне могут быть случаи, когда собственный opt_A_t окажется удобнее boost::optional.

Тем не менее, работа с opt_A_t или boost::optional<A> все равно оставляет поле для потенциальных ошибок. Поскольку для безопасного обращения к config_t::m_a нужно сначала сделать проверку на наличие в нем значения (boost::optional имеет еще метод get_value_or, который, однако, не применим в ряде нужных мне сценариев). А такая проверка – это лишнее действие, про которое можно и забыть, и выбросить по ошибке.

Тогда как в функциональных языках с алгебраическими типами и паттерн-матчингом есть возможность возложить контроль за корректностью работы с опциональными значениями на компилятор. Например, в Scala определен тип Option[T]. Если бы config_t::m_a имел тип Option[T], то разработчику пришлось бы задействовать для доступа к m_a паттерн-матчинг и компилятор сам бы проверял корректность обращения к значению:

val cfg: Config = ...;
cfg.m_a match {
    case Some[value] => ... // Безопасная работа со значением
    case None => ; // Отсутствие значения нас не интересует
}

В С++ такое, к сожалению, не возможно.

Однако, захотелось попробовать сварганить какое-то подобие в рамках приобщения к C++11. В частности, заменить паттерн-матчинг на методы, которые получают в аргументами лямбда-функции.

В качестве тривиального эскиза получился вот такой шаблонный класс:

template< class T >
class opt_value_t
   {
   private :
      T m_value;
      bool m_defined;

   public :
      opt_value_t() : m_defined( false ) {}
      opt_value_t( const T & v ) : m_value( v ), m_defined( true ) {}

      bool
      defined() const { return m_defined; }

      void
      when_defined( std::function< void(const T &) > value_handler ) const 
         {
            if( defined() )
               value_handler( m_value );
         }

      void
      when_undefined( std::function< void() > nil_handler ) const
         {
            if( !defined() )
               nil_handler();
         }

      void
      handle_both(
         std::function< void(const T &) > value_handler,
         std::function< void() > nil_handler ) const 
         {
            if( defined() )
               value_handler( m_value );
            else
               nil_handler();
         }
   };

Т.е. если нужно обработать ситуацию, когда опциональное значение гарантированно есть, то используется метод when_defined, которому в качестве параметра передается функция-обработчик значения. Аналогично, если нужна обработка отсутствия значения, то вызывается метод when_undefined. Если же хочется сразу учесть оба варианта – то тогда handle_both.

Однако, все это оказалось довольно многословно. Например, если вместо короткого абстрактного имени A задействовать что-то более реальное, тот же std::string, то получается:

opt_value_t<std::string> value( "Sample" );
value.handle_both(
   [](const std::string & v) { std::cout << "VALUE: " << v << std::endl; },
   []() { std::cout << "NIL" << std::endl; } );

Лично мне указание типа параметра для лямбды (в данном случае это const std::string&) портит всю малину :( Если бы можно было писать так:

opt_value_t<std::string> value( "Sample" );
value.handle_both(
   [](v) { std::cout << "VALUE: " << v << std::endl; },
   []() { std::cout << "NIL" << std::endl; } );

было бы намного интереснее :)

В любом случае, лямбды в C++11 – это вкусно. Пора начинать процесс освоения C++11 и плавного переползания на него.

четверг, 15 сентября 2011 г.

[prog.thoughts] Продолжение темы о глупости применительно к программированию

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

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

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

  1. Непониманием. Например, разработчик недопонял условия задачи и не реализовал обработку каких-то граничных случаев, поскольку посчитал, что их не будет в принципе. Скажем, в тестовом задании, которое я практиковал, одна из проблем была в том, что разработчик не понимал, что лицензионная информация может быть где угодно в файле, а не только в начале. Из-за чего программа была обречена в ряде случаев работать неправильно.
  2. Незнанием. Например, разработчик не знал о каких-то специфических особенностях той или иной функции (или предметной области вообще). Скажем, функция возвращает char* динамически выделенный блок памяти и этот блок отдается во владение программисту, который затем должен его сам освободить. Если программист об этом не знает, то произойдет утечка памяти.
  3. Небрежностью/невнимательностью. Типичная C/C++ная ошибка – написание в if-е присваивания вместо равенства. Такие ошибки могут вызываться целым рядом факторов – от врожденной безалаберности разработчика, до авральных условий работы под давлением со стороны начальства/клиента.

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

Я думаю, что ни одна из перечисленных выше причин не содержит достаточного пространства для глупости. Причем если в причинах #2 и #3 такого простора нет изначально, то вот с #1 ситуация интереснее.

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

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

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

Отчасти еще и потому, что человек привыкает мыслить шаблонно. И для разрывов шаблона нужны некоторые усилия. Достаточно вспомнить логические головоломки, например, как из шести спичек сложить четыре равнобедренных треугольника. Здесь всего лишь нужен опыт – чем больше разноплановых задач приходится встречать человеку, тем лучше. А подобного рода опыт в нашей индустрии, где бал правит молодость, а разработчики “в годах” – редкость, является дефицитом.

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

Вышесказанным я хотел сказать, что в программировании достаточно объективных сложностей, чтобы совершать большое количество ошибок, в том числе и “глупых”. Что вовсе не является следствием глупых решений – т.е. без обдумывания или с доминированием иррациональных доводов. Так что глупости в программировании не так уже и много. Хотя встречается.

Сразу вспоминаются такие случаи, которые кроме как глупостью объяснить не получается. Спрашиваешь у человека: “Ты сделал вот это?” -- “Да, сделал!” Берешь его код из репозитория, а он даже не компилируется, поскольку содержит синтаксические(!) ошибки. Исходя из каких соображений человек говорил, что у него все сделано и работает, я не представляю.

Еще одно проявление – стремление старое выбросить и переписать все заново. Когда это хочет сделать молодой разработчик – это еще объясняется недостатком опыта. Но когда опытный… Тут либо глупость, либо же крайняя степень безысходности (у меня однажды такое было, когда окончательно задолбавшись вычищать чужое дерьмо чужие баги я всерьез предлагал написать с нуля новый, чистовой вариант).

Ну и еще одно проявление, опять же достаточно часто встречающееся и испытанное на собственной шкуре. Это языковой фанатизм. Например, слепая вера в то, что C++ – это язык на все случаи жизни. Или что кроме Java ничего и не нужно. Или что достаточно взять Erlang или Haskell и ваши волосы станут мягкими и шелковистыми программы будут содержать намного меньше проблем. К счастью, с возрастом это проходит :)

А одним из самых тяжелых проявлений глупости в программировании я бы назвал стадию “Я все видел, я все знаю”, до которой я сам доходил неоднократно доходят некоторые опытные разработчики. После чего перестают учиться и с трудом воспринимают новое. А новое в программировании все-таки появляется постоянно, хоть и не так часто, как это кажется молодым хакерам. Можете мне поверить – я видел, я знаю! ;)

воскресенье, 11 сентября 2011 г.

[life.wow] Вот это дротик отскочил, так отскочил

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

PS. Имхо, отличная демонстрация существования законов Мерфи :)

[life.sport.darts] О поездке в Раубичи на Belarus Open 2011

С 19-го по 21-е августа в Раубичах состоялся международный турнир Belarus Open 2011. “Немного” моих воспоминаний об участии в этом мероприятии под катом.

суббота, 10 сентября 2011 г.

[life.thoughts] Поток сознания о глупых, как кому-то может показаться, поступках

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

Жизненный опыт подсказывает мне, что действительно умственно ограниченные люди, принимающие глупые решения, именно от недостатка ума – редкость (пусть психиатры поправят меня по поводу распространенности этого явления). А когда мы оцениваем чьи-то действия как “глупые” или “дурацкие”, то мы, как правило, попадаем в одну из двух ловушек (а то и в обе сразу):

1. Мы судим о поступках других людей исходя из того, что известно нам. Но не на основании имевшейся в их распоряжении информации, их оценок, их опыта, их желаний и опасений. Посему то, что кажется глупым нам, может не казаться таковым им самим, и наоборот. Еще более сложной и глубокой эта ловушка становится если мы оцениваем поступки, имеющие долгосрочную перспективу. Например, в 90-м, когда я поступил на математический факультет на специальность “программное обеспечение” некоторые мои знакомые считали этот выбор глупым.

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

Мне представляется, что в ловушку #2 мы попадаем намного чаще. И если читателям обидно, что я употребляю “мы” и говорю слишком обобщенно, то прошу считать это результатом слишком смелой экстраполяции моих собственных недостатков на все человечество.

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

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

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

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

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

Ну а теперь для тех, кто смог вытерпеть весь предшествующий поток сознания, переход к первопричине. Которой стала статья “Survival Of The Stupidest”, ссылку на которую дал у себя тов.thesz.

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

A stupid person is a person who causes losses to another person or to a group of persons while himself deriving no gain and even possibly incurring losses.

Т.е.

Глупый человек – это человек, который нанося вред другому человеку или группе людей, не получая никакой выгоды для себя или даже причиняя вред себе.

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

Этот пример, на мой взгляд, явно демонстрирует ловушку #2. Т.е. стратегия Элис и Боба кажется вам дурацкой потому, что это вы теряете деньги. А у Элис с Бобом на разумность их ходов совершенно другой взгляд.

Кроме того, здесь видны и следы ловушки #1. Вы можете не знать исходя из каких соображений Элис делала свой ход. Может у нее в прошлом уже был успешный опыт такого поведения. Например, когда мы по молодости играли в 1000, у нас распространенным явлением было задирать ставки против того, кто “сидит на бочке”, даже если изначально было понятно, что ты свою ставку не возьмешь. Мы тем самым наносили прямой ущерб себе, но зато и не давали выиграть противнику. Ну и в дополнении к ловушке #1 – может быть прямой сговор между Элис и Бобом по выбрасыванию вас из игры, но замаскированный под серию глупых ходов :)

Второй пример в статье – это теннисная дилемма. Вам и двум вашим приятелям дают возможность сыграть в теннис. Общаться между собой вы не можете, но каждому из вас нужно сделать выбор корта. Есть корт “А”, на котором играть можно всего 45 минут. И есть корт “B”, на котором можно играть 60 минут. При этом, если вы трое сделаете одинаковый выбор, то вы втроем сможете занять выбранный корт. Т.е. каждый из вас играет 2/3 отведенного времени. Однако, если двое выберут один корт, а третий – другой, то третий выбывает и не сможет сыграть со сделавшими одинаковый выбор игроками.

Автор статьи говорит, что неглупые люди сделают выбор в пользу корта B. А вот глупые – корт A.

Этот пример, с моей стороны, явно не соответствует процитированному выше определению глупого человека. Поскольку выгода от выбора корта A в некоторых случаях очевидна. Если оставить в стороне патологических олигофренов, не способных вычислить, что 2/3 от 45 минут явно меньше, чем 2/3 от 60 минут, то какие разумные причины могут толкнуть кого-то из ваших приятелей назвать корт А вместо корта B?

Ну, например: “Я точно знаю, что Боб выберет корт B. А я бы лучше поиграл 45 минут с Элис. Поэтому лучше я выберу B и, надеюсь, Элис сделает то же самое”. По мне, так это вовсе не глупость, а эгоизм.

Или, например, у Элис и Боба есть свободное время только для того, чтобы провести на корте 45 минут, а не 60. Глупостью ли с их стороны в этом случае будет выбрать корт А, а не B?

Да и ловушка #2 здесь так же не сильно спрятана. Сетовать на глупость выбора корта A будут, в основном, те, кто сам сделает выбор в пользу B и окажется вне игры :)

Т.е. в статье приведены примеры, которые, будучи сильно абстрактными и, посему, допускающими широкое толкование, не доказывают точку зрения автора статьи. Посему примеры хреновые, да и сама статья – фигня, с явным оттенком “интеллектуального снобизма”. Поскольку в качестве контр-примеров к примерам можно подвести вполне разумные объяснения “глупым” на первый взгляд поступкам. И я не считаю такие контр-примеры домыслами.

PS. Я помню чей-то совет о том, что не нужно искать следы разума в том, что можно объяснить глупостью. Тем более, что если прислушаться к проф.Савельеву, то работа мозга – это серьезная нагрузка на организм, а организм стремится минимизировать нагрузки. Поэтому лишний раз не подумать (т.е. совершить глупость) – это оправданное с точки зрения расхода энергии поведение :)

PPS. У меня все же остаются сомнения в том, что тов.thesz употреблял “дурость” как синоним “глупости”. Поскольку цитируя определение дурости он в скобках указал “м*дачество”. А в моем понимании “дурак” и “мудак” – это совершенно разные понятия (собственно, мое понимание ближе к изложенному в Википедии).

пятница, 9 сентября 2011 г.

[prog] Афоризм Алана Перлиса в тему волны языкостроения

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

Хотел разродится пространной заметкой на эту тему. Действительно, за последний год или чуть больше, появились сведения о новых языках от, что важно, солидных игроков на софтверном рынке. Go от Google, Ceylon от RedHat, недавно, кажется, Kotlin от JetBrians. Теперь вот Dart опять от Google (не знаю, как язык, но название уже зачетное ;) Только вот, на мой взгляд, не взлетят. По крайней мере шансы есть только у Go.

Собираясь потратить кучу слов на объяснение своей точки зрения я, к счастью, стал перечитывать афоризмы Алана Перлиса. И обнаружил очень точное и лаконичное объяснение:

A language that doesn't affect the way you think about programming, is not worth knowing.

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

Когда я учил Паскаль после Бейсика – это поменяло мое сознание. Равно как и С с ассемблером после Паскаля. Еще больше мозги перестроились после C++. Даже сильно похожая на C++ Java и то на какие-то вопросы заставила смотреть сильно иначе. Точно как и Ruby. И, в какой-то степени, OCaml.

Но вот Ceylon, Kotlin, да и в изрядной степени, Go, не заставляют думать иначе. Потому их лучше и не знать ;) А значит и не взлетят :)