среда, 18 марта 2015 г.

[prog.c++11] Еще о многословности кода на примерах

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

В левом столбце код, написанный в варианте quick-and-dirty, т.е. быстренько что-нибудь слабать, чтобы заработало как можно скорее. В правом столбце -- немножко доработанный напильником. И там, и там несколько приватных методов класса. Менялись только методы, остальная начинка класса (т.е. атрибуты и публичные методы) не изменилась ни на грамм.

void
evt_data_source( const msg_register_data_source & evt )
{
  m_data_sources.push_back( evt.m_data );

  if( m_data_sources_count == m_data_sources.size() )
    {
      this >>= st_light_data_iterations;
      m_started_at = std::chrono::high_resolution_clock::now();

      start_light_data_iteration();
    }
}

void
evt_light_data_iteration_finished()
{
  if( m_data_processor_count <= ++m_processor_iterations_passed )
    {
      if( m_iteration_count <= ++m_iterations_passed )
        {
          show_result( "light_data",
              std::chrono::high_resolution_clock::now() );

          this >>= st_heavy_data_iterations;
          m_started_at = std::chrono::high_resolution_clock::now();
          m_iterations_passed = 0;

          start_heavy_data_iteration();
        }
      else
        start_light_data_iteration();
    }
}

void
evt_heavy_data_iteration_finished()
{
  if( m_data_processor_count <= ++m_processor_iterations_passed )
    {
      if( m_iteration_count <= ++m_iterations_passed )
        {
          show_result( "heavy_data",
              std::chrono::high_resolution_clock::now() );

          so_deregister_agent_coop_normally();
        }
      else
        start_heavy_data_iteration();
    }
}
void
evt_data_source( const msg_register_data_source & evt )
  {
    m_data_sources.push_back( evt.m_data );

    if( m_data_sources_count == m_data_sources.size() )
      start_measure< light_data >( st_light_data_iterations );
  }

void
evt_light_data_iteration_finished()
  {
    handle_iteration_finish< light_data >( [this] {
          show_result( "light_data" );
          start_measure< heavy_data >( st_heavy_data_iterations );
        } );
  }

void
evt_heavy_data_iteration_finished()
  {
    handle_iteration_finish< heavy_data >( [this] {
            show_result( "heavy_data" );
            so_deregister_agent_coop_normally();
        } );
  }

Ну а вот вспомогательные методы, которые использовались в трех показанных выше. Никакой магии. Хотя, возможно, многие C++ники до сих пор не знают, что внутри обычного класса можно определять шаблонные методы, причем еще со времен C++98/03. Тем не менее, C++11/14 -- это уже совсем другой C++, не перестаю это повторять, т.к. сам зачастую получаю удовольствие о того, насколько удобнее и проще программировать на C++ сейчас, чем даже лет 6-7 назад.

void
start_light_data_iteration()
{
  start_iteration< light_data >();
}

void
start_heavy_data_iteration()
{
  start_iteration< heavy_data >();
}

templatetypename MSG >
void
start_iteration()
{
  m_processor_iterations_passed = 0;

  forauto d : m_data_sources )
    so_5::send< MSG >( m_data_mbox,
        d->m_name, d->m_suffix, d->m_gauge );
}

void
show_result(
   const char * what,
   std::chrono::high_resolution_clock::time_point finish_at )
{
  using namespace std::chrono;

  std::cout << what << ": "
    << duration_cast< microseconds >( finish_at - m_started_at )
        .count() / 1000.0 << "ms"
    << std::endl;
}
templateclass MSG >
void
start_measure( const so_5::rt::state_t & state )
  {
    this >>= state;
    m_iterations_passed = 0;

    m_started_at = std::chrono::high_resolution_clock::now();

    next_iteration< MSG >();
  }

templatetypename MSG >
void
next_iteration()
  {
    m_processor_iterations_passed = 0;

    forauto d : m_data_sources )
      so_5::send< MSG >( m_data_mbox,
          d->m_name, d->m_suffix, d->m_gauge );
  }

templatetypename MSG, typename ON_MEASURE_FINISH >
void
handle_iteration_finish( ON_MEASURE_FINISH reaction )
  {
    if( m_data_processor_count <= ++m_processor_iterations_passed )
      {
        if( m_iteration_count <= ++m_iterations_passed )
          reaction();
        else
          next_iteration< MSG >();
      }
  }

void
show_result( const char * what )
  {
    using namespace std::chrono;

    std::cout << what << ": "
      << duration_cast< microseconds >( high_resolution_clock::now() -
            m_started_at ).count() / 1000.0 << "ms"
      << std::endl;
  }

Что забавно: если сравнивать оба варианта кода целиком, то новый компактнее старого всего на четырнадцать строк. Эту разницу можно было бы почти полностью нивелировать, если бы в старом варианте отказаться от start_light_data_iteration/start_heavy_data_iteration, а вызывать напрямую start_iteration с соответствующим параметром шаблона. При этом еще и вспомогательные методы в новом варианте оказались объемнее. Тем не менее понятность трех основных методов, реализующих основную прикладную логику, изменилась значительно.

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