目录

if语法

✏️ 写python的时候经常使用for循环,嵌套if语句来进行使用,这样的场景很多。会用if以及它的几种分支结构,记住优先级,和快乐路径原则。其他罗里吧嗦的不要去记,记住怎么用,用在什么的场景下

1. 体验if-else语法

1.1 单个if-else

1
2
3
4
5
6
age := 19
if age > 18 {
	fmt.Println("澳门首家线上赌场开业啦")
} else {
	fmt.Println("该好好学习啦")
}

out

1
澳门首家线上赌场开业啦

1.2 if-else if-else

1
2
3
4
5
6
7
8
age := 19
if age > 35 {
			fmt.Println("老了")
		} else if age > 18 {
			fmt.Println("还行")
		} else {
			fmt.Println("study")
		}

out

1
还行

1.3 if语句中的变量

age变量此时只在判断语句中生效

1
2
3
4
5
6
// age变量此时只在判断语句中生效
if age := 8; age > 18 {
	fmt.Println("dududu")
} else {
	fmt.Println("study")
}

out

1
study

2. Go中的控制结构

Go 语言对分支与循环两种控制结构的支持是怎么样的呢?针对程序的分支结构,Go 提供了 ifswitch-case 两种语句形式;而针对循环结构,Go 只保留了 for 这一种循环语句形式,是没有while循环的。

  • Go 坚持“一件事情仅有一种做法的理念”,只保留了 for 这一种循环结构,去掉了 C 语言中的 while 和 do-while 循环结构;所以不要在Go里面习惯性的写while了!
  • Go 填平了 C 语言中 switch 分支结构中每个 case 语句都要以 break 收尾的“坑”;
  • Go 支持了 type switch 特性,让“类型”信息也可以作为分支选择的条件;这是一个坑,我有一次习惯性在case语句加break,程序没有退出来!
  • Go 的 switch 控制结构的 case 语句还支持表达式列表,让相同处理逻辑的多个分支可以合并为一个分支,等等。

3. Go中的if

if 语句是 Go 语言中提供的一种分支控制结构,它也是 Go 中最常用、最简单的分支控制结构。它会根据布尔表达式的值,在两个分支中选择一个执行。我们先来看一个最简单的、单分支结构的 if 语句的形式:

1
2
3
4
if boolean_expression {
    // 新分支
}
// 原分支

分支结构是传统结构化程序设计中的基础构件,这个 if 语句中的代码执行流程就等价于下面这幅流程图:

/go基础/20230422113303.png

从图中我们可以看到,代码执行遇到 if 分支结构后,首先会对其中的布尔表达式(boolean_expression)进行求值,如果求值结果为 true,那么程序将进入新分支执行,如果布尔表达式的求值结果为 false,代码就会继续沿着原分支的路线继续执行。

虽然各种编程语言几乎都原生支持了 if 语句,但 Go 的 if 语句依然有着自己的特点:第一,和 Go 函数一样,if 语句的分支代码块的左大括号与 if 关键字在同一行上,这也是 Go 代码风格的统一要求,gofmt 工具会帮助我们实现这一点;

第二,if 语句的布尔表达式整体不需要用括号包裹,一定程度上减少了开发人员敲击键盘的次数。而且,if 关键字后面的条件判断表达式的求值结果必须是布尔类型,即要么是 true,要么是 false

1
2
3
if runtime.GOOS == "linux" {
    println("we are on linux os")    
}

如果判断的条件比较多,我们可以用多个逻辑操作符连接起多个条件判断表达式,比如这段代码就是用了多个逻辑操作符 && 来连接多个布尔表达式:

1
2
3
4
if (runtime.GOOS == "linux") && (runtime.GOARCH == "amd64") &&
    (runtime.Compiler != "gccgo") {
    println("we are using standard go compiler on linux os for amd64")
}

3. 优先级😀

除了逻辑操作符 && 之外,Go 还提供了另外两个逻辑操作符

/go基础/20230422113434.png

