减少“类型验证然后使用”的情况代码重复模式

发布于 2024-11-27 12:32:35 字数 914 浏览 0 评论 0原文

该系统的模块之一处理基于 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个地方必须改)。

您将如何重塑这些模式以实现它们的功能而不需要所有代码重复? (注意,所有主键和少数子键都是可选的,大多数子键是必需的。)

One of modules of the system deals with a JSON-based protocol that is used to transfer a wide variety of data. This results in almost a hundred of small sections of code that look like this:

 /*
  * 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; }
 }

Not only is the "flesh" of each block often a line or two with some 10 lines of parameter validation, this leads to countless copy-paste errors and maintainability problems (a key changes name, it must be changed in some 6 places).

How would you reshape these patterns to do what they do without all that code duplication?
(note, all main keys and few sub-keys are optional, most sub-keys are obligatory.)

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(2

旧竹 2024-12-04 12:32:35

您没有列出诸如 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;
}

设置需要更多方法,但现在使用它非常简单和干净。

You didn't list out the types of things like bar, so I'm just using Bar for that type.

You can give yourself some helper methods, making use of status codes or exceptions, depending on how common you expect failures to be.

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;
}

Then you can use it with:

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; }
}

Which is slightly cleaner, but not by leaps and bounds. If you expect the things you're looking for to be present, and failures are unlikely, then we can use exceptions. And we can go all out with templates too:

// 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;
}

The setup requires more methods, but now using it is very simple and clean.

橘寄 2024-12-04 12:32:35

由于您想要自动命名标识符,因此基本上只能使用预处理器。你可以尝试这样的事情:

#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" "]");
}

Since you want to automate the naming of identifiers, you're basically down to using the preprocessor. You could try something like this:

#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" "]");
}
~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文