четверг, 12 февраля 2009 г.

Открыта Google-группа SObjectizer

Рад сообщить, что благодоря Дмитрию Вьюкову (aka remark) появилась на свет Google-группа SObjectizer. Приглашаю посетить ее всем, кто интересуется агентными системами вообще и особенно тех, кто интересуется SObjectizer-ом! :)

Присоединяйтесь!

Интересное мнение о кодогенерации

В книге Flow-Based Programming наткнулся на интересное высказывание по поводу кодогенерации (стр.20):

A related type of tool are program generators - this is also source-level reuse with a slightly different emphasis. As above, an important question is whether you can modify the generated code. If you can't, you are limited to the choices built into the generator; if you can, your original source material becomes useless from a maintenance point of view, and can only be regarded as a high-level (and perhaps even misleading) specification. Like out of date documentation, it might almost be safer to throw it away...

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

Я могу вспомнить следующие характерные случаи использования кодогенерации в своей практике:

  • генерация кода синтаксических парсеров (вроде Yacc, Coco/R, Antlr). Гибкость тут достигается возможностью включения фрагментов пользовательского кода в спецификацию грамматики;
  • генерация кода для сериализации/десериализации данных (вроде ASN.1, IDL или моей ObjESSty). Здесь гибкость, в общем-то и не нужна: что заложено в механизм преобразования данных, то и используется;
  • генерация кода GUI-форм из описания GUI-формы в Qt3 (когда из ui-файлов генерировались h/cpp-файлы с заготовкой класса для GUI-формы). Гибкость тут достигается тем, что для создания результирующего класса для GUI-формы программист наследуется от сгенерированной заготовки и в классе-наследнике реализует необходимое ему поведение. Нужно добавить, что в Qt4 данный подход серьезно изменился: теперь из ui-файлов генерируется специальный класс, который должен быть атрибутом (а не базовым классом) в пользовательском классе GUI-формы;
  • мои собственные генераторы C++ кода для различных целей (простых структур с набором полей и методами getter-ами/setter-ами, классов для представления PDU протокола EMI).

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

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

среда, 11 февраля 2009 г.

Code Less?

Ходят слухи (на RSDN, например) об исследованиях, по результатам которых получается, что плотность ошибок в программах практически не зависит от языка программирования. Грубо говоря, на 1000 строк C++ кода будет приходиться такое же количество ошибок на 1000 строк Java кода. Поэтому тот язык лучше, который позволяет писать меньше кода для решения задачи. Крайним проявлением этого можно считать современное внимание к функциональным языкам программирования, в которых сложные операции могут записываться в одну строчку. Например:


[ arr!name | (_,name,_) <- foos ]

(это получения списка элементов из ассоциативного контейнера, ключом поиска для которых является имя элемента из списка foos).

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

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

std::string message_id = parsed.message_id();
if( message_id.empty() )
  message_id = make_fake_message_id();

Я переписал его так:

std::string original_message_id;
std::string actual_message_id;
take_message_id_from_submit_sm_resp(
parsed,
info->m_msisdn,
original_message_id,
actual_message_id );

Да еще добавил новый метод:

void
a_channel_t::take_message_id_from_submit_sm_resp(
const smpp_pdu_1::submit_sm_resp_t & pdu,
const std::string & msisdn,
std::string & original_message_id,
std::string & actual_message_id )
{
     original_message_id = pdu.message_id();
     if( original_message_id.empty() )
       actual_message_id = make_fake_message_id();
     else
       actual_message_id = m_message_id_formatter.transform(
           original_message_id,
           msisdn );
   }

Хотя можно было бы просто вставить в код on_submit_sm_resp:

std::string original_message_id(parsed.message_id());
std::string actual_message_id;
if( original_message_id.empty() )
   actual_message_id = make_fake_message_id();
else
   actual_message_id = m_message_id_formatter.transform(
     original_message_id,
     info->m_msisdn );

Что, конечно бы привело к меньшему количеству кода. Но был бы этот короткий код лучше?

Я считаю, что нет.

Во-первых, такая вставка увеличивает размер метода on_submit_sm_resp.

Во-вторых, такая вставка добавляет в метод on_submit_sm_resp технические подробности, которые являются слишком низкоуровневыми для метода on_submit_sm_resp: для него важно наличие original_message_id и actual_message_id, а детали их получения лежат на более низком уровне абстракции.

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

В-четвертых, задокументировать алгоритм вычисления actual_message_id и причины, по которым это вообще делается гораздо проще в Doxygen-комментариях к take_message_id_from_submit_sm_resp, чем к on_submit_sm_resp.

Вот такой микро-пример, который показывает, как увеличение объема кода приводит к повышению его качества.

понедельник, 9 февраля 2009 г.

I'm not standard. Add-on

Когда начинал писать серию "I'm not standard" очень хотел прокомментировать фразу:

Он считает, что С++ отличный язык программирования, а меня от него тошнит.

