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

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



这种逻辑是否有名称?
我也是OCD吗?

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

评论

@Emmad Kareem:两个DefaultAction调用违反了DRY原理

感谢您的答复,但我认为这是可以的,除了不使用try / catch之外,因为可能存在一些错误,这些错误不会返回结果并会导致异常终止(取决于您的编程语言)。
我认为这里的主要问题是您在不一致的抽象级别上工作。更高的抽象级别是:确保我具有适用于DoSomething()的有效数据,然后具有DoSomething()。否则,请使用DefaultAction()。确保您具有DoSomething()的数据的实质内容是较低的抽象级别,因此应使用其他函数。此函数将在较高的抽象级别中具有名称,而其实现将是低级别的。下面的好答案解决了这个问题。

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

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

#1 楼

将其提取为单独的函数(方法)并使用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类型)。无论如何,这都不是这个问题。

评论


+1以获取多个回报-当方法变得足够小时,这种方法对我来说效果最好

– gna
2011年11月30日在11:31

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

–布赖恩·诺伯劳赫(Brian Knoblauch)
2011-11-30 19:37

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

–太棒了
2011年12月1日下午5:37

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

–楔
2011年12月1日7:09

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

–user8
2011年12月1日19:37

#2 楼

如果您使用的编程语言(0)短路二进制比较(即,如果SomeTest返回false,则不调用FileExists),并且(1)赋值返回一个值(将OpenFile的结果分配给contents,然后该值是作为参数传递给SomeTest),您可以使用类似以下内容的方法,但仍建议您注释该代码,指出单个=是有意的。 -cs prettyprint-override“> if( FileExists(file) && SomeTest(contents = OpenFile(file)) ) { DoSomething(contents); } else { DefaultAction(); }

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

评论


这就是我要做的。

–安东尼
2011年12月1日7:08

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

– moteutsch
2011年12月1日在22:07

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

– Gorpik
2011年12月2日,12:40

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

– klaar
15年10月28日在7:43

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

– Piskvor离开了建筑物
17-10-17在13:24



#3 楼

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

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



 

但是接下来又有一个要求,就是我们也不能打开超过2Gb的大文件。
好吧,我们只是再次更新:

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


应该很清楚,这样的代码风格将是一个巨大的维护难题。

这里有很多答案正确地正交编写的是Abyx的第二个示例和Jan Hudec的答案,所以我不再重复,只是指出在这两个答案中添加两个要求就是

 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();
}
 


(或if(! LessThan2Gb(file)) return null; if(OnNetworkDisk(file)) return null; 而不是goto notexists;),除了添加的这些行以外,不影响任何其他代码。例如。

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

评论


为我+1。提早退货有助于避免出现反箭头形态。请参阅codinghorror.com/blog/2006/01/flattening-arrow-code.html和lostechies.com/chrismissal/2009/05/27/…在阅读此模式之前,我始终订阅每个功能的1个入口/出口。理论是由于15年前左右我所受的教育。我认为这使代码更易于阅读,并且您提到了更易于维护。

–穆斯先生
2011年12月1日,2:10

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

– outis
2011年12月1日5:59



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

– Benjol
2011年12月1日6:03



@outis。谢谢。我已经添加了答案。在Hlovdal的职位上,箭头反模式当然是相关的,他的后卫条款很好地绕过了他们。我不知道您如何回答第二点。我没有资格诊断:)

–穆斯先生
2011年12月1日7:53

+1表示“测试例外,不是正常情况”。

–罗伊·廷克(Roy Tinker)
2011年12月1日下午17:54

#4 楼

显然:

 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还是可取的。将“邪恶”定义为:



这意味着您通常应该避免这样的事情,但并非总是应该避免的事情。例如,每当它们是“邪恶替代方案中的最小邪恶”时,您最终将使用这些“邪恶”事物。


这在这种情况下适用于goto。结构化流控制结构通常在大多数情况下会更好,但是当您遇到太多自身弊端的情况时,例如在条件分配中,嵌套超过3个层次的深度,重复代码或长时间条件,goto可能只是最终变得更少邪恶。

评论


我的光标悬停在接受按钮上……只是为了just弃所有纯粹主义者。哦,诱惑:D

– Benjol
2011年11月30日12:33

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

–喜剧
2011-11-30 19:25

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

–喜剧
2011年11月30日19:48

说到邪恶的解决方案:我可以不用goto就可以得到您的解决方案:for(;;){if(!FileExists(file))break;内容= OpenFile(文件); if(!SomeTest(contents))中断; DoSomething(内容);返回; } / *细分* / DefaultAction();

–老公
2011年11月30日在21:29



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

– Jan Hudec
2011年12月2日于7:11

#5 楼

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


...

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


评论


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

– UncleZeiv
2011年11月30日12:08

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

– Abyx
2011年11月30日13:11

是。依靠。 @Benjol更了解。

–老公
2011年11月30日13:18

...当然,我的意思是“多奔波” ... :)

– UncleZeiv
2011-11-30 15:36

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

–老公
2011年11月30日15:42



#6 楼

一种可能:



 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()显然是错误的情况,因此适合在其上引发异常。


评论


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

– Abyx
2011年11月30日10:36

