处理if(if else)else的优雅方法


161

这是个小问题,但是每次我必须编写类似这样的代码时,重复都会困扰我,但是我不确定任何解决方案都不会更糟。

if(FileExists(file))
{
    contents = OpenFile(file); // <-- prevents inclusion in if
    if(SomeTest(contents))
    {
        DoSomething(contents);
    }
    else
    {
        DefaultAction();
    }
}
else
{
    DefaultAction();
}
  • 这种逻辑有名称吗?
  • 我也有点强迫症吗?

如果出于好奇,我愿意接受邪恶的代码建议...


8
@Emmad Kareem:有两个DefaultAction电话违反了DRY原则
Abyx

感谢您的答复,但我认为这是可以的,除了不使用try / catch之外,因为可能存在一些错误,这些错误不会返回结果并会导致异常终止(取决于您的编程语言)。
NoChance 2011年

20
我认为这里的主要问题是您在不一致的抽象级别上工作。更高的抽象级别是:make sure I have valid data for DoSomething(), and then DoSomething() with it. Otherwise, take DefaultAction()。确保您具有DoSomething()的数据的实质内容是较低的抽象级别,因此应使用其他函数。该函数将在较高的抽象级别中具有名称,而其实现将是低级别的。下面的好答案解决了这个问题。
Gilad Naor

6
请指定一种语言。可能的解决方案,标准习语和长期的文化规范因不同的语言而异,并且会导致对您的问题的不同答案
。– Caleb

1
您可以参考“重构:改进现有代码的设计”这本书。有关if-else结构的部分内容,这是非常有用的实践。
Vacker 2011年

Answers:


96

将其提取为单独的函数(方法)并使用return语句:

if(FileExists(file))
{
    contents = OpenFile(file); // <-- prevents inclusion in if
    if(SomeTest(contents))
    {
        DoSomething(contents);
        return;
    }
}

DefaultAction();

或者,也许更好的是,分别获取内容及其处理:

contents_t get_contents(name_t file)
{
    if(!FileExists(file))
        return null;

    contents = OpenFile(file);
    if(!SomeTest(contents)) // like IsContentsValid
        return null;

    return contents;
}

...

contents = get_contents(file)
contents ? DoSomething(contents) : DefaultAction();

更新:

为什么不例外,为什么OpenFile不引发IO例外:
我认为这确实是通用问题,而不是有关文件IO的问题。名称,如FileExistsOpenFile可以是混乱的,但如果将它们与替代FooBar等等, -这将是更清楚的是DefaultAction,可以为经常叫DoSomething,所以它可能是不例外的情况。PéterTörök 在回答的最后写了这个

为什么在第二个变体中有三元条件运算符:
如果会有[C ++]标记,我会在其条件部分写if有声明的语句contents

if(contents_t contents = get_contents(file))
    DoSomething(contents);
else
    DefaultAction();

但是对于其他(类似C的)语言,if(contents) ...; else ...;它与带有三元条件运算符的表达式语句完全相同,但是更长。因为代码的主要部分是get_contents函数,所以我只使用了较短的版本(也省略了contents类型)。无论如何,这超出了这个问题。


93
+1多回报 -当方法被制造得足够小,这种方法的工作最适合我
蚊蚋

尽管我偶尔会使用它,但它并不是多重收益的忠实拥护者。在简单的事情上这是相当合理的,但是扩展性不好。我们的标准是对于所有疯狂的简单方法都避免使用它,因为方法的大小趋于增长而不是缩小。
Brian Knoblauch

3
多个返回路径在C ++程序中可能会对性能产生负面影响,从而破坏了优化器使用RVO的努力(也包括NRVO,除非每个路径都返回相同的对象)。
37分

