mirror of
https://github.com/Rain-kl/OpenFlare.git
synced 2026-10-10 17:26:38 +08:00
wavelet init
This commit is contained in:
@@ -0,0 +1,193 @@
|
||||
---
|
||||
name: go-control-flow
|
||||
description: Use when writing conditionals, loops, or switch statements in Go — including if with initialization, early returns, for loop forms, range, switch, type switches, and blank identifier patterns. Also use when writing a simple if/else or for loop, even if the user doesn't mention guard clauses or variable scoping. Does not cover error flow patterns (see go-error-handling).
|
||||
license: Apache-2.0
|
||||
metadata:
|
||||
sources: "Effective Go, Google Style Guide"
|
||||
---
|
||||
|
||||
# Go 控制流
|
||||
|
||||
> 在使用 switch 语句、类型 switch 或带标签的 break 时,阅读 [references/SWITCH-PATTERNS.md](references/SWITCH-PATTERNS.md)
|
||||
|
||||
> 在使用 `_`、空白标识符导入或编译时接口检查时,阅读 [references/BLANK-IDENTIFIER.md](references/BLANK-IDENTIFIER.md)
|
||||
|
||||
---
|
||||
|
||||
## 带初始化的 If
|
||||
|
||||
`if` 和 `switch` 接受可选的初始化语句。使用它将变量限定在条件块作用域内:
|
||||
|
||||
```go
|
||||
if err := file.Chmod(0664); err != nil {
|
||||
log.Print(err)
|
||||
return err
|
||||
}
|
||||
```
|
||||
|
||||
如果需要在 `if` 之后超出几行的范围使用该变量,请单独声明并使用标准 `if`:
|
||||
|
||||
```go
|
||||
x, err := f()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
// 大量使用 x 的代码
|
||||
```
|
||||
|
||||
## 缩进错误流(守卫子句)
|
||||
|
||||
当 `if` 主体以 `break`、`continue`、`goto` 或 `return` 结尾时,省略不必要的 `else`。保持成功路径不缩进:
|
||||
|
||||
```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)
|
||||
```
|
||||
|
||||
当 `if` 已经返回时,绝不要将正常流程埋在 `else` 中。
|
||||
|
||||
---
|
||||
|
||||
## 重新声明和重新赋值
|
||||
|
||||
`:=` 短声明允许在同一作用域中重新声明变量:
|
||||
|
||||
```go
|
||||
f, err := os.Open(name) // 声明 f 和 err
|
||||
d, err := f.Stat() // 声明 d,重新赋值 err
|
||||
```
|
||||
|
||||
变量 `v` 即使已经声明过,也可以出现在 `:=` 声明中,前提是:
|
||||
|
||||
1. 声明在与现有 `v` **相同的作用域**中
|
||||
2. 值**可赋值**给 `v`
|
||||
3. 声明中至少创建了**一个其他新变量**
|
||||
|
||||
### 变量遮蔽
|
||||
|
||||
**警告**:如果 `v` 在外层作用域中声明,`:=` 会创建一个**新的**遮蔽变量 — 这是常见的 bug 来源:
|
||||
|
||||
```go
|
||||
// Bug:if 块内的 ctx 遮蔽了外层的 ctx
|
||||
if *shortenDeadlines {
|
||||
ctx, cancel := context.WithTimeout(ctx, 3*time.Second)
|
||||
defer cancel()
|
||||
}
|
||||
// 此处的 ctx 仍然是原始的 — 被遮蔽的 ctx 没有逃逸
|
||||
|
||||
// 修复:使用 = 而不是 :=
|
||||
var cancel func()
|
||||
ctx, cancel = context.WithTimeout(ctx, 3*time.Second)
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## For 循环
|
||||
|
||||
Go 的 `for` 是唯一的循环结构,统一了 `while`、`do-while` 和 C 风格的 `for`:
|
||||
|
||||
```go
|
||||
// 仅条件(Go 的 "while")
|
||||
for x > 0 {
|
||||
x = process(x)
|
||||
}
|
||||
|
||||
// 无限循环
|
||||
for {
|
||||
if done() { break }
|
||||
}
|
||||
|
||||
// C 风格的三组件形式
|
||||
for i := 0; i < n; i++ { ... }
|
||||
```
|
||||
|
||||
### Range
|
||||
|
||||
`range` 遍历切片、映射、字符串和通道:
|
||||
|
||||
```go
|
||||
for i, v := range slice { ... } // 索引 + 值
|
||||
for k, v := range myMap { ... } // 键 + 值(顺序不确定)
|
||||
for i, r := range "héllo" { ... } // 字节索引 + rune(不是字节)
|
||||
for v := range ch { ... } // 接收直到通道关闭
|
||||
```
|
||||
|
||||
**关键规则:**
|
||||
- 对字符串 range 产生 **rune**,不是字节 — `i` 是字节偏移量
|
||||
- 对映射 range 的顺序**不确定** — 不要依赖它
|
||||
- 使用 `_` 丢弃索引或值:`for _, v := range slice`
|
||||
|
||||
### 并行赋值
|
||||
|
||||
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:带标签的 Break
|
||||
|
||||
`for` 循环内 `switch` 中的 `break` 只会中断 switch。使用带标签的 `break` 退出外层循环:
|
||||
|
||||
```go
|
||||
Loop:
|
||||
for _, v := range items {
|
||||
switch v.Type {
|
||||
case "done":
|
||||
break Loop // 中断 for 循环
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
关于类型 switch,参见 **go-interfaces**:类型 Switch。
|
||||
|
||||
---
|
||||
|
||||
## 空白标识符
|
||||
|
||||
**绝不要随意丢弃错误** — 空指针解引用 panic 可能随之而来。
|
||||
|
||||
在编译时验证接口实现:`var _ io.Writer = (*MyType)(nil)`。
|
||||
参见 **go-interfaces** 中的接口满足检查模式。
|
||||
|
||||
---
|
||||
|
||||
## 快速参考
|
||||
|
||||
| 模式 | Go 惯用法 |
|
||||
|------|-----------|
|
||||
| If 初始化 | `if err := f(); err != nil { }` |
|
||||
| 提前返回 | 当 if 主体返回时省略 `else` |
|
||||
| 重新声明 | `:=` 在相同作用域 + 新变量时重新赋值 |
|
||||
| 遮蔽陷阱 | `:=` 在内层作用域创建新变量 |
|
||||
| 并行赋值 | `i, j = i+1, j-1` |
|
||||
| 无表达式 switch | `switch { case cond: }` |
|
||||
| 逗号 case | `case 'a', 'b', 'c':` |
|
||||
| 无 fallthrough | 默认行为(需要时显式使用 `fallthrough`) |
|
||||
| 从 switch 中跳出循环 | `break Label` |
|
||||
| 丢弃值 | `_, err := f()` |
|
||||
| 副作用导入 | `import _ "pkg"` |
|
||||
| 接口检查 | `var _ Interface = (*Type)(nil)` |
|
||||
|
||||
---
|
||||
|
||||
## 相关技能
|
||||
|
||||
- **错误流程**:在构建守卫子句、提前返回或错误优先模式时,参见 [go-error-handling](../go-error-handling/SKILL.md)
|
||||
- **类型 switch**:在使用类型 switch、comma-ok 惯用法或接口满足检查时,参见 [go-interfaces](../go-interfaces/SKILL.md)
|
||||
- **减少嵌套**:在减少嵌套深度或解决格式问题时,参见 [go-style-core](../go-style-core/SKILL.md)
|
||||
- **变量作用域**:在使用 if 初始化、`:=` 重新声明或减少变量作用域时,参见 [go-declarations](../go-declarations/SKILL.md)
|
||||
@@ -0,0 +1,71 @@
|
||||
# 空白标识符模式
|
||||
|
||||
空白标识符 `_` 在 Go 中有多种用途:丢弃不需要的值、为副作用导入包、以及在编译时验证接口实现。
|
||||
|
||||
---
|
||||
|
||||
## 多重赋值
|
||||
|
||||
使用 `_` 丢弃多值表达式中不需要的值:
|
||||
|
||||
```go
|
||||
if _, err := os.Stat(path); os.IsNotExist(err) {
|
||||
fmt.Printf("%s does not exist\n", path)
|
||||
}
|
||||
```
|
||||
|
||||
### 绝不要随意丢弃错误
|
||||
|
||||
静默丢弃错误会引发空指针 panic:
|
||||
|
||||
```go
|
||||
// 不好:忽略错误会在路径不存在时崩溃
|
||||
fi, _ := os.Stat(path)
|
||||
if fi.IsDir() { ... } // 空指针解引用
|
||||
```
|
||||
|
||||
如果确实不需要错误,请记录原因:
|
||||
|
||||
```go
|
||||
_ = logger.Sync() // 尽力刷新;错误不可操作
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 副作用导入
|
||||
|
||||
使用空白标识符仅为了 `init()` 副作用而导入包:
|
||||
|
||||
```go
|
||||
import _ "net/http/pprof" // 注册 HTTP 处理器
|
||||
import _ "image/png" // 注册 PNG 解码器
|
||||
```
|
||||
|
||||
这通常用于注册驱动、编解码器或调试处理器,它们在 `init()` 期间将自己注册到注册表中。
|
||||
|
||||
---
|
||||
|
||||
## 接口实现检查
|
||||
|
||||
在编译时验证类型是否实现了接口,方法是将 nil 指针赋值给接口类型的空白标识符变量:
|
||||
|
||||
```go
|
||||
var _ io.Writer = (*MyType)(nil)
|
||||
```
|
||||
|
||||
如果 `*MyType` 不满足 `io.Writer`,这会产生编译错误,在运行时之前捕获缺失的方法。
|
||||
|
||||
**何时使用**:将此检查放在定义该类型的同一文件中,通常在类型声明之后。当类型必须满足另一个包中定义的接口时特别有用。
|
||||
|
||||
参见 [go-interfaces](../../go-interfaces/SKILL.md):接口满足检查,获取关于何时何地使用此模式的完整指导。
|
||||
|
||||
---
|
||||
|
||||
## 快速参考
|
||||
|
||||
| 模式 | 语法 |
|
||||
|------|------|
|
||||
| 丢弃值 | `_, err := f()` |
|
||||
| 在 if 初始化中丢弃 | `if _, err := f(); err != nil { }` |
|
||||
| 副作用导入 | `import _ "pkg"` |
|
||||
| 接口检查 | `var _ Interface = (*Type)(nil)` |
|
||||
@@ -0,0 +1,109 @@
|
||||
# Switch 模式
|
||||
|
||||
Go `switch` 语句的详细模式,包括无表达式 switch、逗号 case、break 行为和带标签的 break。
|
||||
|
||||
---
|
||||
|
||||
## 无自动 Fallthrough
|
||||
|
||||
Go `switch` 的 case 默认**不会** fall through(与 C/Java 不同)。每个 case 主体隐式地 break。仅在明确需要时使用 `fallthrough` — 这在惯用 Go 中很少见。
|
||||
|
||||
```go
|
||||
switch n {
|
||||
case 1:
|
||||
fmt.Println("one")
|
||||
// 无 fallthrough — 下一个 case 不会执行
|
||||
case 2:
|
||||
fmt.Println("two")
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 无表达式 Switch
|
||||
|
||||
没有表达式的 `switch` 对 `true` 进行 switch。在将单个变量与多个条件进行比较时,用它来替代 if-else-if 链:
|
||||
|
||||
```go
|
||||
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
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 逗号分隔的 Case
|
||||
|
||||
多个值可以使用逗号共享一个 case 主体 — 不需要 `fallthrough`:
|
||||
|
||||
```go
|
||||
func shouldEscape(c byte) bool {
|
||||
switch c {
|
||||
case ' ', '?', '&', '=', '#', '+', '%':
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 带标签的 Break
|
||||
|
||||
`switch` 中的 `break` 仅终止 switch,**不会**终止外层的 `for` 循环。使用标签来跳出循环:
|
||||
|
||||
```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 循环
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
另一个常见模式 — 从 switch 内部中断 range 循环:
|
||||
|
||||
```go
|
||||
Loop:
|
||||
for _, v := range items {
|
||||
switch v.Type {
|
||||
case "done":
|
||||
break Loop // 中断 for 循环
|
||||
case "skip":
|
||||
break // 仅中断 switch
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**经验法则**:当 `for` 循环内有 `switch` 且需要从 case 中退出循环时,始终使用带标签的 break。
|
||||
|
||||
---
|
||||
|
||||
## 类型 Switch
|
||||
|
||||
关于类型 switch(`switch v := x.(type)`),参见 [go-interfaces](../../go-interfaces/SKILL.md):类型 Switch。
|
||||
|
||||
---
|
||||
|
||||
## 快速参考
|
||||
|
||||
| 模式 | 语法 |
|
||||
|------|------|
|
||||
| 无表达式 switch | `switch { case cond: }` |
|
||||
| 逗号 case | `case 'a', 'b', 'c':` |
|
||||
| 无 fallthrough | 默认行为;需要时使用 `fallthrough` 关键字 |
|
||||
| 仅中断 switch | case 内使用 `break` |
|
||||
| 中断外层循环 | 使用带标签的 `for` 和 `break Label` |
|
||||
Reference in New Issue
Block a user