<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom"><channel><title>liaohch3</title><link>https://liaohch3.com/</link><description>Recent content on liaohch3</description><generator>Hugo -- gohugo.io</generator><language>zh-cn</language><lastBuildDate>Sun, 03 May 2026 23:42:53 +0800</lastBuildDate><atom:link href="https://liaohch3.com/index.xml" rel="self" type="application/rss+xml"/><item><title>claude-tap：把 Claude Code / Codex 的真实请求抓出来</title><link>https://liaohch3.com/posts/claude-tap-trace-viewer/</link><pubDate>Sun, 03 May 2026 23:42:53 +0800</pubDate><guid>https://liaohch3.com/posts/claude-tap-trace-viewer/</guid><description>&lt;p&gt;今天发了一条推文介绍 &lt;a class="link" href="https://github.com/liaohch3/claude-tap" target="_blank" rel="noopener"
 &gt;claude-tap&lt;/a&gt;。这里把背景和截图整理成一篇博客，方便以后回看。&lt;/p&gt;
&lt;p&gt;我的主业是 Agent 开发，日常经常需要研究 Claude Code / Codex 这类工具的上下文工程：system prompt 怎么写，messages 怎么组织，tools 怎么传，tool results 怎么回灌，token 又花在了哪里。&lt;/p&gt;
&lt;p&gt;以前社区里有一个 &lt;code&gt;claude-trace&lt;/code&gt;，但它已经不太适配最新的 Claude Code。所以我重新做了一个工具：&lt;code&gt;claude-tap&lt;/code&gt;。&lt;/p&gt;
&lt;h2 id="claude-tap-做什么"&gt;claude-tap 做什么
&lt;/h2&gt;&lt;p&gt;一句话：&lt;strong&gt;把 Claude Code / Codex 的真实请求抓出来，生成本地 HTML trace viewer。&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;它会在本地启动一个 proxy，把客户端发给上游模型服务的请求记录下来，最后生成 JSONL trace 和一个自包含的 HTML 页面。这个页面可以直接打开，不依赖服务端。&lt;/p&gt;
&lt;img src="https://liaohch3.com/images/claude-tap/github-overview.jpg" alt="claude-tap GitHub 项目首页" loading="lazy"&gt;
&lt;p&gt;它主要解决的是一个很具体的问题：不要只猜 Agent harness 怎么设计，而是直接看真实请求。&lt;/p&gt;
&lt;h2 id="看见完整上下文"&gt;看见完整上下文
&lt;/h2&gt;&lt;p&gt;Claude Code / Codex 真正发给模型的内容，通常比我们肉眼在终端里看到的复杂很多。除了用户消息，还有 system prompt、developer instructions、工具定义、历史消息、continuation 信息、token usage 等。&lt;/p&gt;
&lt;p&gt;这些东西如果只靠猜，很容易误判。trace viewer 能把每次 LLM call 展开看：&lt;/p&gt;
&lt;img src="https://liaohch3.com/images/claude-tap/trace-overview.jpg" alt="在 trace viewer 中查看 Codex 请求上下文" loading="lazy"&gt;
&lt;p&gt;对我来说，最有价值的是能看到这些细节：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;system prompt / instructions&lt;/li&gt;
&lt;li&gt;messages / input&lt;/li&gt;
&lt;li&gt;tools schema&lt;/li&gt;
&lt;li&gt;tool calls 和 tool results&lt;/li&gt;
&lt;li&gt;token usage&lt;/li&gt;
&lt;li&gt;WebSocket / SSE 事件&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;这对于研究一个 Agent 工具为什么这样行动、为什么上下文变大、为什么某次调用结果不稳定，都很有帮助。&lt;/p&gt;
&lt;h2 id="看见工具调用和结果"&gt;看见工具调用和结果
&lt;/h2&gt;&lt;p&gt;Agent 工具链里，LLM call 本身只是一部分。很多行为真正发生在工具调用阶段。&lt;/p&gt;
&lt;p&gt;比如 Codex 调用 &lt;code&gt;exec_command&lt;/code&gt; 时，模型到底生成了什么参数，工具返回了什么结果，下一轮又如何把结果喂回模型，这些都可以在 viewer 里展开看。&lt;/p&gt;
&lt;img src="https://liaohch3.com/images/claude-tap/tool-calls.jpg" alt="查看工具调用和工具结果" loading="lazy"&gt;
&lt;p&gt;这类信息平时散落在终端输出和内部状态里，直接读很麻烦。放进 trace viewer 后，就比较适合复盘和对比。&lt;/p&gt;
&lt;h2 id="对比连续请求"&gt;对比连续请求
&lt;/h2&gt;&lt;p&gt;另一个我常用的功能是 diff。&lt;/p&gt;
&lt;p&gt;Agent 在连续几轮调用中，新增了哪些消息，工具结果如何进入上下文，&lt;code&gt;previous_response_id&lt;/code&gt;、metadata、prompt cache 等字段怎么变化，都可以直接对比。&lt;/p&gt;
&lt;img src="https://liaohch3.com/images/claude-tap/diff-view.jpg" alt="对比连续请求的差异" loading="lazy"&gt;
&lt;p&gt;这对调试上下文工程很有用。尤其是在改 prompt、改 tool schema、改 harness 逻辑之后，可以直接看新旧请求差异，而不是只看最终回答。&lt;/p&gt;
&lt;h2 id="为什么是本地-html"&gt;为什么是本地 HTML
&lt;/h2&gt;&lt;p&gt;我做这个工具时有一个很明确的边界：数据默认留在本地。&lt;/p&gt;
&lt;p&gt;trace 里会包含 system prompt、上下文、工具结果，有些项目里还会出现路径、日志、内部配置等信息。把它做成一个本地 HTML viewer，比较适合个人研究、团队内部复盘和问题定位。&lt;/p&gt;
&lt;p&gt;它不是一个生产监控平台，也不想先把数据上传到某个云服务。它更像一个开发者工具：需要的时候打开，用完之后 trace 文件还在你自己的机器上。&lt;/p&gt;
&lt;h2 id="试用"&gt;试用
&lt;/h2&gt;&lt;p&gt;项目地址：&lt;/p&gt;
&lt;p&gt;&lt;a class="link" href="https://github.com/liaohch3/claude-tap" target="_blank" rel="noopener"
 &gt;https://github.com/liaohch3/claude-tap&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;安装：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;uv tool install claude-tap
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;追踪 Claude Code：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;claude-tap
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;追踪 Codex：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;claude-tap --tap-client codex
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;如果你也在研究 Agent、Claude Code、Codex、上下文工程，欢迎试用、提 issue 或 star。&lt;/p&gt;
&lt;p&gt;原推文：&lt;/p&gt;
&lt;p&gt;&lt;a class="link" href="https://x.com/liaohch3/status/2050964318070673581" target="_blank" rel="noopener"
 &gt;https://x.com/liaohch3/status/2050964318070673581&lt;/a&gt;&lt;/p&gt;</description></item><item><title>我给 OpenClaw 装了高德 API，方便找合适的租房位置</title><link>https://liaohch3.com/posts/amap-api-vs-mcp/</link><pubDate>Sun, 22 Feb 2026 00:00:00 +0000</pubDate><guid>https://liaohch3.com/posts/amap-api-vs-mcp/</guid><description>&lt;p&gt;今天想通过 OpenClaw 找深圳地图上适合租房的位置。需求是两个人在不同地方上班，一个开车一个坐地铁，要找一个对两边通勤都合理的片区。但发现 Agent 自己无法准确输出通勤时间——全靠估算，数据不靠谱。所以就想给 Agent 接上高德地图 API，让它基于真实行程数据来回答。&lt;/p&gt;