我建议反转第二种解决方案的逻辑:{if(file存在){设置内容;if(testtest){返回内容;返回空值;}它简化了流程并减少了行数。

1
嗨,Abyx,我注意到您在这里结合了评论的一些反馈意见:谢谢您的帮助。我已经清理了您的答案和其他答案中提到的所有内容。

56

如果您正在使用的编程语言(0)短路了二进制比较(即,SomeTest如果FileExists返回false 则不调用),并且(1)赋值返回了一个值(将结果OpenFile分配给contents,然后将该值作为参数传递到SomeTest),您可以使用类似以下内容的代码,但仍建议您注释该代码,指出该代码=是有意的。

if( FileExists(file) && SomeTest(contents = OpenFile(file)) )
{
    DoSomething(contents);
}
else
{
    DefaultAction();
}

根据if的复杂程度,最好有一个flag变量(将成功/失败条件的测试与处理DefaultAction这种情况下的错误的代码分开)


这就是我要做的。
安东尼

13
if在我看来,在语句中放入如此多的代码相当麻烦。
moteutsch 2011年

15
相反,我喜欢这种“如果某物存在并满足此条件”的陈述。+1
Gorpik

我也做!我个人不喜欢人们使用多重回报的方式,但某些前提无法满足。您为什么不反转那些if并遇到它们的情况下执行代码?
克拉

“如果存在并满足此条件”是可以的。OTOH:“如果某物存在并且在这里做切线相关的事情并且满足此条件”,这令人困惑。换句话说,我不喜欢这种情况下的副作用。
Piskvor,

26

比重复调用DefaultAction更严重的是样式本身,因为代码是非正交编写的(有关正交编写的充分理由,请参见此答案)。

为了说明为什么非正交代码不好,请考虑原始示例,当引入了一项新要求,即如果文件存储在网络磁盘上时,我们不应该打开该文件。好吧,那么我们可以将代码更新为以下内容:

if(FileExists(file))
{
    if(! OnNetworkDisk(file))
    {
        contents = OpenFile(file); // <-- prevents inclusion in if
        if(SomeTest(contents))
        {
            DoSomething(contents);
        }
        else
        {
            DefaultAction();
        }
    }
    else
    {
        DefaultAction();
    }
}
else
{
    DefaultAction();
}

但是,随之而来的是,我们也不应打开超过2Gb的大文件。好吧,我们只是再次更新:

if(FileExists(file))
{
    if(LessThan2Gb(file))
    {
        if(! OnNetworkDisk(file))
        {
            contents = OpenFile(file); // <-- prevents inclusion in if
            if(SomeTest(contents))
            {
                DoSomething(contents);
            }
            else
            {
                DefaultAction();
            }
        }
        else
        {
            DefaultAction();
        }
    else
    {
        DefaultAction();
    }
}
else
{
    DefaultAction();
}

很清楚,这样的代码风格将带来巨大的维护麻烦。

在这里正确正交地写出的答案中,有Abyx的第二个例子Jan Hudec的答案,因此,我不再赘述,只是指出在这两个答案中加上两个要求就是

if(! LessThan2Gb(file))
    return null;

if(OnNetworkDisk(file))
    return null;

(或goto notexists;代替return null;),除了添加的行之外不影响任何其他代码。例如正交。

测试时,一般规则应该是测试异常,而不是正常情况


8
为我+1。尽早退货有助于避免出现反箭头形态。请参阅codinghorror.com/blog/2006/01/flattening-arrow-code.htmllostechies.com/chrismissal/2009/05/27/…在阅读此模式之前,我总是订阅每个功能的1个入口/出口。理论是由于15年前左右我所受的教育。我觉得这使代码更易于阅读,并且正如您所说的更具可维护性。
穆斯先生

3
@MrMoose:您提到的箭头反模式回答了Benjol的明确问题:“这种逻辑有名称吗?” 将其发布为答案,您就会得到我的投票。
2011年

这是一个很好的答案,谢谢。@MrMoose:“箭头反模式”可能会回答我的第一个项目符号,所以是的,请发布它。我不能保证会接受,但值得投票!
Benjol 2011年

@outis。谢谢。我添加了答案。箭头反模式在赫洛瓦尔(Hlovdal)的职位上肯定是相关的,他的后卫条款在绕过它们时效果很好。我不知道您如何回答第二点。我没有资格诊断:)
穆斯先生

4
+1表示“测试例外,不是正常情况”。
罗伊·廷克

25

明显:

Whatever(Arguments)
{
    if(!FileExists(file))
        goto notexists;
    contents = OpenFile(file); // <-- prevents inclusion in if
    if(!SomeTest(contents))
        goto notexists;
    DoSomething(contents);
    return;
notexists:
    DefaultAction();
}

您说您甚至对邪恶的解决方案持开放态度,所以使用邪恶的goto计数是不是?

实际上,根据具体情况,此解决方案的邪恶程度可能不及两次执行该行为的邪恶程度或邪恶的额外变量。我将其包装在一个函数中,因为在long函数的中间绝对不行(尤其是由于中间的返回)。但是比长功能不行,期限。

当您有异常时,它们将更容易阅读,特别是如果您可以让OpenFile和DoSomething在不满足条件的情况下简单地引发异常,那么您根本就不需要显式检查。另一方面,在C ++中,Java和C#引发异常是一个缓慢的操作,因此从性能角度来看,goto还是可取的。


关于“邪恶”的说明:C ++ FAQ 6.15将“邪恶”定义为:

这意味着某某是你应该避免大部分的时间,但不是你应该避免所有的时间。例如,每当这些“邪恶”事物成为“邪恶选择中的最小邪恶”时,您最终将使用它们。

这适用goto于这种情况。结构化流控制结构通常在大多数情况下会更好,但是当您遇到这种情况时,它们会积累太多自己的弊端,例如有条件分配,嵌套约三层以上的深度,重复代码或冗长的条件,goto可能会结束减少邪恶。


11
我的光标悬停在接受按钮上……只是为了...弃所有纯粹主义者。噢噢,诱惑:D
Benjol 2011年

2
是的是的!这是编写代码的绝对“正确”方法。现在,代码的结构应为“如果有错误,则处理错误。正常操作。如果有错误,则处理错误。正常操作”,它应该是完全一样的。所有“普通”代码都只使用一个单级缩进编写,而所有与错误相关的代码都具有两个缩进级。因此,正常且最重要的代码会在视觉上占据最突出的位置,并且可以非常顺畅,轻松地按顺序向下读取流程。一定要接受这个答案。
hlovdal

2
另一个方面是,以这种方式编写的代码是正交的。例如,两行“ if(!FileExists(file))\ n \ tgoto不存在;” 现在仅与处理此单个错误方面(KISS)有关,最重要的是,它不影响其他任何一行。这个答案stackoverflow.com/a/3272062/23118列出了保持代码正交的几个很好的理由。
hlovdal

5
谈到邪恶的解决方案:我可以不用goto就可以得到您的解决方案:for(;;) { if(!FileExists(file)) break; contents = OpenFile(file); if(!SomeTest(contents)) break; DoSomething(contents); return; } /* broken out */ DefaultAction();
Herby

4
@herby:您的解决方案比邪恶goto,因为您break以一种没人期望它会被滥用的方式进行滥用,因此与goto明确声明相比,阅读代码的人在看到中断导致问题的方向上会遇到更多问题。此外,您使用的是无限循环,该循环只会运行一次,这会造成混乱。不幸的do { ... } while(0)是,它也不是完全可读的,因为当您到达最后时,您只会看到它只是一个有趣的块,并且C不支持从其他块中突破(与perl不同)。
日1月Hudec

12
function FileContentsExists(file) {
    return FileExists(file) ? OpenFile(file) : null;
}

...

contents = FileContentExists(file);
if(contents && SomeTest(contents))
{
    DoSomething(contents);
}
else
{
    DefaultAction();
}

或去额外的男性,并创建一个额外的FileExistsAndConditionMet(file)方法...
UncleZeiv 2011年

SomeTest如果SomeTest检查文件类型,例如检查.gif确实是GIF文件,则@herby 可以具有与文件存在相同的语义。
Abyx 2011年

1
是。要看。@Benjol更了解。
herby

3
...当然,我的意思是“走出困境” ... :)
UncleZeiv

2
即使我不去也要把馄饨带到四肢(我对此非常极端)...考虑到,现在我认为它很容易读懂contents && f(contents)。有两个功能可以保存一个?
herby

12

一种可能性:

boolean handled = false;

if(FileExists(file))
{
    contents = OpenFile(file); // <-- prevents inclusion in if
    if(SomeTest(contents))
    {
        DoSomething(contents);
        handled = true;
    }
}
if (!handled)
{
    DefaultAction();
}

当然,这会使代码以不同的方式稍微复杂一些。因此,这在很大程度上是一个样式问题。

另一种方法是使用异常,例如:

try
{
    contents = OpenFile(file); // throws IO exception if file not found
    DoSomething(contents); // calls SomeTest() and throws exception on failure
}
catch(Exception e)
{
    DefaultAction();
    // and the exception should be at least logged...
}

这看起来更简单,但是仅适用于

  • 我们确切地知道期望什么样的例外,并DefaultAction()适合每个例外
  • 我们希望文件处理能够成功,并且丢失文件或失败SomeTest()显然是错误的情况,因此在其上抛出异常是合适的。

19
不!不是标志变量,这绝对是错误的方式,因为它会导致复杂,难以理解(where-that-flag-becomes-true)并且难以重构代码。
Abyx 2011年

如果将其限制在尽可能局部的范围内,则不会。(function () { ... })()用Javascript { flag = false; ... }或类似C的语言
编写

+1为异常逻辑,根据情况,这很可能是最合适的解决方案。
史蒂文·杰里斯

4
+1这个共同的“不!” 很有趣。我认为在某些情况下,状态变量和提前归还都是合理的。在更复杂的例程中,我会选择状态变量,因为它实际上使逻辑明确,而不是增加复杂性,而不是增加复杂性。没有错。
grossvogel

1
这是我们工作的首选格式。2个主要可用选项似乎是“多重收益”和“标志变量”。平均而言,两者似乎都没有任何真正的优势,但两者在某些情况下都比其他情况更适合。必须配合您的典型案例。只是另一场“ Emacs”对“ Vi”宗教战争。:-)
Brian Knoblauch

