пятница, 10 апреля 2015 г.

[prog.sobjectizer] Вот таким макаром и уменьшается многословность кода

Как можно было недавно убедиться, написанный с использованием SObjectizer код бывает многословным. Хотя работа в направлении уменьшения многословности ведется постоянно, процесс этот идет не быстро. Как раз тот случай, когда "простота не предшествует сложности, а следует за ней" ((с) Алан Перлис).

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

Тем не менее, процесс идет. И ниже один из примеров в качестве подтверждения.

В декабре 2013-го в версии 5.2.3 в SO-5 из SO-4 была повзаимствована поддержка "дочерних коопераций". Т.е. кооперацию агентов можно было подчинить другой кооперации, родительской. И SO-5 гарантировал, что дочерняя кооперация гарантированно завершит свою работу раньше родительской. В C++, где всеми ресурсами программист управляет вручную, это очень полезная гарантия. Ну да не суть. Суть в том, как программист должен был написать создание дочерней кооперации внутри какого-то из агентов родительской кооперации. Выглядело это так:

void parent_agent::so_evt_start() override
{
   // Создание экземпляра кооперации и назначение ей родителя.
   // У кооперации должно быть уникальное имя. Создаем его на
   // основе имени родительской кооперации.
   auto coop = so_environment().create_coop( so_coop_name() + "/child" );
   coop->set_parent_coop_name( so_coop_name() );
   // Наполнение кооперации.
   coop->add_agent( new first_child_agent( so_environment(), some_params() ) );
   coop->add_agent( new second_child_agent( so_environment(), some_params() ) );
   // Регистрация.
   so_environment().register_coop( std::move( coop ) );
}

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

void parent_agent::so_evt_start() override
{
   // Создание экземпляра кооперации и назначение ей родителя.
   // Про имя кооперации не думаем.
   auto coop = so_environment().create_coop( so_5::autoname );
   coop->set_parent_coop_name( so_coop_name() );
   // Наполнение кооперации.
   coop->add_agent( new first_child_agent( so_environment(), some_params() ) );
   coop->add_agent( new second_child_agent( so_environment(), some_params() ) );
   // Регистрация.
   so_environment().register_coop( std::move( coop ) );
}

Т.к. дочерние кооперации -- штука удобная и практичная, пользоваться ей доводилось все чаще и чаще, поэтому объем писанины хотелось сократить еще больше. Очередной шажок в этом направлении происходит в феврале 2015-го в версии 5.5.3, в которой добавляется вспомогательная функция create_child_coop:

void parent_agent::so_evt_start() override
{
   // Создание экземпляра кооперации и назначение ей родителя.
   // Про имя кооперации не думаем.
   auto coop = so_5::rt::create_child_coop( *this, so_5::autoname );
   // Наполнение кооперации.
   coop->add_agent( new first_child_agent( so_environment(), some_params() ) );
   coop->add_agent( new second_child_agent( so_environment(), some_params() ) );
   // Регистрация.
   so_environment().register_coop( std::move( coop ) );
}

Кода стало всего на одну строку меньше, но это важная строка -- про нее иногда забываешь и новая кооперация оказывается без родителя, после чего где-нибудь в другом месте программы могут возникать, а могут и не возникать сбои при работе с ресурсами вроде коннектов к БД. Поэтому работа через create_child_coop надежнее, нет возможности ошибиться и забыть вызвать set_parent_coop_name().

В версии 5.5.4, вышедшей буквально на днях, появился метод make_agent, который позволяет компактнее записывать создание экземпляров агентов. Так что код еще чуть-чуть подсократился:

void parent_agent::so_evt_start() override
{
   // Создание экземпляра кооперации и назначение ей родителя.
   // Про имя кооперации не думаем.
   auto coop = so_5::rt::create_child_coop( *this, so_5::autoname );
   // Наполнение кооперации.
   coop->make_agent< first_child_agent >( some_params() );
   coop->make_agent< second_child_agent >( some_params() );
   // Регистрация.
   so_environment().register_coop( std::move( coop ) );
}

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

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

void parent_agent::so_evt_start() override
{
   // Создание экземпляра кооперации и назначение ей родителя.
   // Про имя кооперации не думаем.
   // Кооперация регистрируется сразу же после завершения создания.
   so_5::rt::introduce_child_coop( *this, [this]( so_5::rt::agent_coop_t & coop ) {
      // Наполнение кооперации.
      coop.make_agent< first_child_agent >( some_params() );
      coop.make_agent< second_child_agent >( some_params() );
   }
}

Ну или для того, чтобы разница была заметна еще лучше, варианты для версий 5.2.3 и 5.5.5 с выброшенными комментариями:

// v.5.2.3
void parent_agent::so_evt_start() override
{
   auto coop = so_environment().create_coop( so_coop_name() + "/child" );
   coop->set_parent_coop_name( so_coop_name() );
   coop->add_agent( new first_child_agent( so_environment(), some_params() ) );
   coop->add_agent( new second_child_agent( so_environment(), some_params() ) );
   so_environment().register_coop( std::move( coop ) );
}

// v.5.5.5
void parent_agent::so_evt_start() override
{
   so_5::rt::introduce_child_coop( *this, [this]( so_5::rt::agent_coop_t & coop ) {
      coop.make_agent< first_child_agent >( some_params() );
      coop.make_agent< second_child_agent >( some_params() );
   }
}

Между двумя этими фрагментами полтора года развития фреймворка. Можно ли было получить вариант с introduce_child_coop намного раньше? И да, и нет.

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

А нет потому, что для воплощения идей нужны инструменты. Пока нужно было поддерживать MSVS2012, в котором variadic templates не было, написание make_agent и introduce_child_coop было бы намного, намного сложнее. Но когда от поддержки MSVS2012 отказались, горизонт возможностей расширился, что и позволило добавить несколько полезных шаблонных методов и функций для облегчения жизни разработчика.

Да, собственно, чего далего за примерами ходить. Если выйти за рамки C++11, то в C++14 создание дочерней кооперации будет требовать еще меньше кода, т.к. там есть полиморфные лямбды и вызов introduce_child_coop можно буде записать вот так:

void parent_agent::so_evt_start() override
{
   so_5::rt::introduce_child_coop( *this, [this]( auto & coop ) {
      coop.make_agent< first_child_agent >( some_params() );
      coop.make_agent< second_child_agent >( some_params() );
   }
}

Так что процесс идет. Не быстро, но идет.

PS. Ну и повторюсь еще раз. С++11 -- это уже совсем другой язык, который поднимает разработку на С++ на совсем другой уровень. Проверенно на людях ;)

Комментариев нет: