我们如何处理巨大的if条件?
这是在我使用过的每种语言中困扰我的东西,我有一个if语句,但是条件部分有很多检查,我不得不将其拆分成多行,使用嵌套的if语句,或者只是接受它的丑陋并继续前进与我的生活。
我们是否发现其他方法可能对我以及遇到相同问题的其他人有用?
示例,全部一行:
if (var1 = true && var2 = true && var2 = true && var3 = true && var4 = true && var5 = true && var6 = true) {
示例,多行:
if (var1 = true && var2 = true && var2 = true && var3 = true && var4 = true && var5 = true && var6 = true) {
嵌套示例:
if (var1 = true && var2 = true && var2 = true && var3 = true) { if (var4 = true && var5 = true && var6 = true) {
解决方案
回答
首先,我将删除所有的" == true"部分,这会使它缩短50%;)
当我有大病时,我会寻找原因。有时我看到我应该使用多态性,有时我需要添加一些状态对象。基本上,这意味着需要重构(代码气味)。
有时我会使用De-Morgan的定律来简化布尔表达式。
回答
我求助于单独的布尔值:
Bool cond1 == (var1 && var2); Bool cond2 == (var3 && var4); if ( cond1 && cond2 ) {}
回答
我见过很多人和编辑者或者用一个选项卡在if语句中缩进每个条件,或者将其与打开的括号匹配:
if (var1 == true && var2 == true && var3 == true ) { /* do something.. */ }
我通常将封闭括号与最后一个条件放在同一行:
if (var1 == true && var2 == true && var3 == true) { /* do something.. */ }
但是我认为这还不算干净。
回答
我经常将它们分成组件布尔变量:
bool orderValid = orderDate < DateTime.Now && orderStatus != Status.Canceled; bool custValid = customerBalance == 0 && customerName != "Mike"; if (orderValid && custValid) { ...
回答
将条件分成几个布尔值,然后使用主布尔值作为条件。
bool isOpaque = object.Alpha == 1.0f; bool isDrawable = object.CanDraw && object.Layer == currentLayer; bool isHidden = hideList.Find(object); bool isVisible = isOpaque && isDrawable && ! isHidden; if(isVisible) { // ... }
更好的是:
public bool IsVisible { get { bool isOpaque = object.Alpha == 1.0f; bool isDrawable = object.CanDraw && object.Layer == currentLayer; bool isHidden = hideList.Find(object); return isOpaque && isDrawable && ! isHidden; } } void Draw() { if(IsVisible) { // ... } }
确保给变量名称实际表示意图而不是功能。这将极大地帮助开发人员维护代码...可能是我们!
回答
好吧,首先,为什么不呢?
if (var1 && var2 && var2 && var3 && var4 && var5 && var6) { ...
而且,重构抽象代码示例非常困难。如果我们显示了一个特定的示例,则可以更容易地找到一个更好的模式来解决该问题。
并没有更好,但是我过去所做的事情:
(以下方法可防止布尔布尔测试短路,即使第一个测试为假,也将运行所有测试。不建议使用这种模式,除非我们知道在返回-Thantom to ptomato之前必须始终执行所有代码!)
boolean ok = cond1; ok &= cond2; ok &= cond3; ok &= cond4; ok &= cond5; ok &= cond6;
与以下内容相同:(不相同,请参见上面的注释!)
ok = (cond1 && cond2 && cond3 && cond4 && cond5 && cond6);
回答
我喜欢按级别细分它们,因此我将格式化示例,如下所示:
if (var1 = true && var2 = true && var2 = true && var3 = true && var4 = true && var5 = true && var6 = true){
当我们有更多嵌套时,这样就很方便了(显然,对于所有情况,实际条件都比" = true"更有趣):
if ((var1 = true && var2 = true) && ((var2 = true && var3 = true) && (var4 = true && var5 = true)) && (var6 = true)){
回答
查看Kent Beck的实现模式。我正在考虑一种特殊的模式,在这种情况下可能会有所帮助……它被称为"警卫队"。我们可以将它们分解成一个警卫器,而不必承担很多条件,这可以使我们清楚哪些是方法中的不利条件。
因此,例如,如果我们有一个方法可以执行某些操作,但是在某些情况下该方法不应执行某些操作,而不是:
public void doSomething() { if (condition1 && condition2 && condition3 && condition4) { // do something } }
我们可以将其更改为:
public void doSomething() { if (!condition1) { return; } if (!condition2) { return; } if (!condition3) { return; } if (!condition4) { return; } // do something }
它有点冗长,但可读性更高,尤其是当我们开始使用怪异的嵌套时,后卫可以提供帮助(结合提取方法)。
我强烈推荐这本书。
回答
我很惊讶没有人得到这个。有专门针对此类问题的重构:
http://www.refactoring.com/catalog/decomposeConditional.html
回答
这里有两个问题要解决:可读性和可理解性
"可读性"解决方案是样式问题,因此可以解释。我的偏好是这样的:
if (var1 == true && // Explanation of the check var2 == true && // Explanation of the check var3 == true && // Explanation of the check var4 == true && // Explanation of the check var5 == true && // Explanation of the check var6 == true) // Explanation of the check { }
或者这个:
if (var1 && // Explanation of the check var2 && // Explanation of the check var3 && // Explanation of the check var4 && // Explanation of the check var5 && // Explanation of the check var6) // Explanation of the check { }
就是说,在扫描代码时(尤其是如果我们不是原始作者),这种复杂的检查可能很难从心理上解析。考虑创建一个辅助方法来抽象一些复杂性:
/// <Summary> /// Tests whether all the conditions are appropriately met /// </Summary> private bool AreAllConditionsMet ( bool var1, bool var2, bool var3, bool var4, bool var5, bool var6) { return ( var1 && // Explanation of the check var2 && // Explanation of the check var3 && // Explanation of the check var4 && // Explanation of the check var5 && // Explanation of the check var6); // Explanation of the check } private void SomeMethod() { // Do some stuff (including declare the required variables) if (AreAllConditionsMet (var1, var2, var3, var4, var5, var6)) { // Do something } }
现在,当以视觉方式扫描" SomeMethod"方法时,测试逻辑的实际复杂性被隐藏了,但语义含义被保留,供人类从高层次上理解。如果开发人员确实需要了解详细信息,则可以检查AreAllConditionsMet方法。
我认为这是正式的"分解条件"重构模式。诸如Resharper或者Refactor Pro之类的工具!可以使这种重构变得容易!
在所有情况下,拥有易读易懂的代码的关键是使用逼真的变量名。虽然我知道这是一个人为的示例,但" var1"," var2"等不是可接受的变量名称。它们的名称应能反映所代表数据的基本性质。
回答
如果我们碰巧是用Python进行编程,那可真是将内置的all()
函数应用到变量列表上了(我在这里只使用布尔文字):
>>> L = [True, True, True, False, True] >>> all(L) # True, only if all elements of L are True. False >>> any(L) # True, if any elements of L are True. True
语言是否有相应的功能(C#?Java?)。如果是这样,那可能是最干净的方法。
回答
如果我们这样做:
if (var1 == true) { if (var2 == true) { if (var3 == true) { ... } } }
然后,我们还可以应对某些不正确的情况。例如,如果我们要验证输入,则可以向用户提供有关如何正确设置其格式的提示,等等。
回答
麦克道尔,
我们是正确的,当使用表达式的两边都求值的单个"&"运算符时。但是,当使用'&&'运算符(至少在C#中)时,返回false的第一个表达式是最后计算的表达式。这使得将评估放在FOR语句之前,就像执行其他任何方式一样。
回答
正如其他人提到的那样,我将分析条件,以查看是否存在将其外包给其他方法以提高可读性的方法。
回答
@tweakt
It's no better, but what I've done in the past: boolean ok = cond1; ok &= cond2; ok &= cond3; ok &= cond4; ok &= cond5; ok &= cond6; Which is the same as: ok = (cond1 && cond2 && cond3 && cond4 && cond5 && cond6);
实际上,在大多数语言中,这两件事并不相同。条件之一为假时,第二个表达式通常将停止评估,如果评估条件很昂贵,这可能会大大提高性能。
为了提高可读性,我个人更喜欢上面的Mike Stone的建议。详细注释很容易,并且保留了能够提早发布的所有计算优势。如果会使代码的组织使条件评估远离其他函数,我们还可以在函数中内联执行相同的技术。有点俗气,但是我们始终可以执行以下操作:
do { if (!cond1) break; if (!cond2) break; if (!cond3) break; ... DoSomething(); } while (false);
while(false)有点俗气。我希望语言有一个作用域范围运算符" once",或者我们可以轻松理解的东西。
回答
尝试查看函子和谓词。 Apache Commons项目具有大量对象,可让我们将条件逻辑封装到对象中。在O'reilly上可以找到它们的使用示例。代码示例摘录:
import org.apache.commons.collections.ClosureUtils; import org.apache.commons.collections.CollectionUtils; import org.apache.commons.collections.functors.NOPClosure; Map predicateMap = new HashMap(); predicateMap.put( isHonorRoll, addToHonorRoll ); predicateMap.put( isProblem, flagForAttention ); predicateMap.put( null, ClosureUtils.nopClosure() ); Closure processStudents = ClosureUtils.switchClosure( predicateMap ); CollectionUtils.forAllDo( allStudents, processStudents );
现在所有这些isHonorRoll谓词的详细信息以及用于评估它们的闭包:
import org.apache.commons.collections.Closure; import org.apache.commons.collections.Predicate; // Anonymous Predicate that decides if a student // has made the honor roll. Predicate isHonorRoll = new Predicate() { public boolean evaluate(Object object) { Student s = (Student) object; return( ( s.getGrade().equals( "A" ) ) || ( s.getGrade().equals( "B" ) && s.getAttendance() == PERFECT ) ); } }; // Anonymous Predicate that decides if a student // has a problem. Predicate isProblem = new Predicate() { public boolean evaluate(Object object) { Student s = (Student) object; return ( ( s.getGrade().equals( "D" ) || s.getGrade().equals( "F" ) ) || s.getStatus() == SUSPENDED ); } }; // Anonymous Closure that adds a student to the // honor roll Closure addToHonorRoll = new Closure() { public void execute(Object object) { Student s = (Student) object; // Add an award to student record s.addAward( "honor roll", 2005 ); Database.saveStudent( s ); } }; // Anonymous Closure flags a student for attention Closure flagForAttention = new Closure() { public void execute(Object object) { Student s = (Student) object; // Flag student for special attention s.addNote( "talk to student", 2005 ); s.addNote( "meeting with parents", 2005 ); Database.saveStudent( s ); } };
回答
我喜欢将每个条件分解为描述性变量。
bool isVar1Valid, isVar2Valid, isVar3Valid, isVar4Valid; isVar1Valid = ( var1 == 1 ) isVar2Valid = ( var2.Count >= 2 ) isVar3Valid = ( var3 != null ) isVar4Valid = ( var4 != null && var4.IsEmpty() == false ) if ( isVar1Valid && isVar2Valid && isVar3Valid && isVar4Valid ) { //do code }
回答
史蒂夫·麦康奈尔(Steve Mcconell)的建议,来自Code Complete:
使用多维表。每个变量都用作表的索引,
if语句变成一个表查找。例如,如果(尺寸== 3 &&重量> 70)
转换为表格入口的决定[大小] [权重组]
回答
如果我在Perl中进行操作,这就是我运行检查的方式。
{ last unless $var1; last unless $var2; last unless $var3; last unless $var4; last unless $var5; last unless $var6; ... # Place Code Here }
如果我们打算在子例程中使用它,则将last
的每个实例替换为return
;
回答
在PHP之类的反射语言中,可以使用变量-变量:
$vars = array('var1', 'var2', ... etc.); foreach ($vars as $v) if ($$v == true) { // do something break; }