go-control-flow

Compare original and translation side by side

🇺🇸

Original

English
🇨🇳

Translation

Chinese

Go 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
do
or
while
loop—only a generalized
for
. There are no parentheses around conditions, and bodies must always be brace-delimited.

来源:《Effective Go》。Go语言的控制结构与C语言相关,但在重要方面存在差异。理解这些差异是编写地道Go代码的关键。
Go语言没有
do
while
循环——只有通用的
for
循环。条件不需要加括号,且循环体必须用大括号包裹。

If Statements

条件判断语句(If Statements)

Basic Form

基础形式

Go's
if
requires braces and has no parentheses around the condition:
go
if x > 0 {
    return y
}
Go语言的
if
语句需要使用大括号,且条件不需要加括号:
go
if x > 0 {
    return y
}

If with Initialization

带初始化的If语句

if
and
switch
accept an optional initialization statement. This is common for scoping variables to the conditional block:
go
// Good: err scoped to if block
if err := file.Chmod(0664); err != nil {
    log.Print(err)
    return err
}
if
switch
支持可选的初始化语句,通常用于将变量的作用域限定在条件代码块内:
go
// 推荐写法:err的作用域限定在if代码块内
if err := file.Chmod(0664); err != nil {
    log.Print(err)
    return err
}

Omit Else for Early Returns

提前返回时省略Else

When an
if
body ends with
break
,
continue
,
goto
, or
return
, omit the unnecessary
else
. This keeps the success path unindented:
go
// 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
}
if
代码块以
break
continue
goto
return
结尾时,可以省略不必要的
else
,这样能让正常执行路径保持无缩进:
go
// 推荐写法:无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
v
may appear in a
:=
declaration even if already declared, provided:
  1. The declaration is in the same scope as the existing
    v
  2. The value is assignable to
    v
  3. At least one other variable is newly created by the declaration
This pragmatic rule makes it easy to reuse a single
err
variable through a chain of operations.
go
// 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
v
is declared in an outer scope,
:=
creates a new variable that shadows it:
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
可以出现在
:=
声明中,即使它已经被声明过,只要满足以下条件:
  1. 声明与现有
    v
    处于同一作用域
  2. 新值可以赋值给
    v
  3. 声明中至少有一个其他变量是新创建的
这条实用规则让我们可以在一系列操作中轻松复用单个
err
变量。
go
// 推荐写法:在多次调用中复用err变量
data, err := fetchData()
if err != nil {
    return err
}
result, err := processData(data)  // err被重新赋值,result被声明
if err != nil {
    return err
}
警告:如果
v
是在外部作用域声明的,
:=
会创建一个新的变量,遮蔽外部的
v
go
// 不推荐写法:意外的变量遮蔽
var err error
if condition {
    x, err := someFunc()  // 这个err遮蔽了外部的err!
    // 外部的err仍然是nil
}

For Loops

循环语句(For Loops)

Go unifies
for
and
while
into a single construct with three forms:
go
// C-style for (only form with semicolons)
for init; condition; post { }

// While-style (condition only)
for condition { }

// Infinite loop
for { }
Go语言将
for
while
统一为单一结构,有三种形式:
go
// C语言风格的for循环(唯一使用分号的形式)
for init; condition; post { }

// While风格(仅保留条件)
for condition { }

// 无限循环
for { }

Range Clause

Range遍历子句

Use
range
to iterate over arrays, slices, strings, maps, and channels:
go
// 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
}
使用
range
遍历数组、切片、字符串、映射(map)和通道:
go
// 遍历键和值
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,
range
iterates over UTF-8 encoded runes (not bytes), handling multi-byte characters automatically.
遍历字符串时,
range
会迭代UTF-8编码的符文(rune)而非字节,自动处理多字节字符。

Parallel 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
switch
is more flexible than C's:
  • Expressions need not be constants or integers
  • Cases are evaluated top to bottom until a match
  • No automatic fall through (no need for
    break
    in each case)
Go语言的
switch
比C语言的更灵活:
  • 表达式不必是常量或整数
  • 从上到下评估case,直到找到匹配项
  • 无自动贯穿(无需在每个case中添加
    break

Expression-less Switch

无表达式的Switch

If the
switch
has no expression, it switches on
true
. This is idiomatic for writing clean
if-else-if
chains:
go
// 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
}
如果
switch
没有表达式,它会基于
true
进行判断。这是编写清晰的
if-else-if
链式判断的惯用写法:
go
// 推荐写法:用无表达式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

break
terminates the switch by default. To break out of an enclosing loop, use a label:
go
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
            }
        }
    }

break
默认只会终止当前switch语句。要跳出外层循环,需使用标签:
go
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 (
v := value.(type)
) since the variable has a different type in each case clause.
When a case lists multiple types (
case int, int64:
), the variable has the interface type.

类型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)
}
惯用写法是复用变量名(
v := value.(type)
),因为变量在每个case子句中都有不同的类型。
当一个case列出多个类型(
case int, int64:
)时,变量的类型为接口类型。

The Blank Identifier

空白标识符

The blank identifier
_
discards values. It's like writing to
/dev/null
.
空白标识符
_
用于丢弃值,类似于写入
/dev/null

Multiple 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 check
Never 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
init()
side effects:
go
import _ "net/http/pprof"  // registers HTTP handlers
import _ "image/png"       // registers PNG decoder
This 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

速查参考

PatternGo Idiom
If initialization
if err := f(); err != nil { }
Early returnOmit
else
when if body returns
Redeclaration
:=
reassigns if same scope + new var
C-style for
for i := 0; i < n; i++ { }
While-style
for condition { }
Infinite loop
for { }
Range with key+value
for k, v := range m { }
Range value only
for _, v := range slice { }
Range key only
for k := range m { }
Parallel assignment
i, j = i+1, j-1
Expression-less switch
switch { case cond: }
Comma cases
case 'a', 'b', 'c':
No fallthroughDefault behavior (explicit
fallthrough
if needed)
Break from loop in switch
break Label
Type switch
switch v := x.(type) { }
Discard value
_, err := f()
Side-effect import
import _ "pkg"
Interface check
var _ Interface = (*Type)(nil)

模式Go惯用写法
带初始化的If语句
if err := f(); err != nil { }
提前返回if代码块返回时省略
else
重声明同一作用域下且有新变量时,
:=
可重新赋值
C风格for循环
for i := 0; i < n; i++ { }
While风格循环
for condition { }
无限循环
for { }
带键值的Range遍历
for k, v := range m { }
仅值的Range遍历
for _, v := range slice { }
仅键的Range遍历
for k := range m { }
并行赋值
i, j = i+1, j-1
无表达式Switch
switch { case cond: }
逗号分隔多case
case 'a', 'b', 'c':
无自动贯穿默认行为(需要时显式使用
fallthrough
在Switch中跳出循环
break Label
类型Switch
switch v := x.(type) { }
丢弃值
_, err := f()
副作用导入
import _ "pkg"
接口检查
var _ Interface = (*Type)(nil)

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:防御式编程模式