Conditional branching is when a segment of code is executed or evaluated based on a condition, typically implemented using an if...else
construct.
条件 分支是根据条件执行或评估一段代码时的条件 分支 ,通常使用if...else
构造实现。
For example:
例如:
if (customerPaid)
{
sendThankYou()
}
else
{
sendReminder();
}
Here, sendThankYou()
will get called if customerPaid
is true
; otherwise sendReminder()
will be called.
在这里,如果customerPaid
为true
,则将调用sendThankYou()
; 否则将调用sendReminder()
。
There’s some thinking in the programming community that if
should be considered harmful or a code smell. Regardless of the validity of this claim, there may be cases where if
isn’t the best approach to branching, or where branching should be avoided altogether.
有一些想法在编程社区if
应被认为是有害的或代码味道。 无论此声明的有效性如何,在某些情况下, if
不是最好的分支方法,或者应完全避免分支。
In this tutorial, I’ll therefore demonstrate six alternatives to if...else
with some discussion on when you might choose to use them.
因此,在本教程中,我将演示if...else
六个替代方法,并讨论何时选择使用它们。
Examples are written in JavaScript, but the concepts are applicable in many other languages.
示例是用JavaScript编写的,但是这些概念适用于许多其他语言。
1.三元运算符 (1. The Ternary Operator)
One of my favourite alternatives to if...else
is the ternary operator.
我最喜欢if...else
替代方法之一是三元运算符。
This takes the form:
它采用以下形式:
condition ? expressionIfTrue : expressionIfFalse
Here expressionIfTrue
will be evaluated if condition
evaluates totrue
; otherwise expressionIfFalse
will be evaluated.
如果condition
值为true
,则将对expressionIfTrue
进行评估; 否则将评估expressionIfFalse
。
The beauty of ternary operators is they can be used on the right-hand side of an assignment.
三元运算符的优点在于可以在作业的右侧使用它们。
For example:
例如:
const labelText = customerPaid ? "Thank You!" : "Payment Overdue";
This will set labelText
to “Thank You!”
if the customer has paid; otherwise, you’ll get “Payment Overdue”
.
这会将labelText
设置为“Thank You!”
客户是否已付款; 否则,您将收到“Payment Overdue”
。
2. Switch
语句 (2. The Switch
Statement)
A switch
statement has the following structure:
switch
语句具有以下结构:
switch (expression)
{
case value1:
// code executed if expression matches value1
[break;]
case value2:
// code executed if expression matches value2
[break;]
...
case valueN:
// code executed if expression matches valueN
[break;]
[default:
// code executed if expression has no match
[break;]]
}
The break
statements are optional and will cause the switch
block to be exited. If a break
is omitted, then execution will proceed to the next case
statement.
break
语句是可选的,将导致退出switch
块。 如果省略break
,则执行将继续执行下一个case
语句。
switch
is useful for branching based on a homogeneous set of values.
switch
对于基于同类值集进行分支很有用。
For example:
例如:
switch (colour)
{
case 'red':
case 'amber':
stop();
break;
case 'green':
case 'flashing amber':
go();
break;
default:
throw 'Invalid colour';
}
3.跳转表 (3. The Jump Table)
A jump table is a method of conditionally branching using a lookup table to branch into a new function or block.
跳转表是一种使用查找表有条件分支的方法,以分支到新功能或块中。
Technically in C, this would be implemented as an array of function pointers. However, unless we strictly need the efficiency of array lookup, we could generalise this to any kind of keyed function lookup.
从技术上讲,这将以C 函数指针数组的形式实现。 但是,除非我们严格需要数组查找的效率,否则我们可以将其推广到任何类型的键控函数查找。
For example, the above switch
statement could be refactored as a jump table in JavaScript, like so:
例如,可以将上述switch
语句重构为JavaScript中的跳转表,如下所示:
const handleColour =
{
'red' : stop,
'amber' : stop,
'green' : go,
'flashing amber' : go
}
Here stop
and go
are function objects. Let’s flesh this out a bit to make it clear how this approach would work in context:
stop
和go
是功能对象。 让我们充实一下,以明确此方法在上下文中的工作方式:
const stop = () => { console.log("stop called"); }
const go = () => { console.log("go called"); }
const colour = 'red';
const handleColour =
{
'red' : stop,
'amber' : stop,
'green' : go,
'flashing amber' : go
}
handleColour[colour](); // logs "stop called"
Here, our code branches conditionally based on the value of the colour
variable. The semantics are the same as our switch
example, but we have less code. And it’s (arguably) clearer to read.
在这里,我们的代码根据colour
变量的值有条件地分支。 语义与我们的switch
示例相同,但是代码更少。 而且(可以说)阅读起来更清晰。
4.动态调度 (4. The Dynamic Dispatch)
Another alternative to using if
statements is a dynamic dispatch. This involves selecting which polymorphic method to call based on an object’s type.
使用if
语句的另一种选择是动态分配。 这涉及根据对象的类型选择要调用的多态方法。
It could be used to branch conditionally, like this:
它可以用于有条件地分支,如下所示:
const handleShape = (shape) => { console.log(shape.area()); }
class Shape
{
constructor(width = 2)
{
this.width = width;
}
area()
{
return 0;
}
}
class Square extends Shape
{
area()
{
return this.width ** 2;
}
}
class Circle extends Shape
{
area()
{
return Math.PI * (this.width / 2) ** 2;
}
}
const shape = new Square();
handleShape(shape); // logs 4
Here, a different code path is taken depending on the type of object passed to the handleShape
function. In our case, Shape
is a Square
, so the area is logged as 4
.
在此,根据传递给handleShape
函数的对象类型,采用不同的代码路径。 在我们的例子中, Shape
是一个Square
,因此该区域记录为4
。
This approach can result in a lot more code, but there are some arguments for its usage over an if...else
. This kind of pattern is generally appropriate when the code already employs OOP
; however, seeking to always engineer branching to use polymorphism seems like overkill.
这种方法可以产生更多的代码,但是在if...else
有一些使用它的参数 。 当代码已经采用OOP
时,这种模式通常是合适的; 但是,试图始终设计分支以使用多态性似乎有些过头。
try' and 'catch' statements
(5. ‘try' and 'catch' statements
)
I’m including this for completeness since try
/catch
should rarely, if ever, be used for control flow.
我将其包括在内是为了完整性,因为try
/ catch
应该很少(如果有的话)用于控制流程。
Much has been written about why this is a bad idea, but my personal take is that try
/catch
are designed for handling exceptions, so by using them for control flow, our intent becomes unclear.
关于为什么这样做不是一个好主意的文章已经写了很多 ,但是我个人认为try
/ catch
是为处理异常而设计的,因此通过将它们用于控制流,我们的意图变得不清楚。
In any case, here’s an example of conditional branching using exceptions:
无论如何,这是一个使用异常的条件分支的示例:
var item;
try
{
item = previousItem;
}
catch (ReferenceError)
{
item = new Item();
previousItem = item;
}
Here, item
will get assigned topreviousItem
if it exists; otherwise, a ReferenceError
exception will be thrown, and a new Item
object will be assigned.
在这里, item
将被分配给previousItem
如果存在); 否则,将抛出ReferenceError
异常,并分配一个新的Item
对象。
6.模式匹配 (6. Pattern Matching)
Pattern matching is the new kid on the block when it comes to branching. It’s generally popular with those who favour a more functional programming style.
模式匹配是分支方面的新手。 它在那些偏爱功能更强大的编程风格的人中普遍流行。
Pattern matching could be thought of as dynamic dispatch (see above) based on a value rather than a type.
模式匹配可以被认为是基于值而不是类型的动态调度(请参见上文)。
A classic example is implementing a factorial function using two functions:
一个经典的示例是使用两个函数来实现阶乘函数:
factorial(0) ::= 1
factorial(n) ::= n * factorial(n-1)
The first is called if the value passed in is zero, in which case 1
is returned. The second is called if the value passed in is nonzero — in which case, the result of the expression n * factorial(n-1)
is returned.
如果传入的值为零,则调用第一个,在这种情况下,将返回1
。 如果传入的值非零,则调用第二个方法-在这种情况下,将返回表达式n * factorial(n-1)
的结果。
Some languages have pattern matching built in. In JavaScript, an external library needs to be used — for example, Z (pattern matching for Javascript).
某些语言内置了模式匹配。在JavaScript中,需要使用一个外部库,例如Z(Java的模式匹配 )。
The factorial function could be written using Z as follows:
阶乘函数可以使用Z编写,如下所示:
var factorial = number => {
return number.matches (
(x = 0) => 1,
(x) => x * factorial (x - 1)
)
}
factorial(6) // returns 720
There are arguably more readable and expressive ways to compute a factorial in JavaScript, but the potential for pattern matching as a means of conditional execution extends well beyond this simple example.
可以说有更多可读性和表达性强的方法来计算JavaScript中的阶乘,但是模式匹配作为条件执行手段的潜力远远超出了这个简单示例。
There is a proposal to build a pattern into the JavaScript language, which is worth a read.
有建议将模式构建到JavaScript语言中,这值得一读。
结论 (Conclusion)
Conditional branching can introduce complexity into a program.
条件分支可以将复杂性引入程序中。
Nested if
and switch
statements can make code less readable and open potential for bugs. Other forms of branching can result in an overengineered codebase. If it’s possible to avoid branching altogether, this is often a good starting point.
嵌套的if
和switch
语句可能会使代码的可读性降低,并可能引发错误。 其他形式的分支会导致代码库过度设计。 如果有可能完全避免分支,这通常是一个很好的起点。
However, if we do need to conditionally branch, it’s good to choose the most appropriate mechanism for the job. Hopefully, in the article I’ve provided some useful options for consideration.
但是,如果确实需要有条件地分支,则最好选择最合适的工作机制。 希望本文中提供了一些有用的选项供您考虑。