11

这是更高的抽象级别:

if (WeCanDoSomething(file))
{
   DoSomething(contents);
}
else
{
   DefaultAction();
} 

并填写详细信息。

boolean WeCanDoSomething(file)
{
    if FileExists(file)
    {
        contents = OpenFile(file);
        return (SomeTest(contents));
    }
    else
    {
        return FALSE;
    }
}

11

函数应该做一件事。他们应该做好。他们只能这样做。
—《干净守则》中的罗伯特·马丁

有人发现这种方法有些极端,但也很干净。请允许我用Python进行说明:

def processFile(self):
    if self.fileMeetsTest():
        self.doSomething()
    else:
        self.defaultAction()

def fileMeetsTest(self):
    return os.path.exists(self.path) and self.contentsTest()

def contentsTest(self):
    with open(self.path) as file:
        line = file.readline()
        return self.firstLineTest(line)

当他说职能应该做一件事时,他意味着件事。 processFile()根据测试结果选择一个动作,这就是全部。 fileMeetsTest()结合了测试的所有条件,仅此而已。 contentsTest()将第一行转移到firstLineTest(),仅此而已。

似乎有很多功能,但实际上读起来像是纯英语:

要处理文件,请检查文件是否符合测试要求。如果是这样,请执行某些操作。否则,请采取默认操作。该文件是否符合测试(如果存在)并通过内容测试。要测试内容,请打开文件并测试第一行。第一行的测试...

