我有一个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;
}
答案 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”中的一个词:
相应地编码,以与之前建议的方式类似的方式避免条件树中的丛生。
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;如果您认为这仍然是一个问题,您可以使用“聪明”代码对各个位中的操作进行编码:
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,并委托如何将它用于我认为的其他东西。