如何将代码重构为子例程但允许提前退出?

时间:2010-06-10 09:58:54

标签: c++ refactoring

在这个(工作)代码中有一个非常明显的重构机会。

bool Translations::compatibleNICodes(const Rule& rule, 
                                     const std::vector<std::string>& nicodes)
{
    bool included = false;

    // Loop through the ni codes.
    for(std::vector<std::string>::const_iterator iter = nicodes.begin();
        iter != nicodes.end();
        ++iter)
    {
        // Match against the ni codes of the rule
        if(rule.get_ni1() == *iter)
        {
            // If there's a match, check if it's flagged include or exclude
            const std::string flag = rule.get_op1();
            // If include, code is included unless a later rule excludes it
            if(flag == "INCLUDE"){ included = true; }
            // If exclude, code is specifically excluded
            else if(flag == "EXCLUDE"){ return false; }
        }
        if(rule.get_ni2() == *iter)
        {
            const std::string flag = rule.get_op2();
            if(flag == "INCLUDE"){ included = true; }
            else if(flag == "EXCLUDE"){ return false; }
        }
        if(rule.get_ni3() == *iter)
        {
            const std::string flag = rule.get_op3();
            if(flag == "INCLUDE"){ included = true; }
            else if(flag == "EXCLUDE"){ return false; }
        }
        if(rule.get_ni4() == *iter)
        {
            const std::string flag = rule.get_op4();
            if(flag == "INCLUDE"){ included = true; }
            else if(flag == "EXCLUDE"){ return false; }
        }
        if(rule.get_ni5() == *iter)
        {
            const std::string flag = rule.get_op5();
            if(flag == "INCLUDE"){ included = true; }
            else if(flag == "EXCLUDE"){ return false; }
        }
    }
    return included;
}

我想将其转为:

bool Translations::compatibleNICodes(const Rule& rule, 
                                     const std::vector<std::string>& nicodes)
{
    bool included = false;

    // Loop through the ni codes.
    for(std::vector<std::string>::const_iterator iter = nicodes.begin();
        iter != nicodes.end();
        ++iter)
    {
        // Match against the ni codes of the rule
        included |= matchNICode(rule.get_ni1(), rule.get_op1);
        included |= matchNICode(rule.get_ni2(), rule.get_op2);
        included |= matchNICode(rule.get_ni3(), rule.get_op3);
        included |= matchNICode(rule.get_ni4(), rule.get_op4);
        included |= matchNICode(rule.get_ni5(), rule.get_op5);
    }
    return included;
}

bool Translations::matchNICode(const std::string& ni, 
                               const std::string& op)
{
    if(ni == *iter)
    {
        if(op == "INCLUDE"){ return true; }
        else if(op == "EXCLUDE"){ /*Return worse than false*/ }
    }
    return false;
}

问题在于,如果它是一个排除声明,我无法解决我想提前退出的问题。

请注意,我无法更改Rule类的结构。

有什么建议吗?

7 个答案:

答案 0 :(得分:3)

一种可能的重构如下,但我不确定是否值得麻烦