当然,这有点罗word,但是请注意,如果维护人员不关心细节,他只需在中的4行代码之后就可以停止阅读processFile(),并且他仍将对该函数的功能有很高的了解。


5
+1这是一个很好的建议,但是什么构成“一件事”取决于当前的抽象层。processFile()是“一件事”,但有两件事:fileMeetsTest()和doSomething()或defaultAction()。我担心“一件事”方面可能会使不具备先验知识的初学者感到困惑。
Caleb

1
这是一个很好的目标……这就是我要说的... ;-)
Brian Knoblauch

1
我不喜欢像这样隐式地将参数作为实例变量传递。您将充满“无用的”实例变量,并且有许多方法可以破坏状态并破坏不变式。
hugomg

@ Caleb,ProcessFile()确实在做一件事。就像卡尔在他的帖子中所说的那样,它正在使用一种测试来决定采取哪种行动,并将行动可能性的实际实现推迟到其他方法上。如果要添加更多替代操作,只要在立即方法中不发生逻辑嵌套,该方法的单一目的条件仍将得到满足。
罗宾斯2011年

6

关于这种情况,随着代码的增长可以处理更多需求,它可以轻松地发展为箭头反模式(如https://softwareengineering.stackexchange.com/a/122625/33922上提供的答案所示),并且然后陷入具有嵌套的条件语句(类似于箭头)的大量代码段的陷阱。

请参阅链接,例如;

http://codinghorror.com/blog/2006/01/flattening-arrow-code.html

http://lostechies.com/chrismissal/2009/05/27/anti-patterns-and-worst-practices-the-arrowhead-anti-pattern/

在Google上可以找到关于此模式和其他反模式的更多信息。

杰夫(Jeff)在他的博客上提供的一些很棒的技巧是:

1)用保护条款代替条件。

