Go流程控制与快乐路径原则
Go流程控制与快乐路径原则
一、流程控制基本介绍
流程控制是每种编程语言控制逻辑走向和执行次序的重要部分,流程控制可以说是一门语言的“经脉”。
那么 Go 语言对分支与循环两种控制结构的支持是怎么样的呢?针对程序的分支结构,Go 提供了 if 和 switch-case 两种语句形式;我们就先从 Go 语言分支结构之一的 if 语句开始讲起。
二、if 语句
2.1 if 语句介绍
if 语句是 Go 语言中提供的一种分支控制结构,它也是 Go 中最常用、最简单的分支控制结构。它会根据布尔表达式的值,在两个分支中选择一个执行。
2.2 单分支结构的 if 语句形式
单分支结构的if语句包含一个条件表达式和一个要执行的代码块。如果条件表达式的值为true,则执行代码块。如果条件表达式的值为false,则代码块将被跳过。以下是单分支结构的if语句的一般形式:
if boolean_expression {
// 新分支
}
// 原分支
这个 if 语句中的代码执行流程就等价于下面这幅流程图:

boolean_expression是一个布尔表达式,通常返回true或false。- 如果
boolean_expression的值为true,则执行// 当条件为真时执行的代码部分的代码块。 - 如果
boolean_expression的值为false,则代码块将被跳过,继续执行下一个语句。
2.3 Go 的 if 语句的特点
2.3.1 分支代码块左大括号与if同行
if 语句的分支代码块的左大括号与 if 关键字在同一行上,这是 Go 代码风格的统一要求,gofmt 工具会帮助我们实现这一点;
2.3.2 条件表达式不需要括号
if 语句的布尔表达式整体不需要用括号包裹,这使得代码更加简洁。而且,if 关键字后面的条件判断表达式的求值结果必须是布尔类型,即要么是 true,要么是 false:
if runtime.GOOS == "darwin" {
println("we are on MacOS")
}
如果判断的条件比较多,我们可以用多个逻辑操作符连接起多个条件判断表达式,比如这段代码就是用了多个逻辑操作符 && 来连接多个布尔表达式:
if (runtime.GOOS == "darwin") && (runtime.GOARCH == "amd64") &&
(runtime.Compiler != "gccgo") {
println("we are using standard go compiler on Mac os for amd64")
}
上面示例代码中的每个布尔表达式都被小括号括上了,这是为了降低你在阅读和理解这段代码时,面对操作符优先级的心智负担。
三、操作符
3.1 逻辑操作符
逻辑操作符除了上面的 && 之外,Go 还提供了另外两个逻辑操作符,如下表:
| 逻辑操作符 | 含义 | 表达式求值举例 |
|---|---|---|
&& |
逻辑与 | a &&b:当a和b都为true时,该表达式的求值 结果为true |
| ` | ` | |
| ` | ` | 逻辑非 |
3.2 操作符的优先级
一元操作符,比如上面的逻辑非操作符,具有最高优先级,其他操作符的优先级如下:
| 优先级(从高到低) | 操作符列表 |
|---|---|
| 5 | *, /, %, <<, >>, &, &^ |
| 4 | +, - |
| 3 | !=, ==, <, <=, >, >= |
| 2 | && |
| 1 | || |
- 优先级5的是乘、除、取模和位操作符
- 优先级4的是加法和减法运算符
- 优先级3的是关系和相等运算符
- 优先级2的是逻辑与
- 优先级最低的是逻辑或
操作符优先级决定了操作数优先参与哪个操作符的求值运算,我们以下面代码中 if 语句的布尔表达式为例:
func main() {
a, b := false,true
if a && b != true {
println("(a && b) != true")
return
}
println("a && (b != true) == false")
}
这段代码会输出得到的是 a && (b != true) == false。这是为什么呢?
这段代码的关键就在于,if 后面的布尔表达式中的操作数 b 是先参与 && 的求值运算,还是先参与!= 的求值运算。根据前面的操作符优先级表,我们知道,!= 的优先级要高于 &&,因此操作数 b 先参与的是!= 的求值运算,这样 if 后的布尔表达式就等价于 a && (b != true) 。
针对以上问题,推荐在 if 布尔表达式中,使用带有小括号的子布尔表达式来清晰地表达判断条件。
这样做不仅可以消除了自己记住操作符优先级的学习负担,当其他人阅读你的代码时,也可以很清晰地看出布尔表达式要表达的逻辑关系,这能让我们代码的可读性更好,更易于理解,不会因记错操作符优先级顺序而产生错误的理解。
三、if 多(N)分支结构
3.1 if else(分支结构)
Go语言中if else(分支结构)条件判断的格式如下:
if boolean_expression {
// 分支1
} else {
// 分支2
}
3.2 if(N)分支结构(if ... else if ... else)
if条件(N)分支结构格式如下:
if boolean_expression1 {
// 分支1
} else if boolean_expression2 {
// 分支2
... ...
} else if boolean_expressionN {
// 分支N
} else {
// 分支N+1
}
我们以下面这个四分支的代码为例,看看怎么拆解这个多分支结构:
if boolean_expression1 {
// 分支1
} else if boolean_expression2 {
// 分支2
} else if boolean_expression3 {
// 分支3
} else {
// 分支4
}
以下是一个示例,演示如何使用if-else结构来判断一个分数的等级:
package main
import "fmt"
func main() {
score := 85
if score >= 90 {
fmt.Println("A")
} else if score >= 80 {
fmt.Println("B")
} else if score >= 70 {
fmt.Println("C")
} else {
fmt.Println("D")
}
}
四、if 语句的自用变量
无论是单分支、二分支还是多分支结构,我们都可以在 if 后的布尔表达式前,进行一些变量的声明,在 if 布尔表达式前声明的变量,叫 if 语句的自用变量。顾名思义,这些变量只可以在 if 语句的代码块范围内使用,比如下面代码中的变量 a、b 和 c:
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 语句的代码时也可以更聚焦。
五、if 语句的“快乐路径”原则
上面我们已经学了 if 分支控制结构的三种形式了,从可读性上来看,单分支结构要优于二分支结构,二分支结构又优于多分支结构。那么显然,我们在日常编码中要减少多分支结构,甚至是二分支结构的使用,这会有助于我们编写出优雅、简洁、易读易维护且不易错的代码。
首先,我们来看一段伪代码段1:
//伪代码段1:
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
}
我们看到单分支控制结构的伪代码段 1 有这几个特点:
- 没有使用 else 分支,失败就立即返回;
- “成功”逻辑始终“居左”并延续到函数结尾,没有被嵌入到 if 的布尔表达式为 true 的代码分支中;
- 整个代码段布局扁平,没有深度的缩进;
- 代码的可读性很高
我们来看一段伪代码段2:
// 伪代码段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
}
}
伪代码段 2 实现了同样逻辑码段 1,就使用了带有嵌套的二分支结构,它的特点如下:
- 整个代码段呈现为“锯齿状”,有深度缩进;
- “成功”逻辑被嵌入到
if的布尔表达式为true的代码分支中;
很明显,伪代码段 1 的逻辑更容易理解,也更简洁。Go 社区把这种 if 语句的使用方式称为 if 语句的“快乐路径(Happy Path)”原则,所谓“快乐路径”也就是成功逻辑的代码执行路径,它的特点是这样的:
仅使用单分支控制结构;
当布尔表达式求值为
false时,也就是出现错误时,在单分支中快速返回;正常逻辑在代码布局上始终“靠左”,这样读者可以从上到下一眼看到该函数正常逻辑的全貌;
函数执行到最后一行代表一种成功状态。
Go 社区推荐 Gopher 们在使用 if 语句时尽量符合这些原则,如果你的函数实现代码不符合“快乐路径”原则,你可以按下面步骤进行重构:
尝试将“正常逻辑”提取出来,放到“快乐路径”中;
如果无法做到上一点,很可能是函数内的逻辑过于复杂,可以将深度缩进到 else 分支中的代码析出到一个函数中,再对原函数实施“快乐路径”原则。
我的博客即将同步至腾讯云开发者社区,邀请大家一同入驻:https://cloud.tencent.com/developer/support-plan?invite_code=1u9q67mdnb338
Go流程控制与快乐路径原则的更多相关文章
- 《快乐编程大本营》java语言训练班-第4课:java流程控制
<快乐编程大本营>java语言训练班-第4课:java流程控制 第1节. 顺序执行语句 第2节. 条件分支语句:if条件语句 第3节. 条件分支语句:switch 条件语句 第4节. 条件 ...
- core java 1~4(HelloWorld & 标识符|关键字|数据类型 & 表达式|流程控制 & 数组)
MODULE 1 Java的编译和运行----------------------------编译:javac -d bin src\HelloWorld.java -d:指定编译后的class 文件 ...
- python 之 数据库(内置函数、流程控制、索引)
10.17 内置函数 强调:mysql内置的函数只能在sql语句中使用 #数学函数 round(x,y) #返回参数x的四舍五入的有y位小数的值 rand() #返回0到1内的随机值,可以通过提供一个 ...
- Shell入门教程:流程控制(1)命令的结束状态
在Bash Shell中,流程控制命令有2大类:“条件”.“循环”.属于“条件”的有:if.case:属于“循环”的有:for.while.until:命令 select 既属于“条件”,也属于“循环 ...
- shell-bash学习04读取输入、分隔符、流程控制
读入输出 输入通常是通过stdin或参数传递给命令; 输出出现在stderr或stdout; 管道,过滤器,管道操作符: cmd1 | cmd2 | cmd3; //最后还有输出 ls | cat - ...
- Java 关键字、标识符、注释、常量与变量、数据类型,算术、赋值、比较、逻辑、位、三元运算符和流程控制、break、continue【3】
若有不正之处,请多多谅解并欢迎批评指正,不甚感激.请尊重作者劳动成果: 本文原创作者:pipi-changing本文原创出处:http://www.cnblogs.com/pipi-changing/ ...
- JAVA:变量,数据类型,运算符,流程控制(简介)<1>
一.安装和配置jdk 1.jdk是什么? (1).jdk全称是Java Development Kit, Java开发工具包; (2).jdk是sun公司开发的; (3).jdk主要包括:jre(Ja ...
- Java学习第一篇:变量,数据类型,运算符,流程控制(简介)
一.安装和配置jdk 1.jdk是什么? (1).jdk全称是Java Development Kit, Java开发工具包; (2).jdk是sun公司开发的; (3).jdk主要包括:jre(Ja ...
- SpringMVC(二)——流程控制
SpringMVC主要就是用来做流程控制的,这篇博客总结一下如何在流程控制添加Interceptor(拦截器),如何将进行流程Mapping映射解析,如何编写Controller(控制器). 一,首先 ...
- 流程控制 - PHP手册笔记
脚本由语句构成,语句靠流程控制实现功能,这一节主要介绍了几个关键字的使用. elseif elseif和else if的行为完全一样,如果用冒号来定义if/elseif条件,那就不能用两个单词的els ...
随机推荐
- mysql 日期和时间戳的转换
(18条消息) MySQL 日期和时间戳的转换 | 以及DATE_FORMAT()用法_慌途L的博客-CSDN博客_date_format能转换时间戳吗 一小时的时间戳是2*3600*1000,这是1 ...
- 【技术积累】Mysql中的SQL语言【一】
建表语句 后续所有内容建立在这些SQL语句上 CREATE TABLE users ( id INT PRIMARY KEY, name VARCHAR(50), age INT ); CREATE ...
- 1.1 熟悉x64dbg调试器
x64dbg 是一款开源.免费.功能强大的动态反汇编调试器,它能够在Windows平台上进行应用程序的反汇编.调试和分析工作.与传统的调试器如Ollydbg相比,x64dbg调试器的出现填补了Olly ...
- CPython, Pypy, MicroPython...还在傻傻分不清楚?
哈喽大家好,我是咸鱼 当我们说 Python 时,通常指的是官方实现的 CPython 但还有很多比如 Pypy.Jython.MicroPython.Brython.RustPython 等 &qu ...
- Centos7 升级 Kubernetes(k8s) 集群
目录 一.系统环境 二.前言 三.Kubernetes(k8s) 集群升级简介 四.升级master主节点 4.1 升级kubeadm 4.2 升级各个组件 4.3 升级 kubelet 和 kube ...
- 【调制解调】SSB 单边带调幅
说明 学习数字信号处理算法时整理的学习笔记.同系列文章目录可见 <DSP 学习之路>目录,代码已上传到 Github - ModulationAndDemodulation.本篇介绍 SS ...
- 语音合成技术汇总1:Glow-TTS:通过单调对齐实现文本到语音的生成流
今天开始开一期语音合成经典论文的翻译 Glow-TTS:通过单调对齐实现文本到语音的生成流 摘要: 最近,文本到语音(Text-to-Speech,TTS)模型,如FastSpeech和ParaNet ...
- 十 Appium环境搭建(Windows版)
注:appium安装到C盘,node.js安装到C盘 一.安装node.js 1.到官网下载node.js:https://nodejs.org/en/download/ 2.获取到安装文件后,直接双 ...
- 使用lame以多进程方式转码wav为mp3
前言 lame以单进程的方式转码wav文件,之前量少,足够使用.如今每日wav文件数量极多,单进程的效率就不够用了,所以这里使用脚本的方式,启动多个lame进程转码wav文件. code01: aut ...
- 从0开始,Cloudreve开源云盘在centos7上部署,并可在外网访问(资料整合)
全程我在网络上收集这些资料,太零碎了,每一个一看就会,一动手就废,而且很多都不能实现我白嫖的梦想 我一个人折腾了快一周,现在可以正常访问手机电脑多端访问 给个赞再走吧 此处为没有公网IP(回去折腾你家 ...