DRY if语句

时间:2012-05-10 14:17:31

标签: c++ if-statement dry

我有一个C ++程序,在许多不同的.cpp文件中,我这样做:

if (!thing1.empty() && !thing2.empty())
{
    if (thing1.property < thing2.property)
        return func1();
    else if (thing2.property < thing1.property)
        return func2();
    else
        return func3();
}
else if (!thing1.empty())
{
    return func1();
}
else if (!thing2.empty())
{
    return func2();
}
else
{
   return func4();
}

如果thing1大于thing2,我试图以某种方式执行func;如果情况相反,我会尝试向后执行func,但如果不存在那么我只会为那一半执行func。如果两者都不存在,我会做一些完全不同的事情。每次使用此模式时,属性,函数和返回类型都不同。对于我想做的事情有没有比这个丑陋的嵌套if语句更好的设计?

编辑:已实现我的示例代码过于简单化了。这里有一些我的真实代码,希望能更好地解释问题(尽管它更加混乱):

if (!diamondsOnly.empty() && !clubsOnly.empty())
{
    if (diamondsOnly.size() < clubsOnly.size())
    {
        if (passHighCards(player.hand, getHighCards(Card::DIAMONDS), result))
            return result;
        if (passHighCards(player.hand, getHighCards(Card::CLUBS), result))
            return result;
    }
    else if (clubsOnly.size() < diamondsOnly.size())
    {
        if (passHighCards(player.hand, getHighCards(Card::CLUBS), result))
            return result;
        if (passHighCards(player.hand, getHighCards(Card::DIAMONDS), result))
            return result;
    }
    else
    {
        if (diamondsOnly.back().value > clubsOnly.back().value)
        {
            if (passHighCards(player.hand, getHighCards(Card::DIAMONDS), result))
                return result;
            if (passHighCards(player.hand, getHighCards(Card::CLUBS), result))
                return result;
        }
        else
        {
            if (passHighCards(player.hand, getHighCards(Card::CLUBS), result))
                return result;
            if (passHighCards(player.hand, getHighCards(Card::DIAMONDS), result))
                return result;
        }
    }
}
else if (!diamondsOnly.empty())
{
    if (passHighCards(player.hand, getHighCards(Card::DIAMONDS), result))
        return result;
}
else if (!clubsOnly.empty())
{
    if (passHighCards(player.hand, getHighCards(Card::CLUBS), result))
        return result;
}

4 个答案:

答案 0 :(得分:5)

决定然后

看一下真实的代码,我注意到的第一件事就是有很多几乎相同的调用只有一个常数变化。我会使用在复杂逻辑中设置的参数在一个地方进行调用。

// Decide what to do.
std::vector<Card::Suit> passOrder;
if (!diamondsOnly.empty() && !clubsOnly.empty()) {
    // .. complicated logic that adds suits to passOrder ..
}

// Do it.
for (auto suit : passOrder) {  // This is C++11 style -- alter as needed
    if (passHighCards(player.hand, getHighCards(suit), result))
        return result;
}

(如果向量总是只有一两个,那么使用向量可能有点过分,但我假设真实代码可以处理所有诉讼。)

这使得阅读更容易。程序员可以看到,首先你决定通过卡片的顺序,然后你实际上通过它们。两个单独的步骤将更加清晰。只有一个调用passCards的地方使得它更容易产生愚蠢的拼写错误,而不是让它的副本分散在整个决策逻辑中。它也会使调试变得更容易,因为您可以在非常特殊的情况下设置断点,或者您可以在循环开始时设置断点并检查passOrder。

简化逻辑

接下来我们要简化决策逻辑。

选项:

  • Sentinels:复杂的部分原因在于,在某些情况下,您需要取消引用其中一个容器中的最后一张卡,如果容器为空,则无法取消。有时候值得考虑在容器中添加一个标记,以便您不需要测试空壳 - 您可以保证它永远不会空着。这可能是可行的,也可能是不可行的。您需要让处理容器的所有其他代码都了解哨兵。

  • 只是例外情况:你可以通过选择默认订单来消除一些条款,例如,钻石然后是俱乐部,然后只测试你需要俱乐部然后钻石的情况。

  • 使用Temporaries进行表达:创建名称极佳的临时工具,简化您必须进行的比较,并根据这些临时表达比​​较。请注意,如果将空/非空案例分解为临时案例,则可以通过选择适当的SENTINEL_VALUE(如0或-1)来消除某些情况。

全部放在一起:

// For readability.
const bool fewerClubs = clubsOnly.size() < diamondsOnly.size();
const bool sameNumber = clubsOnly.size() == diamondsOnly.size();
const int lastDiamondValue =  diamondsOnly.empty() ? -1 : diamondsOnly.back().value;
const int lastClubValue    =  clubsOnly   .empty() ? -1 : clubsOnly   .back().value;

// Decide what order to select cards for passing.
std::vector<Card::Suit> passOrder;
passOrder.push_back(Cards::DIAMONDS);  // default order
passOrder.push_back(Cards::CLUBS);

// Do we need to change the order?
if (fewerClubs || (sameNumber && lastClubValue > lastDiamondValue)) {
    // Yep, so start with the clubs instead.
    passOrder[0] = Cards::CLUBS;
    passOrder[1] = Cards::DIAMONDS;
}

// Do it.
for (auto suit : passOrder) {  // This is C++11 style -- alter as needed
    if (passHighCards(player.hand, getHighCards(suit), result))
        return result;
}

这假设getHighCards处理可能为空的容器作为输入。

答案 1 :(得分:4)

