ast.inspect 是统计函数数量最稳妥的方法,它深度优先遍历整棵树,自动跳过 nil 字段且不栈溢出;需类型断言 *ast.funcdecl 并过滤 init 函数,直接遍历 f.decls 会漏掉嵌套函数。

用 ast.Inspect 遍历 *ast.FuncDecl 统计函数数量
直接遍历 ast.File.Decls 容易漏掉嵌套函数(如 FuncLit)或 panic,ast.Inspect 是最稳的入口。它会深度优先走完整棵树,自动跳过 nil 字段,且不栈溢出。
关键点是类型断言必须写对:只认 *ast.FuncDecl,不包括匿名函数、方法、init 函数(虽然 init 也是 *ast.FuncDecl,但名字是 "init",需额外过滤)。
- 先判断
node是否为*ast.FuncDecl,再检查funcDecl.Name.Name != "init"才计入常规函数 - 若要包含
init和main,就去掉名字判断;若只统计导出函数,加ast.IsExported(funcDecl.Name.Name) - 别在回调里修改任何 node 字段——AST 是只读副本,改了无效
ast.Inspect(f, func(n ast.Node) bool {
if fn, ok := n.(*ast.FuncDecl); ok && fn.Name.Name != "init" {
count++
}
return true
})
为什么 parser.ParseFile 后不能直接 len(f.Decls)
f.Decls 只包含文件顶层声明,比如 func hello()、var x int、type T struct{},但完全不包含函数体内的匿名函数(go func(){} 或 func() int { return 42 })。所以直接算 len(f.Decls) 会严重低估。
更隐蔽的问题是:如果源码有语法错误,parser.ParseFile 默认只报第一个错,其余 AST 节点可能缺失或不完整。建议加上 parser.AllErrors 模式:
- 传参时第 4 个参数用
parser.Mode(parser.AllErrors | parser.ParseComments) - 否则遇到
func foo() { if true { }这种缺右括号的代码,后面所有函数都可能丢掉
统计前必须判空:FuncDecl.Type.Params 和 Results
很多教程示例直接写 fn.Type.Params.List,但无参函数(如 func hello())会让 Params 为 nil,不是空切片——直接访问会 panic。
这不是统计函数数量的直接坑,但一旦你后续想扩展功能(比如统计参数个数、返回值个数),这里就是高频崩溃点:
- 安全写法:先
if fn.Type.Params != nil再遍历Params.List - 同理,
if fn.Type.Results != nil才处理返回值 - 函数名始终可用:
fn.Name.Name,但init和main都合法,别默认当成业务函数
想快一点?换 inspector.New + Nodes
如果只是单纯数函数,ast.Inspect 已够用;但如果你还要同时提取签名、找 defer、查未使用变量,重复调用 ast.Inspect 多次会变慢。golang.org/x/tools/go/ast/inspector 更合适。
它一次性构建节点索引,后续匹配极快。注意初始化方式:
- 必须传
[]ast.Node{f}(注意是切片,且元素是*ast.File类型) - 匹配函数用
insp.Nodes([]ast.Node{(*ast.FuncDecl)(nil)}),不是字符串"FuncDecl" - 低于 Go 1.21 的项目慎用,部分旧工具链不兼容
真正容易被忽略的是:统计结果依赖解析是否完整。哪怕只少 parse 了一个文件,总数就错。别只测单个 .go 文件就认为逻辑正确。
golang免费学习笔记(深入):立即使用
在学习笔记中,你将探索golang的核心概念和高级技巧!











