суббота, 7 ноября 2009 г.

[life.photo] Еще одно потрясающее спортивное фото

обнаружилось в очередном выпуске Photos of the Week:

В WSJ’s Picture of the Day/Week часто публикуются классные спортивные фотографии. Но вот такие, чтобы все камешки были видны… Внушаить!

[comp.prog] Сравнение GC и malloc/free

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

Quantifying the Performance of Garbage Collection vs. Explicit Memory Management – 14-ти страничная статья с описанием сравнения (PDF).

Quantifying the Performance of Garbage Collection vs. Explicit Memory Management – 36-ти страничная презентация с более наглядным изложением материала из первой статьи, поэтому легче воспринимается (PDF).

Обе работы датируются 2005-м годом.

В сухом остатке: GC может быть даже быстрее (до 10%), чем ручное управление памятью, но для этого ему требуется в 5 (пять) раз больше свободной памяти.

пятница, 6 ноября 2009 г.

[comp.prog.flame] Почему мне не нравятся соревнования на длину программы

В комментарии к одной из предыдущих заметок ув.тов.Ctapmex поделился ссылкой на отбор задачи для очередного сравнения Haskell vs C (да и вообще все против всех). Ну не подходит тов._adept_-у The Computer Language Benchmark Game (равно как и WideFinder, и Raytracer), ну что поделать – нужно придумать свой конкурс ;)

Я к такого рода соревнованиям отношусь скептически. Хотя и положительные стороны у них так же есть. Если не брать в расчет замеры скоростей на микробенчмарках (чем и является Benchmark Game), то самое важное достоинство таких соревнований – это демонстрация более-менее обычного кода на конкретном языке программирования. Поскольку примеры типа ping-pong на Erlang и QuickSort на Haskell, равно как и числа Фибоначчи на OCaml уже всех забабахали. А тут можно увидеть “живую” программу на языке, о котором ты раньше только слышал. Ну и впечатлиться. Или в очередной раз сказать: “ну нах”.

На этом достоинства, пожалуй, заканчиваются. А начинаются расхождения с реальной жизнью. Главное из которых – это несоответствие масштабов. Подобные соревнования обречены на микроскопический масштаб: делается все это just for fun, в свободное время, которое мало кто хочет тратить на написание большой и ненужной ему программы. Что плохо, поскольку:

  • программирование ради удовольствия сильно отличается от программирования за едуденьги. Ведь когда все делается с интересом, с удовольствием, с соревновательным азартом, возможности конкретного программиста значительно возрастают. Кроме того, программист может не замечать каких-то отрицательных моментов. Тогда как в обычной жизни очень многим приходится решать задачи, которые бы мы с радостью не решали. В таких условиях какой-то мелкий недостаток языка будет восприниматься как большая проблема и будет регулярно отнимать время и вызывать фразы, вроде “вот бл*, как меня это все заеб*ло!” (в качестве примера – в C/C++ после объявления типа структуры/класса нужно ставить точку с запятой, что я временами забываю делать). Поэтому, на мой взгляд, высоки шансы того, что разработчик с удовольствием решает соревновательные задачки на Ruby, но потом плюется от необходимости сопровождения 30K строк собственного Ruby-кода в mission critical приложении;
  • написание больших и маленьких программ очень сильно отличается по трудозатратам. И некоторые качества языка непосредственно сказываются на возможностях писать программы большого объема. Например, наличие пространств имен или модульности, наличие сборки мусора, наличие/отсутствие статической типизации и т.д. Поэтому, например, на маленьких задачках Ruby и Python по лаконичности будут рвать C++/Java/C# в клочья. Но на объемах свыше 100KLoc ситуация сильно изменится, а на объемах свыше 1MLoc станет совершенно другой;
  • программы редко пишутся в одиночку. Обычно это командный процесс. Но даже если над программой изначально работает один-два человека, то успешная программа, как правило, переживает не одну серьезную переделку разными людьми. Маленькие программки не позволят оценить удобство языка в условиях командной разработки. Так, один-два хороших C++ника способны наваять отличную, быструю и безглючную программу на C++. Но, стоит увеличить команду разработчиков в 10-20 раз, как вероятность успешного создания очень большой программы на C++ окажется сильно ниже, чем аналогичной на Java. Точно так же, пока над Lisp-евским кодом корпит один человек – это одно, но когда этот код будет передан второму разработчику на сопровождение, затем третьему, затем четвертому – насколько удобно каждому следующему программисту будет разбираться с творчеством предыдущих разработчиков?
  • в мелких соревновательных программах внешние библиотеки играют совсем незначительную роль. По определению. Ведь задача изначально ставится так, чтобы показать возможности языка – это предполагает, что все решение придется писать с нуля и полностью, с минимальным использованием внешних библиотек. Тогда как в обыденном, повседневном программировании приходится комбинировать обращения к сторонним компонентам/библиотекам;
  • не учитывается, что принцип “для каждой задачи свой язык” применим далеко не всегда. Например, есть группа C++ программистов, работающих над С++ проектом. Один из них сталкивается со вспомогательной задачкой, которую проще решить на Ruby (Python, OCaml, Haskell), а ее решение на C++ обойдется на 30-50% дороже. Имеет ли смысл брать Ruby? Зачастую нет. Поскольку кто это решение дальше будет сопровождать? Ведь это команда C++ников, отношение которых к Ruby может быть самым разнообразным. И если вы думаете, что выгодно научить кого-то языку Ruby, то задумайтесь, хотите ли вы, чтобы вас учили какому-нибудь Perl или Rebol?