你可能也注意到了,上面示例代码中的每个布尔表达式都被小括号括上了,这又是什么原因呢?这是为了降低你在阅读和理解这段代码时,面对操作符优先级的心智负担,这也是我个人的编码习惯。

Go 语言的操作符是有优先级的。这里你要记住,一元操作符,比如上面的逻辑非操作符,具有最高优先级,其他操作符的优先级如下:

/go基础/20230422113517.png

操作符优先级决定了操作数优先参与哪个操作符的求值运算,我们以下面代码中 if 语句的布尔表达式为例:

1
2
3
4
5
6
7
8
func main() {
    a, b := false,true
    if a && b != true {
        println("(a && b) != true")
        return
    }
    println("a && (b != true) == false")
}

这段代码的关键就在于,if 后面的布尔表达式中的操作数 b 是先参与 && 的求值运算,还是先参与!= 的求值运算。根据前面的操作符优先级表,我们知道,!= 的优先级要高于 &&,因此操作数 b 先参与的是!= 的求值运算,这样 if 后的布尔表达式就等价于 a && (b != true) ,而不是我们最初认为的 (a && b) != true。 如果你有时候也会记不住操作符优先级,不用紧张。从学习和使用 C 语言开始,我自己就记不住这么多操作符的优先级,况且不同编程语言的操作符优先级还可能有所不同,所以我个人倾向在 if 布尔表达式中,使用带有小括号的子布尔表达式来清晰地表达判断条件。

这样做不仅可以消除了自己记住操作符优先级的学习负担,同时就像前面说的,当其他人阅读你的代码时,也可以很清晰地看出布尔表达式要表达的逻辑关系,这能让我们代码的可读性更好,更易于理解,不会因记错操作符优先级顺序而产生错误的理解。

其实很简单,遇到这种复杂形式的if语句,直接加()得了,不要去记什么优先级。但是要会排查问题。

4. 多分支结构

二分支控制结构比较好理解。比如下面这个例子,当 boolean_expression 求值为 true 时,执行分支 1,否则,执行分支 2:

1
2
3
4
5
if boolean_expression {
  // 分支1
} else {
  // 分支2
}

多分支结构由于引入了 else if,理解起来稍难一点点,它的标准形式是这样的:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
if boolean_expression1 {
  // 分支1
} else if boolean_expression2 {
  // 分支2

... ...

} else if boolean_expressionN {
  // 分支N
} else {
  // 分支N+1
}

要理解这个略复杂一些的分支结构,其实很简单。我们只需要把它做一下等价变换,变换为我们熟悉的二分支结构就好了,变换后的代码如下:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
if boolean_expression1 {
    // 分支1
} else {
    if boolean_expression2 {
        // 分支2
    } else { 
        if boolean_expression3 {
            // 分支3
        } else {
            // 分支4
        } 
    }
}

这样等价转换后,我们得到一个层层缩进的二分支结构,通过上面我们对二分支的分析,再来理解这个结构就十分容易了。

5. 支持声明if语句自用变量

无论是单分支、二分支还是多分支结构,我们都可以在 if 后的布尔表达式前,进行一些变量的声明,在 if 布尔表达式前声明的变量,我叫它 if 语句的自用变量。顾名思义,这些变量只可以在 if 语句的代码块范围内使用,比如下面代码中的变量 a、b 和 c:

1
2
3
4
5
6
7
8
9
func main() {
    if a, c := f(), h(); a > 0 {
        println(a)
    } else if b := f(); b > 0 {
        println(a, b)
    } else {
        println(a, b, c)
    }
}

我们可以看到自用变量声明的位置是在每个 if 语句的后面,布尔表达式的前面,而且,由于声明本身是一个语句,所以我们需要把它和后面的布尔表达式通过分号分隔开。

在 if 语句中声明自用变量是 Go 语言的一个惯用法,这种使用方式直观上可以让开发者有一种代码行数减少的感觉,提高可读性。同时,由于这些变量是 if 语句自用变量,它的作用域仅限于 if 语句的各层隐式代码块中,if 语句外部无法访问和更改这些变量,这就让这些变量具有一定隔离性,这样你在阅读和理解 if 语句的代码时也可以更聚焦。

