Install
openclaw skills install @thcjp/golang-toolkit-freeopenclaw skills install @thcjp/golang-toolkit-free本工具为 Go 开发者提供代码陷阱防范与优选实践指引,覆盖并发编程、接口与类型系统、错误处理、集合操作等核心场景。通过自然语言指令驱动,帮助开发者识别和规避 Goroutine 泄漏、Channel 死锁、接口误用、Slice 共享内存等常见陷阱,编写更加可靠、高效的 Go 代码。免费版聚焦个人开发者高频遇到的语言陷阱,提供简明速查表与代码示例。
| 能力模块 | 描述 | 典型陷阱 |
|---|---|---|
| Goroutine 管理 | 协程泄漏检测与防范 | 无发送者的 Channel 阻塞导致泄漏 |
| Channel 使用 | 通道陷阱识别 | 向已关闭 Channel 发送导致 panic |
| Defer 机制 | 延迟执行陷阱 | 循环中 defer 堆积 |
| 接口与类型 | 类型系统陷阱 | nil 接口判断错误 |
| 错误处理 | 错误处理规范 | 忽略返回的 error |
| 集合操作 | Slice/Map 陷阱 | Slice 共享底层数组 |
技术实现要点:核心能力基于input_params参数与output_format配置实现,支持创建/查询/修改/删除等操作模式,通过config_options进行运行时配置。 |
用input_params参数进行配置。
输入: 用户提供核心功能执行所需的指令和必要参数。 处理: 按照skill规范执行核心功能执行操作,遵循单一意图原则。 输出: 返回核心功能执行的执行结果,包含操作状态和输出数据。
input_params参数,支持创建/查询/导出操作用config_options参数进行配置。
输入: 用户提供参数配置与调用所需的指令和必要参数。 处理: 按照skill规范执行参数配置与调用操作,遵循单一意图原则。 输出: 返回参数配置与调用的执行结果,包含操作状态和输出数据。
config_options参数,支持修改/重置/导入操作用output_format参数进行配置。
输入: 用户提供结果处理与输出所需的指令和必要参数。 处理: 按照skill规范执行结果处理与输出操作,遵循单一意图原则。 输出: 返回结果处理与输出的执行结果,包含操作状态和输出数据。
output_format参数,支持导出/保存/转换操作
能力覆盖范围:本skill的核心能力覆盖以下场景关键词:语言陷阱防范与优、选实践指南、覆盖并发、错误处理等核心场、开发者的代码陷阱、防范工具、帮助编写可靠的、陷阱识别与正确使、接口与类型系统陷、阱规避、错误处理优选实践、String、常见陷阱等。这些关键词对应description中声明的使用场景,均已在上述能力点中提供对应的操作支持。开发者编写了并发代码,担心 Goroutine 泄漏。
用户:我写了这段并发代码,帮我检查是否有 Goroutine 泄漏
助手:检查以下常见泄漏模式
1. Channel 无发送者 → 泄漏
// 错误:没有 goroutine 向 ch 发送数据
func bad() {
ch := make(chan int)
go func() {
<-ch // 永远阻塞
}()
}
2. Context 未检查 → 泄漏
// 错误:未检查 ctx.Done()
func bad(ctx context.Context) {
go func() {
for {
// 应该检查 ctx.Done()
select {
case <-ctx.Done():
return
default:
time.Sleep(time.Second)
}
}
}()
}
3. 正确模式
func good(ctx context.Context) {
ch := make(chan int, 1)
go func() {
defer close(ch)
for {
select {
case <-ctx.Done():
return
case ch <- compute():
}
}
}()
}
开发者使用接口时遇到 nil 判断异常。
用户:为什么我的接口变量明明是 nil,但判断 i != nil 却是 true
助手:这是 Go 接口的经典陷阱
// 陷阱:nil 具体值放入接口后不为 nil
var p *MyType = nil
var i interface{} = p
fmt.Println(i == nil) // false!
// 原因:接口包含 (类型, 值) 两个部分
// p 的类型是 *MyType,值是 nil
// 接口 i 的类型不为 nil,所以 i != nil
// 正确做法:直接用 nil 接口
var i interface{} = nil
fmt.Println(i == nil) // true
// 或在赋值前判断
func process(v interface{}) {
if v == nil {
return
}
// 使用类型断言
if p, ok := v.(*MyType); ok && p != nil {
// 安全使用 p
}
}
开发者修改 Slice 时发现原始数据被意外修改。
// 陷阱:Slice 共享底层数组
func main() {
original := []int{1, 2, 3, 4, 5}
// 切片共享内存
sub := original[1:3] // [2, 3]
sub[0] = 99
fmt.Println(original) // [1, 99, 3, 4, 5] 原始数据被修改!
// 正确做法:使用 copy 创建独立副本
sub2 := make([]int, 2)
copy(sub2, original[1:3])
sub2[0] = 99
fmt.Println(original) // [1, 2, 3, 4, 5] 原始数据不变
// Append 可能或可能不重新分配
appended := append(original, 6)
appended[0] = 0
fmt.Println(original) // 可能被修改也可能不被修改,取决于容量
}
// 安全的 Slice 操作模式
func safeSlice(original []int, start, end int) []int {
result := make([]int, end-start)
copy(result, original[start:end])
return result
}
// 1. 始终确保 Channel 关闭或使用 context
func worker(ctx context.Context, ch <-chan int) {
for {
select {
case <-ctx.Done():
return
case val, ok := <-ch:
if !ok {
return
}
// 处理 val
}
}
}
// 2. 使用 WaitGroup 等待所有 goroutine
func processAll(items []int) {
var wg sync.WaitGroup
for _, item := range items {
wg.Add(1)
go func(i int) {
defer wg.Done()
// 处理 i
}(item)
}
wg.Wait()
}
// 3. 使用缓冲 Channel 防止阻塞
func pipeline() {
ch := make(chan int, 10) // 适当缓冲
go func() {
defer close(ch)
for i := 0; i < 100; i++ {
ch <- i
}
}()
for val := range ch {
// 处理 val
}
}
// 1. 只有发送方关闭 Channel
func producer(ch chan<- int) {
defer close(ch) // 发送方负责关闭
for i := 0; i < 10; i++ {
ch <- i
}
}
// 2. 向已关闭 Channel 发送会 panic
func bad(ch chan int) {
close(ch)
ch <- 1 // panic: send on closed channel
}
// 3. 从 nil Channel 接收永远阻塞
func nilChannelTrap() {
var ch chan int // nil channel
<-ch // 永远阻塞
}
// 4. select 多路复用
func selectPattern(ch1, ch2 <-chan int) {
for {
select {
case v1 := <-ch1:
// 处理 v1
case v2 := <-ch2:
// 处理 v2
}
}
}
// 1. 始终检查 error
func readFile(path string) ([]byte, error) {
data, err := os.ReadFile(path)
if err != nil {
return nil, fmt.Errorf("读取文件失败: %w", err)
}
return data, nil
}
// 2. 使用 errors.Is 判断包装的错误
if errors.Is(err, os.ErrNotExist) {
// 文件不存在
}
// 3. 哨兵错误定义
var ErrNotFound = errors.New("not found")
// 4. 自定义错误类型
type ValidationError struct {
Field string
Message string
}
func (e *ValidationError) Error() string {
return fmt.Sprintf("%s: %s", e.Field, e.Message)
}
// 1. defer 参数立即求值
func deferArgs() {
i := 1
defer fmt.Println(i) // 输出 1,不是 2
i = 2
}
// 2. 循环中 defer 堆积
func loopDeferTrap() {
for i := 0; i < 1000000; i++ {
defer func() {}() // 所有 defer 在函数结束时才执行
}
}
// 正确做法:将循环体提取为函数
func loopDeferFixed() {
for i := 0; i < 1000000; i++ {
func() {
defer func() {}()
}()
}
}
// 3. 命名返回值与 defer
func namedReturn() (err error) {
defer func() {
if err != nil {
err = fmt.Errorf("包装: %w", err)
}
}()
// ...
return nil
}
// 1. nil map 读取返回零值,写入 panic
var m map[string]int
_ = m["key"] // 返回 0,不 panic
m["key"] = 1 // panic: assignment to entry in nil map
// 正确:使用 make 初始化
m := make(map[string]int)
m["key"] = 1 // OK
// 2. Map 迭代顺序随机
for k, v := range m {
// 顺序不保证,每次运行可能不同
}
// 3. Map 非并发安全
// 错误:并发读写 map
func concurrentMapBad() {
m := make(map[int]int)
go func() {
m[1] = 1 // 可能 panic
}()
go func() {
_ = m[1] // 可能 panic
}()
}
// 正确:使用 sync.Map 或 mutex
var mu sync.RWMutex
m := make(map[int]int)
func set(key, val int) {
mu.Lock()
defer mu.Unlock()
m[key] = val
}
func get(key int) int {
mu.RLock()
defer mu.RUnlock()
return m[key]
}
始终检查 error:忽略 error 是最常见的 Go 缺陷
if err != nil {
return err
}
使用 context 控制 goroutine 生命周期:避免泄漏
只有发送方关闭 Channel:接收方关闭会导致发送方 panic
Slice 操作用 copy:避免共享内存的意外修改
Map 并发使用 sync.Map 或 mutex:原生 map 非线程安全
defer 用于资源释放:确保文件、连接、锁被释放
类型断言用 comma-ok 模式:避免 panic
v, ok := i.(Type)
// 使用 runtime 查看 goroutine 数量
import "runtime"
fmt.Println(runtime.NumGoroutine())
// 生产环境使用 pprof
import _ "net/http/pprof"
go http.ListenAndServe("localhost:6060", nil)
// 访问 http://localhost:6060/debug/pprof/goroutine
// 错误
for _, f := range files {
file, _ := os.Open(f)
defer file.Close() // 堆积到函数结束
}
// 正确
for _, f := range files {
func() {
file, _ := os.Open(f)
defer file.Close()
// 处理文件
}()
}
// 使用 %w 包装错误
if err != nil {
return fmt.Errorf("处理用户 %s 失败: %w", userID, err)
}
// 上游可以使用 errors.Is 判断
if errors.Is(err, sql.ErrNoRows) {
// 处理未找到
}
s := "你好"
fmt.Println(len(s)) // 6(字节数,UTF-8 编码每个中文 3 字节)
// 获取字符数
fmt.Println(utf8.RuneCountInString(s)) // 2
// range 遍历的是 rune
for i, r := range s {
fmt.Printf("%d: %c\n", i, r)
}
// 指针接收者:方法可以修改对象状态
func (s *Server) Start() {
s.running = true
}
// 值接收者:方法不会修改对象
func (s Server) Status() string {
return s.status
}
// 规则:如果有一个方法用指针接收者,所有方法都应该用指针接收者
// 只有发送方关闭,使用 sync.Once 确保只关闭一次
type SafeChan struct {
ch chan struct{}
once sync.Once
}
func (sc *SafeChan) Close() {
sc.once.Do(func() {
close(sc.ch)
})
}
| 依赖项 | 类型 | 是否必需 | 获取方式 |
|---|---|---|---|
| Go | 编译器/运行时 | 必需 | golang.org 下载 |
| LLM API | API | 必需 | 由 Agent 内置 LLM 提供 |