&lt;h2 id="没有-api-时ai-靠猜"&gt;没有 API 时：AI 靠猜
&lt;/h2&gt;&lt;p&gt;没接地图 API 之前，AI 会自信地输出完整的通勤方案表格——哪个片区开车多少分钟、地铁多少分钟——看起来很专业，但全是根据「大约每站 2.5-3 分钟」估算的：&lt;/p&gt;
&lt;p&gt;&lt;img alt="AI 幻觉输出的通勤数据" loading="lazy" sizes="(max-width: 767px) calc(100vw - 30px), (max-width: 1023px) 700px, (max-width: 1279px) 950px, 1232px"&gt;&lt;/p&gt;
&lt;p&gt;问它数据来源，才老实交代：&lt;/p&gt;

 &lt;blockquote&gt;
 &lt;p&gt;「说实话，我手头没有高德/百度地图 API key，刚才的通勤时间是根据地铁线路站数 + 路网距离估算的，不是 API 返回的精确数据。」&lt;/p&gt;

 &lt;/blockquote&gt;
&lt;p&gt;设置好 API Key 后，AI 立刻开始调真实接口，结果打脸了：&lt;/p&gt;
&lt;p&gt;&lt;img alt="设置 API 后的对话" loading="lazy" sizes="(max-width: 767px) calc(100vw - 30px), (max-width: 1023px) 700px, (max-width: 1279px) 950px, 1232px"&gt;&lt;/p&gt;

 &lt;blockquote&gt;
 &lt;p&gt;「高德 API 的真实数据出来了，跟我之前的估算差距很大，尤其地铁时间被低估了不少。」&lt;/p&gt;

 &lt;/blockquote&gt;
