Смягчение паттерна «тип-проверка-затем-использование» - PullRequest
0 голосов
/ 02 августа 2011

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

 /*
  * Data package Foo reports Fooness level
  */
 if(root.isMember("foo") && root["foo"].isInt())
 {
     int foo = root["foo"].asInt();
     // do things with foo
 }


 /*
  * Data package Bar gives ID number and name of a newly arrived bar.
  */
 if(root.isMember("bar") && root["bar"].isObject())
 {
     JSON::Value bar = root["bar"];

     if(bar.isMember("baz") && bar["baz"].isString()
     && bar.isMember("buzz") && bar["buzz"].isInt())
     {
          std::string baz = bar["baz"].asString();
          int buzz = bar["buzz"].asInt();
         // do things with baz and buzz

     }
     else{ err["bar"] = argument_error; }
 }

Мало того, что «плоть» каждого блока часто представляет собой одну или две строки с примерно 10 строками проверки параметров, это приводит к бесчисленным ошибкам копирования и вставки и проблемам с обслуживаемостью (ключ меняет имя, оно должно быть изменено примерно через 6 места).

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

Ответы [ 2 ]

2 голосов
/ 02 августа 2011

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

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

bool get_string(std::string& result, const Bar& bar, const char* name)
{
  if(bar.isMember(name) && bar[name].isString())
  {
    result = bar[name].asString();
    return true;
  }
  return false;
}

// and similarly
bool get_int(int& result, const Bar& bar, const char* name)
{
  if(bar.isMember(name) && bar[name].isInt())
  {
    result = bar[name].asInt();
    return true;
  }
  return false;
}

Затем вы можете использовать его с:

JSON::Value bar;
std::string baz;
int buzz;
if(get_object(bar, root, "bar"))
{
  if (get_string(baz, bar, "baz")
      && get_int(buzz, bar, "buzz"))
  {
     // do things with baz and buzz
  }
  else{ err["bar"] = argument_error; }
}

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

// set up template forms to check types
template<typename T> bool is_a(const Bar& b);
template<> bool is_a<std::string>(const Bar& b) { return b.isString(); }
template<> bool is_a<int>        (const Bar& b) { return b.isInt();    }
template<> bool is_a<JSON::Value>(const Bar& b) { return b.isObject(); }

// templates to extract as a type
template<typename T> T as_type(const Bar& b);
template<> std::string as_type<std::string>(const Bar& b) { return b.asString(); }
template<> int         as_type<int>        (const Bar& b) { return b.asInt();    }
template<> JSON::Value as_type<JSON::Value>(const Bar& b) { return b.asObject(); }

// the one extraction method
template<typename T>
T get(const Bar& bar, const char* name)
{
  if ( ! bar.isMember(name))  throw std::runtime_error("not a member");
  if ( ! is_a<T>(bar[name]))  throw std::runtime_error("wrong type");
  return as_type<T>(bar[name]);
}

// and now we use it
try
{
  JSON::Value bar = get<JSON::Value>(root, "bar");
  std::string baz = get<std::string>(bar, "baz");
  int buzz = get<int>(bar, "buzz");
  // do things with baz and buzz
}
catch (const std::runtime_error& exc)
{
  err["bar"] = argument_error;
}

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

1 голос
/ 02 августа 2011

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

#define FOO(var, name, cpp, json) cpp name((var.isMember(#name) && var[#name].is##json) ? var[#name].as##json() : "[ERROR in " #name "]")
int main()
{
  FOO(bar, foo, std::string, String);

  // translates into:
  std::string foo((bar.isMember("foo") && bar["foo"].isString) ? bar["foo"].asString() : "[ERROR in " "foo" "]");
}
...