it-swarm.cn

您将如何重构嵌套的IF语句?

当我碰到有关GOTO的帖子时,我正在编程博客圈中徘徊:

http://giuliozambon.blogspot.com/2010/12/programmers-tabu.html

在这里,作者谈论“如何必须得出结论,即在某些情况下,GOTO使得代码更具可读性和可维护性”,然后继续展示类似于以下示例:

if (Check#1)
{
    CodeBlock#1
    if (Check#2)
    {
        CodeBlock#2
        if (Check#3)
        {
            CodeBlock#3
            if (Check#4)
            {
                CodeBlock#4
                if (Check#5)
                {
                    CodeBlock#5
                    if (Check#6)
                    {
                        CodeBlock#6
                        if (Check#7)
                        {
                            CodeBlock#7
                        }
                        else
                        {
                            rest - of - the - program
                        }
                    }
                }
            }
        }
    }
}

然后作者提出,使用GOTO可以使此代码更易于阅读和维护。

我个人可以考虑至少三种不同的方法来使其变平并使该代码更具可读性,而无需采用破坏流程的GOTO。这是我的两个最爱。

1-嵌套小函数。将每个if及其代码块转换为一个函数。如果布尔检查失败,则返回。如果通过,则调用链中的下一个函数。 (男孩,这听起来很像递归,您能在一个带有函数指针的循环中完成它吗?)

2-Sentinal变量。对我来说,这是最简单的。只需使用blnContinueProcessing变量,然后在if检查中检查它是否仍然为真。然后,如果检查失败,请将变量设置为false。

这种编码问题可以通过几种不同的方式重构,以减少嵌套并增加可维护性?

34
saunderl

在不知道不同支票如何相互作用的情况下,很难说出来。可能会进行严格的重构。创建根据对象类型执行正确块的对象拓扑,策略模式或状态模式也可以解决问题。

在不知道如何做的最好的情况下,我会考虑两种可能的简单重构,可以通过提取更多方法来进一步重构。

我不喜欢的第一个,因为我一直喜欢某个方法中的小出口(最好是一个)

if (!Check#1)
{ 
    return;
}
CodeBlock#1

if (!Check#2)
{
    return;
}
CodeBlock#2
...

第二个消除了多重收益,但也增加了很多噪音。 (基本上只删除嵌套)

bool stillValid = Check#1
if (stillValid)
{
  CodeBlock#1
}

stillValid = stillValid && Check#2
if (stillValid)
{
  CodeBlock#2
}

stillValid = stillValid && Check#3
if (stillValid)
{
  CodeBlock#3
}
...

最后一个可以很好地重构为函数,当您给它们起好名字时,结果可能是合理的。

bool stillValid = DoCheck1AndCodeBlock1()
stillValid = stillValid && DoCheck2AndCodeBlock2()
stillValid = stillValid && DoCheck3AndCodeBlock3()

public bool DoCheck1AndCodeBlock1()
{
   bool returnValid = Check#1
   if (returnValid)
   {
      CodeBlock#1
   }
   return returnValid
}

总而言之,最有可能的方式是更好的选择

33
KeesDijk

由于带有适当缩进的代码的形状,因此称为“箭头代码”。

杰夫·阿特伍德(Jeff Atwood)在Coding Horror上有一篇不错的博客文章,介绍如何弄平箭头:

平箭头代码

阅读文章以获取完整的治疗方法,但是这里是要点。

  • 用保护子句替换条件
  • 将条件块分解为单独的函数
  • 将消极支票转化为积极支票
  • 总是有机会从函数中尽快返回
40
JohnFx

我知道有人会说这是goto,但是return;是明显的重构,即.

if (!Check#1)
{ 
        return;
}
CodeBlock#1
if (!Check#2)
{
    return;
}
CodeBlock#2
.
.
.
if (Check#7)
{
    CodeBlock#7
}
else
{
    rest - of - the - program
}

如果在运行代码之前确实只是一堆警卫检查,那么这很好。如果它比这更复杂,那么这只会使其更简单,并且您需要其他解决方案之一。

26
Richard Gadsden

该意大利面条式代码似乎是将其重构为状态机的理想选择。

10
Pemdas

可能已经提到过这一点,但是我对此处显示的“箭头反模式”的“ go-to”(双关语意味)答案是颠倒ifs。测试当前条件的相反情况(使用not运算符很容易),如果是,则从方法中返回。没有麻烦(尽管从学步上讲,无效返回仅是跳转到调用代码行,而这是从堆栈中弹出一个框架的琐碎额外步骤)。

举个例子,这是原始的:

if (Check#1)
{
    CodeBlock#1
    if (Check#2)
    {
        CodeBlock#2
        if (Check#3)
        {
            CodeBlock#3
            if (Check#4)
            {
                CodeBlock#4
                if (Check#5)
                {
                    CodeBlock#5
                    if (Check#6)
                    {
                        CodeBlock#6
                        if (Check#7)
                        {
                            CodeBlock#7
                        }
                        else
                        {
                            rest - of - the - program
                        }
                    }
                }
            }
        }
    }
}

重构:

if (!Check#1) return;

CodeBlock#1

if (!Check#2) return;

CodeBlock#2

if (!Check#3) return;

CodeBlock#3

if (!Check#4) return;

CodeBlock#4

if (!Check#5) return;

CodeBlock#5

if (!Check#6) return;

CodeBlock#6

if (Check#7)
    CodeBlock#7
else
{
    //rest of the program
}

在每个if处,我们基本上都会检查是否应该继续。它的工作方式与原始方法完全相同,只嵌套了一层。

如果在此代码段末​​尾还有其他内容也应运行,请将该代码提取到其自己的方法中,并从当前代码所在的位置调用它,然后再继续执行此代码段之后的代码。在每个代码块中都有足够的实际LOC的情况下,代码片段本身足够长,足以证明可以拆分出其他几种方法,但是我离题了。

6
KeithS

如果您常规地具有实际上需要if检查金字塔的逻辑,则可能(隐喻地)使用扳手锤击钉子。使用一种比线性if/_else if/_else- style更好的结构来支持这种扭曲和复杂逻辑的语言,最好能处理这种扭曲,复杂的逻辑结构体。

可能更适合这种结构的语言包括SNOBOL4(具有奇异的双GOTO样式分支)或逻辑语言(如PrologMercury)(具有统一和回溯功能)能力,更不用说他们的DCG了,它们简洁明了地表达了复杂的决策。

当然,如果这不是一个选择(因为大多数程序员是悲剧性的,而不是多元语言),其他人提出的想法就很好,例如使用各种 [〜#〜] oop [〜#〜] - 结构将子句分解成函数 甚至,如果您不顾一切,也不要介意大多数人使用 状态机

但是,我的解决方案仍在寻求一种语言,该语言允许您以一种更容易理解的方式来表达您要表达的逻辑(假设这在您的代码中很常见)。

1
JUST MY correct OPINION

来自 此处

当我查看一组pac-man ifs时,经常会发现,如果我只绘制出涉及所有条件的真值表之类的东西,我就能找到解决该问题的更好方法。

这样,您还可以评估是否有更好的方法,如何进一步分解以及(在这种代码中这是很大的)逻辑中是否有任何漏洞。

完成此操作后,您可能可以将其分解为几个switch语句和几个方法,并为下一个不得不通过代码处理的可怜的笨蛋省去很多麻烦。

1
Jim G.

就个人而言,我喜欢将这些if语句包装到单独的函数中,如果函数成功,则返回bool。

结构如下:


if (DoCheck1AndCodeBlock1() && 
    DoCheck2AndCodeBlock2() && 
    DoCheck3AndCodeBlock3()) 
{
   // ... you may perform the final operation here ....
}

唯一的缺点是这些函数必须使用引用或成员变量传递的属性来输出数据。

1
gogisan

使用OO)的复合模式,其中叶子是简单条件,组成简单元素的并集使得这种代码可扩展和适应

1
guiman

如果您想要一个可能的面向对象的解决方案,那么代码看起来像是使用 责任链设计模式 进行重构的规范示例。

0
FinnNk

将它们拆分为功能可能有帮助?

您调用第一个函数,当它完成时将调用下一个函数,该函数要做的第一件事就是测试对该函数的检查。

0
Toby

这很大程度上取决于每个代码块的大小和总大小,但是我倾向于通过在块上使用直接的“提取方法”或将无条件部分移动到单独的方法中来进行拆分。但是提到的警告@KeesDijk适用。前一种方法为您提供了一系列功能,每个功能

if (Check#1)
{
    CodeBlock#1
    NextFunction
}

可以很好地工作,但确实会导致代码膨胀和“仅使用一次的方法”反模式。所以我通常更喜欢这种方法:

if (CheckFunctionOne)
{
    MethodOneWithDescriptiveName
    if (CheckFunctionTwo)
    {
        MethodTwoWithDescriptiveName
        ....

适当使用私有变量和参数传递就可以完成。扫过识字编程可以为您提供帮助。

0
Мסž