<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom"><channel><title>Debug on DK Blog</title><link>/tags/debug/</link><description>Recent content in Debug on DK Blog</description><generator>Hugo -- gohugo.io</generator><language>zh-CN</language><lastBuildDate>Thu, 18 Sep 2025 20:42:00 +0800</lastBuildDate><atom:link href="/tags/debug/index.xml" rel="self" type="application/rss+xml"/><item><title>几个我觉得值得放进 Go 项目的开源工具</title><link>/dkblog/025-go-open-source-tools/</link><pubDate>Thu, 18 Sep 2025 20:42:00 +0800</pubDate><guid>/dkblog/025-go-open-source-tools/</guid><description>&lt;img src="https://images.unsplash.com/photo-1555066931-4365d14bab8c?auto=format&amp;fit=crop&amp;w=900&amp;q=60" alt="Featured image of post 几个我觉得值得放进 Go 项目的开源工具" /&gt;&lt;p&gt;最近整理了一下几个 Go 项目里的开发工具。不是那种“必装清单”，更多是我在实际项目里会愿意留下来的东西。&lt;/p&gt;
&lt;p&gt;有些工具刚开始看起来只是锦上添花，但项目稍微变大之后，它们能少掉不少重复劳动。&lt;/p&gt;
&lt;p&gt;我现在选工具有个很朴素的标准：它最好能落到项目里的固定入口，而不是只存在于某个人的本机习惯里。比如 &lt;code&gt;golangci-lint&lt;/code&gt; 能进 CI，&lt;code&gt;gotestsum&lt;/code&gt; 能产出报告，&lt;code&gt;GoReleaser&lt;/code&gt; 能跟 tag 发布绑定，这类工具的收益会比“我本地装了很好用”稳定得多。&lt;/p&gt;
&lt;h2 id="delve调试-go-程序还是绕不开它"&gt;&lt;a href="#delve%e8%b0%83%e8%af%95-go-%e7%a8%8b%e5%ba%8f%e8%bf%98%e6%98%af%e7%bb%95%e4%b8%8d%e5%bc%80%e5%ae%83" class="header-anchor"&gt;&lt;/a&gt;Delve：调试 Go 程序还是绕不开它
&lt;/h2&gt;&lt;p&gt;Go 里当然可以一直靠日志定位问题，但遇到 goroutine、变量状态、条件分支比较绕的时候，还是调试器更直接。&lt;/p&gt;
&lt;p&gt;Delve 是 Go 生态里比较常用的调试器，命令也不复杂：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt" id="hl-0-1"&gt;&lt;a class="lnlinks" href="#hl-0-1"&gt;1&lt;/a&gt;
&lt;/span&gt;&lt;span class="lnt" id="hl-0-2"&gt;&lt;a class="lnlinks" href="#hl-0-2"&gt;2&lt;/a&gt;
&lt;/span&gt;&lt;span class="lnt" id="hl-0-3"&gt;&lt;a class="lnlinks" href="#hl-0-3"&gt;3&lt;/a&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;dlv debug
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;dlv &lt;span class="nb"&gt;test&lt;/span&gt; ./...
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;dlv attach &amp;lt;pid&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;我比较常用的是本地 &lt;code&gt;dlv debug&lt;/code&gt;，还有偶尔调测试时用 &lt;code&gt;dlv test&lt;/code&gt;。如果服务跑在容器里，远程调试要注意端口、权限和编译参数，不要默认把调试端口暴露到外网。&lt;/p&gt;
&lt;p&gt;还有一个实际点：不要把 Delve 当成日志的替代品。它适合看“当时这个变量是什么”“这个分支为什么走到这里”，但线上问题还是要靠日志、指标和 trace 留证据。调试器解决的是复现后的观察问题，不解决线上证据不足。&lt;/p&gt;
&lt;h2 id="air本地开发热重载"&gt;&lt;a href="#air%e6%9c%ac%e5%9c%b0%e5%bc%80%e5%8f%91%e7%83%ad%e9%87%8d%e8%bd%bd" class="header-anchor"&gt;&lt;/a&gt;Air：本地开发热重载
&lt;/h2&gt;&lt;p&gt;写 HTTP 服务时，反复 &lt;code&gt;go run main.go&lt;/code&gt; 其实挺打断节奏。Air 做的事情很简单：监听文件变化，自动重新构建和启动。&lt;/p&gt;
&lt;p&gt;一般项目根目录放一个 &lt;code&gt;.air.toml&lt;/code&gt; 就够了：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt" id="hl-1-1"&gt;&lt;a class="lnlinks" href="#hl-1-1"&gt;1&lt;/a&gt;
&lt;/span&gt;&lt;span class="lnt" id="hl-1-2"&gt;&lt;a class="lnlinks" href="#hl-1-2"&gt;2&lt;/a&gt;
&lt;/span&gt;&lt;span class="lnt" id="hl-1-3"&gt;&lt;a class="lnlinks" href="#hl-1-3"&gt;3&lt;/a&gt;
&lt;/span&gt;&lt;span class="lnt" id="hl-1-4"&gt;&lt;a class="lnlinks" href="#hl-1-4"&gt;4&lt;/a&gt;
&lt;/span&gt;&lt;span class="lnt" id="hl-1-5"&gt;&lt;a class="lnlinks" href="#hl-1-5"&gt;5&lt;/a&gt;
&lt;/span&gt;&lt;span class="lnt" id="hl-1-6"&gt;&lt;a class="lnlinks" href="#hl-1-6"&gt;6&lt;/a&gt;
&lt;/span&gt;&lt;span class="lnt" id="hl-1-7"&gt;&lt;a class="lnlinks" href="#hl-1-7"&gt;7&lt;/a&gt;
&lt;/span&gt;&lt;span class="lnt" id="hl-1-8"&gt;&lt;a class="lnlinks" href="#hl-1-8"&gt;8&lt;/a&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-toml" data-lang="toml"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nx"&gt;root&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;.&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nx"&gt;tmp_dir&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;tmp&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;build&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nx"&gt;cmd&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;go build -o ./tmp/app ./cmd/server&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nx"&gt;bin&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;./tmp/app&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nx"&gt;include_ext&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;go&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;tpl&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;tmpl&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;html&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nx"&gt;exclude_dir&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;tmp&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;vendor&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;node_modules&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;这个工具不复杂，但能让本地开发体验舒服很多。&lt;/p&gt;
&lt;h2 id="golangci-lint把代码风格问题提前拦住"&gt;&lt;a href="#golangci-lint%e6%8a%8a%e4%bb%a3%e7%a0%81%e9%a3%8e%e6%a0%bc%e9%97%ae%e9%a2%98%e6%8f%90%e5%89%8d%e6%8b%a6%e4%bd%8f" class="header-anchor"&gt;&lt;/a&gt;golangci-lint：把代码风格问题提前拦住
&lt;/h2&gt;&lt;p&gt;Go 自带 &lt;code&gt;gofmt&lt;/code&gt; 已经解决了大部分格式问题，但项目里经常还会有未使用代码、错误处理不完整、复杂度过高、潜在 bug 之类的问题。&lt;/p&gt;
&lt;p&gt;&lt;code&gt;golangci-lint&lt;/code&gt; 的好处是可以把多个 linter 统一起来跑：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt" id="hl-2-1"&gt;&lt;a class="lnlinks" href="#hl-2-1"&gt;1&lt;/a&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;golangci-lint run ./...
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;我不太建议一上来把所有规则都打开。比较现实的做法是先从少量规则开始，比如 &lt;code&gt;govet&lt;/code&gt;、&lt;code&gt;staticcheck&lt;/code&gt;、&lt;code&gt;errcheck&lt;/code&gt;，等团队习惯了再逐步加。否则第一次接入 CI，可能会刷出一大堆历史问题，最后没人愿意修。&lt;/p&gt;
&lt;h2 id="sqlcsql-还是手写但类型别手写"&gt;&lt;a href="#sqlcsql-%e8%bf%98%e6%98%af%e6%89%8b%e5%86%99%e4%bd%86%e7%b1%bb%e5%9e%8b%e5%88%ab%e6%89%8b%e5%86%99" class="header-anchor"&gt;&lt;/a&gt;sqlc：SQL 还是手写，但类型别手写
&lt;/h2&gt;&lt;p&gt;如果项目里 SQL 比较多，我会考虑用 sqlc。它的思路不是发明一个 ORM，而是让你继续写 SQL，然后从 SQL 生成类型安全的 Go 代码。&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt" id="hl-3-1"&gt;&lt;a class="lnlinks" href="#hl-3-1"&gt;1&lt;/a&gt;
&lt;/span&gt;&lt;span class="lnt" id="hl-3-2"&gt;&lt;a class="lnlinks" href="#hl-3-2"&gt;2&lt;/a&gt;
&lt;/span&gt;&lt;span class="lnt" id="hl-3-3"&gt;&lt;a class="lnlinks" href="#hl-3-3"&gt;3&lt;/a&gt;
&lt;/span&gt;&lt;span class="lnt" id="hl-3-4"&gt;&lt;a class="lnlinks" href="#hl-3-4"&gt;4&lt;/a&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-sql" data-lang="sql"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;-- name: GetUser :one
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;SELECT&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;email&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;FROM&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;users&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;WHERE&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;$&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;生成之后，在 Go 里调用时就有明确的参数和返回结构。这样比手动 &lt;code&gt;Scan&lt;/code&gt; 一堆字段更稳，也比在业务代码里拼 SQL 更容易维护。&lt;/p&gt;
&lt;p&gt;我个人更喜欢这种方式：复杂 SQL 仍然能看得见，类型检查交给工具。&lt;/p&gt;
&lt;p&gt;它不适合所有项目。SQL 经常动态拼接、查询结构高度不稳定时，sqlc 反而会显得束手束脚。但如果核心查询比较稳定，尤其是后台服务、管理端、报表导出这类场景，它比手写一堆 &lt;code&gt;Scan&lt;/code&gt; 靠谱。&lt;/p&gt;
&lt;h2 id="task比-makefile-更像配置文件"&gt;&lt;a href="#task%e6%af%94-makefile-%e6%9b%b4%e5%83%8f%e9%85%8d%e7%bd%ae%e6%96%87%e4%bb%b6" class="header-anchor"&gt;&lt;/a&gt;Task：比 Makefile 更像配置文件
&lt;/h2&gt;&lt;p&gt;Makefile 很经典，但在跨平台场景下有时会遇到 shell 差异。Task 使用 &lt;code&gt;Taskfile.yml&lt;/code&gt;，写起来更接近普通配置。&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt" id="hl-4-1"&gt;&lt;a class="lnlinks" href="#hl-4-1"&gt; 1&lt;/a&gt;
&lt;/span&gt;&lt;span class="lnt" id="hl-4-2"&gt;&lt;a class="lnlinks" href="#hl-4-2"&gt; 2&lt;/a&gt;
&lt;/span&gt;&lt;span class="lnt" id="hl-4-3"&gt;&lt;a class="lnlinks" href="#hl-4-3"&gt; 3&lt;/a&gt;
&lt;/span&gt;&lt;span class="lnt" id="hl-4-4"&gt;&lt;a class="lnlinks" href="#hl-4-4"&gt; 4&lt;/a&gt;
&lt;/span&gt;&lt;span class="lnt" id="hl-4-5"&gt;&lt;a class="lnlinks" href="#hl-4-5"&gt; 5&lt;/a&gt;
&lt;/span&gt;&lt;span class="lnt" id="hl-4-6"&gt;&lt;a class="lnlinks" href="#hl-4-6"&gt; 6&lt;/a&gt;
&lt;/span&gt;&lt;span class="lnt" id="hl-4-7"&gt;&lt;a class="lnlinks" href="#hl-4-7"&gt; 7&lt;/a&gt;
&lt;/span&gt;&lt;span class="lnt" id="hl-4-8"&gt;&lt;a class="lnlinks" href="#hl-4-8"&gt; 8&lt;/a&gt;
&lt;/span&gt;&lt;span class="lnt" id="hl-4-9"&gt;&lt;a class="lnlinks" href="#hl-4-9"&gt; 9&lt;/a&gt;
&lt;/span&gt;&lt;span class="lnt" id="hl-4-10"&gt;&lt;a class="lnlinks" href="#hl-4-10"&gt;10&lt;/a&gt;
&lt;/span&gt;&lt;span class="lnt" id="hl-4-11"&gt;&lt;a class="lnlinks" href="#hl-4-11"&gt;11&lt;/a&gt;
&lt;/span&gt;&lt;span class="lnt" id="hl-4-12"&gt;&lt;a class="lnlinks" href="#hl-4-12"&gt;12&lt;/a&gt;
&lt;/span&gt;&lt;span class="lnt" id="hl-4-13"&gt;&lt;a class="lnlinks" href="#hl-4-13"&gt;13&lt;/a&gt;
&lt;/span&gt;&lt;span class="lnt" id="hl-4-14"&gt;&lt;a class="lnlinks" href="#hl-4-14"&gt;14&lt;/a&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-yaml" data-lang="yaml"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nt"&gt;version&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;3&amp;#39;&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nt"&gt;tasks&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;test&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;cmds&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;- &lt;span class="l"&gt;go test ./...&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;lint&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;cmds&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;- &lt;span class="l"&gt;golangci-lint run ./...&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;dev&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;cmds&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;- &lt;span class="l"&gt;air&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;如果项目成员里有人用 Windows，有人用 macOS/Linux，Taskfile 的可读性会好一点。当然，如果项目已经有成熟 Makefile，也没必要为了换工具而换。&lt;/p&gt;
&lt;h2 id="goreleaser-和-gotestsum"&gt;&lt;a href="#goreleaser-%e5%92%8c-gotestsum" class="header-anchor"&gt;&lt;/a&gt;GoReleaser 和 gotestsum
&lt;/h2&gt;&lt;p&gt;GoReleaser 适合 CLI 工具或需要跨平台二进制发布的项目。比如同时构建 Linux、macOS、Windows，不同架构，再顺手生成压缩包、checksum、GitHub Release。&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt" id="hl-5-1"&gt;&lt;a class="lnlinks" href="#hl-5-1"&gt;1&lt;/a&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;goreleaser release --clean
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;&lt;code&gt;go test ./...&lt;/code&gt; 的输出在包多的时候不太好看，失败信息容易被淹没。gotestsum 的价值就是把测试结果整理得更适合人读，也方便 CI 消费。&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt" id="hl-6-1"&gt;&lt;a class="lnlinks" href="#hl-6-1"&gt;1&lt;/a&gt;
&lt;/span&gt;&lt;span class="lnt" id="hl-6-2"&gt;&lt;a class="lnlinks" href="#hl-6-2"&gt;2&lt;/a&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;gotestsum -- ./...
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;gotestsum --junitfile test.xml -- ./...
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;如果只能先放几个，我会按这个顺序来：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;code&gt;golangci-lint&lt;/code&gt;：最适合进 CI，收益稳定。&lt;/li&gt;
&lt;li&gt;&lt;code&gt;Delve&lt;/code&gt;：排查复杂问题时很实用。&lt;/li&gt;
&lt;li&gt;&lt;code&gt;gotestsum&lt;/code&gt;：测试包多之后很明显。&lt;/li&gt;
&lt;li&gt;&lt;code&gt;Air&lt;/code&gt;：本地服务开发体验提升明显。&lt;/li&gt;
&lt;li&gt;&lt;code&gt;sqlc&lt;/code&gt;：项目 SQL 多时再接入。&lt;/li&gt;
&lt;li&gt;&lt;code&gt;Task&lt;/code&gt; / &lt;code&gt;Makefile&lt;/code&gt;：选一个就行，关键是命令入口统一。&lt;/li&gt;
&lt;li&gt;&lt;code&gt;GoReleaser&lt;/code&gt;：有发布二进制需求时再上。&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;工具不需要一次性堆满。我的经验是，先把“检查、测试、启动、发布”这几件事规范起来，项目后面会轻松不少。&lt;/p&gt;
&lt;p&gt;我也不建议把这类文章看成工具排名。真正应该排优先级的是项目痛点：如果 CI 经常因为低级问题失败，先接 linter；如果本地启动麻烦，先接 Air 或 Task；如果发布靠手工压包，GoReleaser 的优先级就比调试器还高。&lt;/p&gt;
&lt;h2 id="参考资料"&gt;&lt;a href="#%e5%8f%82%e8%80%83%e8%b5%84%e6%96%99" class="header-anchor"&gt;&lt;/a&gt;参考资料
&lt;/h2&gt;&lt;ul&gt;
&lt;li&gt;&lt;a class="link" href="https://github.com/go-delve/delve" target="_blank" rel="noopener"
 &gt;Delve&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a class="link" href="https://github.com/air-verse/air" target="_blank" rel="noopener"
 &gt;Air&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a class="link" href="https://golangci-lint.run/" target="_blank" rel="noopener"
 &gt;golangci-lint&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a class="link" href="https://sqlc.dev/" target="_blank" rel="noopener"
 &gt;sqlc&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a class="link" href="https://taskfile.dev/" target="_blank" rel="noopener"
 &gt;Task&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a class="link" href="https://goreleaser.com/" target="_blank" rel="noopener"
 &gt;GoReleaser&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a class="link" href="https://github.com/gotestyourself/gotestsum" target="_blank" rel="noopener"
 &gt;gotestsum&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;</description></item></channel></rss>