&lt;p&gt;典型案例：家到公司，AI 估算 28 分钟，高德实测 &lt;strong&gt;41 分钟&lt;/strong&gt;（含步行+换乘），&lt;strong&gt;误差 46%&lt;/strong&gt;。这种误差在租房决策中是致命的。&lt;/p&gt;
&lt;h2 id="高德-api-能力测试"&gt;高德 API 能力测试
&lt;/h2&gt;&lt;p&gt;注册高德开放平台（个人认证，免费），拿到 Web 服务 Key：&lt;/p&gt;
&lt;p&gt;&lt;img alt="高德开放平台控制台" loading="lazy" sizes="(max-width: 767px) calc(100vw - 30px), (max-width: 1023px) 700px, (max-width: 1279px) 950px, 1232px"&gt;&lt;/p&gt;
&lt;p&gt;个人认证开发者的免费配额：&lt;/p&gt;
&lt;p&gt;&lt;img alt="配额说明" loading="lazy" sizes="(max-width: 767px) calc(100vw - 30px), (max-width: 1023px) 700px, (max-width: 1279px) 950px, 1232px"&gt;&lt;/p&gt;
&lt;p&gt;用深圳 5 个随机地标做了 14 项 API 全面测试，全部通过：&lt;/p&gt;
&lt;p&gt;&lt;img alt="测试地标地图" loading="lazy" sizes="(max-width: 767px) calc(100vw - 30px), (max-width: 1023px) 700px, (max-width: 1279px) 950px, 1232px"&gt;&lt;/p&gt;
&lt;p&gt;&lt;img alt="测试报告" loading="lazy" sizes="(max-width: 767px) calc(100vw - 30px), (max-width: 1023px) 700px, (max-width: 1279px) 950px, 1232px"&gt;&lt;/p&gt;
&lt;p&gt;高德 API 提供的核心能力：地理编码（地址⇄坐标互转）、路径规划（驾车/公交/步行/骑行）、距离批量测量、POI 关键字搜索、周边搜索、行政区域查询、天气预报、静态地图生成。个人认证每天 5000 次路径规划调用，免费够用。&lt;/p&gt;
&lt;h2 id="实战从估算到精确"&gt;实战：从估算到精确
&lt;/h2&gt;&lt;p&gt;有了 API 后，Agent 自动组合多个接口完成一次完整的租房通勤分析：地理编码（地铁站名→坐标）→ 驾车规划（到工作地点）→ 公交规划（地铁换乘方案）→ 批量测距（10+ 个站同时算）→ 周边搜索（附近小区/餐厅）。最终生成了 16 个地铁站的通勤排名表，每个数据都是 API 实测。&lt;/p&gt;
&lt;h2 id="为什么不需要-mcp"&gt;为什么不需要 MCP
&lt;/h2&gt;&lt;p&gt;高德很早就封装过 MCP（Model Context Protocol）。但实际用下来，有文件系统的 Agent 完全不需要 MCP，原因有三：&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;1. API 的可组合性。&lt;/strong&gt; MCP 把每个能力封装成独立 tool，调用方式固定。Agent 直接调 HTTP API + 写脚本，想怎么组合怎么组合，不受 tool schema 约束。&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-python" data-lang="python"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;for&lt;/span&gt; station &lt;span style="color:#f92672"&gt;in&lt;/span&gt; stations:
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; coords &lt;span style="color:#f92672"&gt;=&lt;/span&gt; geocode(station)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; drive &lt;span style="color:#f92672"&gt;=&lt;/span&gt; route_drive(coords, work)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; transit &lt;span style="color:#f92672"&gt;=&lt;/span&gt; route_transit(coords, work2)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; pois &lt;span style="color:#f92672"&gt;=&lt;/span&gt; search_around(coords, &lt;span style="color:#e6db74"&gt;&amp;#34;小区&amp;#34;&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;&lt;strong&gt;2. 渐进式上下文加载。&lt;/strong&gt; MCP 需要把所有 tool 的 schema（名称、描述、参数定义）预加载到上下文中，高德十几个 API 对应十几个 tool 定义，光 schema 就占了大量 context——即使这次只用路径规划，天气、POI、地理编码的 schema 也全在上下文里白白占着。而 Agent 直接调 API + 文件系统，按需加载：这次只要路径规划就只读路径规划的脚本，不需要把所有能力的定义塞进上下文。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;3. 文件系统 = 天然的中间层。&lt;/strong&gt; Agent 把脚本存到文件系统下次复用，MCP 没有这个能力。文件系统给了 Agent 持久化的工具链，脚本可以不断迭代优化。&lt;/p&gt;
&lt;h2 id="结论"&gt;结论
&lt;/h2&gt;&lt;p&gt;给 AI Agent 一个 API Key，比装十个 MCP 工具有用得多。14 种高德能力全部通过 curl + Python 脚本调用，零 MCP 插件。上下文更省，组合更灵活，脚本可复用。&lt;/p&gt;
&lt;p&gt;目前还有一块短板：租房房源数据。贝壳、链家、自如全部没有公开 API，房源信息只能手动查。下一步计划通过 Agent 接管浏览器（Playwright）来自动化房源采集，与高德通勤数据交叉分析，实现「找房→算通勤→决策」全流程自动化。&lt;/p&gt;</description></item><item><title>AI Agent 工具链中的模型版本管理陷阱</title><link>https://liaohch3.com/posts/ai-agent-model-version-rot/</link><pubDate>Fri, 20 Feb 2026 21:00:00 +0800</pubDate><guid>https://liaohch3.com/posts/ai-agent-model-version-rot/</guid><description>&lt;h2 id="问题"&gt;问题
&lt;/h2&gt;&lt;p&gt;今天让 OpenClaw（AI Agent）创建一个「播客翻译」Skill，Agent 自动选了 &lt;code&gt;google/gemini-2.0-flash-001&lt;/code&gt; 做转录、&lt;code&gt;anthropic/claude-sonnet-4&lt;/code&gt; 做翻译。这两个模型都已过时——当前最新分别是 &lt;code&gt;gemini-2.5-flash&lt;/code&gt; 和 &lt;code&gt;claude-sonnet-4.6&lt;/code&gt;。但旧模型仍然能跑通、不报错，性能在悄悄退化。&lt;/p&gt;
&lt;p&gt;&lt;img alt="问题截图：新建脚本中硬编码的过时模型" loading="lazy" sizes="(max-width: 767px) calc(100vw - 30px), (max-width: 1023px) 700px, (max-width: 1279px) 950px, 1232px"&gt;&lt;/p&gt;
&lt;h2 id="根因"&gt;根因
&lt;/h2&gt;&lt;p&gt;Agent 的模型认知来源于预训练知识（training data cutoff）。训练数据截止时，&lt;code&gt;gemini-2.0-flash-001&lt;/code&gt; 和 &lt;code&gt;claude-sonnet-4&lt;/code&gt; 就是最新的。Agent 写代码时自然用了它&amp;quot;记忆中&amp;quot;的最新版本——但现实世界的模型已经迭代了好几代。&lt;/p&gt;
&lt;p&gt;预训练知识是静态的，模型版本是动态的。这个时间差导致 Agent 每次生成代码都可能写入已过时的模型名。&lt;/p&gt;
&lt;h2 id="解决方案"&gt;解决方案
&lt;/h2&gt;&lt;p&gt;三步走 — &lt;strong&gt;动态模型注册表 + Skill 语义化引用 + 定时自动更新&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;第一步：TOOLS.md 作为模型注册表。&lt;/strong&gt; Agent 每次启动自动加载到上下文，覆盖过时的预训练知识。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;第二步：Skill 文档中用语义描述替代具体模型名。&lt;/strong&gt; Agent 执行时读到「最强 Flash 模型」后去 TOOLS.md 查具体名称，通过 &lt;code&gt;--model&lt;/code&gt; 传入脚本。Skill 文件永不过时。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;第三步：Cron 定时任务自动更新 TOOLS.md。&lt;/strong&gt; 每天拉取 OpenRouter API 最新模型列表，写入 TOOLS.md。人不需要手动维护。&lt;/p&gt;
&lt;p&gt;&lt;img alt="解决方案架构图：踩坑 vs 解决方案对比" loading="lazy" sizes="(max-width: 767px) calc(100vw - 30px), (max-width: 1023px) 700px, (max-width: 1279px) 950px, 1232px"&gt;&lt;/p&gt;
&lt;h2 id="实践成果定时任务首次运行"&gt;实践成果：定时任务首次运行
&lt;/h2&gt;&lt;p&gt;定时任务创建完成后的首次运行报告截图。任务自动拉取 OpenRouter 最新模型列表，识别出新增的 Gemini 3.1 Pro、GPT-5.2 等模型，完成连通性测试后更新 TOOLS.md。&lt;/p&gt;
&lt;p&gt;&lt;img alt="定时任务首次运行报告" loading="lazy" sizes="(max-width: 767px) calc(100vw - 30px), (max-width: 1023px) 700px, (max-width: 1279px) 950px, 1232px"&gt;&lt;/p&gt;
&lt;h2 id="修复前后对比"&gt;修复前后对比
&lt;/h2&gt;&lt;h3 id="skillmd-修复前"&gt;SKILL.md 修复前
&lt;/h3&gt;&lt;p&gt;Agent 用预训练知识生成的 SKILL.md，直接硬编码了具体模型版本。以下是 &lt;code&gt;article-to-podcast/SKILL.md&lt;/code&gt; 的实际片段：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-markdown" data-lang="markdown"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;## 模型
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;-&lt;/span&gt; 音频转录: &lt;span style="color:#e6db74"&gt;`google/gemini-2.0-flash-001`&lt;/span&gt;（支持音频输入）
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;-&lt;/span&gt; 翻译/改写: &lt;span style="color:#e6db74"&gt;`anthropic/claude-sonnet-4`&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;-&lt;/span&gt; TTS: &lt;span style="color:#e6db74"&gt;`gemini-2.5-flash-preview-tts`&lt;/span&gt;（Vertex AI）
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;脚本中也是同样的硬编码：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-python" data-lang="python"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;# podcast_pipeline.py — Agent 生成的原始版本&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;def&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;call_llm&lt;/span&gt;(messages, model&lt;span style="color:#f92672"&gt;=&lt;/span&gt;&lt;span style="color:#e6db74"&gt;&amp;#34;google/gemini-2.0-flash-001&amp;#34;&lt;/span&gt;, timeout&lt;span style="color:#f92672"&gt;=&lt;/span&gt;&lt;span style="color:#ae81ff"&gt;120&lt;/span&gt;):
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;...&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;def&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;translate_faithful&lt;/span&gt;(english_text, prev_context&lt;span style="color:#f92672"&gt;=&lt;/span&gt;&lt;span style="color:#e6db74"&gt;&amp;#34;&amp;#34;&lt;/span&gt;):
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#75715e"&gt;# 翻译用 claude-sonnet-4，同样是过时版本&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;return&lt;/span&gt; call_llm([&lt;span style="color:#f92672"&gt;...&lt;/span&gt;], model&lt;span style="color:#f92672"&gt;=&lt;/span&gt;&lt;span style="color:#e6db74"&gt;&amp;#34;anthropic/claude-sonnet-4&amp;#34;&lt;/span&gt;, timeout&lt;span style="color:#f92672"&gt;=&lt;/span&gt;&lt;span style="color:#ae81ff"&gt;120&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
 &lt;blockquote&gt;
 &lt;p&gt;风险：这些过时的模型导致执行性能明显低于预期。转录准确率更低、翻译质量更差，但因为不报错、能跑通，很难被发现。&lt;/p&gt;

 &lt;/blockquote&gt;
&lt;h3 id="skillmd-修复后"&gt;SKILL.md 修复后
&lt;/h3&gt;&lt;p&gt;修复后的 SKILL.md 不再出现任何具体模型名，改为语义化描述。以下是 &lt;code&gt;article-to-podcast/SKILL.md&lt;/code&gt; 修复后的实际片段：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-markdown" data-lang="markdown"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;## 模型选择策略（不要硬编码具体版本号！）
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;执行时根据当前可用模型动态选择，参考 TOOLS.md 里的最新模型列表：
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;| 用途 | 选择标准 |
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;|------|---------|
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;| 音频转录 | Gemini 系列最强 Flash 模型（需支持音频输入） |
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;| 翻译/改写 | Claude 系列最强性价比模型（Sonnet 级别） |
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;| TTS 生成 | Gemini 最强 TTS 模型（Vertex AI） |
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;原则：脚本中的默认模型可能过时，执行前先检查 TOOLS.md 中的
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;「旗舰选型」表确认当前最优模型，通过 --model 参数覆盖。
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;如果 TOOLS.md 信息也过时，可通过 OpenRouter Models 查询各家最新模型。
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
 &lt;blockquote&gt;
 &lt;p&gt;关键变化：表格里只有语义描述（&amp;ldquo;Gemini 系列最强 Flash 模型&amp;rdquo;），完全没有具体版本号。Agent 执行时去 TOOLS.md 查最新模型名，TOOLS.md 过时则去 OpenRouter 查。&lt;/p&gt;

 &lt;/blockquote&gt;
&lt;h3 id="toolsmd-模型注册表"&gt;TOOLS.md 模型注册表
&lt;/h3&gt;&lt;p&gt;Agent 每次启动自动加载到上下文，作为模型选择的唯一真相源：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-markdown" data-lang="markdown"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;旗舰选型（2026-02-20）
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;| 场景 | 首选 | 路径 |
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;|----------|-------------------------------|-------------------|
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;| 最强智能 | Gemini 3.1 Pro / Opus 4.6 | Vertex / Anthropic |
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;| 日常编码 | Sonnet 4.6 ($3/$15) | Anthropic |
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;| 大量文本 | Gemini 2.5 Flash ($0.30/$2.50) | Vertex |
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;| 生图 | GPT-5 Image | OpenRouter |
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;| 音频 | GPT Audio | OpenRouter |
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h3 id="cron-定时更新任务"&gt;Cron 定时更新任务
&lt;/h3&gt;&lt;p&gt;每天自动拉取最新模型列表，更新 TOOLS.md，确保 Agent 的模型认知永远是最新的：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-yaml" data-lang="yaml"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;# OpenClaw cron job 配置&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#f92672"&gt;schedule&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;kind&lt;/span&gt;: &lt;span style="color:#ae81ff"&gt;cron&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;expr&lt;/span&gt;: &lt;span style="color:#e6db74"&gt;&amp;#34;0 8 * * *&amp;#34;&lt;/span&gt; &lt;span style="color:#75715e"&gt;# 每天早上 8 点（北京时间）&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;tz&lt;/span&gt;: &lt;span style="color:#ae81ff"&gt;Asia/Shanghai&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#f92672"&gt;payload&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;kind&lt;/span&gt;: &lt;span style="color:#ae81ff"&gt;agentTurn&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;message&lt;/span&gt;: &lt;span style="color:#e6db74"&gt;&amp;#34;去 OpenRouter API 拉取各家最新模型列表，更新 TOOLS.md 中的旗舰选型表&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#f92672"&gt;sessionTarget&lt;/span&gt;: &lt;span style="color:#ae81ff"&gt;isolated&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
 &lt;blockquote&gt;
 &lt;p&gt;这个 cron job 让 Agent 每天自动检查模型更新。Agent 会调用 OpenRouter API 获取最新模型列表，对比当前 TOOLS.md，有变化就更新。人不需要手动维护。&lt;/p&gt;

 &lt;/blockquote&gt;
&lt;h2 id="经验总结"&gt;经验总结
&lt;/h2&gt;&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;Agent 的预训练知识会过时&lt;/strong&gt; — 它&amp;quot;记住&amp;quot;的最新模型可能已经是旧的&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;旧模型不报错是最大的坑&lt;/strong&gt; — 不像依赖库会直接 break，模型退化是无声的&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;用语义描述 + 动态选择&lt;/strong&gt; — Skill 写「最强 Flash 模型」而不是具体版本号&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;TOOLS.md 作为模型选型的唯一真相源&lt;/strong&gt; — 用运行时上下文覆盖过时的预训练知识&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;定时自动更新 &amp;gt; 人工维护&lt;/strong&gt; — 人会忘，cron 不会&lt;/li&gt;
&lt;/ol&gt;</description></item><item><title>Golang defer 关键字的三个细节</title><link>https://liaohch3.com/posts/golang-defer/</link><pubDate>Thu, 25 Apr 2024 13:13:41 +0800</pubDate><guid>https://liaohch3.com/posts/golang-defer/</guid><description>&lt;p&gt;这是我第一篇正式的技术博客，灵感来自前段时间某位同事向我请教的问题：为什么 Golang defer 函数没有按预期执行？我觉得这是一个很容易踩坑的点，所以我在网上学习后，写出这篇博客。&lt;/p&gt;
&lt;h2 id="1-defer-的作用"&gt;1. defer 的作用
&lt;/h2&gt;&lt;p&gt;在 Golang 中，我们常常需要编写一些成对出现的代码，一个前置操作固定搭配一个后置操作，例如&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;上锁后，要解锁&lt;/li&gt;
&lt;li&gt;打开文件，或发起网络请求后，执行文件/网络句柄的关闭操作&lt;/li&gt;
&lt;li&gt;执行完一段业务逻辑后，统一打印这段逻辑的业务日志和耗时&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;通常我们需要在执行完前置操作后，执行一段复杂的业务逻辑，然后再执行后置操作。例如发起打开文件后，解析对文件的内容进行解析，然后再关闭文件句柄，打开和关闭文件两个操作可能中间间隔一段复杂的业务逻辑。 如果将关闭文件、打印日志、解锁操作放在与他们成对出现的前置操作太远的地方，就会导致我们可能在某些执行分支遗漏编写后置语句，导致文件没有正常关闭，锁没有正常释放。&lt;/p&gt;
&lt;p&gt;好在，Golang 提供了 defer 关键字，方便你在前置操作完成后，写一个后置操作的代码，等到整段业务逻辑执行完成，再执行 defer 中的操作。 然而，defer 在使用时有一些值得注意的地方，如果用得不好的话，可能会有不符合预期的事情发生。&lt;/p&gt;
&lt;h2 id="2-问题现象"&gt;2. 问题现象
&lt;/h2&gt;&lt;p&gt;我的同事使用 defer 来打印请求的整体日志和整体耗时，他在 debug 的时候发现日志里打印出来的内容都是 0，大概的代码如下&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-go" data-lang="go"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;// process 执行某项操作，并记录结果、错误（如果有的话）以及执行时长。&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;func&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;process&lt;/span&gt;() (&lt;span style="color:#a6e22e"&gt;result&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;string&lt;/span&gt;, &lt;span style="color:#a6e22e"&gt;err&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;error&lt;/span&gt;) {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;start&lt;/span&gt; &lt;span style="color:#f92672"&gt;:=&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;time&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;Now&lt;/span&gt;()
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;defer&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;logReq&lt;/span&gt;(&lt;span style="color:#a6e22e"&gt;result&lt;/span&gt;, &lt;span style="color:#a6e22e"&gt;err&lt;/span&gt;, &lt;span style="color:#a6e22e"&gt;start&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#75715e"&gt;// 调用 doSomething 并将结果赋值给 result 和 err。&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;result&lt;/span&gt;, &lt;span style="color:#a6e22e"&gt;err&lt;/span&gt; = &lt;span style="color:#a6e22e"&gt;doSomething&lt;/span&gt;()
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;if&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;err&lt;/span&gt; &lt;span style="color:#f92672"&gt;!=&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;nil&lt;/span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;return&lt;/span&gt; &lt;span style="color:#e6db74"&gt;&amp;#34;&amp;#34;&lt;/span&gt;, &lt;span style="color:#a6e22e"&gt;err&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; }
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;return&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;result&lt;/span&gt;, &lt;span style="color:#66d9ef"&gt;nil&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;// doSomething 模拟一个需要一些时间来完成的过程。&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;func&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;doSomething&lt;/span&gt;() (&lt;span style="color:#66d9ef"&gt;string&lt;/span&gt;, &lt;span style="color:#66d9ef"&gt;error&lt;/span&gt;) {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;time&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;Sleep&lt;/span&gt;(&lt;span style="color:#a6e22e"&gt;time&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;Second&lt;/span&gt;) &lt;span style="color:#75715e"&gt;// 模拟执行任务通过休眠一秒。&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;return&lt;/span&gt; &lt;span style="color:#e6db74"&gt;&amp;#34;done&amp;#34;&lt;/span&gt;, &lt;span style="color:#66d9ef"&gt;nil&lt;/span&gt; &lt;span style="color:#75715e"&gt;// 返回成功的结果。&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;// logReq 记录 process 函数的执行结果、错误和耗时。&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;func&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;logReq&lt;/span&gt;(&lt;span style="color:#a6e22e"&gt;result&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;string&lt;/span&gt;, &lt;span style="color:#a6e22e"&gt;err&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;error&lt;/span&gt;, &lt;span style="color:#a6e22e"&gt;start&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;time&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;Time&lt;/span&gt;) {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;log&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;Printf&lt;/span&gt;(&lt;span style="color:#e6db74"&gt;&amp;#34;result: %s, err: %v, time: %vms&amp;#34;&lt;/span&gt;, &lt;span style="color:#a6e22e"&gt;result&lt;/span&gt;, &lt;span style="color:#a6e22e"&gt;err&lt;/span&gt;, &lt;span style="color:#a6e22e"&gt;time&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;Since&lt;/span&gt;(&lt;span style="color:#a6e22e"&gt;start&lt;/span&gt;).&lt;span style="color:#a6e22e"&gt;Milliseconds&lt;/span&gt;())
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;func&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;main&lt;/span&gt;() {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;process&lt;/span&gt;() 
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;这段代码执行的结果我放在下面，我们预期 defer 能在业务逻辑执行完成后，打印出 result 的值，但是实际执行的结果却不是这样。实际上 result 打印出来是一个空字符串，而不是我们预期的 &amp;ldquo;done&amp;rdquo;，这是为什么呢？&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;2024/04/25 13:11:50 result: , err: &amp;lt;nil&amp;gt;, time: 1001ms
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;在解答这个问题之前，我们先对 defer 有一个简要的介绍，介绍在使用 Golang defer 关键字时需要注意的三个地方&lt;/p&gt;
&lt;h2 id="3-defer-的作用"&gt;3. defer 的作用
&lt;/h2&gt;&lt;p&gt;在 Golang 中，&lt;code&gt;defer&lt;/code&gt; 关键字用于预定一个函数调用，这个函数会在包围它的函数执行完毕（即将返回）之前被调用。&lt;code&gt;defer&lt;/code&gt; 通常用于执行一些清理工作，例如释放资源、关闭文件、解锁互斥量等。使用 &lt;code&gt;defer&lt;/code&gt; 可以确保函数退出时，无论因为正常返回还是因为发生错误（如 panic），相关资源都能被正确释放或清理。&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-go" data-lang="go"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;func&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;main&lt;/span&gt;() {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#75715e"&gt;// 定义一个匿名函数，并使用 defer 关键字预定它的执行。&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#75715e"&gt;// 这个匿名函数将在 main 函数即将返回之前执行。&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;defer&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;func&lt;/span&gt;() {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;fmt&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;Println&lt;/span&gt;(&lt;span style="color:#e6db74"&gt;&amp;#34;这是通过 defer 延迟执行的语句&amp;#34;&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; }()
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#75715e"&gt;// 主函数的其他代码&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;fmt&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;Println&lt;/span&gt;(&lt;span style="color:#e6db74"&gt;&amp;#34;主函数中的代码执行&amp;#34;&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;在这个例子中，我们在 &lt;code&gt;main&lt;/code&gt; 函数中使用 &lt;code&gt;defer&lt;/code&gt; 关键字来延迟执行一个匿名函数，该匿名函数只包含一个打印语句。这个 &lt;code&gt;defer&lt;/code&gt; 语句确保无论 &lt;code&gt;main&lt;/code&gt; 函数的执行路径如何，匿名函数都会在 &lt;code&gt;main&lt;/code&gt; 函数返回之前执行。运行这段代码，你将看到以下输出：&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;主函数中的代码执行
这是通过 defer 延迟执行的语句
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;这个输出清楚地表明，尽管 &lt;code&gt;defer&lt;/code&gt; 语句在打印 &amp;ldquo;主函数中的代码执行&amp;rdquo; 之前声明，但是它延迟的函数实际上是在所有其他语句执行完成后，&lt;code&gt;main&lt;/code&gt; 函数即将返回前才执行的。&lt;/p&gt;
&lt;h2 id="4-defer-值得关注的三个细节"&gt;4. defer 值得关注的三个细节
&lt;/h2&gt;&lt;h3 id="41-defer-执行顺序"&gt;4.1 defer 执行顺序
&lt;/h3&gt;&lt;p&gt;当函数中有多个 defer 的时候，每一个 defer 函数的执行顺序是按照 defer 声明顺序的倒序。本质上是一个栈，后进先出。&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-go" data-lang="go"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;func&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;process&lt;/span&gt;() {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;defer&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;fmt&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;Println&lt;/span&gt;(&lt;span style="color:#e6db74"&gt;&amp;#34;1&amp;#34;&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;defer&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;fmt&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;Println&lt;/span&gt;(&lt;span style="color:#e6db74"&gt;&amp;#34;2&amp;#34;&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;defer&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;fmt&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;Println&lt;/span&gt;(&lt;span style="color:#e6db74"&gt;&amp;#34;3&amp;#34;&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;output:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-text" data-lang="text"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;3
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;2
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;1
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;实际开发过程中，使用多个 defer 的场景并不多见，遇到了通常也不会有太大问题&lt;/p&gt;
&lt;h3 id="42-defer-函数的执行发生在函数结束的时候而不是代码块的结束"&gt;4.2 defer 函数的执行发生在函数结束的时候，而不是代码块的结束
&lt;/h3&gt;&lt;p&gt;有时候我们会把代码封在一个代码块里，通常是 if for 等关键字的代码块，此时使用 defer 关键字要格外注意他的执行时机。&lt;/p&gt;
&lt;p&gt;代码块结束时 defer 并不会被执行，直到整个函数结束时，defer 才会执行。稍不注意，可能导致 defer 函数实际执行的时间比你预期的要晚一些。&lt;/p&gt;
&lt;p&gt;示例如下&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-go" data-lang="go"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;func&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;main&lt;/span&gt;() {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;done&lt;/span&gt; &lt;span style="color:#f92672"&gt;:=&lt;/span&gt; make(&lt;span style="color:#66d9ef"&gt;chan&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;bool&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;go&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;func&lt;/span&gt;() {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;defer&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;func&lt;/span&gt;() {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;fmt&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;Println&lt;/span&gt;(&lt;span style="color:#e6db74"&gt;&amp;#34;Defer in function scope is being executed.&amp;#34;&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;done&lt;/span&gt; &lt;span style="color:#f92672"&gt;&amp;lt;-&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;true&lt;/span&gt; &lt;span style="color:#75715e"&gt;// 发送信号表示 defer 函数已执行&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; }()
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; 
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#75715e"&gt;// 这是函数内的一个代码块&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;defer&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;func&lt;/span&gt;() {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;fmt&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;Println&lt;/span&gt;(&lt;span style="color:#e6db74"&gt;&amp;#34;Defer in code block scope is being executed.&amp;#34;&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; }()
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;fmt&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;Println&lt;/span&gt;(&lt;span style="color:#e6db74"&gt;&amp;#34;This is inside the code block.&amp;#34;&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; } &lt;span style="color:#75715e"&gt;// 代码块结束&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; 
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#75715e"&gt;// 注意，虽然代码块已经结束，但是代码块内的 defer 函数还没有被执行。&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#75715e"&gt;// 它会在整个函数即将返回之前执行。&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;fmt&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;Println&lt;/span&gt;(&lt;span style="color:#e6db74"&gt;&amp;#34;This is outside the code block, but still within the function.&amp;#34;&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#75715e"&gt;// 函数的其他操作...&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;time&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;Sleep&lt;/span&gt;(&lt;span style="color:#ae81ff"&gt;2&lt;/span&gt; &lt;span style="color:#f92672"&gt;*&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;time&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;Second&lt;/span&gt;) &lt;span style="color:#75715e"&gt;// 模拟函数执行时间&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; }() &lt;span style="color:#75715e"&gt;// 函数结束&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; 
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#75715e"&gt;// 等待接收来自匿名函数的信号&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;&amp;lt;-&lt;/span&gt;&lt;span style="color:#a6e22e"&gt;done&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; 
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;fmt&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;Println&lt;/span&gt;(&lt;span style="color:#e6db74"&gt;&amp;#34;The go routine has returned, and the main function is about to exit.&amp;#34;&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; }
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;output:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-text" data-lang="text"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;output:
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;This is inside the code block.
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;This is outside the code block, but still within the function.
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;Defer in code block scope is being executed.
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;Defer in function scope is being executed.
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;The go routine has returned, and the main function is about to exit.
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;可以看到，defer 函数不会在代码块结束时执行，而是在函数结束时执行。&lt;/p&gt;
&lt;h3 id="43-defer-的参数求值时机"&gt;4.3 defer 的参数求值时机
&lt;/h3&gt;&lt;p&gt;我们常常看到两种 defer 的使用方式&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;defer 直接接函数调用，例如&lt;/li&gt;
&lt;/ol&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-go" data-lang="go"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;defer&lt;/span&gt; recover()
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;ol start="2"&gt;
&lt;li&gt;defer 接一个匿名函数，例如&lt;/li&gt;
&lt;/ol&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-go" data-lang="go"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;defer&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;func&lt;/span&gt;(){
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;fmt&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;Printf&lt;/span&gt;(&lt;span style="color:#e6db74"&gt;&amp;#34;request done, result is xxx&amp;#34;&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;}()
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;两种方法的差异就是我同事所遇到问题的原因，这里暗含着 Golang defer 参数求值时机的问题，defer 的参数是在声明 defer 的时候就计算好的，而不是等到 defer 执行的时候才计算&lt;/p&gt;
&lt;p&gt;回到同事向我提问的例子，执行下面的代码，我们会看到什么？&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-go" data-lang="go"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;// process 执行某项操作，并记录结果、错误（如果有的话）以及执行时长。&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;func&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;process&lt;/span&gt;() (&lt;span style="color:#a6e22e"&gt;result&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;string&lt;/span&gt;, &lt;span style="color:#a6e22e"&gt;err&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;error&lt;/span&gt;) {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;start&lt;/span&gt; &lt;span style="color:#f92672"&gt;:=&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;time&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;Now&lt;/span&gt;()
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#75715e"&gt;// 延迟调用 logReq 函数。注意此处的 result 和 err 是在 defer 语句执行时求值的。&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#75715e"&gt;// 这意味着记录到的 result 和 err 将始终是它们的零值（&amp;#34;&amp;#34;，nil），&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#75715e"&gt;// 而不是 doSomething 返回的实际值。&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;defer&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;logReq&lt;/span&gt;(&lt;span style="color:#a6e22e"&gt;result&lt;/span&gt;, &lt;span style="color:#a6e22e"&gt;err&lt;/span&gt;, &lt;span style="color:#a6e22e"&gt;start&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#75715e"&gt;// 调用 doSomething 并将结果赋值给 result 和 err。&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;result&lt;/span&gt;, &lt;span style="color:#a6e22e"&gt;err&lt;/span&gt; = &lt;span style="color:#a6e22e"&gt;doSomething&lt;/span&gt;()
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;if&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;err&lt;/span&gt; &lt;span style="color:#f92672"&gt;!=&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;nil&lt;/span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;return&lt;/span&gt; &lt;span style="color:#e6db74"&gt;&amp;#34;&amp;#34;&lt;/span&gt;, &lt;span style="color:#a6e22e"&gt;err&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; }
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;return&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;result&lt;/span&gt;, &lt;span style="color:#66d9ef"&gt;nil&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;// doSomething 模拟一个需要一些时间来完成的过程。&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;func&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;doSomething&lt;/span&gt;() (&lt;span style="color:#66d9ef"&gt;string&lt;/span&gt;, &lt;span style="color:#66d9ef"&gt;error&lt;/span&gt;) {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;time&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;Sleep&lt;/span&gt;(&lt;span style="color:#a6e22e"&gt;time&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;Second&lt;/span&gt;) &lt;span style="color:#75715e"&gt;// 模拟执行任务通过休眠一秒。&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;return&lt;/span&gt; &lt;span style="color:#e6db74"&gt;&amp;#34;done&amp;#34;&lt;/span&gt;, &lt;span style="color:#66d9ef"&gt;nil&lt;/span&gt; &lt;span style="color:#75715e"&gt;// 返回成功的结果。&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;// logReq 记录 process 函数的执行结果、错误和耗时。&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;func&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;logReq&lt;/span&gt;(&lt;span style="color:#a6e22e"&gt;result&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;string&lt;/span&gt;, &lt;span style="color:#a6e22e"&gt;err&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;error&lt;/span&gt;, &lt;span style="color:#a6e22e"&gt;start&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;time&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;Time&lt;/span&gt;) {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;log&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;Printf&lt;/span&gt;(&lt;span style="color:#e6db74"&gt;&amp;#34;result: %s, err: %v, time: %vms&amp;#34;&lt;/span&gt;, &lt;span style="color:#a6e22e"&gt;result&lt;/span&gt;, &lt;span style="color:#a6e22e"&gt;err&lt;/span&gt;, &lt;span style="color:#a6e22e"&gt;time&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;Since&lt;/span&gt;(&lt;span style="color:#a6e22e"&gt;start&lt;/span&gt;).&lt;span style="color:#a6e22e"&gt;Milliseconds&lt;/span&gt;())
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;func&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;main&lt;/span&gt;() {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;process&lt;/span&gt;()
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;output:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-text" data-lang="text"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;2024/04/25 13:11:50 result: , err: &amp;lt;nil&amp;gt;, time: 1001ms
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;在这段代码中，&lt;code&gt;defer logReq(result, err, start)&lt;/code&gt; 的参数在 defer 声明的时候就拷贝了一份，此时 result 为空字符串，自然在执行 logReq 时，打印出来的 result 就不是预期的 &amp;ldquo;done&amp;rdquo;&lt;/p&gt;
&lt;p&gt;而修复这个问题的方法也很简单，只需要使用一个匿名函数把 logReq包起来即可，示例如下&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-go" data-lang="go"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;defer&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;func&lt;/span&gt;() {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;logReq&lt;/span&gt;(&lt;span style="color:#a6e22e"&gt;result&lt;/span&gt;, &lt;span style="color:#a6e22e"&gt;err&lt;/span&gt;, &lt;span style="color:#a6e22e"&gt;start&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;}()
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#a6e22e"&gt;output&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#ae81ff"&gt;2024&lt;/span&gt;&lt;span style="color:#f92672"&gt;/&lt;/span&gt;&lt;span style="color:#ae81ff"&gt;04&lt;/span&gt;&lt;span style="color:#f92672"&gt;/&lt;/span&gt;&lt;span style="color:#ae81ff"&gt;25&lt;/span&gt; &lt;span style="color:#ae81ff"&gt;13&lt;/span&gt;:&lt;span style="color:#ae81ff"&gt;52&lt;/span&gt;:&lt;span style="color:#ae81ff"&gt;15&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;result&lt;/span&gt;: &lt;span style="color:#a6e22e"&gt;done&lt;/span&gt;, &lt;span style="color:#a6e22e"&gt;err&lt;/span&gt;: &amp;lt;&lt;span style="color:#66d9ef"&gt;nil&lt;/span&gt;&amp;gt;, &lt;span style="color:#a6e22e"&gt;time&lt;/span&gt;: &lt;span style="color:#ae81ff"&gt;1001&lt;/span&gt;&lt;span style="color:#a6e22e"&gt;ms&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;此时执行代码，就能打印出 result 实际的值啦，这就是 defer 的参数求值实际问题。&lt;/p&gt;
&lt;h2 id="5-结尾"&gt;5. 结尾
&lt;/h2&gt;&lt;p&gt;综上，本文讨论了 Golang defer 函数的作用，以及使用时需要注意的三个细节。由于本人对 Golang 底层的运行机制尚不了解，本文不涉及 defer 底层的执行机制，感兴趣的朋友可以阅读本文的参考来源。&lt;/p&gt;
&lt;p&gt;如果有什么想要讨论的话题，或者对我的内容有不同的意见，欢迎留下你的评论，谢谢！&lt;/p&gt;
&lt;h2 id="6-参考"&gt;6. 参考
&lt;/h2&gt;&lt;ul&gt;
&lt;li&gt;关于 Golang 语言 defer 的底层实现机制，我对这块不太熟悉，&lt;a class="link" href="https://draveness.me/golang/docs/part2-foundation/ch05-keyword/golang-defer/" target="_blank" rel="noopener"
 &gt;这篇文章&lt;/a&gt;写的很好&lt;/li&gt;
&lt;li&gt;&lt;a class="link" href="https://tiancaiamao.gitbooks.io/go-internals/content/zh/03.4.html" target="_blank" rel="noopener"
 &gt;这篇文章&lt;/a&gt; 提到了一个简便的确认 defer 执行逻辑的方法&lt;/li&gt;
&lt;/ul&gt;</description></item><item><title>我的第一篇博客</title><link>https://liaohch3.com/posts/my-first-post/</link><pubDate>Tue, 23 Apr 2024 23:27:11 +0800</pubDate><guid>https://liaohch3.com/posts/my-first-post/</guid><description>&lt;p&gt;你好，这里是 liaohch3 的个人博客，很高兴你能来看我的博客。&lt;/p&gt;
&lt;p&gt;我是 liaohch3，一名在 SaaS 行业工作了四年的后端工程师，主要做在线表格的开发工作，熟悉 Golang 后端开发，对 MySQL、RocketMQ、ElasticSearch、Redis 等技术感兴趣，希望能将大模型和 AI 应用到在线表格中，实现真正的用户价值。&lt;/p&gt;
&lt;p&gt;我喜欢阅读、编程和健身，希望通过博客分享我的经验和见解。我正在练习写作，如你所见，在这方面我是一个十足的菜鸟，希望能通过持续输出博客内容来提升自己的写作能力。&lt;/p&gt;
&lt;p&gt;创建这个博客是因为我最近读了《软技能》这本书，我希望能讲我平常学习到的知识，在工作和生活中的感悟记录在这里，通过传递知识来加强自己的理解。&lt;/p&gt;
&lt;p&gt;希望我的博客能对你有所帮助，如果你对内容感兴趣，或者有不同的看法，请留下你的评论，谢谢。&lt;/p&gt;</description></item></channel></rss>