В общем, если резюмировать, то подобные соревнования так же объективны, как соревнования между аккумуляторной дрелью и перфоратором. Дрелью вы сможете сделать маленькие и аккуратненькие отверстия в деревянном ящике, но не сделаете ни одной дырки в бетоне. А с перфоратором наоборот. Поэтому сравнивать языки нужно на больших задачах (например, пусть бы кто-нибудь реализовал gold не на C++, а на своем любимом языке – это было бы более адекватным сравнением).

Реализация простой задачи на разных языках имеет большой смысл в одном случае: когда вы сами хотите получить впечатление от работы на каком-то языке. Ведь почему мы пробуем новые языки? Потому, что нас в них что-то цепляет. Скажем, я когда-то давно перешел на C++ потому, что там можно было создать класс “множество” и этот класс не отличался бы от встроенного в язык типа. Этого мне в C/Pascal очень не хватало.

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

Для себя такую задачу я уже нашел. Но, думаю, у каждого она должна быть своей.

[comp.prog.flame] Примеры Java приложений для десктопа

Благодаря ссылкам ув.тов.Skynin, удалось слегка расширить свое представление о том, как Java используется в десктопных приложениях. Вот эти ссылки:

Что можно сказать – я был не прав. Хотя сам я Java-приложения не использую, но должен признать, что они вполне себе живут и здравствуют.

Пару-тройку скриншотов для демонстрации:

(IVAS is an intelligent virtual agent system – на платформе NetBeans)

(The Bioclipse project is aimed at creating a Java-based, open source, visual platform for chemo- and bioinformatics based on the Eclipse RCP).

(EasyMonitoring® is a 4D visualisation platform that makes easier to supervise and analyse complex systems in 3D and in real time.)

Хочется сказать, что Java, на мой взгляд, является отличным примером действенности принципа Worse Is Better. Далеко не самый лучший язык программирования, но какое распространение! И какие результаты!

Сдается мне, что не являясь языком для фриков, Java стал отличной платформой для разработки рабочего софта. Очень большие сомнения у меня существуют, что нынешние адепты функционального программирования когда-нибудь напишут на своих любимых Haskell-ях с OCaml-ами приложения, которые хотя бы выглядеть будут так, как вот эта тройка. Поскольку Haskell/OCaml сейчас востребован либо фриками, либо учеными, которым бесконечные списки, синтаксический анализ и потактовое моделирование аппаратуры гораздо важнее и интереснее, чем разработка удобного и полезного рядовым пользователям ПО. А вот Java-программистам – наоборот, за что им респект и уважуха! :)))

PS. Имхо, C++ники в этом плане гораздо ближе к Haskell-истам. Вряд ли бы C++ получил бы такое широкое применение, если бы не “пьянящее чувство победы”, которое программисты испытывают, когда их C++ная программа работает быстро и надежно ;)

[life.photo] Не интересно смотреть фотографии без названия

