<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0"
	xmlns:content="http://purl.org/rss/1.0/modules/content/"
	xmlns:wfw="http://wellformedweb.org/CommentAPI/"
	xmlns:dc="http://purl.org/dc/elements/1.1/"
	xmlns:atom="http://www.w3.org/2005/Atom"
	xmlns:sy="http://purl.org/rss/1.0/modules/syndication/"
	xmlns:slash="http://purl.org/rss/1.0/modules/slash/"
	>

<channel>
	<title>Tony Bai &#187; 反模式</title>
	<atom:link href="http://tonybai.com/tag/%e5%8f%8d%e6%a8%a1%e5%bc%8f/feed/" rel="self" type="application/rss+xml" />
	<link>https://tonybai.com</link>
	<description>一个程序员的心路历程</description>
	<lastBuildDate>Mon, 08 Jun 2026 23:32:23 +0000</lastBuildDate>
	<language>en</language>
	<sy:updatePeriod>hourly</sy:updatePeriod>
	<sy:updateFrequency>1</sy:updateFrequency>
	<generator>http://wordpress.org/?v=3.2.1</generator>
		<item>
		<title>Claude Code 官方最佳实践：50 条没人告诉你的“核心军规”</title>
		<link>https://tonybai.com/2026/01/25/claude-code-official-best-practices-50-core-rules/</link>
		<comments>https://tonybai.com/2026/01/25/claude-code-official-best-practices-50-core-rules/#comments</comments>
		<pubDate>Sun, 25 Jan 2026 00:24:12 +0000</pubDate>
		<dc:creator>bigwhite</dc:creator>
				<category><![CDATA[技术志]]></category>
		<category><![CDATA[AgenticCodingEnvironment]]></category>
		<category><![CDATA[Antipatterns]]></category>
		<category><![CDATA[AutomatedWorkflow]]></category>
		<category><![CDATA[BestPractices]]></category>
		<category><![CDATA[CIIntegration]]></category>
		<category><![CDATA[Claude.md]]></category>
		<category><![CDATA[ClaudeCode]]></category>
		<category><![CDATA[CLITools]]></category>
		<category><![CDATA[CodeIntelligence]]></category>
		<category><![CDATA[CodeStyle]]></category>
		<category><![CDATA[configuration]]></category>
		<category><![CDATA[ContextWindow]]></category>
		<category><![CDATA[CoreRules]]></category>
		<category><![CDATA[CustomSubagents]]></category>
		<category><![CDATA[DeveloperExperience]]></category>
		<category><![CDATA[DomainKnowledge]]></category>
		<category><![CDATA[FanoutPattern]]></category>
		<category><![CDATA[HeadlessMode]]></category>
		<category><![CDATA[ImportRules]]></category>
		<category><![CDATA[ModelTier]]></category>
		<category><![CDATA[ParallelSessions]]></category>
		<category><![CDATA[PermissionsAllowlist]]></category>
		<category><![CDATA[PermissionsManagement]]></category>
		<category><![CDATA[PlanMode]]></category>
		<category><![CDATA[PromptEngineering]]></category>
		<category><![CDATA[RealtimeFeedback]]></category>
		<category><![CDATA[RootCause]]></category>
		<category><![CDATA[SandboxMode]]></category>
		<category><![CDATA[SelfCorrection]]></category>
		<category><![CDATA[StructuredOutput]]></category>
		<category><![CDATA[TaskDecomposition]]></category>
		<category><![CDATA[TaskFraming]]></category>
		<category><![CDATA[Verification]]></category>
		<category><![CDATA[WorkflowAutomation]]></category>
		<category><![CDATA[上下文窗口]]></category>
		<category><![CDATA[代码智能]]></category>
		<category><![CDATA[代码风格]]></category>
		<category><![CDATA[任务分解]]></category>
		<category><![CDATA[任务框架]]></category>
		<category><![CDATA[反模式]]></category>
		<category><![CDATA[命令行工具]]></category>
		<category><![CDATA[子智能体]]></category>
		<category><![CDATA[实时反馈]]></category>
		<category><![CDATA[导入规则]]></category>
		<category><![CDATA[并行会话]]></category>
		<category><![CDATA[开发者体验]]></category>
		<category><![CDATA[扇出模式]]></category>
		<category><![CDATA[持续集成]]></category>
		<category><![CDATA[提示词工程]]></category>
		<category><![CDATA[无头模式]]></category>
		<category><![CDATA[最佳实践]]></category>
		<category><![CDATA[权限白名单]]></category>
		<category><![CDATA[权限管理]]></category>
		<category><![CDATA[核心军规]]></category>
		<category><![CDATA[根本原因]]></category>
		<category><![CDATA[模型层级]]></category>
		<category><![CDATA[沙箱模式]]></category>
		<category><![CDATA[流程自动化]]></category>
		<category><![CDATA[结构化输出]]></category>
		<category><![CDATA[自主智能体]]></category>
		<category><![CDATA[自动化工作流]]></category>
		<category><![CDATA[自我纠错]]></category>
		<category><![CDATA[计划模式]]></category>
		<category><![CDATA[配置文件]]></category>
		<category><![CDATA[项目宪法]]></category>
		<category><![CDATA[领域知识]]></category>
		<category><![CDATA[验证]]></category>

		<guid isPermaLink="false">https://tonybai.com/?p=5772</guid>
		<description><![CDATA[本文永久链接 &#8211; https://tonybai.com/2026/01/25/claude-code-official-best-practices-50-core-rules 大家好，我是Tony Bai。 在使用 Claude Code 的过程中，你是否遇到过这种情况： 有时候它简直是神，几秒钟就能重构一个复杂的模块；但有时候它又蠢得让人抓狂，甚至会一本正经地写出跑不通的代码，或者把你刚刚纠正过的错误再犯一遍。 为什么？是模型不稳定吗？ 不，这通常是因为你的“打开方式”不对。 Claude Code 本质上是一个运行在 CLI 环境中的自主智能体（Agentic Coding Environment）。它受限于一个核心物理法则：上下文窗口（Context Window）。 上下文满了，它就会“失忆”。 指令不清晰，它就会“幻觉”。 没有反馈，它就会“盲目自信”。 为了帮你跨越从“新手”到“高玩”的门槛，我精读了 Anthropic 刚刚发布的官方最佳实践文档，并结合实战经验，提炼出了这 50 条核心军规。 掌握了它们，你就是指挥 AI 军团的编排者（Orchestrator）了。 基础心法——对抗熵增 (Foundational Tips) 核心逻辑： 上下文是稀缺资源，清晰度是最高杠杆。 Clear Task Framing： 开局第一句话决定成败。明确告诉它：Role（角色） + Task（任务） + Context（背景）。 Front Load Instructions： 最重要的约束（比如“绝对不要修改配置文件”），必须放在 Prompt 的最前面。 Verification (最高杠杆)： 这是最重要的 Tip。 必须给 [...]]]></description>
			<content:encoded><![CDATA[<p><img src="https://tonybai.com/wp-content/uploads/2026/claude-code-official-best-practices-50-core-rules-1.png" alt="" /></p>
<p><a href="https://tonybai.com/2026/01/25/claude-code-official-best-practices-50-core-rules">本文永久链接</a> &#8211; https://tonybai.com/2026/01/25/claude-code-official-best-practices-50-core-rules</p>
<p>大家好，我是Tony Bai。</p>
<p>在使用 Claude Code 的过程中，你是否遇到过这种情况：</p>
<p>有时候它简直是神，几秒钟就能重构一个复杂的模块；但有时候它又蠢得让人抓狂，甚至会一本正经地写出跑不通的代码，或者把你刚刚纠正过的错误再犯一遍。</p>
<p>为什么？是模型不稳定吗？</p>
<p>不，这通常是因为<strong>你的“打开方式”不对</strong>。</p>
<p>Claude Code 本质上是一个运行在 CLI 环境中的<strong>自主智能体（Agentic Coding Environment）</strong>。它受限于一个核心物理法则：<strong>上下文窗口（Context Window）</strong>。</p>
<ul>
<li>上下文满了，它就会“失忆”。</li>
<li>指令不清晰，它就会“幻觉”。</li>
<li>没有反馈，它就会“盲目自信”。</li>
</ul>
<p>为了帮你跨越从“新手”到“高玩”的门槛，我精读了 Anthropic 刚刚发布的<a href="https://code.claude.com/docs/en/best-practices">官方最佳实践文档</a>，并结合实战经验，提炼出了这 <strong>50 条核心军规</strong>。</p>
<p>掌握了它们，你就是指挥 AI 军团的<strong>编排者（Orchestrator）</strong>了。</p>
<p><img src="https://tonybai.com/wp-content/uploads/2025/paid/ai-app-dev-primer-qr.png" alt="" /></p>
<hr />
<h2>基础心法——对抗熵增 (Foundational Tips)</h2>
<p><strong>核心逻辑：</strong> 上下文是稀缺资源，清晰度是最高杠杆。</p>
<ol>
<li><strong>Clear Task Framing：</strong> 开局第一句话决定成败。明确告诉它：<strong>Role（角色） + Task（任务） + Context（背景）</strong>。</li>
<li><strong>Front Load Instructions：</strong> 最重要的约束（比如“绝对不要修改配置文件”），必须放在 Prompt 的最前面。</li>
<li><strong>Verification (最高杠杆)：</strong> <strong>这是最重要的 Tip。</strong> 必须给 Claude 一个“验证它自己工作”的方法。
<ul>
<li>❌ “修复这个 Bug。”</li>
<li>✅ “修复这个 Bug，并编写一个测试用例来验证修复是否成功。”</li>
</ul>
</li>
<li><strong>Provide Screenshots：</strong> 涉及 UI 修改，直接粘贴截图。Claude 现在的视觉能力极强，一张图胜过千言万语。</li>
<li><strong>Address Root Causes：</strong> 遇到报错，明确告诉它：“不要仅仅消除报错（Suppress），要解决根本原因。”</li>
<li><strong>Plan Mode (Shift+Tab)：</strong> 复杂任务（涉及 >2 个文件）必须先进 Plan 模式。
<ul>
<li><strong>Explore -> Plan -> Implement</strong>。</li>
</ul>
</li>
<li><strong>Review the Plan：</strong> 在它动手写代码前，先 Review 它的计划。这时候纠偏成本最低。</li>
<li><strong>One-shot vs Plan：</strong> 改个拼写错误？直接干。重构模块？必须 Plan。</li>
<li><strong>Specific Context：</strong> 不要让它通读整个仓库。用 @ 引用具体文件。</li>
<li><strong>Rich Content：</strong> 善用 cat error.log | claude，直接把日志管道喂给它。</li>
<li><strong>Clear Context：</strong> 任务做完了？立刻运行 /clear。不要在垃圾堆里盖新楼。</li>
<li><strong>Summarize Before Clear：</strong> 如果想保留记忆，先让它 /compact（压缩上下文），再继续。</li>
</ol>
<hr />
<h2>工程化配置——给 AI 立规矩 (Configuration)</h2>
<p><strong>核心逻辑：</strong> 不要每次都手动教，把规则固化到文件里。</p>
<ol>
<li><strong>CLAUDE.md 是宪法：</strong> 在根目录创建 CLAUDE.md，这是它每次启动必读的“员工手册”。</li>
<li><strong>Use /init：</strong> 运行 /init 命令，让 Claude 自动分析项目并生成初始的 CLAUDE.md。</li>
<li><strong>Prune Ruthlessly：</strong> <strong>CLAUDE.md 不要写废话！</strong>
<ul>
<li>❌ “请写出优雅的代码。”（浪费 Token）</li>
<li>✅ “使用 npm run test:unit 运行单元测试。”（高价值信息）</li>
</ul>
</li>
<li><strong>Bash Commands：</strong> 在文档里告诉它项目特有的命令（如构建、部署脚本）。</li>
<li><strong>Code Style：</strong> 明确约定：用 Tab 还是 Space？用 TypeScript 还是 JS？</li>
<li><strong>Import Rules：</strong> 告诉它 @src/ 别名指向哪里，避免它瞎猜路径。</li>
<li><strong>Child Directories：</strong> 对于 Monorepo，可以在子目录放单独的 CLAUDE.md，它会自动继承。</li>
<li><strong>Permissions Allowlist：</strong> 别做“点点点”工程师。用 /permissions 把 ls, grep, npm test 加入白名单。</li>
<li><strong>Sandbox Mode：</strong> 对于不信任的任务，开启 /sandbox，让它在隔离环境中撒欢。</li>
<li><strong>Dangerously Skip：</strong> 只有在完全可控（断网/沙箱）时，才使用 &#8211;dangerously-skip-permissions。</li>
<li><strong>CLI Tools：</strong> 安装 gh (GitHub CLI)，让 Claude 能直接提 PR、看 Issue。</li>
<li><strong>MCP Connect：</strong> 使用 claude mcp add 连接 Postgres 或 Notion。数据不再是孤岛。</li>
<li><strong>Learn CLI：</strong> 不知道怎么用某个工具？让 Claude 先运行 tool &#8211;help 自学。</li>
</ol>
<hr />
<h2>技能与自动化——扩展能力 (Skills &amp; Automation)</h2>
<p><strong>核心逻辑：</strong> 把重复的流程封装成“技能”，把 AI 集成到流水线。</p>
<ol>
<li><strong>Skills Definition：</strong> 在 .claude/skills/ 下创建 SKILL.md，定义可复用的能力。</li>
<li><strong>Domain Knowledge：</strong> 把复杂的业务逻辑（如“订单状态流转规则”）封装成 Skill，用到时才加载。</li>
<li><strong>Disable Model Invocation：</strong> 对于高风险 Skill，设置 disable-model-invocation: true，强制人工确认。</li>
<li><strong>Custom Subagents：</strong> 定义专门的 .claude/agents/security-reviewer.md。
<ul>
<li>让它扮演“安全专家”，只负责 Review，不负责写代码。</li>
</ul>
</li>
<li><strong>Delegate to Subagents：</strong> 在主会话中说：“用 security-reviewer 检查刚才的代码。”</li>
<li><strong>Install Plugins：</strong> 运行 /plugin，去市场找现成的技能包（如 Python 代码分析）。</li>
<li><strong>Code Intelligence Plugin：</strong> 必装！给 Claude 提供“跳转定义”和“查找引用”的能力（基于 LSP）。</li>
<li><strong>Hooks：</strong> 设置 .claude/settings.json 中的 Hooks。
<ul>
<li>例如：每次 Auto-fix 后自动运行 Lint。</li>
</ul>
</li>
<li><strong>Headless Mode：</strong> claude -p “prompt”。这是自动化的神器。</li>
<li><strong>CI Integration：</strong> 在 GitHub Actions 里用 Headless Mode 自动 Review PR。</li>
<li><strong>Structured Output：</strong> 使用 &#8211;output-format json，让脚本能解析 Claude 的回答。</li>
<li><strong>Fan-out Pattern：</strong> 批量修改 100 个文件？写个 Shell 脚本循环调用 claude -p。</li>
</ol>
<hr />
<h2>避坑指南——反模式 (Anti-patterns)</h2>
<p><strong>核心逻辑：</strong> 识别“失败的味道”，及时止损。</p>
<ol>
<li><strong>The Kitchen Sink Session：</strong> 试图在一个 Session 里修 Bug、写新功能、又写文档。
<ul>
<li><strong>后果：</strong> 上下文污染，智商直线下降。</li>
<li><strong>解法：</strong> 一事一议，做完就 /clear。</li>
</ul>
</li>
<li><strong>Over-correcting：</strong> 纠正了两次还不对？
<ul>
<li><strong>后果：</strong> 错误路径被强化，越改越错。</li>
<li><strong>解法：</strong> 别纠缠！直接 /clear，优化 Prompt 后重来。</li>
</ul>
</li>
<li><strong>The Trust-then-Verify Gap：</strong> 还没测试就觉得“看起来是对的”。
<ul>
<li><strong>后果：</strong> 生产环境事故。</li>
<li><strong>解法：</strong> 没有 Pass 测试的代码，一行都别信。</li>
</ul>
</li>
<li><strong>The Infinite Exploration：</strong> 让它“调查一下代码库”，不给范围。
<ul>
<li><strong>后果：</strong> 读了几百个文件，Token 耗尽，还没开始干活。</li>
<li><strong>解法：</strong> 限制搜索范围，或者用 Subagent 去做调研。</li>
</ul>
</li>
<li><strong>Vague Error Reporting：</strong> 只说“不行”或“报错了”。
<ul>
<li><strong>后果：</strong> Claude 只能瞎猜。</li>
<li><strong>解法：</strong> 粘贴完整的 Stack Trace。</li>
</ul>
</li>
</ol>
<hr />
<h2>高阶操作——神级技巧 (Pro Moves)</h2>
<ol>
<li><strong>Resume Session：</strong> 昨天没干完？claude &#8211;resume 接着聊。</li>
<li><strong>Rename Session：</strong> 用 /rename 给会话起个好名字（如 feat-login-oauth），方便找回。</li>
<li><strong>Rewind (Esc+Esc)：</strong> 走错方向了？双击 Esc 回滚到上一步，比改代码快。</li>
<li><strong>Let Claude Interview You：</strong> 不知道怎么写 Spec？
<ul>
<li>Prompt：<em>“我想做个 X 功能。请作为一个资深架构师，向我提问，直到你觉得信息足够写出 Spec 为止。”</em></li>
</ul>
</li>
<li><strong>Self-Correction Loop：</strong> 让它自己改自己的作业。
<ul>
<li>Prompt：<em>“分析你刚才生成的代码，找出 3 个潜在的 Edge Case，并修复它们。”</em></li>
</ul>
</li>
<li><strong>Model Tier Selection：</strong> 简单的 Lint 修复用 Haiku（快且便宜），架构设计用 Opus（聪明但贵）。</li>
<li><strong>Parallel Sessions：</strong> 开两个终端。一个写代码（Writer），一个做 Review（Reviewer）。左右互搏，质量倍增。</li>
<li><strong>Develop Intuition：</strong> 最后的建议——多用。建立对“上下文容量”和“模型能力边界”的体感。</li>
</ol>
<hr />
<h2>小结：从 直觉 到 方法论</h2>
<p>刚开始使用 Claude Code，你可能靠的是<strong>直觉</strong>。但要在大规模工程中稳定产出，你必须依靠<strong>方法论</strong>。</p>
<p>这 50 条军规，就是从“抽盲盒”走向“工业化生产”的桥梁。掌握了它们，你就不再是被动的 User，而是这支硅基军团的 <strong>Commander</strong>。</p>
<p>资料链接：https://code.claude.com/docs/en/best-practices</p>
<hr />
<p><strong>深度实战：构建你的“AI 原生工作流”</strong></p>
<p>Tip 只是冰山一角。真正的威力在于将这些技巧组合成一套<strong>“开发工作流”</strong>。</p>
<p>在我的极客时间专栏<strong>《<a href="http://gk.link/a/12EPd">AI 原生开发工作流实战</a>》</strong>中，我将带你实战演示：</p>
<ul>
<li>CLAUDE.md 实战：如何从零编写一个完美的、模块化的项目宪法？</li>
<li>驾驭Claude Code：实现工作流自动化</li>
<li>从“AI使用者”进化为规范驱动开发的“工作流指挥家”</li>
</ul>
<p><strong>别再用蛮力写代码了。扫描下方二维码，学会用 AI 的杠杆。</strong></p>
<p><img src="https://tonybai.com/wp-content/uploads/2025/ai-native-dev-workflow-qr.png" alt="" /></p>
<hr />
<p>你的Go技能，是否也卡在了“熟练”到“精通”的瓶颈期？</p>
<ul>
<li>想写出更地道、更健壮的Go代码，却总在细节上踩坑？</li>
<li>渴望提升软件设计能力，驾驭复杂Go项目却缺乏章法？</li>
<li>想打造生产级的Go服务，却在工程化实践中屡屡受挫？</li>
</ul>
<p>继《<a href="http://gk.link/a/10AVZ">Go语言第一课</a>》后，我的《<a href="http://gk.link/a/12yGY">Go语言进阶课</a>》终于在极客时间与大家见面了！</p>
<p>我的全新极客时间专栏 《<a href="http://gk.link/a/12yGY">Tony Bai·Go语言进阶课</a>》就是为这样的你量身打造！30+讲硬核内容，带你夯实语法认知，提升设计思维，锻造工程实践能力，更有实战项目串讲。</p>
<p>目标只有一个：助你完成从“Go熟练工”到“Go专家”的蜕变！ 现在就加入，让你的Go技能再上一个新台阶！</p>
<p><img src="https://tonybai.com/wp-content/uploads/course-card/iamtonybai-banner-2.gif" alt="" /></p>
<hr />
<p>商务合作方式：撰稿、出书、培训、在线课程、合伙创业、咨询、广告合作。如有需求，请扫描下方公众号二维码，与我私信联系。</p>
<p><img src="http://image.tonybai.com/img/tonybai/iamtonybai-wechat-qr.png" alt="" /></p>
<p style='text-align:left'>&copy; 2026, <a href='https://tonybai.com'>bigwhite</a>. 版权所有. </p>
]]></content:encoded>
			<wfw:commentRss>https://tonybai.com/2026/01/25/claude-code-official-best-practices-50-core-rules/feed/</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>Go 的“显式哲学”为何在接口上“食言”了？—— 探秘隐式接口背后的设计智慧</title>
		<link>https://tonybai.com/2026/01/14/go-explicit-philosophy-implicit-interfaces-design-wisdom/</link>
		<comments>https://tonybai.com/2026/01/14/go-explicit-philosophy-implicit-interfaces-design-wisdom/#comments</comments>
		<pubDate>Wed, 14 Jan 2026 00:17:21 +0000</pubDate>
		<dc:creator>bigwhite</dc:creator>
				<category><![CDATA[技术志]]></category>
		<category><![CDATA[CompilationAssertion]]></category>
		<category><![CDATA[ConsumerDefinedInterfaces]]></category>
		<category><![CDATA[Decoupling]]></category>
		<category><![CDATA[DuckTyping]]></category>
		<category><![CDATA[ExplicitPhilosophy]]></category>
		<category><![CDATA[Go]]></category>
		<category><![CDATA[Golang]]></category>
		<category><![CDATA[IDE]]></category>
		<category><![CDATA[ImplicitInterface]]></category>
		<category><![CDATA[Interface]]></category>
		<category><![CDATA[Mocking]]></category>
		<category><![CDATA[SmallInterface]]></category>
		<category><![CDATA[Stub]]></category>
		<category><![CDATA[Testing]]></category>
		<category><![CDATA[事后抽象]]></category>
		<category><![CDATA[代码复杂度]]></category>
		<category><![CDATA[代码演进]]></category>
		<category><![CDATA[依赖倒置]]></category>
		<category><![CDATA[依赖关系]]></category>
		<category><![CDATA[元数据]]></category>
		<category><![CDATA[冗长]]></category>
		<category><![CDATA[动态性]]></category>
		<category><![CDATA[动态语言]]></category>
		<category><![CDATA[单元测试]]></category>
		<category><![CDATA[反模式]]></category>
		<category><![CDATA[契约]]></category>
		<category><![CDATA[契约精神]]></category>
		<category><![CDATA[安全性]]></category>
		<category><![CDATA[小接口哲学]]></category>
		<category><![CDATA[接口定义]]></category>
		<category><![CDATA[接口污染]]></category>
		<category><![CDATA[接口设计]]></category>
		<category><![CDATA[显式哲学]]></category>
		<category><![CDATA[智慧]]></category>
		<category><![CDATA[最小化Mock]]></category>
		<category><![CDATA[架构解耦]]></category>
		<category><![CDATA[桩]]></category>
		<category><![CDATA[消费者]]></category>
		<category><![CDATA[消费者定义接口]]></category>
		<category><![CDATA[消费者端]]></category>
		<category><![CDATA[灵活性]]></category>
		<category><![CDATA[生产代码]]></category>
		<category><![CDATA[生产者]]></category>
		<category><![CDATA[生产者端]]></category>
		<category><![CDATA[简练]]></category>
		<category><![CDATA[类型分类]]></category>
		<category><![CDATA[结构化]]></category>
		<category><![CDATA[结构化动态性]]></category>
		<category><![CDATA[编译期断言]]></category>
		<category><![CDATA[耦合]]></category>
		<category><![CDATA[解耦]]></category>
		<category><![CDATA[设计哲学]]></category>
		<category><![CDATA[设计智慧]]></category>
		<category><![CDATA[软件架构]]></category>
		<category><![CDATA[阅读成本]]></category>
		<category><![CDATA[陷阱]]></category>
		<category><![CDATA[隐式接口]]></category>
		<category><![CDATA[静态语言]]></category>
		<category><![CDATA[魔法]]></category>
		<category><![CDATA[魔法代码]]></category>
		<category><![CDATA[鸭子类型]]></category>

		<guid isPermaLink="false">https://tonybai.com/?p=5719</guid>
		<description><![CDATA[本文永久链接 &#8211; https://tonybai.com/2026/01/14/go-explicit-philosophy-implicit-interfaces-design-wisdom 大家好，我是Tony Bai。 “Go 倾向于显式、冗长的代码，而不是‘魔法’。那么，为什么接口实现却是隐式的呢？这让理解代码变得困难多了，简直让我抓狂。” 前不久，一位 Gopher 在 Reddit 上发出了这样的灵魂拷问。这不仅仅是一个新手的问题，它触及了 Go 语言设计中最有趣、也最常被误解的一个矛盾：在一个崇尚“显式”的语言里，为什么最核心的抽象机制（接口）却选择了极致的“隐式”？ 相比于 Java 的 implements 或 Rust 的 impl for，Go 的这种“只要方法匹配，就自动实现”的 Duck Typing 风格，确实显得格格不入。 是 Go 的设计者们“双标”了吗？还是这背后隐藏着某种更深层的、我们尚未完全领悟的智慧？本文将带你深入 Go 的设计哲学，揭开这个“反直觉”设计背后的真相。 显式实现的“原罪”——被倒置的依赖 要理解 Go 为何选择隐式，我们首先要看看“显式实现”带来了什么问题。在 Java 或 C# 中，如果你想让你的类实现一个接口，你必须在定义类的时候就显式声明： // Java public class MyReaderImpl implements MyReaderIntf { ... } 这看起来很清晰，但它引入了一个致命的耦合：生产者（具体类型）必须知道消费者（接口）的存在。 这意味着： 你无法为第三方类型实现接口：如果你使用了一个第三方库的结构体，而你想让它实现你自己定义的接口，你做不到。因为你无法修改第三方库的源码去加上 implements MyInterface。 [...]]]></description>
			<content:encoded><![CDATA[<p><img src="https://tonybai.com/wp-content/uploads/2025/go-explicit-philosophy-implicit-interfaces-design-wisdom-1.png" alt="" /></p>
<p><a href="https://tonybai.com/2026/01/14/go-explicit-philosophy-implicit-interfaces-design-wisdom">本文永久链接</a> &#8211; https://tonybai.com/2026/01/14/go-explicit-philosophy-implicit-interfaces-design-wisdom</p>
<p>大家好，我是Tony Bai。</p>
<p>“Go 倾向于显式、冗长的代码，而不是‘魔法’。那么，为什么接口实现却是<strong>隐式</strong>的呢？这让理解代码变得困难多了，简直让我抓狂。”</p>
<p>前不久，<a href="https://www.reddit.com/r/golang/comments/1pa6t2m/go_prefers_explicit_verbose_code_over_magic_so/">一位 Gopher 在 Reddit 上发出了这样的灵魂拷问</a>。这不仅仅是一个新手的问题，它触及了 Go 语言设计中最有趣、也最常被误解的一个矛盾：<strong>在一个崇尚“显式”的语言里，为什么最核心的抽象机制（接口）却选择了极致的“隐式”？</strong></p>
<p>相比于 Java 的 implements 或 Rust 的 impl for，Go 的这种“只要方法匹配，就自动实现”的 Duck Typing 风格，确实显得格格不入。</p>
<p>是 Go 的设计者们“双标”了吗？还是这背后隐藏着某种更深层的、我们尚未完全领悟的智慧？本文将带你深入 Go 的设计哲学，揭开这个“反直觉”设计背后的真相。</p>
<p><img src="https://tonybai.com/wp-content/uploads/2026/distributed-system-guide-qr.png" alt="" /></p>
<h2>显式实现的“原罪”——被倒置的依赖</h2>
<p>要理解 Go 为何选择隐式，我们首先要看看“显式实现”带来了什么问题。在 Java 或 C# 中，如果你想让你的类实现一个接口，你必须<strong>在定义类的时候</strong>就显式声明：</p>
<pre><code class="java">// Java
public class MyReaderImpl implements MyReaderIntf { ... }
</code></pre>
<p>这看起来很清晰，但它引入了一个致命的耦合：<strong>生产者（具体类型）必须知道消费者（接口）的存在。</strong></p>
<p>这意味着：</p>
<ol>
<li><strong>你无法为第三方类型实现接口</strong>：如果你使用了一个第三方库的结构体，而你想让它实现你自己定义的接口，你做不到。因为你无法修改第三方库的源码去加上 implements MyInterface。</li>
<li><strong>“上帝接口”的诞生</strong>：为了规避第1点，库的设计者倾向于预定义庞大的、包罗万象的接口（如 IUser），强迫所有实现者都去依赖这个庞大的契约。这导致了<strong>接口定义的早产</strong>和<strong>不必要的依赖</strong>。</li>
</ol>
<p>Go 的设计者们敏锐地捕捉到了这一点。他们认为，<strong>接口应当由消费者（Consumer）定义，而不是生产者（Producer）。</strong></p>
<h2>解耦的艺术——消费者定义的接口</h2>
<p>Go 的隐式接口，彻底反转了这种依赖关系。</p>
<p>在 Go 中，<strong>具体的类型（如struct）不需要知道接口的存在</strong>。它只需要专注地实现它该有的方法。而接口的定义，可以发生在<strong>任何时间、任何地点</strong>，通常是在<strong>使用方（调用者）</strong>的代码中。</p>
<p>正如 Reddit 上高赞评论所言：</p>
<blockquote>
<p><strong>“Define interfaces at the receiving end.”（在接收端定义接口）</strong></p>
</blockquote>
<p>这带来了前所未有的灵活性：</p>
<ul>
<li><strong>事后抽象</strong>：你可以先写具体的实现代码。等到某一天，你发现需要对这部分逻辑进行抽象或测试时，你可以在调用方就地定义一个接口，而无需修改原有的具体类型代码。</li>
<li><strong>小接口哲学</strong>：因为接口是消费者按需定义的，所以 Go 鼓励定义极小的接口（如 io.Reader 只有一个方法）。如果必须显式声明，开发者会倾向于定义大接口以减少声明的繁琐，而隐式接口则让 interface{ Read(&#8230;) } 这种微型契约变得轻量且自然。</li>
</ul>
<p><strong>这就是隐式的代价换来的价值：彻底的解耦。</strong> 它打破了“实现”与“抽象”之间的强绑定，让代码的演进变得更加自由。</p>
<h2>测试与 Mock 的天堂：只 Mock 你关心的</h2>
<p>在 Java 或 C# 这样的显式接口语言中，如果你要测试一个依赖了 Database 类的函数，你通常面临两个选择：</p>
<ol>
<li>引入 Database 所在的庞大包。</li>
<li>为了测试，不得不为 Database 定义一个包含其<em>所有</em>方法的 IDatabase 接口，哪怕你只用了其中一个 Query 方法。这被称为“接口污染”。</li>
</ol>
<p>而在 Go 中，<strong>隐式接口允许我们在“测试现场”定义接口</strong>。这被称为<strong>“最小化 Mock”</strong>。</p>
<p>假设有这样一个场景：我们需要编写一个 WeatherReporter（天气播报员），它依赖一个庞大的第三方天气 SDK 来获取数据。</p>
<p><strong>第三方库代码（我们无法修改，且很庞大）：</strong></p>
<pre><code class="go">// thirdparty/weather.go
type HeavyWeatherClient struct { ... } // 包含几百个方法
func (c *HeavyWeatherClient) GetTemp(city string) float64 { ... } // 我们只用这一个
func (c *HeavyWeatherClient) GetHumidity() float64 { ... }
func (c *HeavyWeatherClient) GetWindSpeed() float64 { ... }
// ... 还有几百个其他方法 ...
</code></pre>
<p><strong>我们的业务代码：</strong></p>
<pre><code class="go">// reporter.go
// 注意：这里我们直接接受具体的 HeavyWeatherClient，或者任何实现了 GetTemp 的东西
func ReportTemperature(client interface{ GetTemp(string) float64 }, city string) {
    temp := client.GetTemp(city)
    if temp &gt; 30 {
        fmt.Println("It's hot!")
    }
}
</code></pre>
<p><strong>我们的测试代码（Test 文件）：</strong></p>
<p>在测试中，我们完全不需要引入那个庞大的 thirdparty 包，也不需要 mock 那几百个无关的方法。我们只需要在测试文件里定义一个极小的接口：</p>
<pre><code class="go">// reporter_test.go

// 1. 定义一个只包含我们所用方法的“本地接口”
// 甚至都不需要给它起名字，匿名接口也可以
type mockFetcher struct{}

func (m *mockFetcher) GetTemp(city string) float64 {
    return 35.0 // 返回一个假数据
}

func TestReportTemperature(t *testing.T) {
    mock := &amp;mockFetcher{}

    // 2. Go 的隐式特性发挥作用：
    // mockFetcher 并没有显式声明实现了任何接口，
    // 但它拥有 GetTemp 方法，所以它可以被传入 ReportTemperature！
    ReportTemperature(mock, "Beijing")

    // 验证逻辑...
}
</code></pre>
<blockquote>
<p><strong>注：关于 Mock 与 Stub 的严谨区分</strong></p>
<p>细心的读者可能发现，严格来说，上例中的 mockFetcher 更像是一个 <strong>Stub (桩)</strong>——它只返回固定数据，不验证调用行为。但在 Go 社区的工程实践中，我们习惯将这类用于替换真实依赖的测试替身统称为 <strong>Mock</strong>。为了方便理解，本文沿用了这一通俗叫法。</p>
</blockquote>
<p>这就是“天堂”的含义：你可以忽略对象 99% 的复杂性，只为你关心的那 1% 编写 Mock。这种<strong>按需定义 (Ad-hoc)</strong> 的能力，让 Go 的单元测试变得极其轻量和纯粹，彻底摆脱了对重型 Mock 框架的依赖。</p>
<p><strong>警惕：不要为了测试而“预定义”接口</strong></p>
<p>这里有一个新手常犯的错误：为了方便测试，在生产代码中为每一个 Struct 都配对写一个 Interface（例如 type UserServiceImpl struct 和 type UserService interface）。</p>
<p>这是一个反模式（Anti-pattern）。 Go 的哲学之一是不要在生产者（Producer）端定义接口，要在消费者（Consumer）端定义接口。如果你在生产代码中定义了一个只被自己实现的接口，你只是在增加代码的复杂度和阅读成本，而没有带来任何解耦的实际价值。</p>
<p><strong>正确的做法</strong>：</p>
<ul>
<li>如果 UserService 是你自己写的，且逻辑简单（纯逻辑，无 I/O），直接测试 Struct 本身即可，<strong>不需要接口</strong>。</li>
<li>如果 UserService 确实包含数据库操作，需要被 Mock，那么请在<strong>调用它的人那里</strong>（或者在测试文件里）定义接口，而不是在 UserService 旁边定义一个“没用”的接口。</li>
</ul>
<p><strong>记住：接口通过解耦来促进测试，但不要为了测试而强行制造接口。</strong></p>
<h2>如何应对“隐式”带来的困扰？</h2>
<p>当然，提问者的困惑是真实的：<strong>“我怎么知道这个结构体实现了哪些接口？”</strong></p>
<p>这种“不可知性”确实是隐式接口的副作用。但在 Go 的工程实践中，我们有成熟的应对方案：</p>
<ol>
<li><strong>IDE 的力量</strong>：现代 IDE（如 GoLand, VS Code，甚至是安装了插件的Vim等）已经完美解决了这个问题。简单的“Find Usages”或“Go to Implementations”就能列出所有匹配的接口。工具弥补了人类肉眼的局限。</li>
<li><strong>编译期断言</strong>：如果你是库的作者，你需要向用户保证你的类型（比如&#42;MyStruct）实现了某个标准接口（例如 io.Writer），为了防止未来修改代码时不小心破坏了这个契约，你可以使用这行经典的“黑魔法”代码：</li>
</ol>
<pre><code class="go">// 这是一道“编译期防线”
var _ io.Writer = (*MyStruct)(nil)
</code></pre>
<p>细心的读者可能会发现，这行代码强制 MyStruct 所在的文件 import 了 io 包。没错，这确实引入了依赖。</p>
<p>但与 Java 强制性的 implements 不同，Go 的这种耦合是<strong>可选的</strong>、<strong>防御性</strong>的。</p>
<ul>
<li>它不是程序运行的必要条件，而是一个<strong>写在源码里的“编译期测试用例”</strong>。</li>
<li>它通常只用于<strong>向标准库或核心框架的稳定接口看齐</strong>。对于业务层那些灵活的、消费者定义的接口，我们通常不需要写这行代码，从而保持代码的纯净与解耦。</li>
</ul>
<h2>小结：显式的代码，隐式的契约</h2>
<p>回到最初的问题：Go 违背了“显式”的哲学吗？</p>
<p>答案是：<strong>没有。Go 追求的是“行为”的显式，而非“类型分类”的显式。</strong></p>
<p>Go 让你显式地编写方法，显式地处理错误，显式地进行类型转换。但在“谁实现了谁”这种<strong>元数据</strong>层面，Go 选择了隐式，因为它认为<strong>“鸭子类型” (If it walks like a duck&#8230;)</strong> 才是对软件组件交互最自然、最解耦的描述。</p>
<p>Go 的隐式接口，不是为了省去敲 implements 这几个字母的懒惰，而是一场关于<strong>软件架构解耦</strong>的深谋远虑。它赋予了 Go 语言一种独特的<strong>“结构化动态性”</strong>——既有静态语言的安全，又有动态语言的灵活。这，正是 Go 设计哲学的精妙所在。</p>
<p>资料链接：https://www.reddit.com/r/golang/comments/1pa6t2m/go_prefers_explicit_verbose_code_over_magic_so</p>
<hr />
<p><strong>你的接口设计习惯</strong></p>
<p>Go 的隐式接口虽然灵活，但也给了开发者极大的自由度。<strong>在你的项目中，你是习惯先定义接口再写实现（顶层设计），还是先写实现再按需提取接口（事后抽象）？你是否也曾陷入过“接口定义泛滥”的陷阱？</strong></p>
<p><strong>欢迎在评论区分享你的设计心得或踩坑故事！</strong> 让我们一起探讨如何用好这把“双刃剑”。</p>
<p><strong>如果这篇文章解开了你对 Go 接口的困惑，别忘了点个【赞】和【在看】，并转发给你的开发伙伴，一起感受 Go 的设计之美！</strong></p>
<hr />
<p>还在为“复制粘贴喂AI”而烦恼？我的新专栏 <strong>《<a href="http://gk.link/a/12EPd">AI原生开发工作流实战</a>》</strong> 将带你：</p>
<ul>
<li>告别低效，重塑开发范式</li>
<li>驾驭AI Agent(Claude Code)，实现工作流自动化</li>
<li>从“AI使用者”进化为规范驱动开发的“工作流指挥家”</li>
</ul>
<p>扫描下方二维码，开启你的AI原生开发之旅。</p>
<p><img src="https://tonybai.com/wp-content/uploads/2025/ai-native-dev-workflow-qr.png" alt="" /></p>
<hr />
<p>你的Go技能，是否也卡在了“熟练”到“精通”的瓶颈期？</p>
<ul>
<li>想写出更地道、更健壮的Go代码，却总在细节上踩坑？</li>
<li>渴望提升软件设计能力，驾驭复杂Go项目却缺乏章法？</li>
<li>想打造生产级的Go服务，却在工程化实践中屡屡受挫？</li>
</ul>
<p>继《<a href="http://gk.link/a/10AVZ">Go语言第一课</a>》后，我的《<a href="http://gk.link/a/12yGY">Go语言进阶课</a>》终于在极客时间与大家见面了！</p>
<p>我的全新极客时间专栏 《<a href="http://gk.link/a/12yGY">Tony Bai·Go语言进阶课</a>》就是为这样的你量身打造！30+讲硬核内容，带你夯实语法认知，提升设计思维，锻造工程实践能力，更有实战项目串讲。</p>
<p>目标只有一个：助你完成从“Go熟练工”到“Go专家”的蜕变！ 现在就加入，让你的Go技能再上一个新台阶！</p>
<p><img src="https://tonybai.com/wp-content/uploads/course-card/iamtonybai-banner-2.gif" alt="" /></p>
<hr />
<p>商务合作方式：撰稿、出书、培训、在线课程、合伙创业、咨询、广告合作。如有需求，请扫描下方公众号二维码，与我私信联系。</p>
<p><img src="http://image.tonybai.com/img/tonybai/iamtonybai-wechat-qr.png" alt="" /></p>
<p style='text-align:left'>&copy; 2026, <a href='https://tonybai.com'>bigwhite</a>. 版权所有. </p>
]]></content:encoded>
			<wfw:commentRss>https://tonybai.com/2026/01/14/go-explicit-philosophy-implicit-interfaces-design-wisdom/feed/</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>像构建 Claude Code 一样构建应用：揭秘 Agent-native 架构的 5 大核心原则</title>
		<link>https://tonybai.com/2026/01/13/agent-native-architecture/</link>
		<comments>https://tonybai.com/2026/01/13/agent-native-architecture/#comments</comments>
		<pubDate>Tue, 13 Jan 2026 00:20:50 +0000</pubDate>
		<dc:creator>bigwhite</dc:creator>
				<category><![CDATA[技术志]]></category>
		<category><![CDATA[AgentasRouter]]></category>
		<category><![CDATA[AgentnativeArchitecture]]></category>
		<category><![CDATA[AIAgent]]></category>
		<category><![CDATA[Anthropic]]></category>
		<category><![CDATA[CapabilityMap]]></category>
		<category><![CDATA[ClaudeCode]]></category>
		<category><![CDATA[Composability]]></category>
		<category><![CDATA[DanShipper]]></category>
		<category><![CDATA[DefensiveToolDesign]]></category>
		<category><![CDATA[DomainTools]]></category>
		<category><![CDATA[EmergentCapability]]></category>
		<category><![CDATA[Granularity]]></category>
		<category><![CDATA[Improvementovertime]]></category>
		<category><![CDATA[LLM]]></category>
		<category><![CDATA[MasterLoop]]></category>
		<category><![CDATA[Parity]]></category>
		<category><![CDATA[SDD]]></category>
		<category><![CDATA[SharedWorkspace]]></category>
		<category><![CDATA[SpecDrivenDevelopment]]></category>
		<category><![CDATA[上下文管理]]></category>
		<category><![CDATA[主循环]]></category>
		<category><![CDATA[判断力]]></category>
		<category><![CDATA[原子级]]></category>
		<category><![CDATA[原语]]></category>
		<category><![CDATA[反模式]]></category>
		<category><![CDATA[可组合性]]></category>
		<category><![CDATA[工程实践]]></category>
		<category><![CDATA[平权]]></category>
		<category><![CDATA[控制权移交]]></category>
		<category><![CDATA[智能体]]></category>
		<category><![CDATA[智能体原生架构]]></category>
		<category><![CDATA[架构原则]]></category>
		<category><![CDATA[涌现能力]]></category>
		<category><![CDATA[结果导向]]></category>
		<category><![CDATA[能力映射表]]></category>
		<category><![CDATA[自动化工作流]]></category>
		<category><![CDATA[规范驱动开发]]></category>
		<category><![CDATA[软件工程范式]]></category>
		<category><![CDATA[随时间进化]]></category>
		<category><![CDATA[领域工具]]></category>
		<category><![CDATA[颗粒度]]></category>
		<category><![CDATA[飞轮效应]]></category>

		<guid isPermaLink="false">https://tonybai.com/?p=5713</guid>
		<description><![CDATA[本文永久链接 &#8211; https://tonybai.com/2026/01/13/agent-native-architecture 大家好，我是Tony Bai。 软件智能体（Software Agents）现在已经能够可靠地工作了。Claude Code 证明了，只要赋予一个大语言模型（LLM）访问 Bash 和文件系统的权限，并让它在一个循环中运行直到达成目标，它就能自主完成复杂的多步骤任务。 而这里有一个令人惊讶的发现：一个优秀的编程 Agent，本质上就是一个优秀的通用 Agent。 支撑 Claude Code 重构代码库的同一套架构，同样可以用来整理你的文件、管理阅读列表，或自动化你的工作流。通过 Claude Code SDK、Google adk-go等，这种能力变得触手可及。你可以构建一种全新的应用：其功能不再是你写死的代码，而是你描述的结果（Outcome）——由一个装备了工具的 Agent，在一个循环中自主实现。 这开启了一个全新的领域：软件开始像 Claude Code 一样工作，但应用场景远超编程。这就是 Anthropic 与 Dan Shipper 联合发布的最新架构理念 —— Agent-native Architecture（智能体原生架构）。 本文将深入拆解这份文档中的核心原则与架构模式，带你领略这一前沿范式。 核心原则 要构建 Agent-native 应用，我们需要遵循以下 5 大核心原则： 平权 (Parity) 原则： 用户通过 UI 能做的任何事，Agent 必须能通过工具（Tools）完成。 这是基石。如果没有平权，其他一切都无从谈起。你必须确保 Agent 拥有一套完整的工具集，能够覆盖 UI 的所有能力。 随机挑选一个 [...]]]></description>
			<content:encoded><![CDATA[<p><img src="https://tonybai.com/wp-content/uploads/2026/agent-native-architecture-1.png" alt="" /></p>
<p><a href="https://tonybai.com/2026/01/13/agent-native-architecture">本文永久链接</a> &#8211; https://tonybai.com/2026/01/13/agent-native-architecture</p>
<p>大家好，我是Tony Bai。</p>
<p>软件智能体（Software Agents）现在已经能够可靠地工作了。<strong>Claude Code</strong> 证明了，只要赋予一个大语言模型（LLM）访问 Bash 和文件系统的权限，并让它在一个循环中运行直到达成目标，它就能自主完成复杂的多步骤任务。</p>
<p>而这里有一个令人惊讶的发现：<strong>一个优秀的编程 Agent，本质上就是一个优秀的通用 Agent。</strong></p>
<p>支撑 Claude Code 重构代码库的同一套架构，同样可以用来整理你的文件、管理阅读列表，或自动化你的工作流。通过 <strong>Claude Code SDK、<a href="https://tonybai.com/2025/11/24/google-adk-go-in-action">Google adk-go</a>等</strong>，这种能力变得触手可及。你可以构建一种全新的应用：其功能不再是你写死的代码，而是你描述的<strong>结果（Outcome）</strong>——由一个装备了工具的 Agent，在一个循环中自主实现。</p>
<p>这开启了一个全新的领域：软件开始像 Claude Code 一样工作，但应用场景远超编程。这就是 <strong>Anthropic</strong> 与 <strong>Dan Shipper</strong> 联合发布的最新架构理念 —— <strong><a href="https://every.to/guides/agent-native">Agent-native Architecture（智能体原生架构）</a></strong>。</p>
<p>本文将深入拆解这份文档中的核心原则与架构模式，带你领略这一前沿范式。</p>
<p><img src="https://tonybai.com/wp-content/uploads/2025/paid/google-adk-in-action-qr.png" alt="" /></p>
<hr />
<h2>核心原则</h2>
<p>要构建 Agent-native 应用，我们需要遵循以下 5 大核心原则：</p>
<h3>平权 (Parity)</h3>
<p><strong>原则：</strong> 用户通过 UI 能做的任何事，Agent 必须能通过工具（Tools）完成。</p>
<p>这是基石。如果没有平权，其他一切都无从谈起。你必须确保 Agent 拥有一套完整的工具集，能够覆盖 UI 的所有能力。</p>
<p>随机挑选一个 UI 动作。Agent 能完成吗？这是验证这一原则的测试标准！</p>
<h3>颗粒度 (Granularity)</h3>
<p><strong>原则：</strong> 工具应该是<strong>原子级（Atomic）</strong>的原语。功能特性（Features）是由 Agent 在循环中通过组合工具达成的结果。</p>
<p>工具是基本能力。功能特性是 Prompt 描述的一个结果，由 Agent 动态组合工具来实现。</p>
<p>要改变软件的行为，你是通过修改 Prompt，还是重构代码？如果是前者，说明颗粒度对了。</p>
<h3>可组合性 (Composability)</h3>
<p><strong>原则：</strong> 拥有了原子工具和平权，你可以仅通过编写新 Prompt 来创造新功能。</p>
<p>比如：想要一个“每周回顾”功能？这只是一个 Prompt：</p>
<blockquote>
<p>“检查本周修改过的文件。总结关键变更。基于未完成项和截止日期，建议下周的三个优先级事项。”</p>
</blockquote>
<p>Agent 会自动调用 list_files、read_file 并结合自身判断力。你描述结果，Agent 负责循环执行。</p>
<p><img src="https://tonybai.com/wp-content/uploads/2026/agent-native-architecture-2.png" alt="" /></p>
<h3>涌现能力 (Emergent Capability)</h3>
<p><strong>原则：</strong> Agent 可以完成你从未显式设计过的任务。</p>
<p>这是 Agent-native 的飞轮效应：</p>
<ol>
<li>你构建原子工具和平权能力。</li>
<li>用户提出了你未预料到的需求。</li>
<li>Agent 组合工具完成了任务（或失败，揭示了工具缺口）。</li>
<li>你观察模式，添加领域工具或专用 Prompt 来优化。</li>
<li><strong>重复上述过程。</strong></li>
</ol>
<p>然后，通过观察你的应用能处理领域内的开放式请求，来验证Agent是否具备这种涌现能力。</p>
<h3>随时间进化 (Improvement over time)</h3>
<p><strong>原则：</strong> Agent-native 应用通过积累上下文和 Prompt 优化而变得更好，而无需重新发版。</p>
<p>与传统软件不同，Agent-native的应用可以通过以下方式自我进化：</p>
<ul>
<li><strong>积累上下文：</strong> 状态通过上下文文件（Context Files）跨会话持久化。</li>
<li><strong>开发者级优化：</strong> 你推送更新的 Prompt，所有用户受益。</li>
<li><strong>用户级定制：</strong> 用户修改 Prompt 以适应自己的工作流。</li>
</ul>
<hr />
<h2>实践中的原则</h2>
<p>有了原则，我们要如何落地？以下是具体的工程实践指南。</p>
<h3>解决“平权”问题</h3>
<p>想象一个笔记 App。用户说：“总结我关于这次会议的笔记，并标记为紧急。”</p>
<p>如果 UI 能做，但 Agent 做不到，Agent 就会卡住。</p>
<p><strong>修正方案：</strong> 建立一张能力映射表（Capability Map）。</p>
<p><img src="https://tonybai.com/wp-content/uploads/2026/agent-native-architecture-3.png" alt="" /></p>
<p>每当添加一个 UI 功能时，都要问：Agent 能达成这个结果吗？如果不能，添加相应的原语工具。将这作为一条工程纪律遵守和贯彻！</p>
<h3>解决“颗粒度”问题：原子化 vs 捆绑逻辑</h3>
<p>要知道：Agent 追求的是通过<strong>判断力（Judgment）</strong>达成结果，而不是执行死板的序列。</p>
<ul>
<li><strong>❌ 错误做法（捆绑逻辑）：</strong>
<ul>
<li>工具：classify_and_organize_files(files)</li>
<li>问题：决策逻辑是你写死的代码(充斥着if、else等)。要改变行为，必须重构代码。灵活性差。</li>
</ul>
</li>
<li><strong>✅ 正确做法（原子化）：</strong>
<ul>
<li>工具：read_file, write_file, move_file, bash</li>
<li>Prompt：<em>“整理下载文件夹&#8230;”</em></li>
<li>优势：Agent 负责决策并组合工具完成。要改变行为，只需修改 Prompt。Agent 拥有了更强地灵活性。</li>
</ul>
</li>
</ul>
<h3>演进路线：从原语到领域工具</h3>
<p>一开始，只提供纯粹的原语（Bash, 文件操作）。这能证明架构可行，并揭示 Agent 真正需要什么。</p>
<p>当模式涌现后，有意识地添加<strong>领域工具（Domain Tools）</strong>：</p>
<ol>
<li><strong>词汇表 (Vocabulary)：</strong> create_note 工具教会了 Agent 你的系统中“笔记”是什么。</li>
<li><strong>护栏 (Guardrails)：</strong> 某些操作需要验证，不应完全交给 Agent 判断。</li>
<li><strong>效率 (Efficiency)：</strong> 将常用操作打包，提升速度和降低成本。</li>
</ol>
<p><strong>领域工具的规则：</strong> 它们应该代表用户视角的一个<strong>概念性动作</strong>。它们可以包含机械验证，但<strong>不要包含“做什么”或“是否做”的判断</strong>——这属于 Prompt。同时，<strong>默认保持原语可用</strong>，不要为了这种封装而关闭底层权限，除非有安全理由。</p>
<h3>升级成为代码 (Graduating to Code)</h3>
<p>有些操作因为性能或可靠性原因，需要从“Agent 编排”升级成为“优化代码”。</p>
<ol>
<li><strong>阶段 1：</strong> Agent 在循环中使用原语（灵活，验证概念）。</li>
<li><strong>阶段 2：</strong> 添加领域工具（更快，但仍由 Agent 编排）。</li>
<li><strong>阶段 3：</strong> 针对热点路径，用优化代码实现（极快，确定性）。</li>
</ol>
<p><strong>注意：</strong> 即使升级为代码，操作时，Agent 仍应保留<strong>直接触发该代码</strong>的能力，并保留<strong>回退到原语</strong>的能力以处理边缘情况。</p>
<hr />
<h2>架构模式：文件即通用接口</h2>
<p>为什么 Claude Code 如此依赖文件系统？因为 <strong>Bash + Filesystem</strong> 是最经受考验的 Agent 接口。</p>
<ul>
<li><strong>已知的：</strong> Agent 天生懂 cat, grep, mv。</li>
<li><strong>可检查的：</strong> 用户能直接看到、编辑、移动文件。没有黑盒。</li>
<li><strong>可移植的：</strong> 导出和备份极其简单。</li>
<li><strong>自文档化：</strong> /projects/acme/notes/ 这种路径本身就携带了语义，比 SELECT * FROM notes WHERE id=123 更容易让 Agent 理解。</li>
</ul>
<h3>实体作用域目录 (Entity-scoped directories)</h3>
<p>这是一种推荐的文件结构模式：</p>
<pre><code class="text">{entity_type}/{entity_id}/
├── primary content  (主内容)
├── metadata         (元数据)
└── related materials (相关素材)
</code></pre>
<p>例如：Research/books/{bookId}/ 包含全文、笔记、来源和 Agent 日志。</p>
<h3>context.md 模式</h3>
<p>Agent 需要知道它在处理什么。系统 Prompt 应该注入以下内容：</p>
<ul>
<li><strong>可用资源：</strong><br />
“`markdown<br />
## Available Data</p>
<ul>
<li>12 notes in /notes</li>
<li>3 active projects in /projects</li>
<li>Preferences at /preferences.md<br />
“`</li>
</ul>
</li>
<li><strong>能力 (Capabilities)：</strong> 描述它能做什么（创建、搜索、整理）。</li>
<li><strong>最近活动 (Recent Activity)：</strong> 用户刚刚做了什么，Agent 上一步做了什么。</li>
</ul>
<h3>文件 vs 数据库</h3>
<ul>
<li><strong>用文件：</strong> 用户需要读写的内容、配置、Agent 生成的内容、大文本。</li>
<li><strong>用数据库：</strong> 高频结构化数据、复杂查询、临时状态（Session/Cache）、强关系数据。</li>
</ul>
<p><strong>冲突处理：</strong> 建议采用 <strong>Shared Workspace</strong> 模式。Agent 和用户在同一个数据空间工作，不搞沙盒。这需要处理并发写入（如 Last-write-wins 或文件锁）。</p>
<hr />
<h2>成功标准与反模式</h2>
<p>在构建 Agent-native 应用时，我们经常会不自觉地滑回传统的软件工程思维。以下是具体的<strong>反模式对照表</strong>：</p>
<h3>Agent 作为路由器 (Agent as Router)</h3>
<ul>
<li><strong>反模式：</strong> Agent 的唯一工作就是分析用户意图，然后调用一个写死的 run_workflow() 函数。</li>
<li><strong>问题：</strong> 你把 Agent 的智能降级成了 if/else。如果用户需求稍微偏离你的预设（例如：“这次运行工作流但跳过最后一步”），系统就会崩溃。</li>
<li><strong>正确姿势：</strong> Agent 应该负责<strong>执行</strong>，而不仅仅是路由。它应该拥有组成该工作流的原子工具，并根据情况决定调用顺序。</li>
</ul>
<h3>“请求/响应”思维 (Request/Response Thinking)</h3>
<ul>
<li><strong>反模式：</strong> 认为 Agent 就像一个 API：给一个输入，它吐出一个输出。</li>
<li><strong>问题：</strong> 这完全丢失了 <strong>Master Loop（Agent主循环）</strong> 的价值。真正的 Agent 是追求<strong>结果（Outcome）</strong>的。它可能会尝试、失败、分析错误、再尝试，直到结果达成。</li>
<li><strong>正确姿势：</strong> 给 Agent 一个目标，让它在一个循环中运行，并在过程中处理意外情况。</li>
</ul>
<h3>防御性工具设计 (Defensive Tool Design)</h3>
<ul>
<li><strong>反模式：</strong> 你因为习惯了防御性编程，所以把工具的输入限制得很死（例如：严格的 Enums，层层校验）。</li>
<li><strong>问题：</strong> 这虽然安全，但也扼杀了<strong>涌现能力</strong>。Agent 无法用你没想到的方式使用工具。</li>
<li><strong>正确姿势：</strong> 默认保持工具开放。除非有明确的安全风险（如删库），否则允许 Agent 传入任何它认为合理的参数。</li>
</ul>
<h3>工作流形状的工具 (Workflow-shaped Tools)</h3>
<ul>
<li><strong>反模式：</strong> 创建一个名为 analyze_and_organize_files() 的工具。</li>
<li><strong>问题：</strong> 你把<strong>“判断逻辑”</strong>捆绑进了工具里。如果要改变组织方式，必须重写代码。</li>
<li><strong>正确姿势：</strong> 拆解为 read、analyze、move。让 Agent 在运行时决定如何组织。</li>
</ul>
<h3>“幸福路径”思维 (Happy Path in Code)</h3>
<ul>
<li><strong>反模式：</strong> 在代码里写死了所有边缘情况的处理逻辑（if error_code == 500: retry）。</li>
<li><strong>问题：</strong> 如果代码处理了所有边缘情况，Agent 就只是一个无脑调用者。</li>
<li><strong>正确姿势：</strong> Agent-native 架构允许 Agent 用<strong>判断力</strong>处理边缘情况。如果遇到 500 错误，Agent 可以决定是重试、还是检查网络、还是通知用户。</li>
</ul>
<h3>成功的终极测试 (The Ultimate Test)</h3>
<p>描述一个你的应用领域内的结果，但针对一个你从未专门开发过的功能。</p>
<p>看看Agent 能否通过在一个循环中运行，自主搞定它？如果能，你构建的就是一个真正的 Agent-native 应用。</p>
<h2>小结：把判断力还给 Agent</h2>
<p>当我们谈论 <strong>Agent-native</strong> 时，我们到底在谈论什么？</p>
<p>其实，这是一场关于<strong>“控制权移交”</strong>的变革。在传统的软件工程中，程序员试图用代码穷尽所有的逻辑分支，我们追求的是确定性。但在 Agent-native 的世界里，我们学会了<strong>放手</strong>。</p>
<p>我们不再编写死板的“步骤”，而是提供灵活的“原语”。我们把<strong>“如何做（How）”</strong>的判断力，从代码中剥离出来，归还给了 Agent。</p>
<ul>
<li><strong>平权</strong> 让 Agent 拥有了和人一样的行动力。</li>
<li><strong>颗粒度</strong> 赋予了 Agent 自由组合的能力。</li>
<li><strong>涌现</strong> 让我们看到了软件在设计之外的可能性。</li>
</ul>
<p>这并不意味着代码不重要了，而是代码的角色变了——从<strong>“指挥官”</strong>变成了<strong>“军火库”</strong>。你负责提供精良的武器（工具），而 Agent 负责在前线根据战况（上下文）灵活作战。</p>
<p>这，才是 AI 时代软件架构的终极形态。</p>
<p>资料链接：https://every.to/guides/agent-native</p>
<hr />
<p><strong>你的 Agent 构想</strong></p>
<p>Agent-native 架构为我们打开了一扇通往无限可能的大门。<strong>如果让你用这种架构重新设计你最熟悉的一个应用（比如待办清单、邮件客户端），你会赋予它哪些以前做不到的“涌现能力”？</strong></p>
<p><strong>欢迎在评论区分享你的脑洞！</strong> 让我们一起畅想软件的未来形态。</p>
<p><strong>如果这篇文章颠覆了你对软件架构的认知，别忘了点个【赞】和【在看】，并转发给你的产品经理和架构师朋友！</strong></p>
<hr />
<p><strong>体验最成功的Agent-native应用</strong></p>
<p><strong>Agent-native</strong> 不仅仅是一种架构，更是一种全新的开发体验。而目前市面上最成熟、最顶级的 Agent-native 实践，就是 <strong>Claude Code</strong> 本身。</p>
<p>想要真正理解什么是“原子化工具”？什么是“循环中的判断力”？最好的方式不是空谈理论，而是<strong>亲自去体验一个优秀的 Agent 是如何工作的。</strong></p>
<p>欢迎关注我的极客时间专栏《<a href="http://gk.link/a/12EPd">AI 原生开发工作流实战</a>》。我们将深入 <strong>Claude Code</strong> 的实战场景，带你亲眼见证它如何利用这些架构原则，把一个模糊的需求变成完美运行的代码。</p>
<p>扫描下方二维码，开启你的AI原生开发之旅。</p>
<p><img src="https://tonybai.com/wp-content/uploads/2025/ai-native-dev-workflow-qr.png" alt="" /></p>
<hr />
<p>你的Go技能，是否也卡在了“熟练”到“精通”的瓶颈期？</p>
<ul>
<li>想写出更地道、更健壮的Go代码，却总在细节上踩坑？</li>
<li>渴望提升软件设计能力，驾驭复杂Go项目却缺乏章法？</li>
<li>想打造生产级的Go服务，却在工程化实践中屡屡受挫？</li>
</ul>
<p>继《<a href="http://gk.link/a/10AVZ">Go语言第一课</a>》后，我的《<a href="http://gk.link/a/12yGY">Go语言进阶课</a>》终于在极客时间与大家见面了！</p>
<p>我的全新极客时间专栏 《<a href="http://gk.link/a/12yGY">Tony Bai·Go语言进阶课</a>》就是为这样的你量身打造！30+讲硬核内容，带你夯实语法认知，提升设计思维，锻造工程实践能力，更有实战项目串讲。</p>
<p>目标只有一个：助你完成从“Go熟练工”到“Go专家”的蜕变！ 现在就加入，让你的Go技能再上一个新台阶！</p>
<p><img src="https://tonybai.com/wp-content/uploads/course-card/iamtonybai-banner-2.gif" alt="" /></p>
<hr />
<p>商务合作方式：撰稿、出书、培训、在线课程、合伙创业、咨询、广告合作。如有需求，请扫描下方公众号二维码，与我私信联系。</p>
<p><img src="http://image.tonybai.com/img/tonybai/iamtonybai-wechat-qr.png" alt="" /></p>
<p style='text-align:left'>&copy; 2026, <a href='https://tonybai.com'>bigwhite</a>. 版权所有. </p>
]]></content:encoded>
			<wfw:commentRss>https://tonybai.com/2026/01/13/agent-native-architecture/feed/</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>Go 语言的“反模式”清单：来自资深 Gopher 血泪教训的 10 条“不要做”</title>
		<link>https://tonybai.com/2025/12/15/go-language-anti-patterns-10-donts/</link>
		<comments>https://tonybai.com/2025/12/15/go-language-anti-patterns-10-donts/#comments</comments>
		<pubDate>Sun, 14 Dec 2025 23:42:30 +0000</pubDate>
		<dc:creator>bigwhite</dc:creator>
				<category><![CDATA[技术志]]></category>
		<category><![CDATA[AcceptInterfacesReturnStructs]]></category>
		<category><![CDATA[AntiPattern]]></category>
		<category><![CDATA[BestPractice]]></category>
		<category><![CDATA[Channel]]></category>
		<category><![CDATA[Codereview]]></category>
		<category><![CDATA[Concurrency]]></category>
		<category><![CDATA[Context]]></category>
		<category><![CDATA[CopyPaste]]></category>
		<category><![CDATA[DependencyManagement]]></category>
		<category><![CDATA[DRY]]></category>
		<category><![CDATA[Go]]></category>
		<category><![CDATA[Golang]]></category>
		<category><![CDATA[Gopher]]></category>
		<category><![CDATA[goroutine]]></category>
		<category><![CDATA[Interface]]></category>
		<category><![CDATA[OverPackaging]]></category>
		<category><![CDATA[PullRequest]]></category>
		<category><![CDATA[reddit]]></category>
		<category><![CDATA[Refactoring]]></category>
		<category><![CDATA[SyncCond]]></category>
		<category><![CDATA[Testing]]></category>
		<category><![CDATA[YAGNI]]></category>
		<category><![CDATA[代码可读性]]></category>
		<category><![CDATA[依赖倒置]]></category>
		<category><![CDATA[包管理]]></category>
		<category><![CDATA[反模式]]></category>
		<category><![CDATA[并发]]></category>
		<category><![CDATA[最佳实践]]></category>
		<category><![CDATA[测试]]></category>
		<category><![CDATA[重构]]></category>

		<guid isPermaLink="false">https://tonybai.com/?p=5538</guid>
		<description><![CDATA[本文永久链接 &#8211; https://tonybai.com/2025/12/15/go-language-anti-patterns-10-donts 大家好，我是Tony Bai。 “有哪些‘不要做’的教训，是你花了好几年才学会的？” 近日，在 r/golang 社区，这个简单的问题，引爆了一场关于 Go 语言“反模式”与“最佳实践”的集体反思。帖子下数百条评论，汇集了无数 Gopher 在真实项目中用“血与泪”换来的宝贵经验。这些教训，往往不是关于某个高深的算法，而是关于那些看似“理所当然”，却在不经意间为代码埋下地雷的日常习惯。 这篇文章，正是对这场集体智慧的一次系统性梳理。我们从中提炼出 10 条最核心的“不要做”法则，它们如同一份“避坑指南”，能帮助你绕开那些最常见的陷阱，更快地从一名“会写 Go 的程序员”，成长为一名“懂 Go 的工程师”。 不要过度封装包 Don&#8217;t overpackage things 初学者往往有一种冲动，想把代码组织成“语义化”的、层层嵌套的包结构。internal/models, internal/services, internal/repositories…… 这种源自其他语言（如 Java）的模式，在 Go 的世界里，往往是一种过早的、不必要的复杂性。 社区忠告：从一个 main.go 文件开始。努力思考，是否真的有必要将代码拆分到多个文件/包中。Go 的包，其主要目的是封装和依赖管理，而不是单纯的文件夹分类。在小型或中型项目中，一个清晰的、扁平的包结构，远比一个复杂的“企业级”目录树更易于维护。 不要滥用 channel 和 goroutine Don&#8217;t just add in channels 并发是 Go 的“名片”，这使得许多开发者（尤其是新手）有一种“锤子心态”——看到任何问题，都想用 goroutine 和 channel 来解决。然而，不必要的并发，是复杂性和 bug 的温床。 社区忠告： 先问“是否需要”：你真的需要并发吗？如果不需要在线程间传递消息，你可能根本不需要 [...]]]></description>
			<content:encoded><![CDATA[<p><img src="https://tonybai.com/wp-content/uploads/2025/go-language-anti-patterns-10-donts-1.png" alt="" /></p>
<p><a href="https://tonybai.com/2025/12/15/go-language-anti-patterns-10-donts">本文永久链接</a> &#8211; https://tonybai.com/2025/12/15/go-language-anti-patterns-10-donts</p>
<p>大家好，我是Tony Bai。</p>
<p>“有哪些‘不要做’的教训，是你花了好几年才学会的？”</p>
<p>近日，在 r/golang 社区，这个简单的问题，引爆了一场关于 Go 语言“反模式”与“最佳实践”的<a href="https://www.reddit.com/r/golang/comments/1pib68y/whats_a_dont_do_this_lesson_that_took_you_years/">集体反思</a>。帖子下数百条评论，汇集了无数 Gopher 在真实项目中用“血与泪”换来的宝贵经验。这些教训，往往不是关于某个高深的算法，而是关于那些看似“理所当然”，却在不经意间为代码埋下地雷的日常习惯。</p>
<p>这篇文章，正是对这场集体智慧的一次系统性梳理。我们从中提炼出 10 条最核心的“不要做”法则，它们如同一份“避坑指南”，能帮助你绕开那些最常见的陷阱，更快地从一名“会写 Go 的程序员”，成长为一名“懂 Go 的工程师”。</p>
<p><img src="https://tonybai.com/wp-content/uploads/2025/paid/api-design-pattern-and-implementation-qr.png" alt="" /></p>
<h2>不要过度封装包</h2>
<p><em>Don&#8217;t overpackage things</em></p>
<p>初学者往往有一种冲动，想把代码组织成“语义化”的、层层嵌套的包结构。internal/models, internal/services, internal/repositories…… 这种源自其他语言（如 Java）的模式，在 Go 的世界里，往往是一种<strong>过早的、不必要的复杂性</strong>。</p>
<p><strong>社区忠告</strong>：从一个 main.go 文件开始。努力思考，是否真的有必要将代码拆分到多个文件/包中。Go 的包，其主要目的是<strong>封装和依赖管理</strong>，而不是单纯的文件夹分类。在小型或中型项目中，一个清晰的、扁平的包结构，远比一个复杂的“企业级”目录树更易于维护。</p>
<h2>不要滥用 channel 和 goroutine</h2>
<p><em>Don&#8217;t just add in channels</em></p>
<p>并发是 Go 的“名片”，这使得许多开发者（尤其是新手）有一种“锤子心态”——看到任何问题，都想用 goroutine 和 channel 来解决。然而，不必要的并发，是复杂性和 bug 的温床。</p>
<p><strong>社区忠告</strong>：</p>
<ul>
<li><strong>先问“是否需要”</strong>：你真的需要并发吗？如果不需要在线程间传递消息，你可能根本不需要 channel。一个简单的 sync.WaitGroup 或 sync.Mutex，在很多场景下都比 channel 更简单、更直接。</li>
<li><strong>并发不是免费的</strong>：Go 让创建 goroutine 变得异常简单，但这并不意味着它是零成本的。过多的 goroutine 会增加调度器的负担，而 channel 的滥用则会使数据流变得难以追踪和调试。</li>
</ul>
<h2>不要盲目追求 DRY</h2>
<p><em>Don&#8217;t be zealous about DRY</em></p>
<p>DRY 是编程的基本原则，但在 Go 的哲学中，它有一个更重要的“上级”——<strong>清晰性</strong>。为了消除几行重复代码，而引入一个复杂的接口或一个晦涩的辅助函数，往往得不偿失。</p>
<p><strong>社区忠告</strong>：“<strong>一点点复制，胜过一点点依赖 (a little copy-paste is better than a little dependency)。</strong>” 当你发现自己在为了 DRY 而绞尽脑汁时，请停下来问问自己：这份重复，是否真的带来了维护上的痛苦？如果不是，那么接受它，可能是一个更明智的选择。</p>
<h2>不要在同一个 PR 中既重构又添加新功能</h2>
<p><em>Don&#8217;t refactor and add features in the same PR</em></p>
<p>在添加一个新功能时，顺手“优化”一下周围的代码，这看起来很高效。但实际上，这会让 Code Review 变得异常痛苦。Reviewer 无法清晰地分辨，哪些改动是为新功能服务的，哪些是纯粹的重构。这不仅增加了审查的难度，也提高了引入新 Bug 的风险。</p>
<p><strong>社区忠告</strong>：遵循“童子军军规”——“让营地比你来时更干净”——是好的。但请将它分解为<strong>两个独立的、目标明确的 PR</strong>：一个只做重构，另一个（基于重构后的代码）只添加新功能。</p>
<h2>不要跳过写测试，“就这一次”</h2>
<p><em>Don&#8217;t skip writing tests “just this once”</em></p>
<p>这是所有开发者都曾屈服过的诱惑。“这个改动太小了”、“我百分之百确定它是对的”、“项目赶时间”…… 每一次“就这一次”的妥协，都在为未来的“技术雪崩”添砖加瓦。</p>
<p><strong>社区忠告</strong>：将测试视为代码不可分割的一部分。在 Go 中，编写测试是如此简单和自然，以至于没有任何借口可以跳过它。你今天节省下来的 10 分钟，可能会在未来，让你或你的同事，花费数天时间去调试一个本可避免的生产问题。</p>
<h2>不要害怕使用 sync.Cond</h2>
<p>channel 非常强大，但它并非解决所有并发同步问题的“银弹”。社区中有一种“反 sync”的情绪，认为所有同步都应该用 channel 来完成。</p>
<p><strong>社区忠告</strong>：sync.Cond 是一个被低估了的、极其强大的并发原语。当你需要<strong>基于某个特定条件来唤醒一个或多个等待的 goroutine</strong> 时（例如，一个任务队列的消费者在队列为空时等待），sync.Cond 往往比用 channel 实现的复杂信令机制，要<strong>更简单、更高效</strong>。不要因为不熟悉，就回避它。</p>
<h2>不要返回接口</h2>
<p><em>Returning interfaces. Don&#8217;t do it.</em></p>
<p>在函数签名中返回一个接口，看似遵循了“依赖倒置”的高级原则，甚至觉得这样更“灵活”。但实际上，这往往是一种<strong>过早的、有害的抽象</strong>。它剥夺了用户访问底层具体类型特有功能的能力，并且如果未来需要添加新方法，接口的变更会极其痛苦。</p>
<p><strong>社区忠告</strong>：遵循 Go 的经典谚语：“<strong>接收接口，返回结构体 (Accept interfaces, return structs)。</strong>”</p>
<ul>
<li><strong>接收接口</strong>：让你的函数接收一个只包含其所需最小方法集的接口作为参数。这使得你的函数更容易被测试和复用（你可以传入任何满足该接口的实现，包括 Mock 对象）。</li>
<li><strong>返回结构体</strong>：让你的函数返回一个具体的类型（通常是指针）。这给了调用者最大的灵活性。</li>
</ul>
<p><strong>经典范例</strong>：</p>
<p>看看标准库中的 os.Open，它返回的是 *os.File（具体结构体），而不是 io.Reader（接口）。<br />
*   <strong>为什么这样做？</strong> 因为 *os.File 不仅能读（Read），还能关闭（Close）、获取状态（Stat）、甚至改变权限（Chmod）。<br />
*   <strong>灵活性</strong>：如果它返回的是接口，用户就无法使用 Chmod 等特有功能了。而返回结构体，用户既可以使用其全部功能，也可以在需要时，轻松地将其赋值给 io.Reader 接口来使用。这就是“返回结构体”带来的自由。</p>
<p><em>(注：只有当返回的类型是包内私有的、不希望外部直接访问的实现细节时，返回接口才是有意义的，例如 context.WithCancel 返回的是 Context 接口。)</em></p>
<h2>不要过度依赖依赖</h2>
<p><em>Don&#8217;t add dependencies without vetting</em></p>
<p>为了解决一个小问题，而引入一个庞大的、闪亮的第三方库。这在 Node.js 生态中很常见，但在 Go 社区，这通常被视为一种“危险信号”。</p>
<p><strong>社区忠告</strong>：</p>
<ul>
<li><strong>先求诸标准库</strong>：在引入任何依赖之前，先问问自己：这个问题，标准库真的解决不了吗？</li>
<li><strong>审慎评估</strong>：如果必须引入依赖，请仔细评估它：它的依赖树有多深？社区是否活跃？维护者是否可靠？一个简单的依赖，可能会为你整个项目，带来潜在的供应链安全风险和维护噩梦。</li>
</ul>
<h2>不要盲从</h2>
<p><em>Don&#8217;t do [or not do] something simply because an authoritative voice recommended it</em></p>
<p>盲目地遵循某个“大神”、某篇“爆款”博客文章、或者某个“权威”推荐的模式，而没有结合自己的具体场景进行批判性思考。</p>
<p><strong>社区忠告</strong>：上下文决定一切。YAGNI (You Aren&#8217;t Gonna Need It) 是一个好原则，但有时你确实需要提前设计。微服务很好，但有时单体就是最佳选择。没有银弹。<strong>最好的实践，是那些在你的团队、你的项目中，被证明行之有效的实践。</strong></p>
<h2>不要忘记，代码是给人读的</h2>
<p>忘记了代码的最终读者是人类，而不是编译器。编写只有自己能看懂的“聪明”代码，或者忽略文档和注释的重要性。</p>
<p><strong>社区忠告</strong>：</p>
<ul>
<li><strong>编写能让你的未来“自已”不会痛骂你的代码。</strong></li>
<li><strong>好的设计不是增加，而是保持本质的简单。代码即是负债 (Code is liability)。</strong></li>
<li><strong>不要忽视清晰文档的重要性。</strong></li>
</ul>
<h2>小结：在“坑”里成长</h2>
<p>这份清单，远非全部。社区的讨论中还充满了诸如“不要用 singleton 来做 mock”、“不要滥用 init 函数”、“不要在疲劳时 Review 代码”等无数宝贵的经验。</p>
<p>它们共同指向了一个核心思想：成为一名优秀的 Go 工程师，其过程不仅仅是学习语言的特性，更是一个不断反思、不断“踩坑”、并从“坑”中总结出属于自己“不要做”清单的修炼过程。希望这份来自社区的集体智慧，能让你在这条路上，走得更稳、也更远。</p>
<p>资料链接：https://www.reddit.com/r/golang/comments/1pib68y/whats_a_dont_do_this_lesson_that_took_you_years/</p>
<hr />
<p>还在为“复制粘贴喂AI”而烦恼？我的新专栏 <strong>《<a href="http://gk.link/a/12EPd">AI原生开发工作流实战</a>》</strong> 将带你：</p>
<ul>
<li>告别低效，重塑开发范式</li>
<li>驾驭AI Agent(Claude Code)，实现工作流自动化</li>
<li>从“AI使用者”进化为规范驱动开发的“工作流指挥家”</li>
</ul>
<p>扫描下方二维码，开启你的AI原生开发之旅。</p>
<p><img src="https://tonybai.com/wp-content/uploads/2025/ai-native-dev-workflow-qr.png" alt="" /></p>
<hr />
<p>你的Go技能，是否也卡在了“熟练”到“精通”的瓶颈期？</p>
<ul>
<li>想写出更地道、更健壮的Go代码，却总在细节上踩坑？</li>
<li>渴望提升软件设计能力，驾驭复杂Go项目却缺乏章法？</li>
<li>想打造生产级的Go服务，却在工程化实践中屡屡受挫？</li>
</ul>
<p>继《<a href="http://gk.link/a/10AVZ">Go语言第一课</a>》后，我的《<a href="http://gk.link/a/12yGY">Go语言进阶课</a>》终于在极客时间与大家见面了！</p>
<p>我的全新极客时间专栏 《<a href="http://gk.link/a/12yGY">Tony Bai·Go语言进阶课</a>》就是为这样的你量身打造！30+讲硬核内容，带你夯实语法认知，提升设计思维，锻造工程实践能力，更有实战项目串讲。</p>
<p>目标只有一个：助你完成从“Go熟练工”到“Go专家”的蜕变！ 现在就加入，让你的Go技能再上一个新台阶！</p>
<p><img src="https://tonybai.com/wp-content/uploads/course-card/iamtonybai-banner-2.gif" alt="" /></p>
<hr />
<p>商务合作方式：撰稿、出书、培训、在线课程、合伙创业、咨询、广告合作。如有需求，请扫描下方公众号二维码，与我私信联系。</p>
<p><img src="http://image.tonybai.com/img/tonybai/iamtonybai-wechat-qr.png" alt="" /></p>
<p style='text-align:left'>&copy; 2025, <a href='https://tonybai.com'>bigwhite</a>. 版权所有. </p>
]]></content:encoded>
			<wfw:commentRss>https://tonybai.com/2025/12/15/go-language-anti-patterns-10-donts/feed/</wfw:commentRss>
		<slash:comments>2</slash:comments>
		</item>
		<item>
		<title>Uber性能优化实践：如何用 GenAI 将 Go 代码调优从数周缩短至数小时？</title>
		<link>https://tonybai.com/2025/07/23/uber-perfinsights/</link>
		<comments>https://tonybai.com/2025/07/23/uber-perfinsights/#comments</comments>
		<pubDate>Wed, 23 Jul 2025 14:17:06 +0000</pubDate>
		<dc:creator>bigwhite</dc:creator>
				<category><![CDATA[技术志]]></category>
		<category><![CDATA[AI]]></category>
		<category><![CDATA[benchmark]]></category>
		<category><![CDATA[CPU]]></category>
		<category><![CDATA[diff]]></category>
		<category><![CDATA[GenAI]]></category>
		<category><![CDATA[Go]]></category>
		<category><![CDATA[Golang]]></category>
		<category><![CDATA[LLM]]></category>
		<category><![CDATA[LLMCheck]]></category>
		<category><![CDATA[Memory]]></category>
		<category><![CDATA[perfinsights]]></category>
		<category><![CDATA[Performance]]></category>
		<category><![CDATA[pprof]]></category>
		<category><![CDATA[profile]]></category>
		<category><![CDATA[profiling]]></category>
		<category><![CDATA[Prompt]]></category>
		<category><![CDATA[top]]></category>
		<category><![CDATA[uber]]></category>
		<category><![CDATA[人工智能]]></category>
		<category><![CDATA[反模式]]></category>
		<category><![CDATA[大模型]]></category>
		<category><![CDATA[少样本提示]]></category>
		<category><![CDATA[幻觉]]></category>
		<category><![CDATA[性能优化]]></category>
		<category><![CDATA[持续剖析]]></category>
		<category><![CDATA[指令]]></category>
		<category><![CDATA[热点]]></category>
		<category><![CDATA[角色]]></category>

		<guid isPermaLink="false">https://tonybai.com/?p=4937</guid>
		<description><![CDATA[本文永久链接 &#8211; https://tonybai.com/2025/07/23/uber-perfinsights 大家好，我是Tony Bai。 对于大多数团队而言，Go 服务的性能优化是一项昂贵且充满挑战的任务。它通常需要资深的工程师花费数天甚至数周的时间进行 profiling、基准测试和代码分析，这在快节奏的开发周期中往往难以持续。Uber 面临着同样的问题，其 Top 10 的 Go 服务每月就产生数百万美元的计算开销，系统性的性能调优迫在眉睫。 PerfInsights 的诞生，旨在将这种依赖专家的、被动的优化过程，转变为一种可扩展、可重复、自动化的实践。它的核心目标是：以最小的人力投入，发现高价值的优化机会。 PerfInsights 工作原理：三步走的自动化流水线 PerfInsights 的核心流程是一个精心设计的三阶段流水线，它巧妙地将传统性能分析与前沿的 GenAI 技术融合在一起。 过滤：从生产噪音中定位“热点函数” 一切始于真实数据。PerfInsights 利用 Uber 的全集群 profiler，收集生产服务在流量高峰期的 CPU 和内存 profiles。 热点识别：通过分析 pprof 数据，系统首先识别出每个服务中 CPU 占用最高的 Top 30 函数。经验表明，这些函数往往占据了绝大部分的 CPU 资源。 静态过滤：这是至关重要的一步。为了避免 GenAI 在无关紧要的代码上浪费“精力”，PerfInsights 会进行一轮静态过滤，排除掉开源依赖库和 Go 运行时的内部函数。这一步极大地缩小了分析范围，确保 AI 的注意力只集中在最有优化价值的业务代码上。Uber 团队称之为“无名英雄”，因为它将一个可能脆弱的 AI 原型，转变为一个专注、高效的优化助手。 分析：GenAI 登场，检测性能反模式 经过滤后的热点函数源代码，会被连同一个预先策划的反模式目录，一同提交给大语言模型（LLM）进行分析。 [...]]]></description>
			<content:encoded><![CDATA[<p><img src="https://tonybai.com/wp-content/uploads/2025/uber-perfinsights-1.png" alt="" /></p>
<p><a href="https://tonybai.com/2025/07/23/uber-perfinsights">本文永久链接</a> &#8211; https://tonybai.com/2025/07/23/uber-perfinsights</p>
<p>大家好，我是Tony Bai。</p>
<p>对于大多数团队而言，Go 服务的性能优化是一项昂贵且充满挑战的任务。它通常需要资深的工程师花费数天甚至数周的时间进行 profiling、基准测试和代码分析，这在快节奏的开发周期中往往难以持续。Uber 面临着同样的问题，其 Top 10 的 Go 服务每月就产生数百万美元的计算开销，系统性的性能调优迫在眉睫。</p>
<p><a href="https://www.uber.com/blog/perfinsights">PerfInsights 的诞生</a>，旨在将这种依赖专家的、被动的优化过程，转变为一种<strong>可扩展、可重复、自动化的实践</strong>。它的核心目标是：以最小的人力投入，发现高价值的优化机会。</p>
<h2>PerfInsights 工作原理：三步走的自动化流水线</h2>
<p>PerfInsights 的核心流程是一个精心设计的三阶段流水线，它巧妙地将传统性能分析与前沿的 GenAI 技术融合在一起。</p>
<p><img src="https://tonybai.com/wp-content/uploads/2025/uber-perfinsights-2.png" alt="" /></p>
<h3>过滤：从生产噪音中定位“热点函数”</h3>
<p>一切始于真实数据。PerfInsights 利用 Uber 的全集群 profiler，收集生产服务在流量高峰期的 CPU 和内存 profiles。</p>
<ul>
<li><strong>热点识别</strong>：通过分析 pprof 数据，系统首先识别出每个服务中 CPU 占用最高的 Top 30 函数。经验表明，这些函数往往占据了绝大部分的 CPU 资源。</li>
<li><strong>静态过滤</strong>：这是至关重要的一步。为了避免 GenAI 在无关紧要的代码上浪费“精力”，PerfInsights 会进行一轮<strong>静态过滤</strong>，排除掉开源依赖库和 Go 运行时的内部函数。这一步极大地缩小了分析范围，确保 AI 的注意力只集中在最有优化价值的业务代码上。Uber 团队称之为“无名英雄”，因为它将一个可能脆弱的 AI 原型，转变为一个专注、高效的优化助手。</li>
</ul>
<h3>分析：GenAI 登场，检测性能反模式</h3>
<p>经过滤后的热点函数源代码，会被连同一个<strong>预先策划的反模式目录</strong>，一同提交给大语言模型（LLM）进行分析。</p>
<p>这个反模式目录是基于 Uber Go 基础团队多年的优化经验和 Go 语言最佳实践整理而成，涵盖了诸如<strong>无边界的内存分配（例如，向没有预分配容量的 slice 中追加元素）、循环内的冗余计算、低效的字符串操作</strong>等常见问题。</p>
<p>通过结合 profiling 提供的“热点”上下文和反模式的“先验知识”，LLM 能够高精度地定位到代码中的低效结构，并给出具体的优化建议。</p>
<h3>验证：建立开发者信任的“双保险”机制</h3>
<p>直接信任 LLM 的输出是危险的，因为它可能产生幻觉或生成不可运行的代码。PerfInsights 的独特之处在于其强大的双重验证机制，旨在将误报率降至最低，建立开发者的信任。</p>
<ul>
<li><strong>LLM 陪审团 (LLM Juries)</strong>：PerfInsights 不会依赖单一模型的判断。相反，它采用一个由多个不同 LLM 组成的“陪审团”。每个模型都会独立评估检测到的反模式和建议的修复方案是否有效。这种集成方法能有效减少单个模型的幻觉和误判。</li>
<li><strong>LLMCheck 框架</strong>：这是一个基于规则的第二层验证系统。它包含了一系列针对特定领域的验证器，用于检查 LLM 响应中常见的错误，例如：
<ul>
<li>混淆 map 和 slice。</li>
<li>将循环不变量错误地识别到循环之外。</li>
<li>误将循环变量识别为不变量。</li>
</ul>
</li>
</ul>
<p>通过这套“AI + 规则”的双重验证，PerfInsights 成功地将误报率从最初的 <strong>80% 以上降低到了百分之十几</strong>的水平。</p>
<h2>Prompt 工程：与 LLM 高效对话的艺术</h2>
<p>为了让 LLM 发挥最大效用，Uber 团队在 Prompt 工程上投入了大量精力，总结出几项关键策略：</p>
<ul>
<li><strong>少样本提示 (Few-Shot Prompting)</strong>：在 Prompt 中提供几个具体的“反模式-正例”代码对，能让模型更好地泛化，显著提升检测准确性。</li>
<li><strong>角色与指令</strong>：明确告知 LLM 它的角色是“Go 专家”，并使用非常具体、积极的指令（避免使用“不要”）。同时，要求模型确保其建议的优化代码是<strong>可运行的</strong>。</li>
<li><strong>置信度评分</strong>：要求 LLM 为其每个判断提供一个 1-10 的置信度分数，这能促使模型进行更深层次的“思考”，并为后续的自动化流程提供决策依据，如下图。</li>
</ul>
<p><img src="https://tonybai.com/wp-content/uploads/2025/uber-perfinsights-3.png" alt="" /></p>
<h2>影响与成果：从理论到数百万美元的节省</h2>
<p>PerfInsights 在 Uber 内部取得了巨大的成功，其影响体现在多个层面：</p>
<ul>
<li><strong>工程效率的量级提升</strong>：过去需要专家团队花费数周甚至数月才能完成的诊断工作，现在被缩短至数小时甚至数分钟。一个案例中，检测和修复一个问题的时间从 <strong>14.5 小时减少到约 1 小时</strong>，实现了 <strong>93.1%</strong> 的时间节省。据估算，该工具每年可为 Uber 节省约 <strong>3,800 个</strong>专家工程小时。</li>
<li><strong>可衡量的成本节约与代码健康</strong>：自推出以来，PerfInsights 已经生成了<strong>数百个被合并的代码优化 diff</strong>，直接带来了可观的计算成本节约。同时，它帮助团队在四个月内将检测到的反模式数量<strong>减少了 33.5%</strong>，使得代码库更健康、审查周期更短、发布更安全。</li>
<li><strong>性能文化的变革</strong>：最重要的是，PerfInsights 将性能调优从一种偶发的、被动的“救火”行动，转变为一种<strong>持续的、数据驱动的、主动嵌入在 CI/CD 和日常开发流程中的规程</strong>。</li>
</ul>
<h2>小结</h2>
<p>Uber 的 PerfInsights 项目为整个行业提供了一个将 GenAI 应用于复杂工程问题的杰出范例。它清晰地表明，GenAI 的力量不在于盲目地替代开发者，而在于与传统的、可靠的工程工具（如 pprof）和严谨的验证流程相结合，从而在特定领域发挥出最大效能。</p>
<p>对于 Go 社区的开发者而言，PerfInsights 带来的启示是深刻的：<br />
1.  <strong>生产数据是金矿</strong>：基于真实 profiling 数据的优化，远比凭空猜测更有效。<br />
2.  <strong>AI 需要“缰绳”</strong>：通过静态过滤缩小范围，并通过多层验证来约束 AI，是成功应用 GenAI 的关键。<br />
3.  <strong>信任是第一要务</strong>：只有当工具的建议可靠、误报率低时，它才能真正被开发者接纳并融入日常工作流。</p>
<p>PerfInsights 的成功，标志着性能工程正迈入一个由 AI 辅助的、更加普惠和高效的新时代。虽然当前PerfInsights还没有开源，但就Uber这篇文章提供的“实践思路”来看，也是非常值得我们思考和借鉴的。</p>
<p>资料链接：https://www.uber.com/blog/perfinsights</p>
<hr />
<p>你的Go技能，是否也卡在了“熟练”到“精通”的瓶颈期？</p>
<ul>
<li>想写出更地道、更健壮的Go代码，却总在细节上踩坑？</li>
<li>渴望提升软件设计能力，驾驭复杂Go项目却缺乏章法？</li>
<li>想打造生产级的Go服务，却在工程化实践中屡屡受挫？</li>
</ul>
<p>继《<a href="http://gk.link/a/10AVZ">Go语言第一课</a>》后，我的《<a href="http://gk.link/a/12yGY">Go语言进阶课</a>》终于在极客时间与大家见面了！</p>
<p>我的全新极客时间专栏 《<a href="http://gk.link/a/12yGY">Tony Bai·Go语言进阶课</a>》就是为这样的你量身打造！30+讲硬核内容，带你夯实语法认知，提升设计思维，锻造工程实践能力，更有实战项目串讲。</p>
<p>目标只有一个：助你完成从“Go熟练工”到“Go专家”的蜕变！ 现在就加入，让你的Go技能再上一个新台阶！</p>
<p><img src="https://tonybai.com/wp-content/uploads/course-card/iamtonybai-banner-2.gif" alt="" /></p>
<hr />
<p>商务合作方式：撰稿、出书、培训、在线课程、合伙创业、咨询、广告合作。如有需求，请扫描下方公众号二维码，与我私信联系。</p>
<p><img src="http://image.tonybai.com/img/tonybai/iamtonybai-wechat-qr.png" alt="" /></p>
<p style='text-align:left'>&copy; 2025, <a href='https://tonybai.com'>bigwhite</a>. 版权所有. </p>
]]></content:encoded>
			<wfw:commentRss>https://tonybai.com/2025/07/23/uber-perfinsights/feed/</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>Go项目设计的“七宗罪”？警惕那些流行的“反模式”</title>
		<link>https://tonybai.com/2025/04/21/go-project-design-antipatterns/</link>
		<comments>https://tonybai.com/2025/04/21/go-project-design-antipatterns/#comments</comments>
		<pubDate>Sun, 20 Apr 2025 23:20:07 +0000</pubDate>
		<dc:creator>bigwhite</dc:creator>
				<category><![CDATA[技术志]]></category>
		<category><![CDATA[anti-pattern]]></category>
		<category><![CDATA[Bug]]></category>
		<category><![CDATA[C]]></category>
		<category><![CDATA[cmd]]></category>
		<category><![CDATA[common]]></category>
		<category><![CDATA[DAG]]></category>
		<category><![CDATA[DesignPattern]]></category>
		<category><![CDATA[DRY]]></category>
		<category><![CDATA[Go]]></category>
		<category><![CDATA[Golang]]></category>
		<category><![CDATA[GoProverbs]]></category>
		<category><![CDATA[handler]]></category>
		<category><![CDATA[helpers]]></category>
		<category><![CDATA[import]]></category>
		<category><![CDATA[Interface]]></category>
		<category><![CDATA[internal]]></category>
		<category><![CDATA[Java]]></category>
		<category><![CDATA[linter]]></category>
		<category><![CDATA[Mock]]></category>
		<category><![CDATA[model]]></category>
		<category><![CDATA[Namespace]]></category>
		<category><![CDATA[Package]]></category>
		<category><![CDATA[pkg]]></category>
		<category><![CDATA[Service]]></category>
		<category><![CDATA[shared]]></category>
		<category><![CDATA[Testing]]></category>
		<category><![CDATA[util]]></category>
		<category><![CDATA[utils]]></category>
		<category><![CDATA[依赖包]]></category>
		<category><![CDATA[函数]]></category>
		<category><![CDATA[包]]></category>
		<category><![CDATA[反模式]]></category>
		<category><![CDATA[循环导入]]></category>
		<category><![CDATA[抽象]]></category>
		<category><![CDATA[接口]]></category>
		<category><![CDATA[接口隔离]]></category>
		<category><![CDATA[最佳实践]]></category>
		<category><![CDATA[标准]]></category>
		<category><![CDATA[测试]]></category>
		<category><![CDATA[简洁]]></category>
		<category><![CDATA[耦合]]></category>
		<category><![CDATA[表驱动测试]]></category>
		<category><![CDATA[规范]]></category>
		<category><![CDATA[设计模式]]></category>
		<category><![CDATA[过度设计]]></category>
		<category><![CDATA[项目布局]]></category>

		<guid isPermaLink="false">https://tonybai.com/?p=4596</guid>
		<description><![CDATA[本文永久链接 &#8211; https://tonybai.com/2025/04/21/go-project-design-antipatterns 大家好，我是Tony Bai。 在软件开发这个行当里，“最佳实践”、“设计模式”、“标准规范”这些词汇总是自带光环。它们总是承诺会带来更好的代码质量、可维护性和扩展性。然而，当这些“圣经”般的原则被生搬硬套到Go语言的语境下时，有时非但不能带来预期的好处，反而可能把我们引入“歧途”，滋生出一些看似“专业”实则有害的“反模式”。 最近我也拜读了几篇国外开发者关于Go项目布局和设计哲学的文章，结合我自己这些年的实践和观察，我愈发觉得，Go社区中确实存在一些需要警惕的、流行的设计“反模式”。这些“反模式”很多人都或多或少的使用过，包括曾经的我自己。 在这篇文章中，我就总结一下我眼中的Go项目设计“七宗罪”，希望能帮助大家在实践中保持清醒，做出更符合Go精神的决策。 第一宗罪：为了结构而结构——过度分层与分组 表现： 项目伊始，不假思索地创建pkg/、internal/、cmd/、util/、model/、handler/、service/ 等层层嵌套的目录，美其名曰“组织清晰”、“符合标准”。 危害： * 违背简洁： Go 的核心哲学是简洁。不必要的目录层级增加了认知负担和导航成本。 * 过早抽象/耦合： 在需求尚不明确时就划分 service、handler 等，可能导致错误的抽象边界和不必要的耦合。 * pkg/ 的迷思： pkg/ 是一个过时的、缺乏语义的约定，Go官方在Go 1.4时将Go项目中的pkg层次去掉了，Go官方的module布局指南中也使用了更多有意义的名字代替了pkg。 * internal/ 的滥用： 它是 Go 工具链的一个特性，用于保护内部实现不被外部导入。但如果你的项目根本不作为库被外部依赖，或者需要保护的代码很少，强制使用 internal/ 只会徒增复杂性。 * cmd/ 的误用： 除非你的仓库包含多个独立的可执行文件，否则将单一的main.go放入cmd/毫无必要。 解药： 保持扁平！从根目录开始，根据实际的功能或领域需要创建有意义的包。让结构随着项目的增长有机演化，而不是一开始就套用模板。 注：笔者当年也是pkg的“忠实粉丝”，新创建一个项目，无论规模大小，总喜欢先将pkg目录预创建出来。现在是时候根据项目的演进和规模的增长来判断是否需要”pkg”这个有点像“namespace”的目录了，即当你有多个希望公开的库时，是否用pkg/作为一个顶层分组，这个是要基于项目的实际情况进行判断的。 第二宗罪：无效的“美化运动”——无价值的重构与移动 表现： 为了让代码看起来“更干净”、“更符合某种设计模式”或“消除Linter警告”，在没有明确收益（修复 Bug、增加功能、提升性能、解决安全问题）的情况下，大规模地移动代码、修改变量名、调整文件结构。 危害： * 浪费时间精力： 投入大量时间做无意义的表面文章。 * 引入风险： 任何修改都有引入新 Bug [...]]]></description>
			<content:encoded><![CDATA[<p><img src="https://tonybai.com/wp-content/uploads/2025/go-project-design-antipatterns-1.jpg" alt="" /></p>
<p><a href="https://tonybai.com/2025/04/21/go-project-design-antipatterns">本文永久链接</a> &#8211; https://tonybai.com/2025/04/21/go-project-design-antipatterns</p>
<p>大家好，我是Tony Bai。</p>
<p>在软件开发这个行当里，“最佳实践”、“设计模式”、“标准规范”这些词汇总是自带光环。它们总是承诺会带来更好的代码质量、可维护性和扩展性。然而，当这些“圣经”般的原则被生搬硬套到Go语言的语境下时，有时非但不能带来预期的好处，反而可能把我们引入“歧途”，滋生出一些看似“专业”实则有害的“反模式”。</p>
<p>最近我也拜读了几篇国外开发者关于Go项目布局和设计哲学的文章，结合我自己这些年的实践和观察，我愈发觉得，Go社区中确实存在一些需要警惕的、流行的设计“反模式”。这些“反模式”很多人都或多或少的使用过，包括曾经的我自己。</p>
<p>在这篇文章中，我就总结一下我眼中的Go项目设计“七宗罪”，希望能帮助大家在实践中保持清醒，做出更符合Go精神的决策。</p>
<h2>第一宗罪：为了结构而结构——过度分层与分组</h2>
<p><img src="https://tonybai.com/wp-content/uploads/2025/go-project-design-antipatterns-2.jpg" alt="" /></p>
<p><strong>表现：</strong> 项目伊始，不假思索地创建pkg/、internal/、cmd/、util/、model/、handler/、service/ 等层层嵌套的目录，美其名曰“组织清晰”、“符合标准”。</p>
<p><strong>危害：</strong><br />
*   <strong>违背简洁：</strong> Go 的核心哲学是简洁。不必要的目录层级增加了认知负担和导航成本。<br />
*   <strong>过早抽象/耦合：</strong> 在需求尚不明确时就划分 service、handler 等，可能导致错误的抽象边界和不必要的耦合。<br />
*   <strong>pkg/ 的迷思：</strong> pkg/ 是一个过时的、缺乏语义的约定，Go官方在<a href="https://tonybai.com/2014/11/04/some-changes-in-go-1-4">Go 1.4</a>时将Go项目中的pkg层次去掉了，<a href="https://go.dev/doc/modules/layout">Go官方的module布局指南</a>中也使用了更多有意义的名字代替了pkg。<br />
*   <strong>internal/ 的滥用：</strong> 它是 Go 工具链的一个特性，用于保护内部实现不被外部导入。但如果你的项目根本不作为库被外部依赖，或者需要保护的代码很少，强制使用 internal/ 只会徒增复杂性。<br />
*   <strong>cmd/ 的误用：</strong> 除非你的仓库包含多个独立的可执行文件，否则将单一的main.go放入cmd/毫无必要。</p>
<p><strong>解药：</strong> 保持扁平！从根目录开始，根据<strong>实际的功能或领域</strong>需要创建<strong>有意义的包</strong>。让结构随着项目的增长<strong>有机演化</strong>，而不是一开始就套用模板。</p>
<blockquote>
<p>注：笔者当年也是pkg的“忠实粉丝”，新创建一个项目，无论规模大小，总喜欢先将pkg目录预创建出来。现在是时候根据项目的演进和规模的增长来判断是否需要”pkg”这个有点像“namespace”的目录了，即当你有多个希望公开的库时，是否用pkg/作为一个顶层分组，这个是要基于项目的实际情况进行判断的。</p>
</blockquote>
<h2>第二宗罪：无效的“美化运动”——无价值的重构与移动</h2>
<p><img src="https://tonybai.com/wp-content/uploads/2025/go-project-design-antipatterns-3.jpg" alt="" /></p>
<p><strong>表现：</strong> 为了让代码看起来“更干净”、“更符合某种设计模式”或“消除Linter警告”，在没有明确收益（修复 Bug、增加功能、提升性能、解决安全问题）的情况下，大规模地移动代码、修改变量名、调整文件结构。</p>
<p><strong>危害：</strong><br />
*   <strong>浪费时间精力：</strong> 投入大量时间做无意义的表面文章。<br />
*   <strong>引入风险：</strong> 任何修改都有引入新 Bug 的风险，没有价值的修改更是得不偿失。<br />
*   <strong>增加 Code Review 负担：</strong> 团队成员需要花费时间理解这些非功能性的变更。<br />
*   <strong>违背价值驱动：</strong> 软件工程的核心是交付价值，而不是追求代码的“艺术感”。</p>
<p><strong>解药：</strong> 坚持<strong>价值驱动</strong>的变更！在做任何结构或代码调整前，严格拷问自己：这个改动解决了什么<strong>真实的、当前存在</strong>的问题？它的收益是否能明确衡量并大于风险？</p>
<h2>第三宗罪：接口的“原罪”——过早、过度的抽象</h2>
<p><img src="https://tonybai.com/wp-content/uploads/2025/go-project-design-antipatterns-4.jpg" alt="" /></p>
<p><strong>表现：</strong><br />
*   在只有一个具体实现的情况下，就为其定义接口。<br />
*   定义庞大、臃肿的接口，包含过多方法。<br />
*   为了“可测试性”而无脑地给所有东西加上接口。</p>
<p><strong>危害：</strong><br />
*   <strong>不必要的抽象：</strong> 接口是为了解耦和多态。在不需要这些时引入接口，只会增加代码量和理解成本。<br />
*   <strong>弱化抽象能力：</strong> “接口越大，抽象越弱”（来自<a href="https://go-proverbs.github.io/">Go谚语</a>）。大接口难以实现和维护，它变得模糊，难以理解哪些方法是真正必要的，也失去了其作为“契约”的精准性。<br />
*   <strong>阻碍演化：</strong> 过早定义接口可能锁定不成熟的设计，后续修改成本更高。<br />
*   <strong>测试的借口：</strong> Go拥有强大的测试工具（如<a href="https://tonybai.com/2024/01/01/go-testing-by-example">表驱动测试</a>），很多时候并不需要接口来实现可测试性。为测试而引入的接口可能扭曲生产代码的设计。</p>
<p><strong>解药：</strong><br />
*   <strong>拥抱具体：</strong> 先写具体实现。<br />
*   <strong>发现接口，而非设计接口：</strong> 只有当你<strong>确实需要</strong>多种实现（包括<a href="https://tonybai.com/2023/04/20/provide-fake-object-for-external-collaborators">测试中的Mock</a>，但要谨慎对待），或者需要<strong>打破循环依赖</strong>时，才考虑提取接口。<br />
*   <strong>保持接口小巧、正交：</strong> 遵循接口隔离原则。</p>
<h2>第四宗罪：“大杂烩”的诱惑——utils/common/shared 黑洞</h2>
<p><img src="https://tonybai.com/wp-content/uploads/2025/go-project-design-antipatterns-5.jpg" alt="" /></p>
<p><strong>表现：</strong> 创建一个名为 utils、common、shared 或 helpers 的包，把各种看似“通用”的函数、类型塞进去。</p>
<p><strong>危害：</strong><br />
*   <strong>职责不清：</strong> 这些包缺乏明确的领域或功能归属，成为代码的“垃圾抽屉”。<br />
*   <strong>依赖洼地：</strong> 随着项目增长，这些包往往会依赖越来越多的其他包，同时也被越来越多的包依赖，极易引发循环依赖或成为构建瓶颈。<br />
*   <strong>降低内聚性：</strong> 本应属于特定领域的功能被剥离出来，破坏了原有包的内聚性。</p>
<p><strong>解药：</strong><br />
*   <strong>就近原则：</strong> 如果一个“工具函数”只被一个包使用，就把它放在那个包里（可以是私有的）。<br />
*   <strong>功能归类：</strong> 如果一个“工具函数”被多个包使用，思考它真正属于哪个<strong>功能领域</strong>，为其创建一个<strong>有意义的</strong>新包（例如 applog 而不是 logutil）。<br />
*   <strong>思考依赖方向：</strong> 真正通用的基础库（如自定义的 string 处理、时间处理）应该处于依赖关系图的底层，不应依赖上层业务逻辑。</p>
<blockquote>
<p>注：坦白说，其他几项“罪过”或许还只是部分开发者的“偶发行为”，但这“第四宗罪”——随手创建 utils 或 common 包——恐怕是我们绝大多数人都曾犯过，甚至习以为常的“通病”。笔者也是如此:)。</p>
</blockquote>
<h2>第五宗罪：对 DRY 的“迷信”——为了“不重复”而引入不当依赖</h2>
<p><img src="https://tonybai.com/wp-content/uploads/2025/go-project-design-antipatterns-6.jpg" alt="" /></p>
<p><strong>表现：</strong> 为了避免几行相似代码的重复，强行提取公共函数或类型，并为此引入新的包依赖，有时甚至导致复杂的依赖关系或循环依赖。</p>
<p><strong>危害：</strong><br />
*   <strong>错误的抽象：</strong> 有时看似重复的代码，在不同的上下文中可能有细微的差别或独立演化的需求。强行合并可能导致错误的抽象。<br />
*   <strong>不必要的耦合：</strong> 为了共享几行代码而引入整个包的依赖，增加了耦合度，可能比少量重复代码的维护成本更高。<br />
*   <strong>违背 Go 谚语：</strong> “A little copying is better than a little dependency.”（一点复制代码胜过一点点依赖）。Go 社区鼓励在权衡后接受适度的代码重复，以换取更低的耦合度和更高的独立性。</p>
<p><strong>解药：</strong><br />
*   <strong>批判性看待重复：</strong> 看到重复代码时，先思考它们是否真的是“同一件事”？它们的演化趋势是否一致？<br />
*   <strong>权衡成本：</strong> 引入依赖的成本（耦合、潜在冲突、维护负担）是否真的低于复制代码的成本？<br />
*   <strong>优先考虑简单：</strong> 在不确定时，保持简单，适度复制代码通常更安全。</p>
<blockquote>
<p>注：这种事儿，恐怕咱们自己或者团队里都遇到过不少：就为了用里面那一两个小函数，咔嚓一下，引入了一个庞大无比的依赖库。</p>
</blockquote>
<h2>第六宗罪：盲目崇拜与跟风——“伪标准”与“最佳实践”的陷阱</h2>
<p><img src="https://tonybai.com/wp-content/uploads/2025/go-project-design-antipatterns-7.jpg" alt="" /></p>
<p><strong>表现：</strong><br />
*   不加批判地复制某个“明星项目”或所谓的“Go 标准项目布局”（如已被社区诟病的<a href="https://github.com/golang-standards/project-layout">golang-standards/project-layout</a>）。<br />
*   将其他语言（如 Java, C#）的复杂模式生搬硬套到 Go 项目中。<br />
*   将任何 Linter 规则或所谓的“最佳实践”奉为圭臬，不考虑具体场景。</p>
<p><strong>危害：</strong><br />
*   <strong>脱离实际：</strong> 别人的“最佳实践”是基于他们的特定问题和上下文演化而来的，未必适合你的项目。<br />
*   <strong>扼杀思考：</strong> 放弃了基于自己项目需求进行独立思考和决策的机会。<br />
*   <strong>违背Go文化：</strong> Go 推崇实用主义和具体问题具体分析，而非僵化的教条。</p>
<p><strong>解药：</strong><br />
*   <strong>保持独立思考：</strong> 理解每个模式或实践要解决的<strong>原始问题</strong>是什么，它是否在你的项目中真实存在？<br />
*   <strong>以我为主，兼收并蓄：</strong> 学习和借鉴，但最终决策要基于你自己的项目需求、团队情况和对 Go 语言的理解。<br />
*   <strong>质疑“最佳”：</strong> 没有万能的“最佳实践”，只有在特定上下文中的“较好实践”。</p>
<blockquote>
<p>注：确实，很多Go初学者（甚至一些老手，包括我自己）都曾长期困惑甚至“抱怨”：官方为何不给出一个项目布局的指导呢？这个呼声持续多年后，Go官方终于在2023年发布了一份<a href="https://tonybai.com/2023/10/05/the-official-guide-of-organizing-go-project/">官方布局指南</a>。这份指南无疑是我们理解官方思路、开始设计Go项目布局的一个重要起点。</p>
</blockquote>
<h2>第七宗罪：与“引力”对抗——忽视 Go 的依赖约束</h2>
<p><img src="https://tonybai.com/wp-content/uploads/2025/go-project-design-antipatterns-8.jpg" alt="" /></p>
<p><strong>表现：</strong><br />
*   设计出隐含循环依赖的架构（例如，某些复杂的 ORM 模式，或者 Service 层与 Repository 层相互调用具体类型）。<br />
*   当遇到 import cycle not allowed 错误时，不从根本上调整结构，而是通过滥用接口、全局变量或 init() 函数等“技巧”来绕过编译错误。</p>
<p><strong>危害：</strong><br />
*   <strong>与语言对抗：</strong> Go禁止循环依赖是其核心设计之一，旨在强制形成清晰的、可管理的依赖关系图 (DAG)。试图绕过它，本质上是在与语言的设计哲学对抗。<br />
*   <strong>隐藏的复杂性：</strong> 用“技巧”解决循环依赖，只是将问题扫到地毯下，使得真实的依赖关系变得模糊不清，增加了维护难度。<br />
*   <strong>错失优化机会：</strong> 循环依赖往往是代码职责不清、耦合过度的信号。解决循环依赖的过程，本身就是一次优化架构、厘清职责的好机会。</p>
<p><strong>解药：</strong><br />
*   <strong>拥抱 DAG：</strong> 理解并尊重 Go 的依赖规则，将其视为架构设计的“向导”。<br />
*   <strong>分析依赖：</strong> 当出现循环依赖时，深入分析其根源，理解是哪个环节的职责划分或耦合出了问题。<br />
*   <strong>结构性解决：</strong> 优先使用移动代码、提取新包（向上或向下）等结构性方法来打破循环。接口解耦是可用手段，但不应是首选或唯一手段。</p>
<h2>小结：回归常识，拥抱简洁</h2>
<p>Go语言的设计哲学是务实和简洁。许多所谓的“最佳实践”和“复杂模式”，在Go的世界里可能水土不服。识别并避免上述这些“反模式”，需要我们：</p>
<ul>
<li><strong>保持批判性思维：</strong> 不盲从，不跟风，时刻追问“为什么”。</li>
<li><strong>坚持价值驱动：</strong> 让每一个设计决策都服务于解决真实问题。</li>
<li><strong>深刻理解Go：</strong> 尊重其核心约束（如无循环依赖），发挥其优势（如简洁性）。</li>
<li><strong>拥抱演化：</strong> 从简单开始，让架构随着需求的明确而有机生长。</li>
</ul>
<p>希望这篇“七宗罪”的总结能给大家带来一些警示和启发。<strong>你是否也曾在项目中遇到过这些“反模式”？你认为还有哪些Go设计中需要警惕的“坑”？欢迎在评论区分享你的看法和经验！</strong></p>
<p>也别忘了点个【赞】和【在看】，让更多Gopher看到这篇“反模式”的总结！</p>
<hr />
<p>避开这些设计“反模式”是迈向Go高手的关键一步。如果你渴望更深层次地理解Go语言精髓，与顶尖Gopher交流切磋，并紧跟Go+AI前沿动态…</p>
<p>那么，我的 <strong>「Go &amp; AI 精进营」知识星球</strong> 正是你需要的！在这里，你可以沉浸式学习【Go原理/进阶/避坑】等独家深度专栏，随时向我提问获<br />
得解析，并与高活跃社区成员碰撞思想火花。</p>
<p><strong>扫码加入，开启你的Go深度学习与精进之旅！</strong></p>
<p><img src="http://image.tonybai.com/img/tonybai/gopher-and-ai-tribe-zsxq-small-card.jpg" alt="img{512x368}" /></p>
<p style='text-align:left'>&copy; 2025, <a href='https://tonybai.com'>bigwhite</a>. 版权所有. </p>
]]></content:encoded>
			<wfw:commentRss>https://tonybai.com/2025/04/21/go-project-design-antipatterns/feed/</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>Go语言中常见的几种反模式[译]</title>
		<link>https://tonybai.com/2021/03/31/common-anti-patterns-in-go/</link>
		<comments>https://tonybai.com/2021/03/31/common-anti-patterns-in-go/#comments</comments>
		<pubDate>Wed, 31 Mar 2021 09:15:34 +0000</pubDate>
		<dc:creator>bigwhite</dc:creator>
				<category><![CDATA[技术志]]></category>
		<category><![CDATA[anti-pattern]]></category>
		<category><![CDATA[API]]></category>
		<category><![CDATA[append]]></category>
		<category><![CDATA[break]]></category>
		<category><![CDATA[case]]></category>
		<category><![CDATA[Channel]]></category>
		<category><![CDATA[for]]></category>
		<category><![CDATA[for-range]]></category>
		<category><![CDATA[Go]]></category>
		<category><![CDATA[Golang]]></category>
		<category><![CDATA[Make]]></category>
		<category><![CDATA[map]]></category>
		<category><![CDATA[nil]]></category>
		<category><![CDATA[select]]></category>
		<category><![CDATA[switch]]></category>
		<category><![CDATA[人月神话]]></category>
		<category><![CDATA[切片]]></category>
		<category><![CDATA[反模式]]></category>
		<category><![CDATA[变量]]></category>
		<category><![CDATA[字段]]></category>
		<category><![CDATA[导出函数]]></category>
		<category><![CDATA[循环]]></category>
		<category><![CDATA[类型]]></category>
		<category><![CDATA[结构体]]></category>

		<guid isPermaLink="false">https://tonybai.com/?p=3148</guid>
		<description><![CDATA[本文翻译自Saif Sadiq的文章《Common anti-patterns in Go》。 众所周知，编码是一门艺术，就像每个拥有精湛艺术并为之感到骄傲的工匠一样，我们作为开发人员也为我们编写的代码感到自豪。为了获得最佳效果，艺术家不断寻找可提高其手艺的方法和工具。同样，作为开发人员，我们也在不断提高自己的技能，并对”如何写出好的代码”这个最重要的问题的答案保持好奇。 弗雷德里克·布鲁克斯（Frederick P. Brooks）在他的书《人月神话》中写道： “程序员和诗人一样，工作时只是稍稍脱离了纯粹的思维定式。他在空气中建造他的城堡，通过发挥想象力进行创作。很少有一种创作媒介是如此灵活，如此容易打磨和重做，如此容易实现宏大的概念结构”。 图片来源：https://xkcd.com/844 这篇文章试图探索上面漫画中大问号的答案。编写良好代码的最简单方法是避免在我们编写的代码中包含反模式。 0. 什么是反模式 一个简单的反模式示例就是编写一个API，而无需考虑该API的使用者如何使用它，如下面的示例1所述。意识到反模式并有意识地避免在编程时使用它们，这无疑是朝着更具可读性和可维护性的代码库迈出的重要一步。在本文中，让我们看一下Go中一些常见的反模式。 当编写代码时没有未来的因素做出考虑时，就会出现反模式。反模式最初可能看起来是一个适当的问题解决方案，但是，实际上，随着代码库的扩大，这些反模式会变得模糊不清，并给我们的代码库添加“技术债务”。 反模式的一个简单例子是，在编写API时不考虑API的消费者如何使用它，就如下面例1那样。意识到反模式，并在编程时有意识地避免使用它们，肯定是迈向更可读和可维护的代码库的重要一步。在这篇文章中，我们来看看Go中常见的几种反模式。 1. 从导出函数(exported function)返回未导出类型(unexported type)的值 在Go中，要导出(export)任何一个字段(field)或变量(variable)，我们都需要确保其名称是以大写字母开头。导出(export)它们的动机是使它们对其他包可见。例如，如果要使用math包中的Pi函数，我们将其定义为math.Pi。而使用math.pi将无法正常工作，并且会报错。 以小写字母开头的名称（结构字段，函数或变量）不会被导出，并且仅在定义它们的包内可见。 使用返回未导出类型值的导出函数或方法可能会令人沮丧，因为其他包中的该函数的调用者将不得不再次定义一个类型才能使用它。 // 反模式 type unexportedType string func ExportedFunc() unexportedType { return unexportedType("some string") } // 推荐 type ExportedType string func ExportedFunc() ExportedType { return ExportedType("some string") } 2. 空白标识符的不必要使用 在各种情况下，将值赋值给空白标识符是不需要，也没有必要的。如果在for循环中使用空白标识符，Go规范中提到： 如果最后一个迭代变量是空白标识符，则range子句等效于没有该标识符的同一子句。 // [...]]]></description>
			<content:encoded><![CDATA[<p><img src="https://tonybai.com/wp-content/uploads/common-anti-patterns-in-go-1.png" alt="" /></p>
<p>本文翻译自Saif Sadiq的文章<a href="https://deepsourcehq.hashnode.dev/common-anti-patterns-in-go">《Common anti-patterns in Go》</a>。</p>
<p>众所周知，编码是一门艺术，就像每个拥有精湛艺术并为之感到骄傲的工匠一样，我们作为开发人员也为我们编写的代码感到自豪。为了获得最佳效果，艺术家不断寻找可提高其手艺的方法和工具。同样，作为开发人员，我们也在不断提高自己的技能，并对”如何写出好的代码”这个最重要的问题的答案保持好奇。</p>
<p>弗雷德里克·布鲁克斯（Frederick P. Brooks）在他的书<a href="https://book.douban.com/subject/26358448/">《人月神话》</a>中写道：</p>
<blockquote>
<p>“程序员和诗人一样，工作时只是稍稍脱离了纯粹的思维定式。他在空气中建造他的城堡，通过发挥想象力进行创作。很少有一种创作媒介是如此灵活，如此容易打磨和重做，如此容易实现宏大的概念结构”。</p>
</blockquote>
<p><img src="https://tonybai.com/wp-content/uploads/common-anti-patterns-in-go-2.jpeg" alt="" /><br />
<center>图片来源：https://xkcd.com/844 </center></p>
<p>这篇文章试图探索上面漫画中大问号的答案。编写良好代码的最简单方法是避免在我们编写的代码中包含反模式。</p>
<h3>0. 什么是反模式</h3>
<p>一个简单的反模式示例就是编写一个API，而无需考虑该API的使用者如何使用它，如下面的示例1所述。意识到反模式并有意识地避免在编程时使用它们，这无疑是朝着更具可读性和可维护性的代码库迈出的重要一步。在本文中，让我们看一下Go中一些常见的反模式。</p>
<p><strong>当编写代码时没有未来的因素做出考虑时，就会出现反模式</strong>。反模式最初可能看起来是一个适当的问题解决方案，但是，实际上，随着代码库的扩大，这些反模式会变得模糊不清，并给我们的代码库添加“技术债务”。</p>
<p>反模式的一个简单例子是，在编写API时不考虑API的消费者如何使用它，就如下面例1那样。意识到反模式，并在编程时有意识地避免使用它们，肯定是迈向更可读和可维护的代码库的重要一步。在这篇文章中，我们来看看Go中常见的几种反模式。</p>
<h3>1. 从导出函数(exported function)返回未导出类型(unexported type)的值</h3>
<p>在Go中，要导出(export)任何一个字段(field)或变量(variable)，我们都需要确保其名称是以大写字母开头。导出(export)它们的动机是使它们对其他包可见。例如，如果要使用math包中的Pi函数，我们将其定义为math.Pi。而使用math.pi将无法正常工作，并且会报错。</p>
<p>以小写字母开头的名称（结构字段，函数或变量）不会被导出，并且仅在定义它们的包内可见。</p>
<p>使用返回未导出类型值的导出函数或方法可能会令人沮丧，因为其他包中的该函数的调用者将不得不再次定义一个类型才能使用它。</p>
<pre><code>// 反模式
type unexportedType string

func ExportedFunc() unexportedType {
    return unexportedType("some string")
} 

// 推荐
type ExportedType string
func ExportedFunc() ExportedType {
    return ExportedType("some string")
}
</code></pre>
<h3>2. 空白标识符的不必要使用</h3>
<p>在各种情况下，将值赋值给空白标识符是不需要，也没有必要的。如果在for循环中使用空白标识符，Go规范中提到：</p>
<blockquote>
<p>如果最后一个迭代变量是空白标识符，则range子句等效于没有该标识符的同一子句。</p>
</blockquote>
<pre><code>// 反模式
for _ = range sequence {
    run()
}
x, _ := someMap[key]
_ = &lt;-ch 

// 推荐
for range something {
    run()
} 

x := someMap[key]
&lt;-ch
</code></pre>
<h3>3. 使用循环/多次append连接两个切片</h3>
<p>将多个切片附加到一个切片时，无需遍历切片并一个接一个地附加(append)每个元素。相反，使用一个append语句执行此操作会更好，更有效率。</p>
<p>例如，下面的代码段通过迭代遍历元素逐个附加元素来连串连接sliceOne和sliceTwo：</p>
<pre><code>for _, v := range sliceTwo {
    sliceOne = append(sliceOne, v)
}
</code></pre>
<p>但是，由于我们知道append是一个<a href="https://www.imooc.com/read/87/article/2424">变长参数函数</a>，我们可以使用零个或多个参数来调用它。因此，可以仅使用一个append函数调用来以更简单的方式重写上面的示例，如下所示：</p>
<pre><code>sliceOne = append(sliceOne, sliceTwo…)
</code></pre>
<h3>4. make调用中的冗余参数</h3>
<p>该make函数是一个特殊的内置函数，用于分配和初始化map、slice或chan类型的对象。为了使用make初始化切片，我们必须提供切片的类型、切片的长度以及切片的容量作为参数。在使用make初始化map的情况下，我们需要传递map的大小作为参数。</p>
<p>但是，make的这些参数已经具有默认值：</p>
<ul>
<li>对于channel，缓冲区容量默认为零（不带缓冲）。</li>
<li>对于map，分配的大小默认为较小的起始大小。</li>
<li>对于切片，如果省略容量，则容量参数的值默认为与长度相等。</li>
</ul>
<p>所以，</p>
<pre><code>ch = make(chan int, 0)
sl = make([]int, 1, 1)
</code></pre>
<p>可以改写为：</p>
<pre><code>ch = make(chan int)
sl = make([]int, 1)
</code></pre>
<p>但是，出于调试或方便数学计算或平台特定代码的目的，将具名常量与channel一起使用不被视为反模式。</p>
<pre><code>const c = 0
ch = make(chan int, c) // 不是反模式
</code></pre>
<h3>5. 函数中无用的return</h3>
<p>return在没有返回值的函数中作为最终语句不是一种好习惯。</p>
<pre><code>// 没用的return，不推荐
func alwaysPrintFoofoo() {
    fmt.Println("foofoo")
    return
} 

// 推荐
func alwaysPrintFoo() {
    fmt.Println("foofoo")
}
</code></pre>
<p>但是，具名返回值的return不应与无用的return相混淆。下面的return语句实际上返回了一个值。</p>
<pre><code>func printAndReturnFoofoo() (foofoo string) {
    foofoo := "foofoo"
    fmt.Println(foofoo)
    return
}
</code></pre>
<h3>6. switch语句中无用的break语句</h3>
<p>在Go中，switch语句不会自动fallthrough。在像C这样的编程语言中，如果前一个case语句块中缺少break语句，则执行将进入下一个case语句中。但是，人们发现，fallthrough的逻辑在switch-case中很少使用，并且经常会导致错误。因此，包括Go在内的许多现代编程语言都将switch-case的默认逻辑改为不fallthrough。</p>
<p>因此，在一个case case语句中，不需要将break语句作为最终语句。以下两个示例的行为相同。</p>
<p>反模式：</p>
<pre><code>switch s {
case 1:
    fmt.Println("case one")
    break
case 2:
    fmt.Println("case two")
}
</code></pre>
<p>好的模式：</p>
<pre><code>switch s {
case 1:
    fmt.Println("case one")
case 2:
    fmt.Println("case two")
}
</code></pre>
<p>但是，为了在Go中switch-case中实现fallthrough机制，我们可以使用fallthrough语句。例如，下面给出的代码段将打印23。</p>
<pre><code>switch 2 {
case 1:
    fmt.Print("1")
    fallthrough
case 2:
    fmt.Print("2")
    fallthrough
case 3: fmt.Print("3")
}
</code></pre>
<h3>7. 不使用辅助函数执行常见任务</h3>
<p>对于一组特定的参数，某些函数具有一些特定表达方式，可以用来简化效率，并带来更好的理解/可读性。</p>
<p>例如，在Go中，要等待多个goroutine完成，可以使用sync.WaitGroup。通过将计数器的值-1直至0，以表示所有goroutine都已经执行完毕：</p>
<pre><code>wg.Add(1) // ...some code
wg.Add(-1)
</code></pre>
<p>但使用sync包提供的辅助函数wg.Done()可以使代码更简单并容易理解。因为它本身会通知sync.WaitGroup所有goroutine即将完成，而无需我们手动将计数器减到0。</p>
<pre><code>wg.Add(1)
// ...some code
wg.Done()

</code></pre>
<h3>8. nil切片上的冗余检查</h3>
<p>nil切片的长度为零。因此，在计算切片的长度之前，无需检查切片是否为nil切片。</p>
<p>例如，下面的nil检查是不必要的。</p>
<pre><code>if x != nil &amp;&amp; len(x) != 0 { // do something
}
</code></pre>
<p>上面的代码可以省略nil检查，如下所示：</p>
<pre><code>if len(x) != 0 { // do something
}
</code></pre>
<h3>9. 太复杂的函数字面量</h3>
<p>可以删除仅调用单个函数且对函数内部的值没有做任何修改的函数字面量，因为它们是多余的。可以改为在外部函数直接调用被调用的内部函数。</p>
<p>例如：</p>
<pre><code>fn := func(x int, y int) int { return add(x, y) }
</code></pre>
<p>可以简化为：</p>
<pre><code>add(x, y)
</code></pre>
<blockquote>
<p>译注：原文少了简化后的代码，这里根据译者的理解补充的。</p>
</blockquote>
<h3>10. 使用仅有一个case语句的select语句</h3>
<p>select语句使goroutine等待多个通信操作。但是，如果只有一个case语句，实际上我们不需要使用select语句。在这种情况下，使用简单send或receive操作即可。如果我们打算在不阻塞地发送或接收操作的情况处理channel通信，则建议在select中添加一个default case以使该select语句变为非阻塞状态。</p>
<pre><code>// 反模式
select {
    case x := &lt;-ch: fmt.Println(x)
} 

// 推荐
x := &lt;-ch
fmt.Println(x)
</code></pre>
<p>使用default:</p>
<pre><code>select {
    case x := &lt;-ch:
        fmt.Println(x)
    default:
        fmt.Println("default")
}
</code></pre>
<h3>11. context.Context应该是函数的第一个参数</h3>
<p>context.Context应该是第一个参数，一般命名为ctx.ctx应该是Go代码中很多函数的（非常）常用参数，由于在逻辑上把常用参数放在参数列表的第一个或最后一个比较好。为什么这么说呢？因为它的使用模式统一，可以帮助我们记住包含该参数。在Go中，由于变量可能只是参数列表中的最后一个，因此建议将context.Context作为第一个参数。各种项目，甚至Node.js等都有一些约定，比如错误先回调。因此，context.Context应该永远是函数的第一个参数，这是一个惯例。</p>
<pre><code>// 反模式
func badPatternFunc(k favContextKey, ctx context.Context) {
    // do something
}

// 推荐
func goodPatternFunc(ctx context.Context, k favContextKey) {
    // do something
}
</code></pre>
<hr />
<p><a href="https://mp.weixin.qq.com/s/jUqAL7hf2GmMun64BJufEA">“Gopher部落”知识星球</a>正式转正（从试运营星球变成了正式星球）！“gopher部落”旨在打造一个精品Go学习和进阶社群！高品质首发Go技术文章，“三天”首发阅读权，每年两期Go语言发展现状分析，>每天提前1小时阅读到新鲜的Gopher日报，网课、技术专栏、图书内容前瞻，六小时内必答保证等满足你关于Go语言生态的所有需>求！部落目前虽小，但持续力很强。在2021年上半年，部落将策划两个专题系列分享，并且是部落独享哦：</p>
<ul>
<li>Go技术书籍的书摘和读书体会系列</li>
<li>Go与eBPF系列</li>
</ul>
<p>欢迎大家加入！</p>
<p><img src="http://image.tonybai.com/img/202103/gopher-tribe-zsxq-card.png" alt="" /></p>
<p>Go技术专栏“<a href="https://www.imooc.com/read/87">改善Go语⾔编程质量的50个有效实践</a>”正在慕课网火热热销中！本专栏主要满足>广大gopher关于Go语言进阶的需求，围绕如何写出地道且高质量Go代码给出50条有效实践建议，上线后收到一致好评！欢迎大家订<br />
阅！</p>
<p><img src="https://tonybai.com/wp-content/uploads/imooc-go-column-pgo-with-qr.jpg" alt="img{512x368}" /></p>
<p>我的网课“<a href="https://coding.imooc.com/class/284.html">Kubernetes实战：高可用集群搭建、配置、运维与应用</a>”在慕课网热卖>中，欢迎小伙伴们订阅学习！</p>
<p><img src="https://tonybai.com/wp-content/uploads/imooc-k8s-practice-with-qr.jpg" alt="img{512x368}" /></p>
<p><a href="https://tonybai.com/">我爱发短信</a>：企业级短信平台定制开发专家 https://tonybai.com/。smspush : 可部署在企业内部的定制化短信平台，三网覆盖，不惧大并发接入，可定制扩展； 短信内容你来定，不再受约束, 接口丰富，支持长短信，签名可选。2020年4月8日，中国三大电信运营商联合发布《5G消息白皮书》，51短信平台也会全新升级到“51商用消息平台”，全面支持5G RCS消息。</p>
<p>著名云主机服务厂商DigitalOcean发布最新的主机计划，入门级Droplet配置升级为：1 core CPU、1G内存、25G高速SSD，价格5$/月。有使用DigitalOcean需求的朋友，可以打开这个<a href="https://m.do.co/c/bff6eed92687">链接地址</a>：https://m.do.co/c/bff6eed92687 开启你的DO主机之路。</p>
<p>Gopher Daily(Gopher每日新闻)归档仓库 &#8211; https://github.com/bigwhite/gopherdaily</p>
<p>我的联系方式：</p>
<ul>
<li>微博：https://weibo.com/bigwhite20xx</li>
<li>微信公众号：iamtonybai</li>
<li>博客：tonybai.com</li>
<li>github: https://github.com/bigwhite</li>
<li>“Gopher部落”知识星球：https://public.zsxq.com/groups/51284458844544</li>
</ul>
<p>微信赞赏：<br />
<img src="https://tonybai.com/wp-content/uploads/wechat-zanshang-code-512x512.jpg" alt="img{512x368}" /></p>
<p>商务合作方式：撰稿、出书、培训、在线课程、合伙创业、咨询、广告合作。</p>
<p style='text-align:left'>&copy; 2021, <a href='https://tonybai.com'>bigwhite</a>. 版权所有. </p>
]]></content:encoded>
			<wfw:commentRss>https://tonybai.com/2021/03/31/common-anti-patterns-in-go/feed/</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>谈谈如何高效地组织和实施内部会议</title>
		<link>https://tonybai.com/2012/12/03/how-to-organize-and-hold-meetings-efficiently/</link>
		<comments>https://tonybai.com/2012/12/03/how-to-organize-and-hold-meetings-efficiently/#comments</comments>
		<pubDate>Mon, 03 Dec 2012 03:02:16 +0000</pubDate>
		<dc:creator>bigwhite</dc:creator>
				<category><![CDATA[思考控]]></category>
		<category><![CDATA[Blog]]></category>
		<category><![CDATA[Blogger]]></category>
		<category><![CDATA[IT]]></category>
		<category><![CDATA[Programmer]]></category>
		<category><![CDATA[会议]]></category>
		<category><![CDATA[博客]]></category>
		<category><![CDATA[反模式]]></category>
		<category><![CDATA[工作]]></category>
		<category><![CDATA[思考]]></category>
		<category><![CDATA[感悟]]></category>
		<category><![CDATA[效率]]></category>
		<category><![CDATA[程序员]]></category>
		<category><![CDATA[职场]]></category>

		<guid isPermaLink="false">http://tonybai.com/?p=1122</guid>
		<description><![CDATA[我个人一直追求高效的工作，无论是在职场中的哪个环节，在我眼中总是应该有提效的空间的，我甚至感觉我在这方面似乎形成了一种偏执，有些时候一看到低效的环节，我就有些情绪激动^_^。 如果要大家投票表决组织内部最低效地活动环节，估计大多数人会将选票投给会议。关于内部会议的组织和实施，有很多反模式，这里列举一二： - 会议组织人突然发出会议通知，两个小时后举行某会议； - 会议通知中没有会议的agenda信息，也没有任何有关会议的资料； - 会议的干系人选择不恰当，有些人本无需参会； - 会议实施过程中主持人无准备，无整体思路主线，想到哪里，就说到哪里； - 所讲内容与会议类型不匹配，无有效价值传达； - 会议无决议，无后续行动计划，大家无所获 &#8211; 三无会议。 这里谈到的会议的效率不仅在于实施时的时间上的长短，更重要的是会议主题内容在单位时间内传递给相关干系人的程度。其实细致高效地组织和实施一次组织内部会议，并非是件多难的事情。一个会议，无非准备、实施、会后跟踪落实三个部分，而每个部分其实又都是有章可循的。 一、准备阶段 * 提前预定好会议室 注意会议室的Size要适宜，别到时侯人多没地儿坐；而人少又显得空旷，显得人气不旺，气氛不足^_^。对于有限时的远程视频会议室，要预留足够长的时间，避免会议超时带来的意外情况。 * 会议通知 在明确会议主题、类型、目标和Agenda之后，可提前数天或更长时间在组织内发出会议通知，这样可以便于干系人安排好自己的任务列表；通知中应说明会议 的主题、目标与Agenda，如果有初步的资料的话，最好能附上，让相关干系人可以更深入了解；会议的干系人选择要谨慎，哪些人必须参会，哪些人需要知道 有这个会议，自由选择参加等等都要明确。 * 会议资料准备 会议的主讲人或主持人(因会议不同而定)需进行精心的资料准备。准备阶段，主讲人应充分考虑会上要向与会者传达哪些信息与价值，要有贯穿会议的清晰的思路 主线。有条件的情况下，可以请相关人评审这份资料，主讲人最好自己做些模拟讲解，以保证在会议上能产生最好的表达效果，以提高与会者的信息接收和理解程 度。另外有些类型会议(如总结会)需要一些第三方提供的资料或需第三方讨论确认的事情，这些务必在会议举行前完成，避免在会上进行细节的讨论，降低效率。 * 参会提醒 会议主持者应提前一天再次发出会议提醒，如果此时已经准备好最新资料了，可将资料附上；但少数主讲人希望保持神秘感，只发提醒也就是了。 二、实施阶段 * 当天的会议提醒 会议举行当天，再次做会议提醒，这次仅一个通知即可。 * 会议室准备 会议的主持人或组织者或主讲人应根据会议类型和具体情况，提前一些时间到达会议室做好各种准备，包括确认会议室的设备完好情况，至少连上投影，插上网线看 看是否可用；若是远程视频会议室，则更是要提早联系管理员做设备调试，确保会议准点开始时，设备是好用的，远程是接通的；类似一些架构讲解会的会议，可能 还需要提前在会议室白板上做板书。总之，这些准备工作目的就是让会议可以准时开始，而不是让与会者坐在那里白白浪费时间。 * 会议进行 不同类型的会议有不同的进行方式。在组织内部，例会、总结会和评审类会议居多。但总体来说，无论哪种会议，如果要高效地进行，都应该按照主持人/主讲人的 思路主线进行，围绕着会议要传达给与会者的主题为中心，详说重点，有理有据，略说细节，避免细节讨论；必要讨论时，主讲人也应引导与会者的讨论，避免跑 题，并及时打断讨论，回到正题上。 * 控制会议时间节奏 在某件事情上，常人保持集中精力于其上的时间是有限度的，超过这个时间，常人肯定会溜号，信息接收和理解的效率自然就会降低。因此为了让与会者可以保持集中精力的投入，主持人需要控制好会议的节奏，适当予以休息。组织内的大部分会议，应不超过一小时为宜。 * 会议要有结论，并与与会者达成一致 会议是以高效地传达某种信息为目的的，这些信息可能是知识、技巧、最佳实践、思路、工具或某种结论，与会者在后续的工作中会用到信息。因此虽然会议类型不同，但会议均应有相关结论，作为后续的行动计划；并且与会者需要这些结论上达成一致。 三、会后跟踪落实 [...]]]></description>
			<content:encoded><![CDATA[<p>我个人一直追求高效的工作，无论是在职场中的哪个环节，在我眼中总是应该有提效的空间的，我甚至感觉我在这方面似乎形成了一种偏执，有些时候一看到低效的环节，我就有些情绪激动^_^。</p>
<p>如果要大家投票表决组织内部最低效地活动环节，估计大多数人会将选票投给<b>会议</b>。关于内部会议的组织和实施，有很多反模式，这里列举一二：</p>
<p>- 会议组织人突然发出会议通知，两个小时后举行某会议；<br />
	- 会议通知中没有会议的agenda信息，也没有任何有关会议的资料；<br />
	- 会议的干系人选择不恰当，有些人本无需参会；<br />
	- 会议实施过程中主持人无准备，无整体思路主线，想到哪里，就说到哪里；<br />
	- 所讲内容与会议类型不匹配，无有效价值传达；<br />
	- 会议无决议，无后续行动计划，大家无所获 &#8211; 三无会议。</p>
<p>这里谈到的会议的效率不仅在于实施时的时间上的长短，更重要的是会议主题内容在单位时间内传递给相关干系人的程度。其实细致高效地组织和实施一次组织内部会议，并非是件多难的事情。一个会议，无非准备、实施、会后跟踪落实三个部分，而每个部分其实又都是有章可循的。</p>
<p><b>一、准备</b><b>阶段</b></p>
<p>* 提前预定好会议室</p>
<p>注意会议室的Size要适宜，别到时侯人多没地儿坐；而人少又显得空旷，显得人气不旺，气氛不足^_^。对于有限时的远程视频会议室，要预留足够长的时间，避免会议超时带来的意外情况。</p>
<p>* 会议通知</p>
<p>在明确会议主题、类型、目标和Agenda之后，可提前数天或更长时间在组织内发出会议通知，这样可以便于干系人安排好自己的任务列表；通知中应说明会议 的主题、目标与Agenda，如果有初步的资料的话，最好能附上，让相关干系人可以更深入了解；会议的干系人选择要谨慎，哪些人必须参会，哪些人需要知道 有这个会议，自由选择参加等等都要明确。</p>
<p>* 会议资料准备</p>
<p>会议的主讲人或主持人(因会议不同而定)需进行精心的资料准备。准备阶段，主讲人应充分考虑会上要向与会者传达哪些信息与价值，要有贯穿会议的清晰的思路 主线。有条件的情况下，可以请相关人评审这份资料，主讲人最好自己做些模拟讲解，以保证在会议上能产生最好的表达效果，以提高与会者的信息接收和理解程 度。另外有些类型会议(如总结会)需要一些第三方提供的资料或需第三方讨论确认的事情，这些务必在会议举行前完成，避免在会上进行细节的讨论，降低效率。</p>
<p>* 参会提醒</p>
<p>会议主持者应提前一天再次发出会议提醒，如果此时已经准备好最新资料了，可将资料附上；但少数主讲人希望保持神秘感，只发提醒也就是了。</p>
<p><b>二、实施阶段</b></p>
<p>* 当天的会议提醒</p>
<p>会议举行当天，再次做会议提醒，这次仅一个通知即可。</p>
<p>* 会议室准备</p>
<p>会议的主持人或组织者或主讲人应根据会议类型和具体情况，提前一些时间到达会议室做好各种准备，包括确认会议室的设备完好情况，至少连上投影，插上网线看 看是否可用；若是远程视频会议室，则更是要提早联系管理员做设备调试，确保会议准点开始时，设备是好用的，远程是接通的；类似一些架构讲解会的会议，可能 还需要提前在会议室白板上做板书。总之，这些准备工作目的就是让会议可以准时开始，而不是让与会者坐在那里白白浪费时间。</p>
<p>* 会议进行</p>
<p>不同类型的会议有不同的进行方式。在组织内部，例会、总结会和评审类会议居多。但总体来说，无论哪种会议，如果要高效地进行，都应该按照主持人/主讲人的 思路主线进行，围绕着会议要传达给与会者的主题为中心，详说重点，有理有据，略说细节，避免细节讨论；必要讨论时，主讲人也应引导与会者的讨论，避免跑 题，并及时打断讨论，回到正题上。</p>
<p>* 控制会议时间节奏</p>
<p>在某件事情上，常人保持集中精力于其上的时间是有限度的，超过这个时间，常人肯定会溜号，信息接收和理解的效率自然就会降低。因此为了让与会者可以保持集中精力的投入，主持人需要控制好会议的节奏，适当予以休息。组织内的大部分会议，应不超过一小时为宜。</p>
<p>* 会议要有结论，并与与会者达成一致</p>
<p>会议是以高效地传达某种信息为目的的，这些信息可能是知识、技巧、最佳实践、思路、工具或某种结论，与会者在后续的工作中会用到信息。因此虽然会议类型不同，但会议均应有相关结论，作为后续的行动计划；并且与会者需要这些结论上达成一致。</p>
<p><b>三、会后跟踪落实</b></p>
<p>会议的效率更多体现在前两个阶段。最后这个阶段更多是用来检验和评估会议后的信息传达效果。另外会议主持人/主讲人需要通过这些跟踪和落实情况，总结信息传达情况；回顾和反省会议是否组织和实施的足够高效；发掘和发现问题，并做持续改进和改善。</p>
<p style='text-align:left'>&copy; 2012 &#8211; 2013, <a href='https://tonybai.com'>bigwhite</a>. 版权所有. </p>
]]></content:encoded>
			<wfw:commentRss>https://tonybai.com/2012/12/03/how-to-organize-and-hold-meetings-efficiently/feed/</wfw:commentRss>
		<slash:comments>2</slash:comments>
		</item>
	</channel>
</rss>