2)将条件块分解为单独的函数。

3)将消极支票转化为肯定支票

4)总是有机会从功能中尽快返回。

参见Jeff博客上有关Steve McConnells关于早期回报的建议的一些评论;

“在增强可读性时使用返回值:在某些例程中,一旦知道答案,就想立即将其返回给调用例程。如果该例程以这样的方式定义,则一旦它不再需要任何进一步的清理,检测到错误,不立即返回就意味着您必须编写更多代码。”

...

“最大程度地减少每个例程的返回次数:当在底部阅读该例程时,如果您不知道例程可能会返回某个位置,则很难理解该例程。因此,请谨慎地使用返回-仅当它们有所改进时才使用可读性。”

由于15年前的学习经历,我一直赞成每个函数1次进入/退出。我觉得这使代码更易于阅读,并且正如您提到的更易于维护


6

在我看来,这符合DRY,no-goto和no-multiple-returns规则,具有可扩展性和可读性:

success = FileExists(file);
if (success)
{
    contents = OpenFile(file);
    success = SomeTest(contents);
}
if (success)
{
    DoSomething(contents);
}
else
{
    DefaultAction();
}

1
符合标准并不一定等于好的代码。我目前还不确定这个代码片段。
Brian Knoblauch

这只是替换2 defaultAction(); 如果条件相同,则使用2个相同的条件,并添加一个标志变量,imo会更糟。
Ryathal

3
使用这样的构造的好处是,随着测试数量的增加,代码不会开始if在其他ifs中嵌套更多s。此外,用于处理不成功case(DefaultAction())的代码仅在一个地方,并且出于调试目的,该代码不会在辅助函数中跳转,并且在success更改变量的行中添加断点可以快速显示通过了哪些测试(在触发之后断点)以及尚未测试的功能(如下)。
Frozenkoi 2011年

1
是的,我有点喜欢,但是我想我改名successok_so_far:)
Benjol 2011年

这与我在以下情况下的工作非常相似:(1)一切正常时该过程非常线性,(2)否则将使用箭头反模式。但是,我尽量避免添加额外的变量,如果您根据下一步的先决条件进行思考(这与询问较早的步骤是否失败有所不同),通常这很容易。如果文件存在,请打开文件。如果文件已打开,请阅读内容。如果我有内容,请对其进行处理,否则请执行默认操作。
Adrian McCarthy

3

我将其提取到一个单独的方法中,然后:

if(!FileExists(file))
{
    DefaultAction();
    return;
}

contents = OpenFile(file);
if(!SomeTest(contents))
{
    DefaultAction();
    return;
}

DoSomething(contents);

这也允许

if(!FileExists(file))
{
    DefaultAction();
    return Result.FileNotFound;
}

contents = OpenFile(file);
if(!SomeTest(contents))
{
    DefaultAction();
    return Result.TestFailed;
}

DoSomething(contents);
return Result.Success;            

那么您可能会删除这些DefaultAction调用并保留DefaultAction为调用者执行:

Result OurMethod(file)
{
    if(!FileExists(file))
    {
        return Result.FileNotFound;
    }

    contents = OpenFile(file);
    if(!SomeTest(contents))
    {
        return Result.TestFailed;
    }

    DoSomething(contents);
    return Result.Success;            
}

void Caller()
{
    // something, something...

    var result = OurMethod(file);
    // if (result == Result.FileNotFound || result == Result.TestFailed), or just
    if (result != Result.Success)        
    {
        DefaultAction();
    }
}

我也喜欢Jeanne Pindar的方法


3

对于这种特殊情况,答案很容易...

FileExists和之间存在竞争条件OpenFile:如果删除文件会怎样?

处理这种特殊情况的唯一明智的方法是跳过FileExists

contents = OpenFile(file);
if (!contents) // open failed
    DefaultAction();
else (SomeTest(contents))
    DoSomething(contents);

巧妙地解决了这个问题并使代码更简洁。

一般而言: 尝试重新考虑问题并设计出另一种解决方案,以完全避免出现此问题。


