选cobra因其自带子命令管理、flag绑定、help生成和bash补全,手写易错且耗时;应扁平化结构、用ANSI色码实现彩色输出、正确使用os.OpenFile标志位确保JSON持久化安全。

为什么选 cobra 而不是手写命令解析
因为 cobra 自带子命令管理、flag 自动绑定、help 自动生成、bash 补全支持——这些你手写一遍至少要 200 行且容易漏兼容性细节。比如 todo add "buy milk" --priority high 这种调用,cobra 一行 cmd.Flags().String("priority", "medium", "task priority") 就搞定,手写得自己 parse os.Args、处理短横线、校验类型、报错格式……而且 --help 输出还难看。
常见错误现象:flag.Parse() 和 cobra 混用导致 flag 被提前消费,后续 cmd.Execute() 报 unknown command "add";或者没调 rootCmd.AddCommand(addCmd),命令根本注册不上。
- 必须在
main()之前完成所有Cmd实例的创建和AddCommand()注册 - 不要在
Run函数里调用flag.Parse()——cobra已经做了 - 如果要用
init()初始化子命令,确保它不依赖未初始化的全局变量(比如还没读取的配置文件)
cobra init 生成的目录结构怎么改才不踩坑
官方 cobra init todo 会建 cmd/ 和 pkg/,但 Todo List 这种小工具根本不需要分层。硬套会导致:命令逻辑散在 cmd/add.go、cmd/list.go,而数据操作(如读写 todos.json)放在 pkg/storage/,结果改个字段就得跳三个文件——实际开发中反而更慢。
建议直接扁平化:main.go 里定义 rootCmd,所有子命令 addCmd、listCmd 都在同一文件声明,数据操作函数(如 loadTodos()、saveTodos())紧挨着放。等项目真长到 50+ 命令再拆。
立即学习“go语言免费学习笔记(深入)”;
- 删掉
pkg/目录,把cmd/root.go重命名为main.go并合并所有cmd/*.go - JSON 文件路径统一用
./todos.json,别用os.UserHomeDir()—— 本地测试时权限和路径容易出错 - 如果加了
--file参数,记得在每个命令里都做os.Stat()检查,否则saveTodos()可能 panic
如何让 todo list 支持颜色和简单表格而不引入大依赖
纯文本输出太难读,但为加颜色引入 github.com/fatih/color 或表格库又太重。其实 Go 标准库的 fmt + ANSI 转义序列就足够:3[32m 是绿色,3[0m 是重置。表格用固定宽度字符串拼接,比调第三方库更可控。
性能影响几乎为零,兼容性也 OK —— 所有现代终端(包括 Windows Terminal、iTerm2、VS Code 内置终端)都支持。唯一例外是某些 CI 日志环境(比如 GitHub Actions 默认不渲染颜色),这时加个 NO_COLOR=1 环境变量判断就行。
- 用
fmt.Printf("\033[32m%d\033[0m %s\n", t.ID, t.Text)渲染 ID 为绿色 - 表格列宽按最长内容算:
fmt.Printf("%-4d %-20s %s\n", t.ID, t.Text[:min(len(t.Text), 20)]+"…", t.Priority) - 检测
os.Getenv("NO_COLOR") != ""时跳过所有\033[序列
持久化用 JSON 文件时,os.OpenFile 的 flag 怎么选
新手常写 os.Create("todos.json"),结果每次启动都清空旧数据。或者用 os.OpenFile(..., os.O_RDWR|os.O_CREATE) 却忘了 os.O_TRUNC,导致写入时从头覆盖但长度没截断,残留脏字节。
正确做法是:读取时用 os.Open(只读),写入时用 os.OpenFile(..., os.O_WRONLY|os.O_CREATE|os.O_TRUNC)。注意 os.O_APPEND 不适用——JSON 不是日志,不能追加。
- 读取失败时,检查是否是
os.IsNotExist(err),如果是就返回空切片,别 panic - 写入前先
json.MarshalIndent()格式化,方便人眼调试;上线后可换json.Marshal()省空间 - 写入后立刻
file.Sync(),避免进程崩溃时数据丢失(对小工具来说值得)
最易被忽略的是文件权限:os.OpenFile 第四个参数传 0644,而不是默认的 0666 —— 否则在某些 Linux 环境下可能被 umask 截断成不可读。










