
字节笔记本
2026年10月7日 · 约 11 分钟读完
缺什么功能就引哪一个库
Go 没有一个官方库把字符串、时间、文件、加解密全部包进同一个名字里。对话里对着这类需求和 Java 里的 hutool 对比,给出的是按功能分开的几个库:切片用 go-funk,字符串和文件用 goutil 或 lancet,时间用 carbon。官方负责依赖的仍是 go mod,这些库是依赖,不是第二套模块系统。

字符串、切片、时间分开引
github.com/thoas/go-funk 的样本是切片里找元素:funk.Contains(names, "John") 对 []string{"John", "Jane"} 得到真。funk.Map 把 []int{1, 2, 3} 每一项乘 2。这是切片变换,不是字符串工具。
github.com/gookit/goutil 的样本把 hello_world 收成驼峰 helloWorld,用的是 goutil.Str.CamelCase。文件是否存在用 goutil.FileExists。切片是否包含用 goutil.Slice.Contains。一个库里分了字符串、文件、切片三块,调用时仍要写明是哪一块。
github.com/duke-git/lancet/v2 的样本更碎:strutil.ReverseStr("hello") 得到 "olleh",random.RandInt(1, 100) 取随机整数,cryptor.Md5String 做摘要。导入时对话建议按子包引,例如 github.com/duke-git/lancet/v2/strutil,而不是把整个模块里用不到的路径都写进业务文件。
时间用 github.com/golang-module/carbon/v2。carbon.Parse("2020-08-05 13:14:15").AddDays(1) 在解析结果上加一天。carbon.Now().SubMonth() 从当前时间减一个月。格式串要和 Parse 的布局一致,换一种日期写法要改解析,而不是改 AddDays。
不要为了像一个工具类再包一层
对话后半写了一个 Utils 结构,里面放 StrUtil、TimeUtil、FileUtil,NewUtils 里分别 strutil.New()、carbon.Now()、fsutil.New()。这是把三个库握在一个值上。业务如果只需要反转字符串,仍然应该直接调用 strutil,而不是先构造一个什么都有的 Utils。握在一起的代价是:时间字段在 New 时就固定成了 carbon.Now() 的那一瞬间,后面再用这个字段表示“现在”,得到的是构造时刻,不是调用时刻。
go-zero 在对话里被当成另一类例子,不是又一个字符串库。样本只演示了 collection.SafeMap 的 Set。那是并发安全的 map,来自 github.com/zeromicro/go-zero/core/collection。要用这一个类型,就只引这个包。对话同时说它重,因为整套框架还带服务、配置和生成代码。只缺一个安全 map 时,不必为了 map 把整套框架引进模块。
协程池是另一条线。样本用 github.com/panjf2000/ants/v2,ants.NewPool(10000),defer pool.Release(),pool.Submit 提交函数。池的大小 10000 是样本数字,不是每台机器都该开这么多。提交失败时样本用了空白标识符接住 NewPool 的错误,生产里这个错误要看,池没建成就 Submit 会直接失败。
依赖仍由 go mod 管理
这些库的安装方式就是普通模块。对话开头写的是 go mod init、go get、go mod tidy。工具库多,不表示要换一套包管理器。go mod tidy 会按源码里的 import 增减 go.sum。引了 lancet 却只在注释里写路径,tidy 不会把它留在依赖里。
选型按对话里的划分:要全面、能接受重,看 go-zero 那一类;要小接口,看 lancet;要字符串文件切片放在同一导入风格下,看 goutil;要 Map、Contains 这种函数式切片操作,看 go-funk。时间单独用 carbon,不要用字符串库去解析日期。
日志、命令行、HTTP、ORM、校验在对话前半还点了 viper、cobra、gin、gorm、zap、validator。那是常见组合,不是 hutool 的替代品。每个仍然单独 go get。已经在用 gin 和 gorm 的项目,不必再为了“统一”把它们换成某一个工具库里的子集。

每个样本只证明它写出来的那一个调用
go-funk 的数组是 ["John", "Jane"],Contains 找 "John" 为真。没写找 "jane" 或大小写不同的结果,不要假定它忽略大小写。Map 的输入是 1, 2, 3,函数把每一项乘 2。结果是一个新的列表,原列表还在。这是变换,不是原地修改。
goutil 的驼峰样本输入是 hello_world,输出是 helloWorld。下划线怎么切,只对这个输入成立。文件样本是 FileExists 接收一个路径字符串,返回是否存在。路径写错会得到假,不会抛“文件找不到”的错。切片样本是 ["a", "b", "c"] 里找 "a"。这三个调用分属 Str、文件函数和 Slice,不要记成一个 goutil.Contains。
lancet 要按子包进口。反转 "hello" 得到 "olleh",来自 strutil。RandInt(1, 100) 来自 random,区间按样本是从 1 到 100。Md5String("lancet") 来自 cryptor,得到的是摘要字符串,不是加密后还能解密的密文。摘要不能当口令存储方案。carbon 的解析串是 "2020-08-05 13:14:15",AddDays(1) 加的是一天,Now().SubMonth() 减的是一个月。解析失败时不要继续链式调用后半段。
Utils 结构把这三块握在一起时,TimeUtil 在 New 里被设成当时的 carbon.Now()。过了几分钟再读这个字段,它仍是构造时的时间。要“现在”,就在调用点写 carbon.Now(),不要从结构体里拿。字符串和文件若只使用一次,直接调用函数,不经过这个结构。
ants 的池大小样本是 10000。NewPool 的错误被样本忽略。池创建失败时后面的 Submit 没有池可用。defer pool.Release() 要写在创建成功之后。任务函数里不要再假设池会等待任务结束才 Release,进程退出前要自己保证任务已经完成,或者接受被丢掉。
go-zero 的样本只有 SafeMap 的 Set("key", "value")。没有 Get 的示例,就不要在文章里编一个返回值。需要并发 map 时再引 core/collection。其余字符串需求不要改道到这个框架里。
缺什么引什么
没有一个包替代全部标准库。字符串用 goutil 或 lancet 的 strutil,切片用 go-funk 或 goutil 的 Slice,时间用 carbon,文件是否存在用 goutil。SafeMap 只在需要并发 map 时从 go-zero 的 collection 引。协程池用 ants 时要处理 NewPool 的错误,并在结束时 Release。依赖进出仍是 go get 和 go mod tidy。
引依赖之前先对上导入路径。go-funk 是 github.com/thoas/go-funk。goutil 是 github.com/gookit/goutil。lancet 是 github.com/duke-git/lancet/v2,字符串、随机数、摘要再加 strutil、random、cryptor。carbon 是 github.com/golang-module/carbon/v2。ants 是 github.com/panjf2000/ants/v2。SafeMap 来自 github.com/zeromicro/go-zero/core/collection。路径写错时 go get 会失败,不要改成一个听起来像的模块名。
每个库只保留样本里出现的调用,不要假设相邻函数也存在。驼峰只演示了 hello_world 到 helloWorld。反转只演示了 hello 到 olleh。摘要只演示了 Md5String,它不可逆。随机数样本是 RandInt(1, 100)。时间解析串是 2020-08-05 13:14:15。池的 NewPool(10000) 必须看错误,成功后再 defer Release。Utils 里的时间在构造时被固定,要当前时间就在调用点写 carbon.Now()。只缺一个函数时不要把 go-zero 整套引进来。go mod tidy 以源码里的 import 为准,注释里的路径不算依赖。
再按库把样本钉死,避免串库。函数式切片那一个,包含判断的例子是两个英文名字,映射是把三个连续整数各乘二。工具集合那一个,驼峰只演示了带下划线的问候,文件存在性对错误路径给假,切片包含只找了第一个字母。另一套按子包拆开的库里,反转只演示了一个短单词。随机整数的区间从一开始到一百。摘要函数不可逆,不能当口令方案。时间库先按给定的年月日时分秒解析,再加一天或减一个月。协程池创建必须看错误,成功后才登记释放。并发映射的样本只有写入一对键值。没有读取示例就不要补一个返回值。依赖整理只认真正出现在源码导入里的路径。注释里的地址不会留下。只为了一个安全映射,不要把整套服务框架引进模块。缺哪一种能力就只加那一个导入。不要为了整齐把五个库包进同一个结构体。时间不要在构造时固定成当时的现在。要当前时间就在调用的那一行再取一次。