На днях в отличном фотоблоге Миллион.ша появилась ссылка на коллекцию фотографий Ингмара Весемана. Фотографии отличные, но очень скоро стала мучить меня одна мысль: очень не хватает подписей к фотографиям. По крайней мере названий. Ведь название – это часть фотоработы, оно так же принимает участие в формировании настроения и отношения зрителя к изображенному*. А уж когда у фотографии есть описание где и как она снята – ну это вАщще ;) (В этом плане мне нравятся фото на сайте photoline.ru, где мастера зачастую пишут небольшую предысторию снимка, а так же рубрика Picture of the Day в Wall Street Journal).

Так вот, вооружившись Google, удалось довольно быстро найти исходную коллекцию фотографий Ингмара Весемана на сайте www.devianart.com. Там есть и названия, и даже, местами, маленькие пояснения. Например, вот эта фотография называется “Пылающие небеса” (Burning Sky) и снята она зимой, чуть южнее Мюнхена:

Burning Sky (c) 2007 Ingmar Wesemann

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

Alpine Cotton Grass (c) 2007 Ingmar Wesemann

* Так вот о названиях. В советское время была такая байка: выставлял один художник на конкурсах свою картину, на которой была изображена опушка леса с несколькими телеграфными столбами на переднем плане. Пока картина называлась “Опушка” он и близко не приближался к числу победителей. Потом переименовал ее: “В тайгу пришел свет”. Разу же выиграл первую премию.

среда, 4 ноября 2009 г.

[comp.prog.thoughts] Почему мне не нравится подход Boost.Serialization

Нашлось время чтобы еще раз перечитать документацию по Boost.Serialization, поэтому выполняю свое давнее обещание рассказать о том, почему я не считаю подход Boost-а к сериализации правильным.

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

Например, решил разработчик использовать простой текстовый/бинарный архив и написал код:

class gps_position
{
    friend class boost::serialization::access;
    friend std::ostream & operator<<(std::ostream &os, const gps_position &gp);

    int degrees;
    int minutes;
    float seconds;