#define NI_CLAUSE(ID) \
        if(rule.get_ni ## ID() == *iter) \
        { \
            const std::string flag = rule.get_op ## ID(); \
            if(flag == "INCLUDE"){ included = true; } \
            else if(flag == "EXCLUDE"){ return false; } \
        }

bool Translations::compatibleNICodes(const Rule& rule, 
                                     const std::vector<std::string>& nicodes)
{
    bool included = false;

    // Loop through the ni codes.
    for(std::vector<std::string>::const_iterator iter = nicodes.begin();
        iter != nicodes.end();
        ++iter)
    {
        NI_CLAUSE(1)
        NI_CLAUSE(2)
        NI_CLAUSE(3)
        NI_CLAUSE(4)
        NI_CLAUSE(5)
    }
    return included;
}

答案 1 :(得分:3)

bool Translations::compatibleNICodes(const Rule& rule,
                                     const std::vector<std::string>& nicodes)
{
    bool included = false;

    struct
    {
      RULE_GET_NI get_ni;
      RULE_GET_OP get_op;
    } method_tbl[] =
    {
      { &Rule::get_ni1, &Rule::get_op1 },
      { &Rule::get_ni2, &Rule::get_op2 },
      { &Rule::get_ni3, &Rule::get_op3 },
      { &Rule::get_ni4, &Rule::get_op4 },
      { &Rule::get_ni5, &Rule::get_op5 },
    };
    // Loop through the ni codes.
    for(std::vector<std::string>::const_iterator iter = nicodes.begin();
        iter != nicodes.end();
        ++iter)
    {
        for(size_t n = 0; n < 5 /* I am lazy here */; ++n)
        {
            if((rule.*(method_tbl[n].get_ni))() == *iter)
            {
                // If there's a match, check if the rule is include or exclude
                const std::string flag = (rule.*(method_tbl[n].get_op))();
                // If include, code is included unless a later rule excludes it
                if(flag == "INCLUDE"){ included = true; }
                // If exclude, code is specifically excluded
                else if(flag == "EXCLUDE"){ return false; }
            }
        }
    }
    return included;
}

编辑答案只包括最终版本。

BTW这个问题很有趣,请给我一些时间,我想出了stl算法和函子......

答案 2 :(得分:3)

我认为get_niX()get_opX()方法有某种副作用;否则,只要你得到true,就可以退出。

如果从matchNICode()返回的内容确实比错误更糟糕,那么它可能是一个例外。在这种情况下,它很简单:

bool Translations::compatibleNICodes(const Rule& rule, 
                                     const std::vector<std::string>& nicodes)
{
    bool included = false;

    try
    {
      // Loop through the ni codes.
      for(std::vector<std::string>::const_iterator iter = nicodes.begin();
          iter != nicodes.end();
          ++iter)
      {
        // Match against the ni codes of the rule
        included |= matchNICode(rule.get_ni1(), rule.get_op1);
        included |= matchNICode(rule.get_ni2(), rule.get_op2);
        included |= matchNICode(rule.get_ni3(), rule.get_op3);
        included |= matchNICode(rule.get_ni4(), rule.get_op4);
        included |= matchNICode(rule.get_ni5(), rule.get_op5);
      }
      return included;
    }
    catch (WorseThanFalseException& ex)
    {
      return false; // Or whatever you have to do and return
    }
}

bool Translations::matchNICode(const std::string& ni, 
                               const std::string& op)
{
    if(ni == *iter)
    {
        if(op == "INCLUDE"){ return true; }
        else if(op == "EXCLUDE"){ throw WorseThanFalseException(); } // Whatever this is
    }
    return false;
}

答案 3 :(得分:2)

如果您可以循环遍历ni op成员RuleRule成员,那么代码显然会更清晰,更简单。如果你不能重构{{1}},也许你可以创建一个包装器来实现这个目标。

如果你有一个这样的代码方法,我不会打扰。如果你能用几种类似的方法消除重复的代码,IMO只会付出代价。

答案 4 :(得分:1)

你可以通过创建某种tribool类并使用延迟评估来解决这个问题。

class TriState
{
public:
  TriState(): mState(KO) {}

  bool isValid() const { return mState != FATAL; }

  bool ok() const { return mState == OK; }

  void update(std::string const& value,
              std::string const& reference,
              std::string const& action)
  {
    if (mState == FATAL) return;

    if (value == reference)
    {
      if (action == "INCLUDE") mState = OK;
      else if (action == "EXCLUDE") mState = FATAL;
    }
  }

private:
  typedef enum { OK, KO, FATAL } State_t;
  State_t mState;
};

然后你可以这样使用循环:

TriState state;

for (const_iterator it = nicodes.begin(), end = nicodes.end();
     it != end && state.isValid(); ++it)
{
   state.update(*it, rule.get_ni1(), rule.get_op1);
   state.update(*it, rule.get_ni2(), rule.get_op2);
   state.update(*it, rule.get_ni3(), rule.get_op3);
   state.update(*it, rule.get_ni4(), rule.get_op4);
   state.update(*it, rule.get_ni5(), rule.get_op5);
}

return state.ok();

现在,如果对规则的操作有某些应该避免的副作用,那么使用包装器来进行延迟评估。

class Wrapper
{
public:
  Wrapper(Rule const& rule): mRule(rule) {}

  std::string const& get_ni(size_t i) const { switch(i) { ... } }
  std::string const& get_action(size_t i) const { switch(i) { ... } }

private:
  Rule const& mRule;
};

重构update

void update(std::string const& value, Wrapper wrapper, size_t index)
{
  if (mState == FATAL) return;

  if (value == wrapper.get_ni(index))
  {
    if (wrapper.get_action(index) == "INCLUDE") mState = OK;
    else if (wrapper.get_action(index) == "EXCLUDE") mState = FATAL;
  }
}

使用双循环:

TriState state;
Wrapper wrapper(rule);

for (const_iterator it = nicodes.begin(), end = nicodes.end();
     it != end && state.isValid(); ++it)
{
  for (size_t index = 1; index != 6 && state.isValid(); ++index)
    state.update(*it, wrapper, index);
}

return state.ok();

指南:包装您无法修改的内容! (查看适配器系列模式)

答案 5 :(得分:0)

这是承诺基于算法的解决方案,它是硬核......他们说STL是为了简化我们的程序(这比我提出的其他解决方案更长)。

struct FUNCTOR : std::unary_function<bool, std::string const &>
{
  public:
    FUNCTOR(Rule const &r) : included(false), rule(r)
    {
    }
    // returns true if exluded
    bool operator()(std::string const &s)
    {
      static METHODS methods[] =
      {
        { &Rule::get_ni1, &Rule::get_op1 },
        { &Rule::get_ni2, &Rule::get_op2 },
        { &Rule::get_ni3, &Rule::get_op3 },
        { &Rule::get_ni4, &Rule::get_op4 },
        { &Rule::get_ni5, &Rule::get_op5 },
      };
      return(std::find_if(&methods[0], &methods[5], FUNCTOR2(rule, s, included)) != &methods[5]);
    }
    operator bool()
    {
      return(included);
    }
  private:
    struct METHODS
    {
      std::string (Rule::*get_ni)() const;
      std::string (Rule::*get_op)() const;
    };
    struct FUNCTOR2 : std::unary_function<bool, METHODS>
    {
      public:
        FUNCTOR2(Rule const &r, std::string const &s, bool &incl) : rule(r), str(s), included(incl)
        {
        }
        // return true if exluded
        bool operator()(METHODS m)
        {
          if((rule.*m.get_ni)() == str)
          {
            // If there's a match, check if the rule is include or exclude
            const std::string flag = (rule.*m.get_op)();
            // If include, code is included unless a later rule excludes it
            if(flag == "INCLUDE")
              included = true;
            // If exclude, code is specifically excluded
            else if(flag == "EXCLUDE")
            {
              included = false;
              return(true);
            }
          }
          return(false);
        }
      private:
        Rule const &rule;
        std::string const &str;
        bool &included;
    };
    Rule const &rule;
    bool included;
};

bool Translations::compatibleNICodes(const Rule& rule,
                                     const std::vector<std::string>& nicodes)
{
  FUNCTOR f(rule);
  std::find_if(nicodes.begin(), nicodes.end(), f);
  return(f);
}

答案 6 :(得分:0)

使用输入/输出参数是获得两个返回值的简单而有效的方法:

另外我认为你需要对rule.get_opN()进行懒惰评估?要做到这一点,你需要使用指向成员的指针函数。

bool Translations::compatibleNICodes(const Rule& rule, 
                                     const std::vector<std::string>& nicodes)
{
    bool included = false;

    // Loop through the ni codes.
    for(std::vector<std::string>::const_iterator iter = nicodes.begin();
        iter != nicodes.end();
        ++iter)
    {
        // Match against the ni codes of the rule
        if (!matchNICode(rule.get_ni1(), rule, &Rule::get_op1, included)) return false;
        if (!matchNICode(rule.get_ni2(), rule, &Rule::get_op2, included)) return false;
        if (!matchNICode(rule.get_ni3(), rule, &Rule::get_op3, included)) return false;
        if (!matchNICode(rule.get_ni4(), rule, &Rule::get_op4, included)) return false;
        if (!matchNICode(rule.get_ni5(), rule, &Rule::get_op5, included)) return false;
    }
    return included;
}

bool Translations::matchNICode(const std::string& ni, const Rule& rule, 
                               std::string (Rule::* const opfn)(), bool& included)
{
    if (ni == *iter) {
        const std::string& op = (rule.*opfn)();
        if (op == "INCLUDE") {
            included = true;
        }
        else if (op == "EXCLUDE") {
            return false;
        }
    }
    return true;
}