6. 快乐路径原则

伪代码段1:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20

func doSomething() error {
  if errorCondition1 {
    // some error logic
    ... ...
    return err1
  }
  
  // some success logic
  ... ...

  if errorCondition2 {
    // some error logic
    ... ...
    return err2
  }

  // some success logic
  ... ...
  return nil

伪代码段2:(写java的人经常这么干,这样的代码谁想看?)

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24

// 伪代码段2:

func doSomething() error {
  if successCondition1 {
    // some success logic
    ... ...

    if successCondition2 {
      // some success logic
      ... ...

      return nil
    } else {
      // some error logic
      ... ...
      return err2
    }
  } else {
    // some error logic
    ... ...
    return err1
  }
}

我们看看只使用了单分支控制结构的伪代码段 1,我们看到代码段 1 有这几个特点:

  • 没有使用 else 分支,失败就立即返回;
  • “成功”逻辑始终“居左”并延续到函数结尾,没有被嵌入到 if 的布尔表达式为 true 的代码分支中;
  • 整个代码段布局扁平,没有深度的缩进;

而另外一个实现了同样逻辑的伪代码段 2,就使用了带有嵌套的二分支结构,它的特点如下:

  • 整个代码段呈现为“锯齿状”,有深度缩进;
  • “成功”逻辑被嵌入到 if 的布尔表达式为 true 的代码分支中;

很明显,伪代码段 1 的逻辑更容易理解,也更简洁。Go 社区把这种 if 语句的使用方式称为 if 语句的“快乐路径(Happy Path)”原则,所谓“快乐路径”也就是成功逻辑的代码执行路径,它的特点是这样的:

  • 仅使用单分支控制结构;
  • 当布尔表达式求值为 false 时,也就是出现错误时,在单分支中快速返回;
  • 正常逻辑在代码布局上始终“靠左”,这样读者可以从上到下一眼看到该函数正常逻辑的全貌;
  • 函数执行到最后一行代表一种成功状态。

Go 社区推荐 Gopher 们在使用 if 语句时尽量符合这些原则,如果你的函数实现代码不符合“快乐路径”原则,你可以按下面步骤进行重构:

  • 尝试将“正常逻辑”提取出来,放到“快乐路径”中;
  • 如果无法做到上一点,很可能是函数内的逻辑过于复杂,可以将深度缩进到 else 分支中的代码析出到一个函数中,再对原函数实施“快乐路径”原则。

7. 小结

分支控制结构是构造现实中复杂算法的三大基础控制结构之一,Go 语言通过 if 与 switch 语句对分支控制结构提供了支持。

第一,if 语句是 Go 语言中最常用的分支控制语句,也是最简单的分支控制结构。if 语句通过对布尔表达式的求值决定了后续代码执行要进入的哪条分支。当需要复杂条件判断时,我们可以使用逻辑操作符连接多个布尔表达式,作为 if 语句的判断条件表达式。如果这么做了,我们还要注意各个操作符的优先级,我个人建议尽量用小括号对各个布尔表达式进行清晰地隔离,这样可以提升代码可读性。

第二,Go 的 if 语句提供了多种使用形式,包括单分支双分支以及多分支。多分支理解起来略有难度,我们可以将它等价转换为双分支来理解。

第三,if 语句支持在布尔表达式前声明自用变量,这些变量作用域仅限于 if 语句的代码块内部。使用 if 自用变量可以一定程度简化代码,并增强与同函数内其他变量的隔离,但这也十分容易导致变量遮蔽问题,你使用时一定要注意。

最后一点,if 语句的三种使用形式的复杂度与可读性不一,我们建议在使用 if 语句时尽量符合“快乐路径”原则,这个原则通常只使用最容易理解的单分支结构,所有正常代码均“靠左”,这让函数内代码逻辑一目了然,提升了代码可读性与可维护性。