    template< class archive >
    void serialize(Archive & ar, const unsigned int /* file_version */){
        ar  & degrees
            & minutes
            & seconds;
    }
    ...

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

template< class archive >
void serialize(Archive & ar, const unsigned int /* file_version */){
    ar  & BOOST_SERIALIZATION_NVP(degrees)
        & BOOST_SERIALIZATION_NVP(minutes)
        & BOOST_SERIALIZATION_NVP(seconds);
}

Теперь он может сериализовать данные как в простую, так и в XML-форму. Но что ему делать, если со временем ему потребуется еще и сериализация на основе TLV (Tag-Length-Value)? А если ему затем захочется делать опциональные атрибуты (т.е. такие, которые присутствуют только при выполнении каких-то условий)? Если ему потребуется в каком-то архиве хранить uint32-поле в четырехбайтовом представлении, а в другом архиве – в компактном (чтобы, скажем, значение 12 хранилось с помощью всего одного байта)? В конце-концов, что ему делать, если бинарный архив потребуется прочитать из программы на другом языке?

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

Для демонстрации своей мысли я буду использовать синтаксис метаописаний своей библиотеки сериализации ObjESSty, т.к. его я знаю хорошо (в отличии от ASN.1, Google Protocol Buffers, Facebook Thrift).

Итак, пусть все начинается с простого случая – бинарной сериализации gps_position. Для нее описание данных будет иметь вид:

{type gps_position
  {attr degrees {of oess_1::int_t}}
  {attr minutes {of oess_1::int_t}}
  {attr seconds {of oess_1::single_t}}
}

Если возникает потребность использовать XML-сериализацию так, чтобы имена XML-тегов совпадали с именами атрибутов, то ничего больше изменять не нужно. Поскольку транслятор DDL-описания и так знает имена атрибутов. Если же нужно использовать для атрибутов minutes и seconds имена min и sec, то это описывается, скажем, так:

{type gps_position
  {attr degrees {of oess_1::int_t}}
  {attr minutes {of oess_1::int_t} {xml {element "min"}}}
  {attr seconds {of oess_1::single_t} {xml {element "sec"}}}
}

Если затем возникает необходимость в TLV-сериализации, то это так же описывается в DDL:

{type gps_position
  {attr degrees {of oess_1::int_t}
    {tlv {tag 0x01}}
  }
  {attr minutes {of oess_1::int_t}
    {xml {element "min"}}
    {tlv {tag 0x02}}
  }
  {attr seconds {of oess_1::single_t}
    {xml {element "sec"}}
    {tlv {tag 0x03}}
  }
}

С XML-элементами и TLV-тегами может произойти неприятная ситуация: со временем имена элементов и значения тегов могут меняться, но нужно будет читать и старые архивы. Поэтому при десериализации нужно будет уметь распознавать несколько имен/тегов. В DDL-описании это сделать не сложно:

{type gps_position
  {attr degrees {of oess_1::int_t}
    {xml {element "d"}}
    {tlv {tag 0x51} {compat-tag 0x01}}
  }
  {attr minutes {of oess_1::int_t}
    {xml {element "m"} {compat-element "min"}}
    {tlv {tag 0x52} {compat-tag 0x02}}
  }
  {attr seconds {of oess_1::single_t}
    {xml {element "s"} {compat-element "sec"}}
    {tlv {tag 0x53} {compat-tag 0x03}}
  }
}

Далее, пусть потребовалось для бинарных архивов одного типа хранить атрибуты degrees и minutes в компактном виде, а для бинарных архивов другого типа – в четырехбайтовом представлении, чтобы экономить время распаковки. В DDL-описании можно оформить и такие условия:

{type gps_position
  {attr degrees {of oess_1::int_t}
    {xml {element "d"}}
    {tlv {tag 0x51} {compat-tag 0x01}
      {default-image-size compact}
      {archive-type "max-speed" {image-size 32bit}}
    }
  }
  ...
}

Все эти описания могут быть автоматически преобразованы транслятором DDL во вспомогательный код на разных языках, что позволит, например, писать архив в C++ программе, а читать его в Java-программе.

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

Общий принцип сериализации таких атрибутов в бинарное представление состоит в том, что в архив помещается флаг, который показывает наличие/отсутствие атрибута (для TLV- или XML-представления ситуация проще). Данный флаг может сохраняться в архиве несколькими способами: в виде отдельного бита в “большой” битовой маске или в виде байта/бита, предшествующего значению атрибута. Конкретный способ – это делали работы архива, программист не должен об этом задумываться. О чем должен думать программист – это о том, чтобы объявить каким-то способом атрибут опциональным. В случае с внешним метаописанием это не сложно:

// Подлежащие сериализации C++ классы.
class compression_info_t
  {
  ...
  public :
    bool is_default() const { ... }
    static compression_info_t default_value() { ... }
    ...
  };
class data_package_t
  {
  ...
    compression_info_t m_compression_info;
  };

// Метаописание.
[type deta_package_t
  ...
  {attr m_compression_info {of compression_info_t}
    {default
      || Значение, которое должен получить атрибут при
      || десериализации, если он не был сериализован.
      {c++ compression_info_t::default_value() }

      || Логическое условие, которое указывает, будет ли
      || атрибут сериализоваться.
      {present_if {c++ !m_compression_info.is_default() }}
    }
  }
}

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

Как тоже самое сделать для текстовых и бинарных архивов в Boost.Serialization – я не очень представляю. Можно, например, вручную управлять флагами:

class data_package_t
  {
    friend class boost::serialization::access;
    BOOST_SERIALIZATION_SPLIT_MEMBER()
// Способ первый: общая битовая маска.
    template< class Archive >
    void save( Archive & ar, const unsigned int version ) const
      {
        bit_mask_t opt_flags;
        if( !m_compression_info.is_default() )
          opt_flags.set_bit( COMPRESSION_INFO );
        ...
        ar & opt_flags;
        ...
        if( opt_flags.is_bit_set( COMPRESSION_INFO ) )
          ar & m_compression_info;
        ...
      }
    template< class Archive >
    void load( Archive & ar, const unsigned int version )
      {
        bit_mask_t opt_flags;
        ar & opt_flags;
        ...
        if( opt_flags.is_bit_set( COMPRESSION_INFO ) )
          ar & m_compression_info;
        else
          m_compression_info = compression_info_t::default_value();
        ...
      }

// Способ второй: булевое поле, предшествующее атрибуту.
    template< class Archive >
    void save( Archive & ar, const unsigned int version ) const
      {
          if( !m_compression_info.is_default() )
            {
              ar & true;
              ar & m_compression_info;
            }
          else
            ar & false;
        ...
      }
    template< class Archive >
    void load( Archive & ar, const unsigned int version )
      {
        bool compression_info_present;
        ar & compression_info_present;
        if( compression_info_present )
          ar & m_compression_info;
        else
          m_compression_info = compression_info_t::default_value();
        ...
      }

Но, во-первых, я не уверен, что для всех типов архивов Boost.Serialization гарантирует запись/чтение значения сразу после выполнения operator&() (ведь для XML-архивов порядок следования атрибутов может быть произвольным). И, во-вторых, как только программист начинает писать подобный код, работа с разными типами архивов (текстовыми, бинарными, XML, TLV) сразу же превращается в т.н. hardcoding. Тогда как в случае с метаописанием всеми этими деталями занимается не разработчик, а транслятор метаописания во вспомогательный код.

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

PS. В ObjESSty нет поддержки TLV- и XML-сериализации. XML-формат никогда не был мне нужен, а TLV-сериализация однажды понадобилась. Упомянутые выше формы описания TLV-тегов в DDL как раз тогда и рассматривались. Развития эта идея пока не получила, т.к. оказалось проще сделать поддержку TLV для десятка атрибутов вручную, чем модифицировать ObjESSty.

[comp.prog.flame] Уж не собирается ли Oracle превратить Java в Cobol?

Как по мне, так Java сейчас и есть современный Cobol – язык для больших Ынтырпрайзных приложений. С редкими исключениями в виде Eclipse/IDEA, Android, Java-приложениями для телефонов, и утилитами типа JEdit, Antlr3, Azureus. Но это мое мнение, которое может не иметь ничего общего с реальностью :)

А вот каковы планы Oracle в отношении Java:

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

Oracle планирует не только расширить и ускорить свои собственные инвестиции в Java-платформу, но так же планирует увеличить вклад в сообщество, который поможет сделать Java повсеместной, инновационной платформой, объединенной вокруг открытых стандартов.

Честно говоря, из этого заявления мне не понятно, входит ли в круг интересов Oracle, скажем, Java для мобильных платформ. Да, развивать Java на server-side Oracle выгодно (как выгодно это и тому же IBM). Но какой смысл Oracle вкладываться в развитие Java для десктопа? Разве что посоревноваться с MS на еще одном поприще.

вторник, 3 ноября 2009 г.

[comp.prog.c++] Yahoo открыл исходники своего Traffic Server-а

Практически во всех современных анти-С++ных флеймах плюсофобы задают вопрос: “А где сейчас ниша C++?” С таким прозрачным намеком на то, что нет уже таких ниш. Мол, даже в области высоконагруженных приложений C++ уже делать нечего.

И вот отличный “наш ответ Чемберлену” – Yahoo открыла свой Traffic Server, написанный как раз на C++. Traffic Server – высокопроизводительный сервер приложений для облачных (could computing) сервисов Yahoo.

Traffic Server отвечает за управление сессиями и конфигурациями, балансировку нагрузки, аутентификацию и маршрутизацию. В настоящее время он обслуживает до 30 миллиардов Web-запросов и до 400TB трафика в день, на тестах производительности показывая скорость в 35000 запросов в секунду (по некоторой информации, на одной машине).

Traffic Server помещен в инкубатор Apache, где можно ознакомиться с его исходными текстами.

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

[comp.prog.flame] Вот так и сравнивают Erlang и C++

Наткнулся на интересную историю “Rewriting Playdar: C++ to Erlang, massive savings”, в которой автор Playdar рассказывает, как он переписал свой проект с C++ на Erlang и получил сокращение кода в 4-ре раза: 2K строк на Erlang вместо 8K строк на C++. Более того, версию на C++ писали около 6-ти месяцев, а переписали на Erlang за 2 недели. Внушаить, не правда ли?

У меня сразу закрались сомнения: при всей своей ущербности, C++ вовсе не такой язык, чтобы написанное на C++ за полгода можно было запросто переписать на каком-то другом языке за две недели. Такие вещи возможны только, если на C++ писали ламеры и если при этом они вручную реализовывали то, что для другого языка представлено в виде готовых инструментов.

Тезис о ламерстве в этой истории легко находит в данной истории свое подтверждение. Для этого достаточно заглянуть в старые C++ исходники: http://github.com/RJ/playdar

Их качество, как говорят, “ниже плинтуса” – у меня бы такой C++ код просто не прошел бы процедуру code review. Тогда как автор кода о своем творении более высокого мнения:

Изначально я написал Playdar на C++ (используя библиотеки Boost и Asio) начав в феврале этого года. Мне повезло работать с несколькими опытными разработчиками, которые помогли мне достичь результатов с C++. Среди нас было трое, кто регулярно писал на C++ еще несколько месяцев назад, и не смотря на то, что я относительный новичок в C++, я могу сказать, что мы получили хорошо спроектированный и надежный код, в котором все было предусмотрено.

I initially wrote Playdar in C++ (using Boost and Asio libraries), starting back in February this year. I was fortunate to be working with some experienced developers who helped me come to terms with C++. There were three of us hacking on it regularly up until a few months ago, and despite being relatively new to C++, I’ll say that we ended up with a well designed and robust codebase, all things considered.

Не откажу себе в удовольствии заглянуть в некоторые места этого “well designed and robust” кода. Пойдем сразу в файл main.cpp. Мы там видим функцию main() на 110(!) строк, в которой выполняются сразу несколько действий, которые более грамотный разработчик обязан был вынести в разные функции: обработка аргументов командной строки, чтение конфигурации, инициализация библиотеки curl, создание класса MyApplication (классное название, не правда ли?), запуск встроенного в приложение HTTP-сервера.

И ладно бы, каждое из этих действий было бы сделано нормально, так ведь нет. Встречаются замечательные перлы:

bool error;
try {
    po::parsed_options parsedopts_cmd =
       po::command_line_parser(ac, av).options(cmdline_options).run();
    store(parsedopts_cmd, vm);
    error = false;
} catch (po::error& ex) {
    // probably an unknown option.
    cerr << ex.what() << "\n";
    error = true;
}

Назначить переменной error значение true или false сразу нельзя было ну никак.

Или вот еще:

app = new MyApplication(conf);
// start http server:
string ip = "0.0.0.0"; 
boost::thread http_thread(
   ... );
        
http_thread.join();
log::info() << "HTTP server finished, destructing app..." << endl;
delete app;
log::info() << "App deleted." << endl;
return 0;

Зачем было создавать экземпляр MyApplication динамически? Он же единственный на всю программу, создается и удаляется в одном и том же месте. Проще всего было бы объявить его прямо на стеке, как обычную локальную переменную. Но даже если захотелось создать его динамически, какая религия запретила обернуть его в std::auto_ptr?

Взглянем на класс MyApplication. Будь автор этого класса моим подчиненным, он уже бы отгреб за одно название. Что не позволило назвать его PlaydarApplication – понять невозможно. Разве что остается вспомнить, что выбор имен классов/методов/переменных – это одна из самых сложных задач в программировании. Ну да ладно с названием, смотрим на сам класс. Его объявление:

class MyApplication
{
public:
    MyApplication(Config c);
    ~MyApplication();
 