我不确定这是一个巨大的进步,但你可以用以下方法消除倦怠:

if (thing1.empty() && thing2.empty())
   return func4();
else if (thing1.empty())
    return func2();
else if (thing2.empty())
    return func1();
else if (thing1.property < thing2.property)
    return func1();
else if (thing2.property < thing1.property)
    return func2();
else
    return func3();

我删除了大括号以保持一致;它们可以恢复,但是如果在可读性方面有任何好处,则增加代码的长度。这也避免了负面影响;他们总是让条件(一点点)难以阅读。这不是代码中的主要问题;它可以在条件复杂时使用。

您可以合理地争辩说,由于所有操作都是return语句,因此每次都可以删除else


考虑到更大的例子,那么根据某些情况,你的所有代码都会导致一个或两个非常相似的行为。

在这种情况下,应该使用Kernighan和Plauger出版的优秀(但略显过时和绝版)的书“The Elements of Programming Style”中的一个词:

  • 子程序调用允许我们总结参数列表[...]
  • 中的不规则性
  • [t]他的子程序本身总结了代码[...]
  • 的规律性

相应地编码,以与之前建议的方式类似的方式避免条件树中的丛生。

CardType pass[2] = { -1, -1 };  // Card::INVALID would be nice

if (clubsOnly.empty() && diamondsOnly.empty())
{
    ...do undocumented action for no diamonds or clubs...
}
else if (diamondsOnly.empty())
{
    pass[0] = Card::CLUBS;
}
else if (clubsOnly.empty())
{
    pass[0] = Card::DIAMONDS;
}
else if (diamondsOnly.size() < clubsOnly.size())
{
    pass[0] = Card::DIAMONDS;
    pass[1] = Card::CLUBS;
}
else if (clubsOnly.size() < diamondsOnly.size())
{
    pass[0] = Card::CLUBS;
    pass[1] = Card::DIAMONDS;
}
else if (diamondsOnly.back().value > clubsOnly.back().value)
{
    pass[0] = Card::DIAMONDS;
    pass[1] = Card::CLUBS;
}
else
{
    pass[0] = Card::CLUBS;
    pass[1] = Card::DIAMONDS;
}

然后,当你已经涵盖所有条件时,执行一个简单的循环来做正确的事情。

for (int i = 0; i < 2; i++)
{
    if (pass[i] != -1 && passHighCards(player.hand, getHighCards(pass[i]), result))
        return result;
}

...undocumented what happens here...

2有点不舒服;它出现了两次。

然而,总体而言,这为您提供了一系列线性测试,每次测试后都采用简单的对称操作(为了保持一致性,此时保留括号,因为操作长度超过一行;一致性比存在或不存在更重要支架本身)。当关于做什么的决定完成时,你实际上就去做了。

答案 2 :(得分:3)

您可以计算所有条件并将它们发送到某个函数,该函数应该做出决定,并返回一个代码,告诉您下一步该做什么。所有重复的“模式”都将在函数内部。

// return decision1 or decision2, depending on the result of comparison of properties
// Note: type is ssize_t to accommodate bool, size_t and whatever type is 'value'
int decision(ssize_t property1, ssize_t property2, int decision1, int decision2)
{
    if (property1 > property2)
        return decision1;
    else if (property2 > property1)
        return decision2;
    else
        return 0;
}

some_func()
{
    int decision = decide(!diamondsOnly.empty(), !clubsOnly.empty(), 1, 2);
    if (!decision)
        decision = decide(diamondsOnly.size(), clubsOnly.size(), 3, 4);
    if (!decision)
        decision = decide(diamondsOnly.back().value, clubsOnly.back().value, 3, 4);

    bool flag;
    switch (decision)
    {
        case 1:
            flag = passHighCards(player.hand, getHighCards(Card::DIAMONDS), result);
            break;
        case 2:
            flag = passHighCards(player.hand, getHighCards(Card::CLUBS), result);
            break;
        case 3:
            flag = passHighCards(player.hand, getHighCards(Card::DIAMONDS), result);
            flag = passHighCards(player.hand, getHighCards(Card::CLUBS), result);
            break;
        case 4:
            flag = passHighCards(player.hand, getHighCards(Card::CLUBS), result);
            flag = passHighCards(player.hand, getHighCards(Card::DIAMONDS), result);
            break;
        default:
            flag = whatever();
            break;
    }

    if (flag)
        return result;
}

在上面的代码中,switch声明违反了DRY;如果您认为这仍然是一个问题,您可以使用“聪明”代码对各个位中的操作进行编码:

  • 第0位:是否要做任何事情
  • 第1位:先做什么
  • 第2位:是否做第二件事
  • 第3位:下一步做什么

if ((decision & 1) == 0) {flag = whatever;}
else
{
    thing1 = (decision & 2) ? Card::DIAMONDS : Card::CLUBS
    flag = passHighCards(player.hand, thing1, result);
    if (decision & 4)
    {
        thing2 = (decision & 8) ? Card::DIAMONDS : Card::CLUBS;
        flag = passHighCards(player.hand, thing2, result);
    }
}

在我看来,这个“符合DRY”的作品看起来比switch更加毛茸茸。

答案 3 :(得分:1)

创建一个接口说IMyCompare。 使用一个获取IMyCompare并返回Func的方法 然后你可以在每件事上实现整个事情

并做 像AThing.MyCompare(otherThing);

如果每个条件的哪个函数没有按事物的类型修复,那么将一个函数数组传递给比较调用。

我很想从MyCompare返回一个int,并委托如何将它用于我认为的其他东西。