go-control-flow
Compare original and translation side by side
🇺🇸
Original
English🇨🇳
Translation
ChineseGo Control Flow
Go语言控制流
Source: Effective Go. Go's control structures are related to C but differ in important ways. Understanding these differences is essential for writing idiomatic Go code.
Go has no or loop—only a generalized . There are no
parentheses around conditions, and bodies must always be brace-delimited.
dowhilefor来源:《Effective Go》。Go语言的控制结构与C语言相关,但在重要方面存在差异。理解这些差异是编写地道Go代码的关键。
Go语言没有或循环——只有通用的循环。条件不需要加括号,且循环体必须用大括号包裹。
dowhileforIf Statements
条件判断语句(If Statements)
Basic Form
基础形式
Go's requires braces and has no parentheses around the condition:
ifgo
if x > 0 {
return y
}Go语言的语句需要使用大括号,且条件不需要加括号:
ifgo
if x > 0 {
return y
}If with Initialization
带初始化的If语句
ifswitchgo
// Good: err scoped to if block
if err := file.Chmod(0664); err != nil {
log.Print(err)
return err
}ifswitchgo
// 推荐写法:err的作用域限定在if代码块内
if err := file.Chmod(0664); err != nil {
log.Print(err)
return err
}Omit Else for Early Returns
提前返回时省略Else
When an body ends with , , , or , omit the
unnecessary . This keeps the success path unindented:
ifbreakcontinuegotoreturnelsego
// Good: no else, success path at left margin
f, err := os.Open(name)
if err != nil {
return err
}
codeUsing(f)go
// Bad: else clause buries normal flow
f, err := os.Open(name)
if err != nil {
return err
} else {
codeUsing(f) // unnecessarily indented
}当代码块以、、或结尾时,可以省略不必要的,这样能让正常执行路径保持无缩进:
ifbreakcontinuegotoreturnelsego
// 推荐写法:无else,正常路径处于左侧边缘
f, err := os.Open(name)
if err != nil {
return err
}
codeUsing(f)go
// 不推荐写法:else子句掩盖了正常执行流程
f, err := os.Open(name)
if err != nil {
return err
} else {
codeUsing(f) // 不必要的缩进
}Guard Clauses for Error Handling
错误处理的守卫子句
Code reads well when the success path flows down the page, eliminating errors as
they arise:
go
// Good: guard clauses eliminate errors early
f, err := os.Open(name)
if err != nil {
return err
}
d, err := f.Stat()
if err != nil {
f.Close()
return err
}
codeUsing(f, d)当正常执行路径沿页面向下延伸,同时尽早处理错误时,代码可读性会更好:
go
// 推荐写法:守卫子句尽早处理错误
f, err := os.Open(name)
if err != nil {
return err
}
d, err := f.Stat()
if err != nil {
f.Close()
return err
}
codeUsing(f, d)Redeclaration and Reassignment
重声明与重新赋值
The short declaration allows redeclaring variables in the same scope under
specific conditions:
:=go
f, err := os.Open(name) // declares f and err
// ...
d, err := f.Stat() // declares d, reassigns err (not a new err)A variable may appear in a declaration even if already declared,
provided:
v:=- The declaration is in the same scope as the existing
v - The value is assignable to
v - At least one other variable is newly created by the declaration
This pragmatic rule makes it easy to reuse a single variable through a
chain of operations.
errgo
// Good: err reused across multiple calls
data, err := fetchData()
if err != nil {
return err
}
result, err := processData(data) // err reassigned, result declared
if err != nil {
return err
}Warning: If is declared in an outer scope, creates a new
variable that shadows it:
v:=go
// Bad: accidental shadowing
var err error
if condition {
x, err := someFunc() // this err shadows the outer err!
// outer err remains nil
}短变量声明允许在特定条件下,在同一作用域内重声明变量:
:=go
f, err := os.Open(name) // 声明f和err
// ...
d, err := f.Stat() // 声明d,重新赋值err(不是新的err变量)变量可以出现在声明中,即使它已经被声明过,只要满足以下条件:
v:=- 声明与现有处于同一作用域
v - 新值可以赋值给
v - 声明中至少有一个其他变量是新创建的
这条实用规则让我们可以在一系列操作中轻松复用单个变量。
errgo
// 推荐写法:在多次调用中复用err变量
data, err := fetchData()
if err != nil {
return err
}
result, err := processData(data) // err被重新赋值,result被声明
if err != nil {
return err
}警告:如果是在外部作用域声明的,会创建一个新的变量,遮蔽外部的:
v:=vgo
// 不推荐写法:意外的变量遮蔽
var err error
if condition {
x, err := someFunc() // 这个err遮蔽了外部的err!
// 外部的err仍然是nil
}For Loops
循环语句(For Loops)
Go unifies and into a single construct with three forms:
forwhilego
// C-style for (only form with semicolons)
for init; condition; post { }
// While-style (condition only)
for condition { }
// Infinite loop
for { }Go语言将和统一为单一结构,有三种形式:
forwhilego
// C语言风格的for循环(唯一使用分号的形式)
for init; condition; post { }
// While风格(仅保留条件)
for condition { }
// 无限循环
for { }Range Clause
Range遍历子句
Use to iterate over arrays, slices, strings, maps, and channels:
rangego
// Iterate with key and value
for key, value := range oldMap {
newMap[key] = value
}
// Key/index only (drop the second variable)
for key := range m {
if key.expired() {
delete(m, key)
}
}
// Value only (use blank identifier for index)
for _, value := range array {
sum += value
}使用遍历数组、切片、字符串、映射(map)和通道:
rangego
// 遍历键和值
for key, value := range oldMap {
newMap[key] = value
}
// 仅遍历键/索引(忽略第二个变量)
for key := range m {
if key.expired() {
delete(m, key)
}
}
// 仅遍历值(用空白标识符忽略索引)
for _, value := range array {
sum += value
}Range Over Strings
遍历字符串
For strings, iterates over UTF-8 encoded runes (not bytes), handling
multi-byte characters automatically.
range遍历字符串时,会迭代UTF-8编码的符文(rune)而非字节,自动处理多字节字符。
rangeParallel Assignment in For
循环中的并行赋值
Go has no comma operator. Use parallel assignment for multiple loop variables:
go
// Reverse a slice
for i, j := 0, len(a)-1; i < j; i, j = i+1, j-1 {
a[i], a[j] = a[j], a[i]
}Note: and are statements, not expressions, so they cannot be used in
parallel assignment.
++--Go语言没有逗号运算符,使用并行赋值来处理多个循环变量:
go
// 反转切片
for i, j := 0, len(a)-1; i < j; i, j = i+1, j-1 {
a[i], a[j] = a[j], a[i]
}注意:和是语句而非表达式,因此不能用于并行赋值。
++--Switch
分支语句(Switch)
Go's is more flexible than C's:
switch- Expressions need not be constants or integers
- Cases are evaluated top to bottom until a match
- No automatic fall through (no need for in each case)
break
Go语言的比C语言的更灵活:
switch- 表达式不必是常量或整数
- 从上到下评估case,直到找到匹配项
- 无自动贯穿(无需在每个case中添加)
break
Expression-less Switch
无表达式的Switch
If the has no expression, it switches on . This is idiomatic for
writing clean chains:
switchtrueif-else-ifgo
// Good: expression-less switch for ranges
func unhex(c byte) byte {
switch {
case '0' <= c && c <= '9':
return c - '0'
case 'a' <= c && c <= 'f':
return c - 'a' + 10
case 'A' <= c && c <= 'F':
return c - 'A' + 10
}
return 0
}如果没有表达式,它会基于进行判断。这是编写清晰的链式判断的惯用写法:
switchtrueif-else-ifgo
// 推荐写法:用无表达式switch处理范围判断
func unhex(c byte) byte {
switch {
case '0' <= c && c <= '9':
return c - '0'
case 'a' <= c && c <= 'f':
return c - 'a' + 10
case 'A' <= c && c <= 'F':
return c - 'A' + 10
}
return 0
}Comma-Separated Cases
逗号分隔的多case
Multiple cases can be combined with commas (no fall through needed):
go
func shouldEscape(c byte) bool {
switch c {
case ' ', '?', '&', '=', '#', '+', '%':
return true
}
return false
}多个case可以用逗号组合(无需考虑贯穿问题):
go
func shouldEscape(c byte) bool {
switch c {
case ' ', '?', '&', '=', '#', '+', '%':
return true
}
return false
}Break with Labels
带标签的Break
breakgo
Loop:
for n := 0; n < len(src); n += size {
switch {
case src[n] < sizeOne:
break // breaks switch only
case src[n] < sizeTwo:
if n+1 >= len(src) {
break Loop // breaks out of for loop
}
}
}breakgo
Loop:
for n := 0; n < len(src); n += size {
switch {
case src[n] < sizeOne:
break // 仅跳出switch
case src[n] < sizeTwo:
if n+1 >= len(src) {
break Loop // 跳出外层for循环
}
}
}Type Switch
类型Switch
A type switch discovers the dynamic type of an interface value using
:
.(type)go
switch v := value.(type) {
case nil:
fmt.Println("value is nil")
case int:
fmt.Printf("integer: %d\n", v) // v is int
case string:
fmt.Printf("string: %q\n", v) // v is string
case bool:
fmt.Printf("boolean: %t\n", v) // v is bool
default:
fmt.Printf("unexpected type %T\n", v)
}It's idiomatic to reuse the variable name () since the
variable has a different type in each case clause.
v := value.(type)When a case lists multiple types (), the variable has the
interface type.
case int, int64:类型switch通过来发现接口值的动态类型:
.(type)go
switch v := value.(type) {
case nil:
fmt.Println("value is nil")
case int:
fmt.Printf("integer: %d\n", v) // v是int类型
case string:
fmt.Printf("string: %q\n", v) // v是string类型
case bool:
fmt.Printf("boolean: %t\n", v) // v是bool类型
default:
fmt.Printf("unexpected type %T\n", v)
}惯用写法是复用变量名(),因为变量在每个case子句中都有不同的类型。
v := value.(type)当一个case列出多个类型()时,变量的类型为接口类型。
case int, int64:The Blank Identifier
空白标识符
The blank identifier discards values. It's like writing to .
_/dev/null空白标识符用于丢弃值,类似于写入。
_/dev/nullMultiple Assignment
多返回值赋值
Discard unwanted values from multi-value expressions:
go
// Only need the error
if _, err := os.Stat(path); os.IsNotExist(err) {
fmt.Printf("%s does not exist\n", path)
}
// Only need the value (discard ok)
value := cache[key] // simpler: just use single-value form
_, present := cache[key] // when you only need presence checkNever discard errors carelessly:
go
// Bad: ignoring error will crash if path doesn't exist
fi, _ := os.Stat(path)
if fi.IsDir() { // nil pointer dereference if path doesn't exist
// ...
}丢弃多值表达式中不需要的值:
go
// 只需要错误信息
if _, err := os.Stat(path); os.IsNotExist(err) {
fmt.Printf("%s 不存在\n", path)
}
// 只需要值(丢弃ok标志)
value := cache[key] // 更简洁:直接使用单值形式
_, present := cache[key] // 仅检查键是否存在切勿随意丢弃错误:
go
// 不推荐写法:忽略错误会在路径不存在时导致崩溃
fi, _ := os.Stat(path)
if fi.IsDir() { // 如果路径不存在,会出现空指针解引用
// ...
}Unused Imports and Variables During Development
开发过程中处理未使用的导入和变量
Silence compiler errors temporarily during active development:
go
import (
"fmt"
"io"
)
var _ = fmt.Printf // silence unused import (remove before committing)
var _ io.Reader
func main() {
fd, _ := os.Open("test.go")
_ = fd // silence unused variable
}在开发过程中临时屏蔽编译器错误:
go
import (
"fmt"
"io"
)
var _ = fmt.Printf // 屏蔽未使用的导入(提交前删除)
var _ io.Reader
func main() {
fd, _ := os.Open("test.go")
_ = fd // 屏蔽未使用的变量
}Import for Side Effect
仅为副作用导入包
Import a package only for its side effects:
init()go
import _ "net/http/pprof" // registers HTTP handlers
import _ "image/png" // registers PNG decoderThis makes clear the package is imported only for side effects—it has no usable
name in this file.
仅为了包的副作用而导入:
init()go
import _ "net/http/pprof" // 注册HTTP处理器
import _ "image/png" // 注册PNG解码器这明确表示导入该包只是为了副作用——在当前文件中无法使用该包的名称。
Interface Compliance Check
接口合规性检查
Verify at compile time that a type implements an interface:
go
// Verify that *MyType implements io.Writer
var _ io.Writer = (*MyType)(nil)
// Verify that MyHandler implements http.Handler
var _ http.Handler = MyHandler{}This fails at compile time if the type doesn't implement the interface, catching
errors early.
在编译时验证类型是否实现了某个接口:
go
// 验证*MyType是否实现了io.Writer
var _ io.Writer = (*MyType)(nil)
// 验证MyHandler是否实现了http.Handler
var _ http.Handler = MyHandler{}如果类型未实现接口,此代码会在编译时失败,从而尽早发现错误。
Quick Reference
速查参考
| Pattern | Go Idiom |
|---|---|
| If initialization | |
| Early return | Omit |
| Redeclaration | |
| C-style for | |
| While-style | |
| Infinite loop | |
| Range with key+value | |
| Range value only | |
| Range key only | |
| Parallel assignment | |
| Expression-less switch | |
| Comma cases | |
| No fallthrough | Default behavior (explicit |
| Break from loop in switch | |
| Type switch | |
| Discard value | |
| Side-effect import | |
| Interface check | |
| 模式 | Go惯用写法 |
|---|---|
| 带初始化的If语句 | |
| 提前返回 | if代码块返回时省略 |
| 重声明 | 同一作用域下且有新变量时, |
| C风格for循环 | |
| While风格循环 | |
| 无限循环 | |
| 带键值的Range遍历 | |
| 仅值的Range遍历 | |
| 仅键的Range遍历 | |
| 并行赋值 | |
| 无表达式Switch | |
| 逗号分隔多case | |
| 无自动贯穿 | 默认行为(需要时显式使用 |
| 在Switch中跳出循环 | |
| 类型Switch | |
| 丢弃值 | |
| 副作用导入 | |
| 接口检查 | |
See Also
延伸阅读
- go-style-core: Core Go style principles and formatting
- go-error-handling: Error handling patterns including guard clauses
- go-naming: Naming conventions for loop variables and labels
- go-concurrency: Goroutines, channels, and select statements
- go-defensive: Defensive programming patterns
- go-style-core:Go语言核心风格原则与格式化规范
- go-error-handling:包含守卫子句的错误处理模式
- go-naming:循环变量与标签的命名规范
- go-concurrency:协程(Goroutines)、通道与select语句
- go-defensive:防御式编程模式