Уж очень меня поражает, когда люди некоторое время пользуются каким-то языком программирования (добровольно выбрав его при всем этом), а затем начинают его поносить. Хотел провести аналогию с тем, как бывшие супруги, бывает, выливают друг на друга помои ведрами. Но не смог это написать так, чтобы мне самому понравилось. Гораздо лучше меня это сделал Дмитрий "Гоблин" Пучков:

Ну, так бывает с дебилами, которые разводятся, а потом бывшую жену/мужа обильно поливают помоями. Дескать, вот ведь гадина какая была, подумать только! И это было не так, и это не эдак, и пр. и др. Дебилы никогда не вспомнят про то, что было хорошего: что была любовь, что была радость, что вместе было хорошо. Ну — не сложилось, да. Но ведь было же хорошо? Было. Так чего ж ты, дебил, помнишь только плохое? Впрочем, дебил — на то и дебил, чтобы постоянно проявлять дебильность.

воскресенье, 8 февраля 2009 г.

Почему я недолюбливаю паттерны проектирования?

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

Итак, почему же я недолюбливаю паттерны проектирования?

Первая причина историческая. По молодости мне приходились заниматься графикой (поддержка мнемосхем технологических процессов, включая редактор этих мнемосхем). А в графике термин паттерн, насколько я помню, относился к стилю линии (пунктир, точки, точки с пунктиром) и стилям заливки. В Borland-овской библиотеке BGI даже были функции со словом pattern в названии: setfillpattern, например. Так что, когда в районе 2000-го начался шум вокруг паттернов, у меня был жуткий психологический дискомфорт -- термин, к которому я уже привык, вдруг стал использоваться совсем в другой интерпретации.

Вторая причина в том, что я время от времени забывал, что именно скрывается за названием некоторых паттернов. Ну не очень хорошая у меня память и если я долго не пользуюсь каким-то термином, то мне бывает трудно вспомнить, что же именно за этим термином спрятано. А одно из назначений паттернов как раз в том, чтобы создать общий словарь для проектировщиков. Мол, скажет один член проектной команды: "Я собираюсь использовать здесь паттерн Bridge", а все остальные должны сразу же понять, что он имел в виду. Но вот я с ходу не могу сказать, чем Bridge отличается от Proxy. Или же вспомнить, в чем суть паттернов State или Strategy.

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

И это как раз четвертая причина: почему паттернам уделяется такое большое внимание? Когда я читал эту знаменитую книгу о паттернах, очень часто я ловил себя на мыслях о том, что ряд описанных в книге паттернов уже давным-давно мной используются. Скажем, такие паттерны, как Command и Proxy вообще, как мне кажется, сами собой выводятся из идей объектно-ориентированного программирования. Поэтому мне было удивительно, почему таким простым и тривиальным вещам уделяется столько времени. Отмечу, что речь идет только о некоторых паттернах описанных в книге банды четырех. Часть паттернов были для меня открытием (вроде State, Strategy, Flyweight и Visitor), но я склонен объяснить это тем, что мне просто не приходилось решать задач, в которых эти паттерны были бы очень нужны (а по поводу полезности паттернов State и Strategy у меня до сих пор есть большие сомнения).

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

Шестая причина перекликается с пятой. Описанные в книге банды четырех паттерны предназначены для использования в объектно-ориентированных языках со статической типизаций вроде C++, Java, C# или Eiffel. Для динамически-типизированных языков не все паттерны актуальны, к примеру, Visitor не имеет смысла для языков вроде Smalltalk, Ruby или Python. Не говоря уже о функциональных языках. Т.е. получается, что паттерны являются не столько паттернами проектирования, сколько паттернами реализации более высокоуровневых проектных решений. Но все же и не паттернами реализации, поскольку реализация паттернов в коде -- это отдельная задача (см. ниже восьмую причину). Так что не очень понятно, на какой уровень проектирования паттерны рассчитаны.

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

Ну и восьмая причина в том, что паттерны -- это не готовый код, не библиотека, которую можно подключить к своему проекту и использовать. Это идеи (некоторые из которых довольно тривиальные и лежащие на поверхности). Причем идеи из очень специфической категории - идеи, ценность которых без реализации не очень высока. А это значит, что разрабочик, может быть, и получает какой-то выигрыш, когда оперирует паттернами. Но он все равно оказывается перед серьезной пропастью -- несоответствием между своим высокоуровневым проектным решением и кодом, который ему предстоит написать. И я сомневаюсь, что паттерны помогают ему эту пропасть преодолевать (скорее они даже увеличивают ее). По крайней мере для таких языков, как С++, Java, C#, Eiffel, D. В частности, попробуйте реализовать потокобезопасный паттерн Singleton для C++. Поэтому для меня предложение вида "Попробуйте использовать здесь паттерн Bridge" аналогично предложению "А вот здесь расскажите анекдот про тещу". Какой анекдот, для какой аудитории? Должен ли он быть с тайным смыслом или плоским, пошлым или детским, из разряда "черных" или добрым, абсолютно новым или знакомым всем присутствующим? Паттерны об этом не говорят. И это, на мой взгляд, не есть хорошо.

Вот такие пироги. Все вышесказанное является злостным ИМХО и не претендует на звание абсолютной истины :)