2

如果您不希望看到太多其他的可能性,那就是完全放弃使用else,并添加额外的return语句。 否则,除非您需要更复杂的逻辑来确定是否存在两个以上的动作可能性,否则其他动作都是多余的。

因此,您的示例可能变为:

void DoABunchOfStuff()
{
    if(FileExists(file))
    {
        DoSomethingWithFileContent(file);
        return;
    }

    DefaultAction();
}

void DoSomethingWithFileContent(file)
{        
    var contents = GetFileContents(file)

    if(SomeTest(contents))
    {
        DoSomething(contents);
        return;
    }

    DefaultAction();
}

AReturnType GetFileContents(file)
{
    return OpenFile(file);
}

我个人不介意使用else子句,因为它明确声明了逻辑应该如何工作,从而提高了代码的可读性。但是,某些代码美化工具倾向于简化为单个if语句,以阻止嵌套逻辑。


2

示例代码中显示的情况通常可以简化为单个if语句。在许多系统上,如果文件不存在,则文件打开功能将返回无效值。有时,这是默认行为。其他时候,必须通过参数指定。这意味着FileExists可以删除测试,这也可以帮助解决由于存在测试和打开文件之间的文件而导致的竞争条件。

file = OpenFile(path);
if(isValidFileHandle(file) && SomeTest(file)) {
    DoSomething(file);
} else {
    DefaultAction();
}

尽管完全消除了多个不可链接的测试的问题,但这并不能直接解决抽象级别混合的问题,尽管消除文件存在性测试并不与分离抽象级别不兼容。假定无效的文件句柄等效于“ false”,并且文件句柄超出范围时将关闭:

OpenFileIfSomething(path:String) : FileHandle {
    file = OpenFile(path);
    if (file && SomeTest(file)) {
        return file;
    }
    return null;
}

...

if ((file = OpenFileIfSomething(path))) {
    DoSomething(file);
} else {
    DefaultAction();
}

2

我同意Frozenkoi的观点,但是无论如何对于C#,我认为遵循TryParse方法的语法将有所帮助。

if(FileExists(file) && TryOpenFile(file, out contents))
    DoSomething(contents);
else
    DefaultAction();
bool TryOpenFile(object file, out object contents)
{
    try{
        contents = OpenFile(file);
    }
    catch{
        //something bad happened, computer probably exploded
        return false;
    }
    return true;
}

1

您的代码很丑陋,因为您在单个函数中做了太多事情。您要处理文件或采取默认操作,因此首先要说:

if (!ProcessFile(file)) { 
  DefaultAction(); 
}

Perl和Ruby程序员编写 processFile(file) || defaultAction()

现在去写ProcessFile:

if (FileExists(file)) { 
  contents = OpenFile(file);
  if (SomeTest(contents)) {
    processContents(contents);
    return true;
  }
}
return false;

1

当然,在这种情况下,您只能走得那么远,但这是一种方法:

interface File<T> {
    function isOK():Bool;
    function getData():T;
}

var appleFile:File<Apple> = appleStorage.get(fileURI);
if (appleFile.isOK())
    eat(file.getData());
else
    cry();

您可能需要其他过滤器。然后执行以下操作:

var appleFile = appleStorage.get(fileURI, isEdible);
//isEdible is of type Apple->Bool and will be used internally to answer to the isOK call
if (appleFile.isOK())
    eat(file.getData());
else
    cry();

尽管这可能同样有意义:

function eat(apple:Apple) {
     if (isEdible(apple)) 
         digest(apple);
     else
         die();
}
var appleFile = appleStorage.get(fileURI);
if (appleFile.isOK())
    eat(appleFile.getData());
else
    cry();

哪个最好?这取决于您面临的现实问题
但是要带走的是:您可以对组成和多态性做很多事情。


1

明显的地方有什么问题

if(!FileExists(file)) {
    DefaultAction();
    return;
}
contents = OpenFile(file);
if(!SomeTest(contents))
{
    DefaultAction();
    return;
}        
DoSomething(contents);

对我来说似乎很标准?对于那种必须执行许多小事情的大型程序,任何一种故障都会阻止后者。如果可以的话,例外会使它更干净。


0

我意识到这是一个古老的问题,但是我注意到一个未被提及的模式。主要是,设置变量以稍后确定要调用的方法(在if ... else ...之外)。