如果您将其限制在尽可能局部的范围内,则不会。 (JavaScript中的(function(){...})(),{flag = false; ...}类似C等等

–老公
2011年11月30日10:43



+1为异常逻辑,根据情况,这很可能是最合适的解决方案。

–史蒂文·杰里斯(Steven Jeuris)
2011-11-30 13:48

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

–grossvogel
2011年11月30日18:58



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

–布赖恩·诺伯劳赫(Brian Knoblauch)
2011年11月30日19:34



#7 楼

这是一个较高的抽象级别:



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


这将填充详细信息。

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


#8 楼


函数应该做一件事。他们应该做好。他们只应该这样做。
— Robert Martin在Clean Code中


有些人觉得这种方法有点极端,但它也很干净。请允许我用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(),仅此而已。

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


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


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

评论


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

–卡莱布
2011-11-30 19:25

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

–布赖恩·诺伯劳赫(Brian Knoblauch)
2011年11月30日19:40

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

–hugomg
2011年12月1日下午16:40

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

–S.Robins
2011年12月2日,11:34

#9 楼

关于这种情况,随着代码的增长可以满足更多要求,它可以轻松地发展为箭头反模式(如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-thearrowhead-anti-pattern /

在Google上可以找到更多关于此和其他反模式的信息。 />杰夫(Jeff)在他的博客上提供的一些很棒的提示是:


1)用保护子句替换条件。

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

3)将消极的支票转换为肯定的支票

4)总是有机会从函数中尽快返回。杰夫博客上有关史蒂夫·麦康奈尔(Steve McConnells)早期回报建议的一些评论;


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

...

“最小化每个例程的返回数量:很难理解一个例程
,当您在底部阅读该例程时,完全没有意识到
返回的可能性。因此,请明智地使用return
-仅在它们提高可读性时使用。“由于15年前的学习经历,我一直赞成每个函数1次进入/退出。我觉得这使代码更易于阅读,正如您提到的更易于维护

#10 楼

我认为这符合DRY,no-goto和no-multiple-returns规则,具有可伸缩性和可读性:

评论


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

–布赖恩·诺伯劳赫(Brian Knoblauch)
2011-11-30 19:41

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

–Ryathal
2011-11-30 19:57

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

– frozenkoi
2011年12月1日在1:17



是的,我有点喜欢,但是我想我将成功重命名为ok_so_far :)

– Benjol
2011年12月1日下午6:05

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

–阿德里安·麦卡锡(Adrian McCarthy)
18年1月12日在19:14

#11 楼

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



 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的方法:好吧。

#12 楼

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

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

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

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


这可以很好地解决此问题并使代码更简洁。

通常:
请尝试重新思考问题并设计出另一种解决方案,以完全避免该问题。 />

#13 楼

如果您不希望看到过多的else,则另一种可能性是完全放弃使用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语句,以阻止嵌套逻辑。

#14 楼

示例代码中显示的情况通常可以简化为单个if语句。在许多系统上,如果文件不存在,则文件打开功能将返回无效值。有时,这是默认行为。其他时候,必须通过参数指定。这意味着可以删除FileExists测试,这也可以帮助解决由于存在测试和打开文件之间的文件删除而导致的争用情况。 -cs prettyprint-override“> file = OpenFile(path); if(isValidFileHandle(file) && SomeTest(file)) { DoSomething(file); } else { DefaultAction(); }

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

...

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


#15 楼

我同意Frozenkoi的观点,但是无论如何对于C#,我认为遵循TryParse方法的语法将有所帮助。 -cs prettyprint-override“> 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;
}
 


#16 楼

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



/ pre>

Perl和Ruby程序员编写if (!ProcessFile(file)) { DefaultAction(); }

现在开始编写ProcessFile:

 processFile(file) || defaultAction() 


#17 楼

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





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

 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();
 


哪个最好?这取决于您所面临的现实世界问题。

#18 楼

显而易见的

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


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

#19 楼

我意识到这是一个古老的问题,但是我注意到一个未被提及的模式。主要是,设置变量以稍后确定您要调用的方法(在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
}
 


#20 楼

要减少嵌套的IF:

1 /提前返回;

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

因此,您的示例可能是像这样重构:

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


#21 楼

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

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


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

#22 楼

这个解决方案怎么样:

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


我假设OpenFile返回了一个指针,但是通过指定一些不可返回的默认值(错误代码或类似的东西),这也可以与值类型返回一起使用。但您永远不知道),因此这也可以看作对SomeTest(contents)调用的NULL指针的额外检查。

#23 楼

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



 #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年12月1日下午6:06

@Benjol您说您愿意接受邪恶的建议,不是吗? ;)

– Peter Olson
2011年12月1日下午6:11



是的,绝对是,这只是您的“避免邪恶” :)

– Benjol
2011年12月1日下午6:23

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

–back2dos
2011年12月1日在18:20

雪莉,你不认真!!!!

–吉姆·德州(Jim In Texas)
2011年12月1日18:40

#24 楼

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

 let
  contents = ReadFile(file)
in
  if FileExists(file) && SomeTest(contents) 
    DoSomething(contents)
  else 
    DefaultAction()