    Resolver * resolver();
    
    Config * conf()
    {
        return &m_config;
    }
    
    // RANDOM UTILITY FUNCTIONS TOSSED IN HERE FOR NOW:
    
    // swap the http://DOMAIN:PORT bit at the start of a URI
    std::string http_swap_base(const std::string& orig, const std::string& newbase)
    {
        if(orig.at(0) == '/') return newbase + orig;
        size_t slash = orig.find_first_of('/', 8); // https:// is at least 8 in
        if(std::string::npos == slash) return orig; // WTF?
        return newbase + orig.substr(slash);
    }
    
    // functor that terminates http server:
    void set_http_stopper(boost::function f) { m_stop_http=f; }
    
    void shutdown(int sig = -1);
    
private:
    
    boost::function m_stop_http;
    Config m_config;
    Resolver * m_resolver;
 
};

Сразу возникают вопросы:

  • почему аргумент передается в конструктор по значению? Почему нельзя было передавать его по константной ссылке?
  • почему методы resolver() и conf() возвращают указатели? Простые указатели, а не константные указатели или ссылки. Почему сами методы не константные?
  • зачем в классе MyApplication неконстантный метод http_swap_base? Он не обращается к внутренностям объекта вообще. Он должен был быть внешней свободной функцией или, в крайнем случае, статическим методом класса (возможно, приватным);
  • странно, что значение для m_stop_http не передается в конструкторе класса. Вряд ли работа класса возможна, если предварительно не вызвать set_http_stopper;
  • не видно методов-сеттеров, которые устанавливали бы значение атрибута m_resolver. Это наводит на мысль о том, что данный атрибут динамически создается в конструкторе.

Смотрим на реализацию класса:

MyApplication::MyApplication(Config c)
    : m_config(c)
{
    string db_path = conf()->get("db", "");
    m_resolver  = new Resolver(this);
}
 
MyApplication::~MyApplication()
{
    log::info() << "DTOR MyApplication" << endl;
    log::info() << "Stopping resolver..." << endl;
    delete(m_resolver);
}
     
void
MyApplication::shutdown(int sig)
{
 
    cout << "Stopping http(" << sig << ")..." << endl;
    if(m_stop_http) m_stop_http();
}
 
Resolver *
MyApplication::resolver()
{
    return m_resolver;
}

Классная строчка в конструкторе с локальной переменной db_path. Дернули конфигурацию, получили значение и все. Похерили это значение нафиг. Молодцы.

А m_resolver действительно создается динамически. И вручную удаляется в деструкторе. Здорово. Про boost::function знаем, про std::auto_ptr не знаем.

Еще не понятно, по каким принципам код методов располагается в .h и .cpp файлах. Почему MyApplication::conf() реализован прямо в .h-файле, а точно такой же MyApplication::resolver() – в .cpp-файле?

Отдельного разговора заслуживает обработка исключений. В показанной выше функции main() исключения ловятся по ссылке. Странно, что не по константной ссылке, но хоть по ссылке. А вот в файле auth.cpp исключение sqlite3pp::database_error ловится уже по значению! А уж бросаются исключения вообще козырно:

        if( val != "2" )
        {
            throw; // not caught here
        }

Я, например, и не знал, что инструкцию throw можно использовать вот так. Т.е. повторно пробросить уже пойманное исключение с помощью простого throw – это нормально. Но чтобы с его помощью выбрасывать новое исключение… Век живи, век учись. А в дополнение к вышесказанному большое количество catch(…){} в коде.

Остается добавить для полноты картины факты возврата объектов типа vector<map<string, string>> или передачи аргументов типа map<string, string> по значению и получится довольно полная картина того, что автор называет well designed and robust codebase.


С качеством кода, можно сказать, закончили. Теперь о том, зачем вообще потребовалось делать Playdar на C++?

Как я понимаю, Playdar – это такая маленькая музыкальная поисковая система. Висит на машине в виде http-сервера, к ней обращаются с запросом, она производит поиск и выдает результат в виде JSON-ответа. Зачем здесь C++?

Первый вариант: скорость работы Playdar. Вряд ли C++ может ее ускорить – это не вычислительная задача и не обработка больших объемов данных.

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

Третий вариант: минимализация дистрибутива. Дистрибутив C++ного варианта был 2.5Mb. Дистрибутив Erlang-ового – 10Mb. Имхо, разница серьезная.

Итак, выбор C++ мог быть здесь оправданным только, если автор хотел получить минимальное потребление ресурсов (чему помешало незнание С++) и/или малый размер дистрибутива (но, судя по приемлимости 10Mb-го дистрибутива, это его не интересовало).

Из чего напрашивается вывод, что для данной конкретной задачи C++ вообще не подходил изначально (добавим сюда и верные слова автора о геморрое, связанном с бинарными плагинами). Ее можно было гораздо успешнее решить на каком-нибудь Python или Ruby. И разрыв между этими языками и Erlang-ом был бы не так велик.

Итого, что в сухом остатке? Человек, который не умеет программировать на C++ почему-то взялся делать свой проект на C++, хотя этот язык для данной задачи не подходил изначально. И еще хорошо, что он вовремя отказался от развития C++ной версии в пользу Erlang-а (хотя Erlang-овую версию я не смотрел, не исключено, что и там качество кода…). Но зато какой громкий заголовок получился!


Почему меня эта история зацепила так, что я решился на очередной “крестовый поход в защиту C++”? А тем, что хочется видеть противопоставление нормального C++ кода нормальному коду на других языках. Но никак не такие ламерские поделки.

PS. По моим впечатлениям, плохой код, который активно использует boost::function, гораздо тяжелее читать, чем просто плохой код.

PPS. Имхо, если бы я переписал этот Playdar на C++ нормально (возможно с использованием Poco вместо Boost), то объем кода получился бы раза в полтора больше. За счет разбиения многих функций на более мелкие и за счет объектной декомпозиции вместо функциональной. Но на написание такой версии ушло бы намного меньше шести месяцев – может быть, как раз те самые две недели.

воскресенье, 1 ноября 2009 г.

[life] Захотелось рассказать про алмазы и бриллианты

Посмотрел вот этот фоторепортаж об огранке алмазов в фирме Тиффани и вспомнилось, что моя первая специальность, полученная еще в школе – огранщик алмазов в бриллианты.

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

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

А идти работать огранщиком не хотелось потому, что это жуткая рутина. По рассказам, для получения хорошей зарплаты огранщику нужно было делать по 100 камней за смену. Просто для сравнения – в начале обучения мы делали нижнюю часть одного круглого камня за 3-4 часа. Перед окончанием обучения у меня получалось сделать весь камень (низ, площадку и верх) минут за 30-40.

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

Нас учили делать круглые бриллианты с 17-ю гранями (фацетами). Это были маленькие камешки, диаметром не более 2мм и массой в какие-то сотые доли карата. Круглые камни большего диаметра гранят уже под 57-мь фацетов. Вот так:

При производстве камня с 57-ю гранями сначала делают базовую огранку с 17-ю гранями, а потом на большие грани наносятся несколько более мелких (называемых клинья). Нас учили делать только базовую огранку, что очень просто (вот ведь, уже 20 лет прошло, а все равно помню).

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

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

Алмаз в приспособлении укрепляется в специальном патроне. Этот патрон вращается, один шаг вращения (“щелчок”, т.к. при вращении слышны щелчки), если не ошибаюсь, 22.5 градуса: 4 щелчка – поворот на 90 градусов, 2 щелчка – поворот на 45 градусов.

Самое первое действие, которое нужно для создания низа камня – это определение “самого мягкого направления”. Если мне не изменяет склероз, то для его поиска нужно найти самую большую грань, затем повернуть камень на два щелчка влево – здесь и будет самое мягкое направление. Здесь делается первая грань низа – ее нужно повести точно к линии рундиста. Опустишь ниже – камень придется уменьшать в диаметре.

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

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

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

Потом камень опускают в кислоту, чтобы убрать мелкие частички металла, прилипшего к камню, и бриллиант готов.

Вот, собственно, и все. Все ходы записаны – нет места ни шагам влево, ни шагам вправо. Нет никакого искусства или намека на творчество. Даже сама схема огранки подобных круглых камней была выработана задолго до моего рождения. Мастерство же огранщика заключается в том, насколько быстро и безошибочно он делает эти стандартные операции. Если нам, школьникам, нужно было потратить минут 10-15 и выполнить 15-20 прикладываний камня к алмазному кругу, чтобы сделать одну грань, то у обучавших нас мастеров на это уходили считанные секунды – одно долгое прикладывания камня к кругу, затем еще одно, если нужно точно подвести грань к линии рундиста, потом заключительное прикладывание к специальной, шлифовочной части круга, затем четыре щелчка и мастер делает уже следующую грань.

Кстати делают огранку только при искусственном освещении – на солнечном свету камень так сильно рассеивает свет, что границ между гранями просто не видно.

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

PS. Огранщиком я ни одного дня не проработал. Повезло поступить в университет и выучиться на программиста. В начале 90-х наш “Кристалл” чувствовал себя весьма неплохо – высокие зарплаты, квартиры для сотрудников и т.д. Даже какие-то попытки “конверсии” предпринимались, вроде организации непрофильных производств – выпуск зажигалок и тому подобной лабуды. Потом, по мере разобщения России и Белоруссии (а сырье все было из Якутии), ситуация становилась все хуже и хуже. Насколько я слышал, в районе 2000-го большое количество самых лучших огранщиков уезжали в Москву, где работали прямо на квартирах. Да, здоровенные ограночные станки, весом в 500-600кг, устанавливались прямо в квартирах и там за 600-800 долларов в месяц люди работали и не собирались возвращаться, т.к. на “Кристалле” платили в разы меньше. Насколько “Кристалл” жизнеспособен сейчас – не знаю, но судя по тому, как активно он сдает площади в аренду, дела идут не ахти. Обычное, в принципе, дело – рыба гниет с головы. Оказалось руководство предприятия неспособным к новым условиям, и уникальное производство, одно из крупнейших в своем роде в Восточной Европе, накрылось медным тазом.

PPS. А бриллианты, сволочи, все-таки очень красивые. Даже при искусственном освещении.