题
这是我使用过的每种语言中都困扰我的事情,我有一个 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)
{
解决方案
将条件分成几个布尔值,然后使用主布尔值作为条件。
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)
{
// ...
}
}
确保你给出的变量名称实际上表明了意图而不是功能。这将极大地帮助开发人员维护您的代码......可能是你!
其他提示
我很惊讶还没有人拿到这个。有一个专门针对此类问题的重构:
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”等是 不是 可接受的变量名称。它们的名称应该反映它们所代表的数据的基本性质。
我经常将它们分成组件布尔变量:
bool orderValid = orderDate < DateTime.Now && orderStatus != Status.Canceled;
bool custValid = customerBalance == 0 && customerName != "Mike";
if (orderValid && custValid)
{
...
首先,我会删除所有 == true
零件,这将使其缩短 50%;)
当我有大的情况时,我会寻找原因。有时我认为我应该使用多态性,有时我需要添加一些状态对象。基本上,它意味着需要重构(代码味道)。
有时我用 德摩根定律 稍微简化布尔表达式。
查看 实施模式 作者:肯特·贝克。我正在考虑一种特殊的模式,在这种情况下可能会有所帮助......它被称为“卫兵”。您可以将它们分解为一个防护,而不是拥有大量条件,这可以清楚地表明方法中哪些是不利条件。
例如,如果您有一个方法可以执行某些操作,但在某些条件下它不应该执行某些操作,而不是:
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
}
它有点冗长,但更具可读性,特别是当您开始进行奇怪的嵌套时,守卫可以提供帮助(与提取方法相结合)。
顺便说一下,我强烈推荐这本书。
我见过很多人和编辑要么用一个制表符缩进 if 语句中的每个条件,要么将其与开放括号匹配:
if (var1 == true
&& var2 == true
&& var3 == true
) {
/* do something.. */
}
我通常将右括号与最后一个条件放在同一行:
if (var1 == true
&& var2 == true
&& var3 == true) {
/* do something.. */
}
但我认为这并不那么干净。
史蒂夫·麦康奈尔的建议,来自 代码完成:使用多维表。每个变量用作表的索引,if语句变成表查找。例如,如果(size == 3 && weight> 70)转化为表条目决策[size] [stoge_group
尝试查看函子和谓词。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 );
}
};
好吧,首先,为什么不呢:
if (var1 && var2 && var2 && var3 && var4 && var5 && var6) {
...
此外,重构抽象代码示例非常困难。如果您展示一个具体的示例,那么会更容易识别出更好的模式来解决问题。
这并没有更好,但是我过去所做的:(以下方法可防止短路布尔测试,即使第一个测试为 false,所有测试都会运行。不是推荐的模式,除非您知道您需要在返回之前始终执行所有代码 - 感谢 ptomato 发现我的错误!)
布尔值 ok = cond1;
好的&=条件2;
好的&=条件3;
好的&=条件4;
好的&=条件5;
好的&=条件6;
这与以下内容相同: (不一样,见上面的注释!)
确定 = (cond1 && cond2 && cond3 && cond4 && cond5 && cond6);
我求助于单独的布尔值:
Bool cond1 == (var1 && var2);
Bool cond2 == (var3 && var4);
if ( cond1 && cond2 ) {}
正如其他人提到的,我会分析您的条件,看看是否有办法将其外包给其他方法以提高可读性。
在像 PHP 这样的反射语言中,您可以使用变量变量:
$vars = array('var1', 'var2', ... etc.);
foreach ($vars as $v)
if ($$v == true) {
// do something
break;
}
我喜欢按级别将它们分解,所以我会像这样格式化您的示例:
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)){
如果您碰巧使用 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?)。如果是这样,这可能是最干净的方法。
麦克道尔,
您是正确的,当使用单个“&”运算符时,表达式的两侧都会进行评估。但是,当使用“&&”运算符(至少在 C# 中)时,第一个返回 false 的表达式是最后一个计算的表达式。这使得将计算放在 FOR 语句之前与任何其他方法一样好。
@调整
这并没有更好,但是我过去所做的:
布尔值 ok = cond1;好的&=条件2;好的&=条件3;好的&=条件4;好的&=条件5;好的&=条件6;
这与以下内容相同:
确定 = (cond1 && cond2 && cond3 && cond4 && cond5 && cond6);
实际上,这两件事在大多数语言中并不相同。一旦其中一个条件为假,第二个表达式通常就会停止计算,如果计算条件的成本很高,这可能会带来很大的性能改进。
为了可读性,我个人更喜欢上面 Mike Stone 的建议。详细注释很容易,并且保留了能够提前退出的所有计算优势。如果将条件计算远离其他函数会混淆代码的组织,您也可以在函数中内联执行相同的技术。这有点俗气,但你总是可以这样做:
do {
if (!cond1)
break;
if (!cond2)
break;
if (!cond3)
break;
...
DoSomething();
} while (false);
while (false) 有点俗气。我希望语言有一个名为“once”的作用域运算符或者您可以轻松摆脱的东西。
如果我在 Perl 中执行此操作,这就是我运行检查的方式。
{
last unless $var1;
last unless $var2;
last unless $var3;
last unless $var4;
last unless $var5;
last unless $var6;
... # Place Code Here
}
如果您打算在子例程中使用它,请替换每个实例 last
和 return
;
我喜欢将每个条件分解为描述性变量。
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
}
if ( (condition_A)
&& (condition_B)
&& (condition_C)
&& (condition_D)
&& (condition_E)
&& (condition_F)
)
{
...
}
相对于
if (condition_A) {
if (condition_B) {
if (condition_C) {
if (condition_D) {
if (condition_E) {
if (condition_F) {
...
}
}
}
}
}
}
和
if ( ( (condition_A)
&& (condition_B)
)
|| ( (condition_C)
&& (condition_D)
)
|| ( (condition_E)
&& (condition_F)
)
)
{
do_this_same_thing();
}
相对于
if (condition_A && condition_B) {
do_this_same_thing();
}
if (condition_C && (condition_D) {
do_this_same_thing();
}
if (condition_E && condition_F) {
do_this_same_thing();
}
如果多个条件表达式不使用显式括号指示表达式分析,而不是依赖于运算符优先级规则和更少的括号,大多数用于检查代码的静态分析工具都会抱怨。
开/闭大括号 {}、开闭括号 ()、带括号的条件表达式和左边的运算符在同一缩进级别上垂直对齐是一种非常有用的做法,这极大地增强了代码的可读性和清晰度,而不是堵塞所有内容可能会被挤在一行上,没有垂直对齐、空格或括号
运算符优先级规则很棘手,例如&&比||的优先级高,但是|比&&优先
所以, ...
if (expr_A & expr_B || expr_C | expr_D & expr_E || expr_E && expr_F & expr_G || expr_H {
}
是一个非常简单的多重条件表达式,对于人类来说,无法正确地阅读和评估。
if ( ( (expr_A)
& (expr_B)
)
|| ( (expr_C)
| ( (expr_D)
& (expr_E)
)
)
|| ( (expr_E)
&& ( (expr_F)
& (expr_G)
)
)
|| (expr_H)
)
{
}
水平空格(换行)、垂直对齐或明确的括号引导表达式求值都没有问题,所有这些都增强了可读性和清晰度
如果你这样做:
if (var1 == true) {
if (var2 == true) {
if (var3 == true) {
...
}
}
}
然后,您还可以对不真实的情况做出回应。例如,如果您正在验证输入,您可以向用户提供有关如何正确设置其格式或其他内容的提示。