这是使代码更易于使用的另一个角度。它还允许您在某些情况下何时要添加另一个要调用的方法或更改需要调用的适当方法。

不必替换所有提及的方法(并且可能会遗漏某些场景),它们都在if ... else ...块的末尾列出,并且更易于阅读和更改。我倾向于在例如调用多个方法的情况下使用此方法,但是在嵌套的if ... else ...内可能在多个匹配项中调用一个方法。

如果设置一个定义状态的变量,则可能会有许多深层嵌套的选项,并在要执行(或不执行)某些操作时更新状态。

这可以用在问题中的示例中,在该示例中,我们正在检查是否已发生“ DoSomething”,如果没有发生,请执行默认操作。或者您可以为每个要调用的方法设置状态,在适用时进行设置,然后在if ... else之外调用适用的方法。

在嵌套的if ... else ...语句的末尾,检查状态并采取相应的措施。这意味着您只需要提及一种方法,而不是应该使用的所有位置。

bool ActionDone = false;

if (Method_1(object_A)) // Test 1
{
    result_A = Method_2(object_A); // Result 1

    if (Method_3(result_A)) // Test 2
    {
        Method_4(result_A); // Action 1
        ActionDone = true;
    }
}

if (!ActionDone)
{
    Method_5(); // Default Action
}

0

要减少嵌套的IF:

1 /提前归还;

2 /复合表达式(可识别短路)

因此,您的示例可以这样重构:

if( FileExists(file) && SomeTest(contents = OpenFile(file)) )
{
    DoSomething(contents);
    return;
}
DefaultAction();

0

我看到了很多使用“返回”的示例,但我有时还是想避免创建新函数并使用循环:

while (1) {
    if (FileExists(file)) {
        contents = OpenFile(file);
        if (SomeTest(contents)) {
           DoSomething(contents);
           break;
        } 
    }
    DefaultAction();
    break;
}

如果您想写更少的行,或者像我一样讨厌无限循环,则可以将循环类型更改为“ do ... while(0)”,并避免最后一个“ break”。


0

该解决方案如何:

content = NULL; //I presume OpenFile returns a pointer 
if(FileExists(file))
    contents = OpenFile(file);
if(content != NULL && SomeTest(contents))
    DoSomething(contents);
else
    DefaultAction();

我假设OpenFile返回一个指针,但是通过指定一些不可返回的默认值(错误代码或类似的东西),这也可以用于返回值类型。

当然,我不希望通过NULL指针上的SomeTest方法执行任何可能的操作(但您永远不会知道),因此这也可以看作是对SomeTest(contents)调用的NULL指针的额外检查。


0

显然,最简洁的解决方案是使用预处理器宏。

#define DOUBLE_ELSE(CODE) else { CODE } } else { CODE }

这样您就可以编写漂亮的代码,如下所示:

if(FileExists(file))
{
    contents = OpenFile(file);
    if(SomeTest(contents))
    {
        DoSomething(contents);
    }
    DOUBLE_ELSE(DefaultAction();)

如果您经常使用此技术,可能很难依靠自动格式设置,并且某些IDE可能会对您错误地认为格式错误的内容大喊大叫。俗话说,一切都是折衷方案,但我认为为避免重复代码的弊端而付出的代价不是一个不小的代价。


对于某些人和某些语言来说,预处理器宏邪恶的代码:)
Benjol 2011年

@Benjol您说您愿意接受邪恶的建议,不是吗?;)
彼得·奥尔森

是的,绝对是,这只是您的“避免罪恶” :)
Benjol 2011年

4
这太可怕了,我只好投票了:D
back2dos

雪莉,你不认真!!!!
Jim in Texas

-1

由于您出于好奇而提出问题,并且您的问题没有使用特定的语言标记(即使很明显您已经想到了命令式语言),因此值得一提的是,支持惰性评估的语言允许使用完全不同的方法。在这些语言中,仅在需要时才对表达式求值,因此您可以定义“变量”,并仅在有意义时使用它们。例如,用一种具有惰性let/ in结构的虚构语言,您会忘记流控制并编写:

let
  contents = ReadFile(file)
in
  if FileExists(file) && SomeTest(contents) 
    DoSomething(contents)
  else 
    DefaultAction()
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.