<?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/%e6%9e%84%e5%bb%ba/feed/" rel="self" type="application/rss+xml" />
	<link>https://tonybai.com</link>
	<description>一个程序员的心路历程</description>
	<lastBuildDate>Sat, 23 May 2026 23:26:24 +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>Go 模块构建与依赖管理：我们到底在“折腾”什么？</title>
		<link>https://tonybai.com/2025/10/27/the-ultimate-guide-to-go-module/</link>
		<comments>https://tonybai.com/2025/10/27/the-ultimate-guide-to-go-module/#comments</comments>
		<pubDate>Mon, 27 Oct 2025 00:08:18 +0000</pubDate>
		<dc:creator>bigwhite</dc:creator>
				<category><![CDATA[技术志]]></category>
		<category><![CDATA[asm]]></category>
		<category><![CDATA[Cgo]]></category>
		<category><![CDATA[CI/CD]]></category>
		<category><![CDATA[dep]]></category>
		<category><![CDATA[exclude]]></category>
		<category><![CDATA[GET]]></category>
		<category><![CDATA[Go]]></category>
		<category><![CDATA[go-get]]></category>
		<category><![CDATA[go.mod]]></category>
		<category><![CDATA[go.sum]]></category>
		<category><![CDATA[go.work]]></category>
		<category><![CDATA[go1.21]]></category>
		<category><![CDATA[Golang]]></category>
		<category><![CDATA[gomodtidy]]></category>
		<category><![CDATA[GoModuleProxy协议]]></category>
		<category><![CDATA[GoModules]]></category>
		<category><![CDATA[GoModules核心原理]]></category>
		<category><![CDATA[GONOSUMDB]]></category>
		<category><![CDATA[GOPATH]]></category>
		<category><![CDATA[Gopher]]></category>
		<category><![CDATA[GOPRIVATE]]></category>
		<category><![CDATA[GOPROXY]]></category>
		<category><![CDATA[Go专家]]></category>
		<category><![CDATA[Go开发]]></category>
		<category><![CDATA[Go插件]]></category>
		<category><![CDATA[Go构建与依赖管理体系]]></category>
		<category><![CDATA[Go构建体系]]></category>
		<category><![CDATA[Go模块]]></category>
		<category><![CDATA[Go模块构建与依赖管理]]></category>
		<category><![CDATA[Go熟练工]]></category>
		<category><![CDATA[Go语言]]></category>
		<category><![CDATA[Go语言第一课]]></category>
		<category><![CDATA[Go语言进阶课]]></category>
		<category><![CDATA[HTTP协议]]></category>
		<category><![CDATA[Kubernetes]]></category>
		<category><![CDATA[list]]></category>
		<category><![CDATA[mvs]]></category>
		<category><![CDATA[npm]]></category>
		<category><![CDATA[pip]]></category>
		<category><![CDATA[Plugin插件]]></category>
		<category><![CDATA[replace]]></category>
		<category><![CDATA[retract]]></category>
		<category><![CDATA[tidy]]></category>
		<category><![CDATA[TonyBai]]></category>
		<category><![CDATA[toolchain]]></category>
		<category><![CDATA[toolchaingo1.22.0]]></category>
		<category><![CDATA[v1]]></category>
		<category><![CDATA[v2]]></category>
		<category><![CDATA[vendor]]></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>
		<category><![CDATA[商务合作]]></category>
		<category><![CDATA[圣经]]></category>
		<category><![CDATA[在线课程]]></category>
		<category><![CDATA[培训]]></category>
		<category><![CDATA[复杂场景]]></category>
		<category><![CDATA[多模块开发]]></category>
		<category><![CDATA[大型Go项目]]></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>
		<category><![CDATA[混合构建]]></category>
		<category><![CDATA[演进史]]></category>
		<category><![CDATA[知其所以然]]></category>
		<category><![CDATA[知其然]]></category>
		<category><![CDATA[知识体系]]></category>
		<category><![CDATA[私信]]></category>
		<category><![CDATA[私有Proxy]]></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=5318</guid>
		<description><![CDATA[本文永久链接 &#8211; https://tonybai.com/2025/10/27/the-ultimate-guide-to-go-module 大家好，我是Tony Bai。 我想问大家一个问题：在你日常的 Go 开发中，有没有哪个瞬间，让你觉得明明是在做一件简单的事，却被工具链“折腾”得心力交瘁？ 或许是那个深夜，CI/CD 系统一片爆红，只因为一位同事提交代码时，忘记删掉了 go.mod 文件里那行指向他本地路径的 replace 指令。 或许是你刚接手一个老项目，面对 GOPATH 和 vendor 的“历史遗迹”，想升级一个包却发现牵一发而动全身，最后只能无奈放弃。 又或许，你只是想在公司内网拉取一个私有库，却被 GOPROXY、GOPRIVATE、GONOSUMDB 这“三兄弟”搞得晕头转向，在 404 和 410 的报错中反复挣扎。 如果这些场景让你会心一笑（或者苦笑），那么，欢迎来到我们的世界。 Go 模块的构建与依赖管理，就像我们呼吸的空气。 它无处不在，支撑着我们所有的Go开发活动。但正因为它如此基础，我们常常满足于“能用就行”，而忽略了其背后深刻的设计哲学和强大的工程能力。直到有一天，我们被一个棘手的构建问题拦住去路，才发现自己对这套最熟悉的工具，其实知之甚少。 为什么我要写这个微专栏？ 市面上关于 Go Modules 的文章很多，但大多是“点状”的：教你一个命令，解决一个问题。但我发现，很少有内容能系统性地回答那几个更深层次的“为什么”： Go 为什么会放弃 GOPATH，经历 vendor、dep 的探索，最终选择了 Go Modules 这条路？这背后是怎样的历史和权衡？ 最小版本选择（MVS）算法到底是什么？它和 npm/pip 的逻辑有何本质不同，为什么说它带来了“高保真”的可重现的构建？ go.mod 里的 go 1.21 和 toolchain go1.22.0 到底是什么关系？它们是如何维系 Go [...]]]></description>
			<content:encoded><![CDATA[<p><img src="https://tonybai.com/wp-content/uploads/2025/the-ultimate-guide-to-go-module.png" alt="" /></p>
<p><a href="https://tonybai.com/2025/10/27/the-ultimate-guide-to-go-module">本文永久链接</a> &#8211; https://tonybai.com/2025/10/27/the-ultimate-guide-to-go-module</p>
<p>大家好，我是Tony Bai。</p>
<p>我想问大家一个问题：在你日常的 Go 开发中，有没有哪个瞬间，让你觉得明明是在做一件简单的事，却被工具链“折腾”得心力交瘁？</p>
<p>或许是那个深夜，CI/CD 系统一片爆红，只因为一位同事提交代码时，忘记删掉了 go.mod 文件里那行指向他本地路径的 replace 指令。</p>
<p>或许是你刚接手一个老项目，面对 GOPATH 和 vendor 的“历史遗迹”，想升级一个包却发现牵一发而动全身，最后只能无奈放弃。</p>
<p>又或许，你只是想在公司内网拉取一个私有库，却被 GOPROXY、GOPRIVATE、GONOSUMDB 这“三兄弟”搞得晕头转向，在 404 和 410 的报错中反复挣扎。</p>
<p>如果这些场景让你会心一笑（或者苦笑），那么，欢迎来到我们的世界。</p>
<p><strong>Go 模块的构建与依赖管理，就像我们呼吸的空气。</strong> 它无处不在，支撑着我们所有的Go开发活动。但正因为它如此基础，我们常常满足于“能用就行”，而忽略了其背后深刻的设计哲学和强大的工程能力。直到有一天，我们被一个棘手的构建问题拦住去路，才发现自己对这套最熟悉的工具，其实知之甚少。</p>
<h2>为什么我要写这个微专栏？</h2>
<p>市面上关于 Go Modules 的文章很多，但大多是“点状”的：教你一个命令，解决一个问题。但我发现，很少有内容能系统性地回答那几个更深层次的“<strong>为什么</strong>”：</p>
<ul>
<li>Go 为什么会放弃 GOPATH，经历 vendor、dep 的探索，最终选择了 Go Modules 这条路？这背后是怎样的历史和权衡？</li>
<li>最小版本选择（MVS）算法到底是什么？它和 npm/pip 的逻辑有何本质不同，为什么说它带来了“高保真”的可重现的构建？</li>
<li>go.mod 里的 go 1.21 和 toolchain go1.22.0 到底是什么关系？它们是如何维系 Go 强大的兼容性承诺的？</li>
<li>GOPROXY 背后那套简单的 HTTP 协议是什么样的？理解了它，我们就能自己动手模拟一次 go get 的全过程。</li>
<li>像Kubernetes这样的大型Go项目是如何进行Go module依赖管理和构建的？有什么值得我们借鉴的地方？</li>
</ul>
<p>这些问题，才是我认为真正能让我们“<strong>从入门到精通</strong>”的关键。</p>
<h2>在这个专栏里，你将得到什么？</h2>
<p>为此，我花了数月时间，整理、实践、并最终策划了这门<strong>《Go 模块构建与依赖管理: 从入门到精通》</strong>的微专栏。</p>
<p>这是一份写给所有 Gopher 的Go构建体系“圣经”，旨在帮助大家彻底搞懂 Go 的“包”罗万象 。我们将：</p>
<ul>
<li><strong>从历史的源头出发</strong>，回顾 Go 依赖管理的演进史，建立完整的认知。</li>
<li><strong>深入核心原理</strong>，彻底搞懂go.mod、MVS、go.sum 和兼容性机制。</li>
<li><strong>精通所有工具</strong>，从 go get、go mod tidy 到 replace、exclude，再到 本地多模块开发 神器 go.work。</li>
<li><strong>覆盖作者和使用者双工作流</strong>，从模块作者的创建与发布(v1)、发布补丁/次要版本、发布主版本（v2+)，到模块使用者的依赖添加与升级、降级与移除。</li>
<li><strong>驾驭复杂场景</strong>，无论是私有仓库、带有cgo/asm的混合构建，还是将 Go 编译成静态库、动态库或Plugin插件。</li>
<li><strong>解剖顶级案例</strong>，看看 Kubernetes 这种巨型项目，是如何管理其“天文数字”般的依赖的。</li>
<li><strong>终结所有“天坑”</strong>，我会把我踩过的所有坑、总结的所有排错技巧，毫无保留地分享给你。</li>
</ul>
<p><strong>以下是本专栏的完整大纲（共 13 讲）：</strong></p>
<p><strong>模块一：历史与原理 (建立认知)</strong></p>
<ol>
<li>前世今生：从 GOPATH 的“混乱”到 Go Modules 的“秩序”</li>
<li>Go Modules 核心原理：go.mod, go.sum 与最小版本选择 (MVS)</li>
<li>兼容性的承诺：深入 go 与 toolchain 指令</li>
</ol>
<p><strong>模块二：工作流与高级操作 (精通工具)</strong></p>
<ol>
<li>日常操作精通：get, tidy, list 三剑客</li>
<li>模块的生命周期：作者与使用者的工作流</li>
<li>依赖关系“手术刀”：replace, exclude 与 retract</li>
<li>告别 replace 泥潭：go.work 与多模块开发</li>
</ol>
<p><strong>模块三：企业级与复杂场景 (驾驭复杂)</strong></p>
<ol>
<li>深入 Go Module Proxy 协议</li>
<li>企业级实践：私有仓库与私有 Proxy</li>
<li>跨越边界：cgo 与 asm 的构建之道</li>
<li>构建模式的魔力: 从静态库、动态库到 Go 插件</li>
</ol>
<p><strong>模块四：案例与排错 (升华与实战)</strong></p>
<ol>
<li>实战解剖：Kubernetes 是如何管理上千个依赖的？</li>
<li>终章：常见构建“天坑”与终极排错指南</li>
</ol>
<h2>小结</h2>
<p>我相信，这是<strong>全网第一份</strong>如此系统、全面、深入 Go 构建与依赖管理体系的中文资料。</p>
<p>如果你也曾被这些问题“折腾”过，如果你也渴望一次性地、系统性地掌握这门 Gopher 的“必修内功”，那么，我诚挚地邀请你加入这场学习之旅。</p>
<p>让我们一起，告别“知其然”，真正做到“知其所以然”，<strong>彻底搞懂 Go 的模块构建与依赖管理</strong>。</p>
<p>点击<a href="https://mp.weixin.qq.com/mp/appmsgalbum?__biz=MzIyNzM0MDk0Mg==&amp;action=getalbum&amp;album_id=4225702928272949254#wechat_redirect">阅读全文</a>/扫描下方二维码，立即订阅《Go 模块构建与依赖管理: 从入门到精通》，开启你的全面且有深度的探索之旅！</p>
<p><img src="https://tonybai.com/wp-content/uploads/2025/paid/the-ultimate-guide-to-go-module-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/10/27/the-ultimate-guide-to-go-module/feed/</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>“骑手与大象”架构：超越微服务与单体之争的务实之道？</title>
		<link>https://tonybai.com/2025/06/17/rider-elephant-arch/</link>
		<comments>https://tonybai.com/2025/06/17/rider-elephant-arch/#comments</comments>
		<pubDate>Tue, 17 Jun 2025 00:13:39 +0000</pubDate>
		<dc:creator>bigwhite</dc:creator>
				<category><![CDATA[技术志]]></category>
		<category><![CDATA[C]]></category>
		<category><![CDATA[Channel]]></category>
		<category><![CDATA[Cpp]]></category>
		<category><![CDATA[CPU]]></category>
		<category><![CDATA[Go]]></category>
		<category><![CDATA[Golang]]></category>
		<category><![CDATA[goroutine]]></category>
		<category><![CDATA[gRPC]]></category>
		<category><![CDATA[JavaScript]]></category>
		<category><![CDATA[microservice]]></category>
		<category><![CDATA[NextJS]]></category>
		<category><![CDATA[Python]]></category>
		<category><![CDATA[Rust]]></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=4827</guid>
		<description><![CDATA[本文永久链接 &#8211; https://tonybai.com/2025/06/17/rider-elephant-arch 大家好，我是Tony Bai。 在软件架构的江湖里，关于“微服务”与“单体”的论战，几乎从未停歇。一方推崇微服务的灵活性、可扩展性和独立部署，另一方则坚守单体的简洁性、低通信开销和易于本地调试。近年来，我们甚至看到像亚马逊 Prime Video 这样重量级的玩家，也公开分享了其从微服务“回归”到某种形式的单体（或者说更粗粒度的服务）的实践，引发了业界新一轮的思考。 这不禁让我们反问：微服务与单体，真的就是非此即彼的“二元对立”吗？ 最近，国外一家名为DealGate公司的一篇文章《Introducing the Rider and Elephant Software Architecture》，提出了一种他们称之为“骑手与大象”的架构模式，试图在这场看似无解的争论中，找到一条务实的中间道路。这种模式不仅在他们的实践中取得了显著成效，其背后的设计哲学和对技术选型的思考，也颇具启发意义。 “骑手与大象”：一个古老隐喻的现代架构演绎 DealGate 将其架构模式命名为“骑手与大象”，其灵感来源于心理学中的一个经典比喻：人类的思维由两部分组成——理性的“骑手”（对应我们发达的前额叶皮层，负责规划、分析和决策）和感性的、更强大的“大象”（对应我们原始的、更底层的“蜥蜴脑”或“穴居人脑”，驱动着本能和情绪）。骑手虽然可以尝试引导大象，但无法完全控制它；而如果骑手想独自前行，又会发现大象的力量是其无法比拟的。只有当骑手与大象协同合作时，才能发挥出最大的效能。 在 DealGate 的架构中，这个隐喻被巧妙地映射到了技术组件上： “大象 (Elephant)”：由 Go语言构建的应用。它不包含任何复杂的业务逻辑，但却承担着所有“脏活累活”——大规模的、高并发的数据处理。在 DealGate 的场景中，这可能意味着在任何时刻都有数万个 goroutine 在处理图像、PDF，抓取数千万级别的网页，并在每个网页上运行数千万次的正则表达式匹配。“大象”的核心职责是：强大、高效、能扛事儿。 “骑手 (Rider)”：由NextJS (Node.js) 构建的应用。它承载了所有的业务逻辑、数据库访问、用户交互等。“骑手”的核心职责是：灵活、敏捷、快速响应业务变化。 缰绳 (Communication)：“骑手”通过 gRPC 来“引导”和控制“大象”，两者之间保持低开销、高效率的通信。 这种架构的核心思想是：将需要极致性能和高并发处理的“重计算”部分（大象），与需要快速迭代和灵活业务逻辑的“轻应用”部分（骑手）进行分离，并让它们通过高效的通信方式协同工作。 为何选择“骑手与大象”？DealGate 的实践与思考 DealGate 之所以采用这种架构，源于他们在实际业务中遇到的挑战和对现有架构模式的反思。 对“微服务 vs 单体”的“虚假二分法”说不：他们认为，单纯地在微服务和单体之间做选择，往往忽略了业务的复杂性和多样性。他们希望能够“have the best of both worlds”（取两者之长）。 Node.js/NextJS 的局限性：尽管 DealGate 的主要应用是用 [...]]]></description>
			<content:encoded><![CDATA[<p><img src="https://tonybai.com/wp-content/uploads/2025/rider-elephant-arch-1.png" alt="" /></p>
<p><a href="https://tonybai.com/2025/06/17/rider-elephant-arch">本文永久链接</a> &#8211; https://tonybai.com/2025/06/17/rider-elephant-arch</p>
<p>大家好，我是Tony Bai。</p>
<p>在软件架构的江湖里，关于“微服务”与“单体”的论战，几乎从未停歇。一方推崇微服务的灵活性、可扩展性和独立部署，另一方则坚守单体的简洁性、低通信开销和易于本地调试。近年来，我们甚至看到像亚马逊 Prime Video 这样重量级的玩家，也公开分享了其从微服务“回归”到某种形式的单体（或者说更粗粒度的服务）的实践，引发了业界新一轮的思考。</p>
<p>这不禁让我们反问：<strong>微服务与单体，真的就是非此即彼的“二元对立”吗？</strong></p>
<p>最近，国外一家名为DealGate公司的一篇文章《Introducing the Rider and Elephant Software Architecture》，提出了一种他们称之为“骑手与大象”的架构模式，试图在这场看似无解的争论中，找到一条务实的中间道路。这种模式不仅在他们的实践中取得了显著成效，其背后的设计哲学和对技术选型的思考，也颇具启发意义。</p>
<h2>“骑手与大象”：一个古老隐喻的现代架构演绎</h2>
<p>DealGate 将其架构模式命名为“骑手与大象”，其灵感来源于心理学中的一个经典比喻：人类的思维由两部分组成——理性的“骑手”（对应我们发达的前额叶皮层，负责规划、分析和决策）和感性的、更强大的“大象”（对应我们原始的、更底层的“蜥蜴脑”或“穴居人脑”，驱动着本能和情绪）。骑手虽然可以尝试引导大象，但无法完全控制它；而如果骑手想独自前行，又会发现大象的力量是其无法比拟的。只有当骑手与大象协同合作时，才能发挥出最大的效能。</p>
<p><img src="https://tonybai.com/wp-content/uploads/2025/rider-elephant-arch-2.jpg" alt="" /></p>
<p>在 DealGate 的架构中，这个隐喻被巧妙地映射到了技术组件上：</p>
<ul>
<li>“大象 (Elephant)”：由 Go语言构建的应用。它不包含任何复杂的业务逻辑，但却承担着所有“脏活累活”——大规模的、高并发的数据处理。在 DealGate 的场景中，这可能意味着在任何时刻都有数万个 goroutine 在处理图像、PDF，抓取数千万级别的网页，并在每个网页上运行数千万次的正则表达式匹配。“大象”的核心职责是：强大、高效、能扛事儿。</li>
<li>“骑手 (Rider)”：由NextJS (Node.js) 构建的应用。它承载了所有的业务逻辑、数据库访问、用户交互等。“骑手”的核心职责是：灵活、敏捷、快速响应业务变化。</li>
<li>缰绳 (Communication)：“骑手”通过 <strong>gRPC</strong> 来“引导”和控制“大象”，两者之间保持低开销、高效率的通信。</li>
</ul>
<p>这种架构的核心思想是：将需要极致性能和高并发处理的“重计算”部分（大象），与需要快速迭代和灵活业务逻辑的“轻应用”部分（骑手）进行分离，并让它们通过高效的通信方式协同工作。</p>
<h2>为何选择“骑手与大象”？DealGate 的实践与思考</h2>
<p>DealGate 之所以采用这种架构，源于他们在实际业务中遇到的挑战和对现有架构模式的反思。</p>
<ul>
<li>对“微服务 vs 单体”的“虚假二分法”说不：他们认为，单纯地在微服务和单体之间做选择，往往忽略了业务的复杂性和多样性。他们希望能够“have the best of both worlds”（取两者之长）。</li>
<li>Node.js/NextJS 的局限性：尽管 DealGate 的主要应用是用 NextJS 编写的，但他们发现，即使 Node.js 在 I/O 和网络处理上有多线程优势，其正则表达式等 CPU 密集型操作仍然受限于单线程（JavaScript 的执行模型）。当需要在后台进行大量正则匹配，同时还要响应 Web 应用请求时，性能瓶颈就显而易见了。</li>
<li>Go 语言的“大象”潜质：文章中明确指出：“Go语言非常适合这种场景，你可以轻松地扔给它数万个CPU密集型进程，它会愉快地处理掉所有这些”。这充分肯定了 Go 语言在并发处理和性能方面的核心优势。</li>
<li>对微服务通信开销的警惕：DealGate 批评了许多微服务架构使用 JSON 进行进程间通信的做法，认为其“序列化和反序列化开销是令人发指的”。他们选择 gRPC，正是为了最大限度地降低“骑手”与“大象”之间的通信成本，确保即使在需要传输大量数据（因为“大象”不包含业务逻辑，需要被视为“愚笨的工人”）的情况下，也能保持高效。</li>
</ul>
<h2>Go 语言：扮演“大象”的理想之选</h2>
<p>在“骑手与大象”的架构中，Go 语言之所以被选中扮演“吃苦耐劳的大象”，并非偶然。这得益于 Go 语言的核心特性：</p>
<ol>
<li>极致的并发性能：Goroutine 和 Channel 机制，配合高效的调度器，使得 Go 能够轻松创建和管理海量的并发任务，这对于处理 DealGate 所述的“数万个 goroutine 同时处理数据”的场景至关重要。</li>
<li>高效的执行效率：Go 语言编译为原生机器码，其性能接近 C/C++，远超解释型语言，非常适合 CPU 密集型的数据处理任务。</li>
<li>强大的标准库：Go 的标准库提供了丰富的网络编程、文本处理（包括正则表达式）、数据编解码等功能，为构建“大象”应用提供了坚实的基础。</li>
<li>简洁的部署：Go 应用可以编译成单个静态链接的可执行文件，部署简单，依赖少。</li>
</ol>
<p>可以说，Go 语言的设计哲学和核心能力，使其成为承载这种“无业务逻辑、高并发、重计算”角色的理想选择。</p>
<h2>语言选型的“二八原则”与“务实主义”</h2>
<p>“骑手与大象”架构的另一个核心启示，在于其对不同技术栈的选择策略，体现了一种深刻的“务实主义”和对“成本效益”的考量。</p>
<p>文章明确反驳了“既然有更高性能的语言（如 Rust 或 Go 本身），为什么不把所有应用都用它来写？”的观点，并将其类比为“那所有应用都应该用汇编来写了”。</p>
<p>其核心逻辑是：</p>
<ul>
<li>高级语言（如 JavaScript, Python）的优势：更安全（内存管理等）、生产力更高（表达力强、语法糖和轮子多）、开发者社群更大、单位时间开发成本相对更低。</li>
<li>高性能/底层语言（如 Go, Rust, C++）的优势：性能极致、对系统资源有更精细的控制。但通常也意味着更陡峭的学习曲线、更高的开发成本、以及（在某些情况下）更长的开发周期。</li>
</ul>
<p>DealGate 的策略是：<strong>“在你必须快的地方快，其他一切都选择高级语言和（相对）单体的模式。”</strong> 这意味着：</p>
<ul>
<li>将昂贵的、需要精细优化的<strong>高性能代码（大象）限制在最小的必要范围内</strong>（例如，只占整个业务系统的 10%）。</li>
<li>将大部分的<strong>业务逻辑、用户交互（骑手）用生产力更高、开发更快的高级语言来实现</strong>。</li>
</ul>
<p>这种“混合编程”或“多语言架构”的思路，实际上是在性能、开发效率、人才获取成本、维护成本等多个维度之间进行权衡和优化。它提醒我们，技术选型不应盲目追求“最新最酷”或“性能极致”，而应服务于业务需求，并充分考虑团队和公司的实际情况。</p>
<p>文章中也提及了对“Just write Rust”（就用 Rust 写）这类口号的反思，指出大多数公司和开发者可能无法承担全员学习和使用像 Rust 这样“高门槛”语言的成本。这并非否定 Rust 的优秀，而是强调技术选型的现实约束。</p>
<h2>小结：“没有完美的解决方案，只有明智的权衡”</h2>
<p>“没有完美的解决方案，只有权衡取舍”。DealGate 的文章以这句经典的名言作为总结，恰如其分。</p>
<p>“骑手与大象”架构，正是在微服务的灵活性、分布式能力与单体的低心智负担、高开发效率之间做出的一种明智权衡。它并非适用于所有场景的“银弹”，但在类似 DealGate 这样需要处理大规模数据密集型任务，同时又需要快速迭代业务逻辑的场景下，无疑提供了一种极具价值的、务实的架构思路。</p>
<p>它也再次印证了一个朴素的道理：优秀的架构设计，往往不是对某种“主义”的盲从，而是对业务需求的深刻理解和对不同技术优劣的精准把握，最终在各种约束条件下找到那个“恰到好处”的平衡点。</p>
<p>或许，在微服务与单体的喧嚣争论之外，我们更应该学习这种“骑手与大象”的智慧——<strong>在正确的地方，用正确的方式，做正确的事情。</strong></p>
<p><strong>参考文献：</strong><br />
Introducing the Rider and Elephant Software Architecture &#8211; https://d-gate.io/blog/rider-and-elephant-architecture</p>
<hr />
<p><strong>聊一聊，也帮个忙：</strong></p>
<ul>
<li><strong>你如何看待 DealGate 提出的“骑手与大象”架构模式？它是否对你的项目有所启发？</strong></li>
<li><strong>在你的工作中，是否也遇到过类似的“微服务 vs 单体”或“高性能 vs 高生产力”的选型困境？你是如何权衡的？</strong></li>
<li><strong>Go 语言在你心目中，更适合扮演“骑手”还是“大象”的角色？或者两者皆可，取决于具体场景？</strong></li>
</ul>
<p>欢迎在<strong>评论区</strong>留下你的思考和经验。如果你觉得这篇文章提供了一个有价值的视角，也请<strong>转发给你身边的开发者和架构师朋友们</strong>，一起探讨更务实的架构之道！</p>
<hr />
<p><strong>精进有道，更上层楼</strong></p>
<p><a href="https://mp.weixin.qq.com/s/GWGWTfCRCsOJ_4Pk-pxpHA">极客时间《Go语言进阶课》上架刚好一个月</a>，受到了各位读者的热烈欢迎和反馈。在这里感谢大家的支持。目前我们已经完成了课程模块一『语法强化篇』的 13 讲，为你系统突破 Go 语言的语法认知瓶颈，打下坚实基础。</p>
<p>现在，我们即将进入模块二『设计先行篇』，这不仅包括 API 设计，更涵盖了项目布局、包设计、并发设计、接口设计、错误处理设计等构建高质量 Go 代码的关键要素。</p>
<p>这门进阶课程，是我多年 Go 实战经验和深度思考的结晶，旨在帮助你突破瓶颈，从“会用 Go”迈向“精通 Go”，真正驾驭 Go 语言，编写出更优雅、<br />
更高效、更可靠的生产级代码！</p>
<p>扫描下方二维码，立即开启你的 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/06/17/rider-elephant-arch/feed/</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>Go语言包设计指南</title>
		<link>https://tonybai.com/2023/06/18/go-package-design-guide/</link>
		<comments>https://tonybai.com/2023/06/18/go-package-design-guide/#comments</comments>
		<pubDate>Sun, 18 Jun 2023 15:03:41 +0000</pubDate>
		<dc:creator>bigwhite</dc:creator>
				<category><![CDATA[技术志]]></category>
		<category><![CDATA[Design]]></category>
		<category><![CDATA[DIP]]></category>
		<category><![CDATA[Go]]></category>
		<category><![CDATA[Golang]]></category>
		<category><![CDATA[ISP]]></category>
		<category><![CDATA[LSP]]></category>
		<category><![CDATA[OCP]]></category>
		<category><![CDATA[Package]]></category>
		<category><![CDATA[SOLID]]></category>
		<category><![CDATA[SRP]]></category>
		<category><![CDATA[wire]]></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=3911</guid>
		<description><![CDATA[本文永久链接 &#8211; https://tonybai.com/2023/06/18/go-package-design-guide 1. Go包的认知 1.1 Go包是基本功能单元 我们知道Go包是Go编程语言中的一个重要概念，它是一组相关的Go源代码文件。并且，在Go中，每个Go源文件都必须属于一个包。 Go包是一个逻辑上独立的单元，是Go的基本功能单元，用来做功能边界的划分。这些基本功能单元的累加就构成了Go应用，因此Go应用的本质就是一组Go包的集合。 Go包这种功能独立的单元为Go开发者提供了“封装”和复用的便利。在Go中，Go包也是代码复用的基本单元，被用来管理和组织代码，Go项目的结构布局本质上就是安排Go包的位置，使得代码更易于维护和重用。 1.2 Go包是基本编译单元 Go包还是编译时的最小单位。也就是说，Go编译器编译代码时会以包为单位进行编译，而不是以文件为单位。这意味着一个包中的所有源文件都将被编译成一个单独的目标文件，而不是多个目标文件。 使用包而不是文件作为编译单元，有助于提高编译效率和管理依赖关系。 注：编译速度快是包这种设计“先进性”的一个表现，即便每次编译都是从零开始。Go编译速度快的几个原因与以包作为编译单元是密不可分的，具体体现在Go源文件在开头处显式地列出所有依赖包，编译器不必读取整个文件就可确定依赖包列表；Go包之间不能存在循环依赖，由于无环，包可以被单独编译，也可以并行编译；已编译的Go包的目标文件中记录了其所依赖包的导出符号信息。Go编译器在读取该目标文件时不需进一步读取其依赖包的目标文件。 1.3 Go包是基本设计单元 这个世界越来越复杂，软件系统同样变得日益复杂。无论你是什么编程语言的开发者，我们要面对的都是如何驯服这种复杂性。到目前为止，我们驯服这种复杂性的思路还很初级，无非是对复杂性进行分解、分解、分解，并按照我们更容易理解的方式重新组合。 Go包是基本功能单元，对于Go开发者，我们要将复杂性分解为一个个包，然后以一种合理的方式将包组合在一起以实现我们要的系统，因此Go包也是我们面对一个系统时的基本设计单元。我们不仅要设计每个包肩负的职责，还要设计包与包之间的关系。 因此，Go系统设计就是面向包进行设计。 接下来，我们就来看看Go包的设计思路。 2. Go包设计思路 即便你没写过Go程序，作为程序员你也应该知道“高内聚，低耦合”这个原则在软件系统设计中的分量。这里我们就以这个原则作为“抓手”来展开看看Go包的设计思路。 高内聚, 低耦合这个顶层原则，适用于所有编程语言的系统设计。但落到Go包设计上面，具体如何体现高内聚与低耦合呢？我们继续往下看。 2.1 功能选桶：自然内聚 面对一个复杂系统，我们通常会做一些系统分析，比如用领域驱动设计的方式从需求中挖掘出一堆术语、事件、命令等(即便你不懂领域设计的纯正方法，你的实际操作过程也或多或少与领域设计的内容重叠)。之后，通常会分层、划分服务，每个服务又要划分模块或包。 在服务这一层次上哪些功能放到哪个包里呢？这个过程我称之为“功能选桶”。 这让我想到了和孩子一起学习动物分类时的书中题目： 有这样一组动物：老虎、狮子、海马、大雁、熊猫、黄鹂、鲸鱼，这些动物能分为哪几个类别呢? 一个稍稍被启蒙了的孩子都会给出这样的分类： 陆地动物：老虎、狮子、熊猫 海洋动物：海马、鲸鱼 天空动物：大雁、黄鹂 而另外一个稍有一些生物学入门知识的大点的孩子可能也会给出下面的分类： 哺乳动物：老虎、狮子、熊猫、鲸鱼 卵生动物：海马、大雁、黄鹂 无论哪种，这些分类都是基于动物行为特征的自然结合。第一种似乎更直观自然，第二种则需要有更“专业”的知识（领域知识）。Go包的“功能选桶”其实是一个道理，相关功能自然结合到一个包中，保证这个包的内聚性。 比如下面有几个功能函数：Add、Subtract、Multiply、Divide、Sum、Average、Histogram，我们如何为这几个函数选桶呢？ 一种不那么内聚的作法是将上述所有函数都放入math包；而更自然内聚一些的作法则是将Add、Subtract、Multiply、Divide放入math包，而将Sum、Average、Histogram等放入stats包(statistics，统计学)。 在功能选桶过程中，越符合常人思维，就越自然，可读性和可理解性大概率就越好。 2.2 包间关系：最小耦合 功能选桶之后，我们再来看包与包之间的关系，通常我们这种关系称为耦合。 用白话来理解耦合就是：当a变化时，b受到影响并随之变化，则说b与a之间存在耦合，即b依赖a。a是引发b变化的一个原因。 程序员都知道：一种理想的耦合情况是正交，即你变你自变，我岿然不动。但现实中这很难达到，我们应该追求的是尽可能地降低包与包间的耦合。 在包依赖层面，Go强制要求不能存在循环依赖，即Go包之间的耦合一定是有向无环的，这一定层度上也能帮助Go包之间降低耦合。 要降低包与包之间的耦合，我们首先要了解Go包间的最低耦合关系是什么呢？ 在代码层面最低的耦合是接口耦合，在Go中，接口的实现是隐式的，即a包实现b包中定义的接口时是不需要显式导入b包的，我们可以在c包中完成对a包与b包的组装，这样c包依赖a包和b包，但a包与b包之间没有任何耦合。 那么负责组装a包与b包的c包能否在代码层面消除掉对a和b的依赖呢？这个就很难了。不过我们可以使用依赖注入技术来消除在代码层面手动基于依赖进行初始化或创建时的复杂性，不过依赖注入技术也是有“门槛”的，它会让你的代码不那么straightforward，代码的可读性和可理解性会下降。 注：个人觉得：依赖注入在Go中并非是一种惯用法。Google开源了像wire这样的依赖注入框架(通过代码生成而实现的编译期依赖注入)，更多是为了解决掉内部大型Go项目初始化时各种创建动作的复杂性。 我们可以参考软件界用于降低代码耦合的原则，比如由Robert C. [...]]]></description>
			<content:encoded><![CDATA[<p><img src="https://tonybai.com/wp-content/uploads/go-package-design-guide-1.png" alt="" /></p>
<p><a href="https://tonybai.com/2023/06/18/go-package-design-guide">本文永久链接</a> &#8211; https://tonybai.com/2023/06/18/go-package-design-guide</p>
<h2>1. Go包的认知</h2>
<h3>1.1 Go包是基本功能单元</h3>
<p>我们知道Go包是Go编程语言中的一个重要概念，它是一组相关的Go源代码文件。并且，在Go中，每个Go源文件都必须属于一个包。</p>
<p>Go包是一个逻辑上独立的单元，是Go的<strong>基本功能单元</strong>，用来做功能边界的划分。这些基本功能单元的累加就构成了Go应用，因此Go应用的本质就是一组Go包的集合。</p>
<p>Go包这种功能独立的单元为Go开发者提供了“封装”和复用的便利。在Go中，Go包也是代码复用的基本单元，被用来管理和组织代码，<a href="https://time.geekbang.org/column/article/429143">Go项目的结构布局</a>本质上就是安排Go包的位置，使得代码更易于维护和重用。</p>
<h3>1.2 Go包是基本编译单元</h3>
<p>Go包还是编译时的最小单位。也就是说，Go编译器编译代码时会以包为单位进行编译，而不是以文件为单位。这意味着一个包中的所有源文件都将被编译成一个单独的目标文件，而不是多个目标文件。</p>
<p>使用包而不是文件作为编译单元，有助于提高编译效率和管理依赖关系。</p>
<blockquote>
<p>注：编译速度快是包这种设计“先进性”的一个表现，即便每次编译都是从零开始。Go编译速度快的几个原因与以包作为编译单元是密不可分的，具体体现在Go源文件在开头处显式地列出所有依赖包，编译器不必读取整个文件就可确定依赖包列表；Go包之间不能存在循环依赖，由于无环，包可以被单独编译，也可以并行编译；已编译的Go包的目标文件中记录了其所依赖包的导出符号信息。Go编译器在读取该目标文件时不需进一步读取其依赖包的目标文件。</p>
</blockquote>
<h3>1.3 Go包是基本设计单元</h3>
<p>这个世界越来越复杂，软件系统同样变得日益复杂。无论你是什么编程语言的开发者，我们要面对的都是如何驯服这种复杂性。到目前为止，我们驯服这种复杂性的思路还很初级，无非是<strong>对复杂性进行分解、分解、分解，并按照我们更容易理解的方式重新组合</strong>。</p>
<p><img src="https://tonybai.com/wp-content/uploads/go-package-design-guide-2.png" alt="" /></p>
<p>Go包是基本功能单元，对于Go开发者，我们要将复杂性分解为一个个包，然后以一种合理的方式将包组合在一起以实现我们要的系统，因此Go包也是我们面对一个系统时的基本设计单元。我们不仅要设计每个包肩负的职责，还要设计包与包之间的关系。</p>
<p>因此，Go系统设计就是面向包进行设计。</p>
<p>接下来，我们就来看看Go包的设计思路。</p>
<h2>2. Go包设计思路</h2>
<p>即便你没写过Go程序，作为程序员你也应该知道“高内聚，低耦合”这个原则在软件系统设计中的分量。这里我们就以这个原则作为“抓手”来展开看看Go包的设计思路。</p>
<p>高内聚, 低耦合这个顶层原则，适用于所有编程语言的系统设计。但落到Go包设计上面，具体如何体现高内聚与低耦合呢？我们继续往下看。</p>
<h3>2.1 功能选桶：自然内聚</h3>
<p>面对一个复杂系统，我们通常会做一些系统分析，比如用领域驱动设计的方式从需求中挖掘出一堆术语、事件、命令等(即便你不懂领域设计的纯正方法，你的实际操作过程也或多或少与领域设计的内容重叠)。之后，通常会分层、划分服务，每个服务又要划分模块或包。</p>
<p>在服务这一层次上哪些功能放到哪个包里呢？这个过程我称之为“功能选桶”。</p>
<p>这让我想到了和孩子一起学习动物分类时的书中题目：</p>
<pre><code>有这样一组动物：老虎、狮子、海马、大雁、熊猫、黄鹂、鲸鱼，这些动物能分为哪几个类别呢?
</code></pre>
<p>一个稍稍被启蒙了的孩子都会给出这样的分类：</p>
<pre><code>陆地动物：老虎、狮子、熊猫
海洋动物：海马、鲸鱼
天空动物：大雁、黄鹂
</code></pre>
<p>而另外一个稍有一些生物学入门知识的大点的孩子可能也会给出下面的分类：</p>
<pre><code>哺乳动物：老虎、狮子、熊猫、鲸鱼
卵生动物：海马、大雁、黄鹂
</code></pre>
<p>无论哪种，这些分类都是基于动物行为特征的自然结合。第一种似乎更直观自然，第二种则需要有更“专业”的知识（领域知识）。Go包的“功能选桶”其实是一个道理，相关功能自然结合到一个包中，保证这个包的内聚性。</p>
<p>比如下面有几个功能函数：Add、Subtract、Multiply、Divide、Sum、Average、Histogram，我们如何为这几个函数选桶呢？</p>
<p>一种不那么内聚的作法是将上述所有函数都放入math包；而更自然内聚一些的作法则是将Add、Subtract、Multiply、Divide放入math包，而将Sum、Average、Histogram等放入stats包(statistics，统计学)。</p>
<p>在功能选桶过程中，越符合常人思维，就越自然，可读性和可理解性大概率就越好。</p>
<h3>2.2 包间关系：最小耦合</h3>
<p>功能选桶之后，我们再来看包与包之间的关系，通常我们这种关系称为耦合。</p>
<p>用白话来理解耦合就是：当a变化时，b受到影响并随之变化，则说b与a之间存在耦合，即b依赖a。a是引发b变化的一个原因。</p>
<p>程序员都知道：一种理想的耦合情况是<strong>正交</strong>，即<strong>你变你自变，我岿然不动</strong>。但现实中这很难达到，我们应该追求的是尽可能地<strong>降低包与包间的耦合</strong>。</p>
<p>在包依赖层面，Go强制要求不能存在循环依赖，即Go包之间的耦合一定是<strong>有向无环</strong>的，这一定层度上也能帮助Go包之间降低耦合。</p>
<p>要降低包与包之间的耦合，我们首先要了解Go包间的最低耦合关系是什么呢？</p>
<p>在代码层面最低的耦合是接口耦合，在Go中，接口的实现是隐式的，即a包实现b包中定义的接口时是不需要显式导入b包的，我们可以在c包中完成对a包与b包的组装，这样c包依赖a包和b包，但a包与b包之间没有任何耦合。</p>
<p>那么负责组装a包与b包的c包能否在代码层面消除掉对a和b的依赖呢？这个就很难了。不过我们可以使用<strong>依赖注入</strong>技术来消除在代码层面手动基于依赖进行初始化或创建时的复杂性，不过依赖注入技术也是有“门槛”的，它会让你的代码不那么<strong>straightforward</strong>，代码的可读性和可理解性会下降。</p>
<blockquote>
<p>注：个人觉得：依赖注入在Go中并非是一种惯用法。Google开源了像wire这样的依赖注入框架(通过代码生成而实现的编译期依赖注入)，更多是为了解决掉内部大型Go项目初始化时各种创建动作的复杂性。</p>
</blockquote>
<p>我们可以参考软件界用于降低代码耦合的原则，比如由Robert C. Martin（通常被称为“Uncle Bob”）在<a href="https://book.douban.com/subject/1140457/">《敏捷软件开发》</a>一书中提出的旨在帮助开发人员设计更加灵活、可扩展和可维护的软件系统的SOLID敏捷设计原则，这些原则如何应用在Go上呢，或者在Go中如何体现呢，我们接下来就来看一下。</p>
<blockquote>
<p>注：“敏捷设计是一个过程，而不是一次事件。它是一个持续应用原则、模式以及实践来改进软件结构和可读性的过程。它致力于保持系统的设计在任何时间都尽可能的简单、整洁和富有表现力。” &#8211; 《敏捷软件开发》</p>
</blockquote>
<h3>2.3 应用SOLID设计原则</h3>
<h4>2.3.1 单一职责原则（SRP）</h4>
<blockquote>
<p>对于一个类而言，应该仅有一个原因会引起它的变化。在SRP的语境中，我们把职责定义为“变化的原因”(a reason for change)。如果你有超过一个的动机去改变一个类，那么这个类就具有多种职责。- 《敏捷软件开发》</p>
</blockquote>
<p>就像Uncle Bob在书中说的那样：“如果你有超过一个的动机去改变一个类，那么这个类就具有多种职责。有时，我们很难注意到这一点。我们习惯于以组(group)的形式去考虑职责”。</p>
<p>在Go包这一层次上，SRP更多体现在功能内聚上，就像前面举的math包和stats包的例子。</p>
<p>再比如我们有一个图形库，它可以绘制不同类型的图形，如矩形、圆形、三角形等。我们可能会定义一个graph包，里面定义了Graphics类型，它具有Draw方法，用于绘制图形。在不遵循SRP的情况下，graph包Graphics类型可能会包含绘制各种类型图形的代码，这会导致类不仅包含多个职责，而且功能不够内聚。</p>
<p>在遵循SRP的情况下，我们可以在graph包中定义Graphics接口，该接口具有Draw方法，然后在rectangle、circle、triangle包中分别定义Graphics接口的实现：Rectangle类型、Circle类型与Triangle类型：</p>
<pre><code>graph/
    - graph.go // 定义Graphics接口
    - rectangle/
        - rectangle.go // 定义Rectangle类型和其Draw方法
    - circle/
        - circle.go // 定义Circle类型和其Draw方法
    - triangle/
        - triangle.go // 定义Triangle类型和其Draw方法
</code></pre>
<p>这样，每个包都只负责一个图形类型，职责更加单一，也更容易维护和扩展。</p>
<h4>2.3.2 开放-关闭原则(OCP)</h4>
<blockquote>
<p>软件实体（类、模块、方法等）应该对扩展开放，但是对修改关闭。- 《敏捷软件开发》</p>
</blockquote>
<p>还以上面的graph等包为例，OCP原则可以体现在两方面：</p>
<ul>
<li>扩展Graphics接口的实现</li>
</ul>
<p>我们无法修改graph.go中的Graphics接口，但如果你要添加一个square包，定义Square类型并实现Draw方法，那么我们可以在graph包下面添加一个square包，这个包和circle等包位于同等位置，都实现了graph包的Draw方法。</p>
<ul>
<li>基于Graphics接口的组合</li>
</ul>
<p>我们无法修改graph.go中的Graphics接口，但是我们可以基于graph.Graphics接口去组合出其他具有更多职责的接口或非接口类型，就像io包中的Reader、Writer接口被组合到ReaderCloser、ReadWriteCloser中一样。</p>
<p>OCP原则的关键是抽象，在Go中建立包与包之间关系抽象的最佳方法就是建立接口类型。前面说过，通过接口的耦合是最低的包间耦合，因此采用OCP原则对于降低包间耦合具有重要意义。</p>
<p>不过，Bob大叔在书中也说了：“遵循OCP的代价也是昂贵的。创建恰当的抽象是要花费时间和精力的。那些抽象也增加了软件设计的复杂性，开发人员有能力处理的抽象数量是有限的”。OCP原则的应用应该被限定在最可能发生的变化上。</p>
<h4>2.3.3 里氏替换原则(LSP)</h4>
<blockquote>
<p>对于里氏替换原则(LSP)，可以如此解释：子类型(subtype)必须能够替换掉它们的基类(base type)。- 《敏捷软件开发》</p>
</blockquote>
<p>Bob大叔在讲解LSP原则时使用的语言是C++和Java，对于这两种静态类型的OO语言来说，支持抽象和多态的关键机制之一是继承(inheritance)。也只有在继承的概念之上，采用基类和子类之分。</p>
<p>不过<a href="https://tonybai.com/2023/03/12/is-go-object-oriented/">Go并非传统意义上的OO语言</a>，它没有继承，没有类型层次体系。即便没有这些，Go也不乏抽象表达能力，最直接的就是接口这个行为的集合。</p>
<p>这样里氏替换原则(LSP)在Go中就可以如此解释：<strong>接口I的所有实现都是可以相互替代的，因为它们履行了同样的契约</strong>。</p>
<h4>2.3.4 接口隔离原则(ISP)</h4>
<blockquote>
<p>客户端程序不应该被迫依赖于它们不需要的方法。- 《敏捷软件开发》</p>
</blockquote>
<p>这个在体现Go包与包关系层面不是那么明显，方法已经告诉你这种耦合是接口耦合，但究竟用的是什么样的接口呢？“胖接口”，不是！我们需要刚刚好，不多不少的接口。</p>
<p>来看一个例子：我们有如下一些接口定义：</p>
<pre><code>type Printer interface {
    Print()
}

type Scanner interface {
    Scan()
}

type PrintSleeper interface {
    Printer
    Sleep()
}

type PrintScanSleeper interface {
    Printer
    Scanner
    Sleep()
}
</code></pre>
<p>现在我们要实现一个打印机打印的API，我们最初的设计是：</p>
<pre><code>func Print(p PrintScanSleeper, data []byte) error {
}
</code></pre>
<p>在这个设计中，Print函数依赖的是PrintScanSleeper，这意味着传入的合法参数的类型必须要实现Print、Scan和Sleep三个方法，但我们的函数只是为了实现打印，它不需要调用Scanner的Scan方法，根据ISP原则，我们不应该强迫Print函数依赖它们本不需要的方法，于是第二版设计如下：</p>
<pre><code>func Print(p Printer, data []byte) error {
}
</code></pre>
<p>这似乎无懈可击。但常识告诉我们，每次打印结束后，都需要让打印机休眠，那么显然仅依赖Printer接口又缺少了点东西，那么最终版的设计如下：</p>
<pre><code>func Print(p PrintSleeper, data []byte) error {
}
</code></pre>
<p>对ISP原则的白话阐述就是：“不多不少，刚刚好”！</p>
<h4>2.3.5 依赖倒置原则(DIP)</h4>
<blockquote>
<p>高层次的模块不应该依赖低层次的模块。两者都应该依赖抽象。抽象不应该依赖于细节，细节应该依赖于抽象。- 《敏捷软件开发》</p>
</blockquote>
<p>如果你的代码符合上面的几条原则的话，那么到这里，你的代码很大可能也是符合DIP原则的。</p>
<p>一提到“依赖抽象”，大家肯定想到的还是<strong>接口</strong>。在Go中，接口是抽象的主要代名词。</p>
<p>在包与包的关系层面上，DIP原则表现为：高层次包依赖接口，低层次的包实现接口，如下图：</p>
<p><img src="https://tonybai.com/wp-content/uploads/go-package-design-guide-3.png" alt="" /></p>
<p>因此，在同等条件下，采用DIP原则设计良好的Go程序的包导入图应该是宽而平的，而不是高而窄的：</p>
<p><img src="https://tonybai.com/wp-content/uploads/go-package-design-guide-4.png" alt="" /></p>
<h2>3. 单包设计</h2>
<p>说完了包的内聚与包间关系的耦合后，我们最后将精力聚焦在单包的设计上面，看看一个Go包在设计方面有哪些值得借鉴的tips。</p>
<h3>3.1 包名</h3>
<p>我们在引用某个包的导出标识符时使用的是：</p>
<pre><code>pkgname.XXX
</code></pre>
<p>由此可见包名的重要性，它可以理解为<strong>一个包的API的重要组成部分</strong>，因此包设计的第一步就是要<strong>为包起个好名字</strong>。</p>
<p>给Go包起名字首先要注意简单达意，比如标准库的fmt、io、os等，并且包名按惯例应该与其目录名一致；</p>
<p>其次，同一工程内部包名最好是唯一的，避免工程内部出现<strong>名字碰撞</strong>；</p>
<p>最后，如果为了简洁而失去了包名的内聚性的内涵(功能和作用)，比如utils、common这些名字基本无法表达包究竟担负的职责，那么莫不如将包名加长一些，点缀上能达意的单词，比如printutil而不仅仅是util。</p>
<h3>3.2 最小暴露表面积</h3>
<p>在单包设计时，要考虑最小暴露表面积，指定是应该尽可能减少包暴露给外部的接口和实现，只暴露必要的最小接口，以提高代码的安全性和可维护性。同时，从用户角度来看，只暴露必要信息的包看起来更易用。</p>
<p>具体来说，可以分为如下几点：</p>
<ul>
<li>使用接口建立抽象</li>
</ul>
<p>在设计包时，应该使用接口来建立抽象，而不是直接暴露实现。这样可以将实现细节隐藏起来，只暴露接口，从而提高代码的安全性和稳定性。</p>
<ul>
<li>最小化暴露的接口</li>
</ul>
<p>即便是暴露接口，在设计包时，也应该尽量减少暴露给外部的接口数量。只有必要的接口应该暴露出来，从而提高代码的安全性和稳定性。</p>
<ul>
<li>使用非导出方法和变量封装实现细节</li>
</ul>
<p>在设计包时，应该使用非导出方法和变量来封装包内部的实现细节，只暴露公共接口。这样可以将实现细节隐藏起来，避免外部代码直接依赖包内部的实现，从而提高代码的可维护性和灵活性。</p>
<ul>
<li>最小化暴露的实现</li>
</ul>
<p>在设计包时，应该尽量减少暴露给外部的实现数量。只有必要的实现应该暴露出来，而不是将所有实现都暴露出去。这样可以避免外部代码直接依赖包内部的实现，从而提高代码的可维护性和灵活性。</p>
<h3>3.3 避免包级变量带来的包级状态</h3>
<p>Go没有显式的全局变量，但包的导出变量本质上就是全局变量。在《<a href="https://tonybai.com/2023/03/22/global-variable-in-go/">聊聊Go语言的全局变量</a>》一文中我们详细说明了全局变量的不足，因此应避免这类充当全局变量的包级变量的暴露。</p>
<h3>3.4 main包应尽可能简洁</h3>
<p>在Go中，main包是特殊的包，用于定义程序的入口函数main。在Go中，main函数应该尽可能简洁，它应该只负责装配其他包，调用其他函数或模块，不应该包含过多的代码逻辑。这样可以提高代码的可读性和可维护性。如果要对main函数进行单测的话，那么可以将main函数的逻辑放置到另外一个函数中，比如run，然后对run函数进行详尽的测试。</p>
<h3>3.5 接口类型定义应放在与使用者更近的地方</h3>
<p>Go接口是隐式实现的，意味着其实现者不需要显式告知实现了该接口，实现者所在包也无需导入接口定义所在的包。</p>
<p>这样一来，将接口类型定义放在与使用者更近的地方，可以使代码更加清晰和易于理解。使用者可以直接看到接口类型定义，了解接口类型的作用和使用方法。但注意：这并非是绝对的规则。</p>
<p>有些接口的实现者喜欢在自己的包中放置var i some_interface = (*T)(nil) ，以利用编译器的静态检查断言自己定义的类型&#42;T实现了接口some_interface，这样一来实际上是显式宣告了在实现者和接口包之间关系，属于“增加耦合”的步骤。</p>
<p>如果接口类型在同一个包里提供了默认实现，那么这么做无可厚非，比如io包。</p>
<h2>4. 小结</h2>
<p>下面对本文内容做个小结：</p>
<ul>
<li>Go包是Go程序设计的基本单元，分解复杂性的基本单位。</li>
<li>Go包应被设计为高内聚，关注同一职责，并尽量与其他包低耦合。</li>
<li>在设计Go包时，可以按照功能选桶，根据自然内聚思维来划分包，并遵循最小耦合原则减少包间依赖。</li>
<li>可以运用SOLID设计原则优化Go包间的关系:
<ul>
<li>SRP：每个包都有单一的职责，具有高内聚。</li>
<li>OCP：包对扩展开放，对修改关闭。</li>
<li>LSP：接口的实现是互相替换的。</li>
<li>ISP：接口将只暴露必要方法。</li>
<li>DIP：高层包和低层包都依赖抽象，细节应依赖抽象。</li>
</ul>
</li>
<li>在单包设计时应考虑最小暴露表面积，只暴露必要的接口和实现。</li>
<li>避免用包级变量带来的全局状态。main包应简洁。</li>
<li>接口类型定义最好放在使用者更近的地方。</li>
</ul>
<p>希望能为大家提供参考！如果有不正确或遗漏的地方，欢迎指出，共同进步。</p>
<hr />
<p><a href="https://wx.zsxq.com/dweb2/index/group/51284458844544">“Gopher部落”知识星球</a>旨在打造一个精品Go学习和进阶社群！高品质首发Go技术文章，“三天”首发阅读权，每年两期Go语言发展现状分析，每天提前1小时阅读到新鲜的Gopher日报，网课、技术专栏、图书内容前瞻，六小时内必答保证等满足你关于Go语言生态的所有需求！2023年，Gopher部落将进一步聚焦于如何编写雅、地道、可读、可测试的Go代码，关注代码质量并深入理解Go核心技术，并继续加强与星友的互动。欢迎大家加入！</p>
<p><img src="http://image.tonybai.com/img/tonybai/gopher-tribe-zsxq-small-card.png" alt="img{512x368}" /><br />
<img src="http://image.tonybai.com/img/tonybai/go-programming-from-beginner-to-master-qr.png" alt="img{512x368}" /></p>
<p><img src="http://image.tonybai.com/img/tonybai/go-first-course-banner.png" alt="img{512x368}" /><br />
<img src="http://image.tonybai.com/img/tonybai/imooc-go-column-pgo-with-qr.jpg" alt="img{512x368}" /></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>微博2：https://weibo.com/u/6484441286</li>
<li>博客：tonybai.com</li>
<li>github: https://github.com/bigwhite</li>
</ul>
<p><img src="http://image.tonybai.com/img/tonybai/iamtonybai-wechat-qr.png" alt="" /></p>
<p>商务合作方式：撰稿、出书、培训、在线课程、合伙创业、咨询、广告合作。</p>
<p style='text-align:left'>&copy; 2023, <a href='https://tonybai.com'>bigwhite</a>. 版权所有. </p>
]]></content:encoded>
			<wfw:commentRss>https://tonybai.com/2023/06/18/go-package-design-guide/feed/</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>Go 1.20中值得关注的几个变化</title>
		<link>https://tonybai.com/2023/02/08/some-changes-in-go-1-20/</link>
		<comments>https://tonybai.com/2023/02/08/some-changes-in-go-1-20/#comments</comments>
		<pubDate>Wed, 08 Feb 2023 15:21:55 +0000</pubDate>
		<dc:creator>bigwhite</dc:creator>
				<category><![CDATA[技术志]]></category>
		<category><![CDATA[arena]]></category>
		<category><![CDATA[Array]]></category>
		<category><![CDATA[benchstat]]></category>
		<category><![CDATA[Build]]></category>
		<category><![CDATA[Cgo]]></category>
		<category><![CDATA[comparable]]></category>
		<category><![CDATA[Compiler]]></category>
		<category><![CDATA[Context]]></category>
		<category><![CDATA[cover]]></category>
		<category><![CDATA[crypto]]></category>
		<category><![CDATA[ecdh]]></category>
		<category><![CDATA[error]]></category>
		<category><![CDATA[generics]]></category>
		<category><![CDATA[Go]]></category>
		<category><![CDATA[go1.17]]></category>
		<category><![CDATA[go1.18]]></category>
		<category><![CDATA[go1.19]]></category>
		<category><![CDATA[go1.20]]></category>
		<category><![CDATA[GOCOVERDIR]]></category>
		<category><![CDATA[Golang]]></category>
		<category><![CDATA[govet]]></category>
		<category><![CDATA[http]]></category>
		<category><![CDATA[Linux]]></category>
		<category><![CDATA[loccount]]></category>
		<category><![CDATA[Makefile]]></category>
		<category><![CDATA[Opensource]]></category>
		<category><![CDATA[Package]]></category>
		<category><![CDATA[PGO]]></category>
		<category><![CDATA[Pointer]]></category>
		<category><![CDATA[port]]></category>
		<category><![CDATA[riscv64]]></category>
		<category><![CDATA[runtime]]></category>
		<category><![CDATA[Rust]]></category>
		<category><![CDATA[Slice]]></category>
		<category><![CDATA[stdlib]]></category>
		<category><![CDATA[unit-test]]></category>
		<category><![CDATA[unsafe]]></category>
		<category><![CDATA[vet]]></category>
		<category><![CDATA[wasi]]></category>
		<category><![CDATA[wasm]]></category>
		<category><![CDATA[WebAssembly]]></category>
		<category><![CDATA[wrap]]></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=3794</guid>
		<description><![CDATA[本文永久链接 &#8211; https://tonybai.com/2023/02/08/some-changes-in-go-1-20 美国时间2023年2月1日，唯一尚未退休的Go语言之父Robert Griesemer代表Go核心开发团队在Go官博撰文正式发布了Go 1.20版本。就像Russ Cox在2022 GopherCon大会所说的那样：Go2永不会到来，Go 1.x.y将无限延续！ 注：似乎新兴编程语言都喜欢停留在1.x.y上无限延续，譬如已经演化到1.67版本的Rust^_^。 在《Go，13周年》之后，Go 1.20新特性在开发主干冻结(2022.11)之前，我曾写过一篇《Go 1.20新特性前瞻》，对照着Go 1.20 milestone中内容，把我认为的主要特性和大家简单过了一遍，不过那时Go 1.20毕竟没有正式发布，前瞻肯定不够全面，某些具体的点与正式版本可能也有差异！现在Go 1.20版本正式发布了，其Release Notes也补充完整了，在这一篇中，我再来系统说说Go 1.20版本中值得关注的那些变化。对于在前瞻一文中详细介绍过的特性，这里不会再重复讲解了，大家参考前瞻一文中的内容即可。而对于其他一些特性，或是前瞻一文中着墨不多的特性，这里会挑重点展开说说。 按照惯例，我们依旧首先来看看Go语法层面都有哪些变化，这可能也是多数Gopher们最为关注的变化点。 一. 语法变化 Go秉持“大道至简”的理念，对Go语法特性向来是“不与时俱进”的。自从Go 1.18大刀阔斧的加入了泛型特性后，Go语法特性就又恢复到了之前的“新三年旧三年，缝缝补补又三年”的节奏。Go 1.20亦是如此啊！Release Notes说Go 1.20版本在语言方面包含了四点变化，但看了变化的内容后，我觉得真正的变化只有一个，其他的都是修修补补。 1. 切片到数组的转换 唯一算是真语法变化的特性是支持切片类型到数组类型(或数组类型的指针)的类型转换，这个特性在前瞻一文中系统讲过，这里就不赘述了，放个例子大家直观认知一下就可以了： // https://github.com/bigwhite/experiments/blob/master/go1.20-examples/lang/slice2arr.go func slice2arrOK() { var sl = []int{1, 2, 3, 4, 5, 6, 7} var arr = [7]int(sl) var parr = (*[7]int)(sl) fmt.Println(sl) // [...]]]></description>
			<content:encoded><![CDATA[<p><img src="https://tonybai.com/wp-content/uploads/some-changes-in-go-1-20-1.png" alt="" /></p>
<p><a href="https://tonybai.com/2023/02/08/some-changes-in-go-1-20">本文永久链接</a> &#8211; https://tonybai.com/2023/02/08/some-changes-in-go-1-20</p>
<p>美国时间2023年2月1日，唯一尚未退休的Go语言之父<a href="https://github.com/griesemer">Robert Griesemer</a>代表Go核心开发团队在<a href="https://go.dev/blog/go1.20">Go官博撰文正式发布了Go 1.20版本</a>。就像<a href="https://www.youtube.com/watch?v=v24wrd3RwGo">Russ Cox在2022 GopherCon大会</a>所说的那样：<strong><a href="https://tonybai.com/2022/12/29/the-2022-review-of-go-programming-language">Go2永不会到来，Go 1.x.y将无限延续</a></strong>！</p>
<blockquote>
<p>注：似乎新兴编程语言都喜欢停留在1.x.y上无限延续，譬如已经<a href="https://www.rust-lang.org">演化到1.67版本的Rust</a>^_^。</p>
</blockquote>
<p>在<a href="https://tonybai.com/2022/11/11/go-opensource-13-years/">《Go，13周年》</a>之后，Go 1.20新特性在开发主干冻结(2022.11)之前，我曾写过一篇《<a href="https://tonybai.com/2022/11/17/go-1-20-foresight">Go 1.20新特性前瞻</a>》，对照着<a href="https://github.com/golang/go/milestone/250">Go 1.20 milestone</a>中内容，把我认为的主要特性和大家简单过了一遍，不过那时Go 1.20毕竟没有正式发布，前瞻肯定不够全面，某些具体的点与正式版本可能也有差异！现在Go 1.20版本正式发布了，其<a href="https://go.dev/blog/go1.20">Release Notes</a>也补充完整了，在这一篇中，我再来系统说说Go 1.20版本中值得关注的那些变化。对于在<a href="https://tonybai.com/2022/11/17/go-1-20-foresight">前瞻一文</a>中详细介绍过的特性，这里不会再重复讲解了，大家参考前瞻一文中的内容即可。而对于其他一些特性，或是前瞻一文中着墨不多的特性，这里会挑重点展开说说。</p>
<p>按照惯例，我们依旧首先来看看Go语法层面都有哪些变化，这可能也是多数Gopher们最为关注的变化点。</p>
<h2>一. 语法变化</h2>
<p>Go秉持“大道至简”的理念，对Go语法特性向来是“不与时俱进”的。自从<a href="https://tonybai.com/2022/04/20/some-changes-in-go-1-18">Go 1.18大刀阔斧的加入了泛型特性</a>后，Go语法特性就又恢复到了之前的“新三年旧三年，缝缝补补又三年”的节奏。Go 1.20亦是如此啊！Release Notes说Go 1.20版本在语言方面包含了四点变化，但看了变化的内容后，我觉得真正的变化只有一个，其他的都是修修补补。</p>
<h3>1. 切片到数组的转换</h3>
<p>唯一算是真语法变化的特性是支持<strong>切片类型到数组类型(或数组类型的指针)的类型转换</strong>，这个特性在<a href="https://tonybai.com/2022/11/17/go-1-20-foresight">前瞻一文</a>中系统讲过，这里就不赘述了，放个例子大家直观认知一下就可以了：</p>
<pre><code>// https://github.com/bigwhite/experiments/blob/master/go1.20-examples/lang/slice2arr.go

func slice2arrOK() {
    var sl = []int{1, 2, 3, 4, 5, 6, 7}
    var arr = [7]int(sl)
    var parr = (*[7]int)(sl)
    fmt.Println(sl)  // [1 2 3 4 5 6 7]
    fmt.Println(arr) // [1 2 3 4 5 6 7]
    sl[0] = 11
    fmt.Println(arr)  // [1 2 3 4 5 6 7]
    fmt.Println(parr) // &amp;[11 2 3 4 5 6 7]
}

func slice2arrPanic() {
    var sl = []int{1, 2, 3, 4, 5, 6, 7}
    fmt.Println(sl)
    var arr = [8]int(sl) // panic: runtime error: cannot convert slice with length 7 to array or pointer to array with leng  th 8
    fmt.Println(arr)     // &amp;[11 2 3 4 5 6 7]

}

func main() {
    slice2arrOK()
    slice2arrPanic()
}
</code></pre>
<p>有两点注意一下就好：</p>
<ul>
<li>切片转换为数组类型的指针，那么该指针将指向切片的底层数组，就如同上面例子中slice2arrOK的parr变量那样；</li>
<li>转换的数组类型的长度不能大于原切片的长度(注意是长度而不是切片的容量哦)，否则在运行时会抛出panic。</li>
</ul>
<h3>2. 其他的修修补补</h3>
<ul>
<li>comparable“放宽”了对泛型实参的限制</li>
</ul>
<p>下面代码在Go 1.20版本之前，比如Go 1.19版本中会无法通过编译：</p>
<pre><code>// https://github.com/bigwhite/experiments/blob/master/go1.20-examples/lang/comparable.go

func doSth[T comparable](t T) {
}

func main() {
    n := 2
    var i interface{} = n // 编译错误：interface{} does not implement comparable
    doSth(i)
}
</code></pre>
<p>之前，comparable约束下的泛型形参需要支持严格可比较(strictly comparable)的类型作为泛型实参，哪些是严格可比较的类型呢？Go 1.20的语法规范做出了进一步澄清：如果一个类型是可比较的，且不是接口类型或由接口类型组成的类型，那么这个类型就是<strong>严格可比较的类型</strong>，包括：</p>
<pre><code>- 布尔型、数值类型、字符串类型、指针类型和channel是严格可比较的。
- 如果结构体类型的所有字段的类型都是严格可比较的，那么该结构体类型就是严格可比较的。
- 如果数组元素的类型是严格可比较的，那么该数组类型就是严格可比较的。
- 如果类型形参的类型集合中的所有类型都是严格可比较的，那么该类型形参就是严格可比较的。
</code></pre>
<p>我们看到：例外的就是接口类型了。接口类型不是“严格可比较的(strictly comparable)”，但未作为类型形参的接口类型是可比较的(comparable)，如果两个接口类型的动态类型相同且值相等，那么这两个接口类型就相等，或两个接口类型的值均为nil，它们也相等，否则不等。</p>
<p>Go 1.19版本及之前，作为非严格比较类型的接口类型是不能作为comparable约束的类型形参的类型实参的，就像上面comparable.go中示例代码那样，但Go 1.20版本开始，这一要求被防控，接口类型被允许作为类型实参赋值给comparable约束的类型形参了！不过这么做之前，你也要明确一点，如果像下面这样两个接口类型底层类型相同且是不可比较的类型（比如切片)，那么代码将在运行时抛panic：</p>
<pre><code>// https://github.com/bigwhite/experiments/blob/master/go1.20-examples/lang/comparable1.go

func doSth[T comparable](t1, t2 T) {
    if t1 != t2 {
        println("unequal")
        return
    }
    println("equal")
}

func main() {
    n1 := []byte{2}
    n2 := []byte{3}
    var i interface{} = n1
    var j interface{} = n2
    doSth(i, j) // panic: runtime error: comparing uncomparable type []uint8
}
</code></pre>
<p>Go 1.20语言规范借此机会还进一步澄清了结构体和数组两种类型比较实现的规范：对于结构体类型，Go会按照结构体字段的声明顺序，逐一字段进行比较，直到遇到第一个不相等的字段为止。如果没有不相等字段，则两个结构体字段相等；对于数组类型，Go会按数组元素的顺序，逐一元素进行比较，直到遇到第一个不相等的元素为止。如果没有不相等的元素，则两个数组相等。</p>
<ul>
<li>unsafe包继续添加“语法糖”</li>
</ul>
<p>继<a href="https://tonybai.com/2021/08/17/some-changes-in-go-1-17">Go 1.17版本</a>在unsafe包增加Slice函数后，Go 1.20版本又增加三个语法糖函数：SliceData、String和StringData：</p>
<pre><code>// $GOROOT/src/unsafe/unsafe.go
func SliceData(slice []ArbitraryType) *ArbitraryType
func String(ptr *byte, len IntegerType) string
func StringData(str string) *byte
</code></pre>
<p>值得注意的是由于string的不可更改性，String函数的参数ptr指向的内容以及StringData返回的指针指向的内容在String调用和StringData调用后不允许修改，但实际情况是怎么样的呢？</p>
<pre><code>// https://github.com/bigwhite/experiments/blob/master/go1.20-examples/lang/unsafe.go

func main() {
    var arr = [6]byte{'h', 'e', 'l', 'l', 'o', '!'}
    s := unsafe.String(&amp;arr[0], 6)
    fmt.Println(s) // hello!
    arr[0] = 'j'
    fmt.Println(s) // jello!

    b := unsafe.StringData(s)
    *b = 'k'
    fmt.Println(s) // kello!

    s1 := "golang"
    fmt.Println(s1) // golang
    b = unsafe.StringData(s1)
    *b = 'h' // fatal error: fault, unexpected fault address 0x10a67e5
    fmt.Println(s1)
}
</code></pre>
<p>我们看到：unsafe.String函数调用后，如果我们修改了传入的指针指向的内容，那么该改动会影响到后续返回的string内容！而StringData返回<br />
的指针所指向的内容一旦被修改，其结果要根据字符串的来源而定了。对于由可修改的底层数组“创建”的字符串(如s)，通过StringData返回的指<br />
针可以“修改”字符串的内容；而对于由字符串字面值初始化的字符串变量(如s1)，其内容是不可修改的(编译器将字符串底层存储分配在了只读数据区)，尝试通过指针修改指向内容，会导致运行时的段错误。</p>
<h2>二. 工具链</h2>
<h3>1. Go安装包“瘦身”</h3>
<p>这些年，Go发布版的安装包“体格”是越来越壮了，动辄100多MB的压缩包，以go.dev/dl页面上的go1.xy.linux-amd64.tar.gz为例，我们看看从Go 1.15版本到Go 1.19版本的“体格”变化趋势：</p>
<pre><code>Go 1.15 - 116MB
Go 1.16 - 123MB
Go 1.17 - 129MB
Go 1.18 - 135MB
Go 1.19 - 142MB
</code></pre>
<p>如果按此趋势，Go 1.20势必要上到150MB以上。但Go团队找到了“瘦身”方法，那就是：<a href="https://github.com/golang/go/issues/47257">从Go 1.20开始发行版的安装包不再为GOROOT中的软件包提供预编译的.a文件了</a>，这样我们得到的瘦身后的Go 1.20版本的size为<strong>95MB</strong>！相较于Go 1.19，Go 1.20的安装包“瘦”了三分之一。安装包解压后这种体现更为明显：</p>
<pre><code>➜  /Users/tonybai/.bin/go1.19 git:(master) ✗ $du -sh
495M    .
➜  /Users/tonybai/.bin/go1.20 git:(master) ✗ $du -sh
265M    .
</code></pre>
<p>我们看到：Go 1.20占用的磁盘空间仅为Go 1.19版本的一半多一点而已。 并且，Go 1.20版本中，GOROOT下的源码将像其他用户包那样在构建后被缓存到本机cache中。此外，go install也不会为GOROOT下的软件包安装.a文件。</p>
<h3>2. 编译器</h3>
<h4>1) PGO(profile-guided optimization)</h4>
<p>Go 1.20编译器的一个最大的变更点是引入了PGO优化技术预览版，这个在前瞻一文中也有<a href="https://tonybai.com/2022/11/17/go-1-20-foresight">对PGO技术的简单介绍</a>。说白了点，PGO技术就是在原有compiler优化技术的基础上，针对程序在生产环境运行中的热点关键路径再进行一轮优化，并且针对热点代码执行路径，编译器会放开一些限制，比如<a href="https://tonybai.com/2022/10/17/understand-go-inlining-optimisations-by-example">Go决定是否对函数进行内联优化的复杂度上限默认值是80</a>，但对于PGO指示的关键热点路径，即便函数复杂性超过80很多，也可能会被inline优化掉。</p>
<p>之前持续性能剖析工具开发商Polar Signals曾发布一篇文章<a href="https://www.polarsignals.com/blog/posts/2022/09/exploring-go-profile-guided-optimizations/">《Exploring Go&#8217;s Profile-Guided Optimizations》</a>，专门探讨了PGO技术可能带来的优化效果，文章中借助了Go项目中自带的测试示例，这里也基于这个示例带大家重现一下。</p>
<p>我们使用的例子在Go 1.20源码/安装包的\$GOROOT/src/cmd/compile/internal/test/testdata/pgo/inline路径下：</p>
<pre><code>$ls -l
total 3156
-rw-r--r-- 1 tonybai tonybai    1698 Jan 31 05:46 inline_hot.go
-rw-r--r-- 1 tonybai tonybai     843 Jan 31 05:46 inline_hot_test.go
</code></pre>
<p>我们首先执行一下inline目录下的测试，并生成用于测试的可执行文件以及对应的cpu profile文件供后续PGO优化使用：</p>
<pre><code>$go test -o inline_hot.test -bench=. -cpuprofile inline_hot.pprof
goos: linux
goarch: amd64
pkg: cmd/compile/internal/test/testdata/pgo/inline
cpu: Intel(R) Core(TM) i7-9700 CPU @ 3.00GHz
BenchmarkA-8        1348        870005 ns/op
PASS
ok      cmd/compile/internal/test/testdata/pgo/inline   1.413s
</code></pre>
<p>接下来，我们对比一下不使用PGO和使用PGO优化，Go编译器在内联优化上的区别：</p>
<pre><code>$diff &lt;(go test -run=none -tags='' -timeout=9m0s -gcflags="-m -m" 2&gt;&amp;1 | grep "can inline") &lt;(go test -run=none -tags='' -timeout=9m0s -gcflags="-m -m -pgoprofile inline_hot.pprof" 2&gt;&amp;1 | grep "can inline")
4a5,6
&gt; ./inline_hot.go:53:6: can inline (*BS).NS with cost 106 as: method(*BS) func(uint) (uint, bool) { x := int(i &gt;&gt; lWSize); if x &gt;= len(b.s) { return 0, false }; w := b.s[x]; w = w &gt;&gt; (i &amp; (wSize - 1)); if w != 0 { return i + T(w), true }; x = x + 1; for loop; return 0, false }
&gt; ./inline_hot.go:74:6: can inline A with cost 312 as: func() { s := N(100000); for loop; for loop }
</code></pre>
<blockquote>
<p>上面diff命令中为Go test命令传入-run=none -tags=”" -gcflags=”-m -m”是为了仅编译源文件，而不执行任何测试。</p>
</blockquote>
<p>我们看到，相较于未使用PGO优化的结果，PGO优化后的结果多了两个inline函数，这两个可以被inline的函数，一个的复杂度开销为106，一个是312，都超出了默认的80，但仍然可以被inline。</p>
<p>我们来看看PGO的实际优化效果，我们分为在无PGO优化与有PGO优化下执行100次benchmark，再用benchstat工具对比两次的结果：</p>
<pre><code>$go test -o inline_hot.test -bench=. -cpuprofile inline_hot.pprof -count=100 &gt; without_pgo.txt
$go test -o inline_hot.test -bench=. -gcflags="-pgoprofile inline_hot.pprof" -count=100 &gt; with_pgo.txt

$benchstat without_pgo.txt with_pgo.txt
goos: linux
goarch: amd64
pkg: cmd/compile/internal/test/testdata/pgo/inline
cpu: Intel(R) Core(TM) i7-9700 CPU @ 3.00GHz
    │ without_pgo.txt │            with_pgo.txt             │
    │     sec/op      │   sec/op     vs base                │
A-8       874.7µ ± 0%   872.6µ ± 0%  -0.24% (p=0.024 n=100)
</code></pre>
<blockquote>
<p>注：benchstat的安装方法：\$go install golang.org/x/perf/cmd/benchstat@latest</p>
</blockquote>
<p>我们看到，在我的机器上(ubuntu 20.04 linux kerenel 5.4.0-132)，PGO针对这个测试的优化效果并不明显(仅仅有0.24%的提升)，Polar Signals原文中的提升幅度也不大，仅为1.05%。</p>
<p>Go官方Release Notes中提到benchmark提升效果为3%~4%，同时官方也提到了，这个仅仅是PGO初始技术预览版，后续会加强对PGO优化的投入，直至对多数程序产生较为明显的优化效果。个人觉得目前PGO尚处于早期，不建议在生产中使用。</p>
<p>Go官方也增加针对<a href="https://go.dev/doc/pgo">PGO的ref页面</a>，大家重点看看其中的FAQ，你会有更多收获！</p>
<h4>2) 构建速度</h4>
<p>Go 1.18泛型落地后，Go编译器的编译速度出现了回退(幅度15%)，Go 1.19编译速度也没有提升。虽然编译速度回退后依然可以“秒杀”竞争对手，但对于以编译速度快著称的Go来说，这个问题必须修复。Go 1.20做到了这一点，让Go编译器的编译速度重新回归到了Go 1.17的水准！相对Go 1.19提升10%左右。</p>
<p>我使用github.com/reviewdog/reviewdog这个库实测了一下，分别使用go 1.17.1、go 1.18.6、go 1.19.1和Go 1.20对这个module进行go build -a构建(之前将依赖包都下载本地，排除掉go get环节的影响)，结果如下：</p>
<pre><code>go 1.20：
$time go build -a github.com/reviewdog/reviewdog/cmd/reviewdog
go build -a github.com/reviewdog/reviewdog/cmd/reviewdog  48.01s user 7.96s system 536% cpu 10.433 total

go 1.19.1：
$time go build -a github.com/reviewdog/reviewdog/cmd/reviewdog
go build -a github.com/reviewdog/reviewdog/cmd/reviewdog  54.40s user 10.20s system 506% cpu 12.757 total

go 1.18.6：
$time go build -a github.com/reviewdog/reviewdog/cmd/reviewdog
go build -a github.com/reviewdog/reviewdog/cmd/reviewdog  53.78s user 9.85s system 545% cpu 11.654 total

go 1.17.1：
$time go build -a github.com/reviewdog/reviewdog/cmd/reviewdog
go build -a github.com/reviewdog/reviewdog/cmd/reviewdog  50.30s user 9.76s system 580% cpu 10.338 total
</code></pre>
<p>虽然不能十分精确，但总体上反映出各个版本的编译速度水准以及Go 1.20相对于Go 1.18和Go 1.19版本的提升。我们看到Go 1.20与Go 1.17版本在一个水平线上，甚至要超过Go 1.17(但可能仅限于我这个个例)。</p>
<h4>3) 允许在泛型函数/方法中进行类型声明</h4>
<p>Go 1.20版本之前下面代码是无法通过Go编译器的编译的：</p>
<pre><code>// https://github.com/bigwhite/experiments/blob/master/go1.20-examples/tools/compiler/local_type_decl.go
package main

func F[T1 any]() {
    type x struct{} // 编译错误：type declarations inside generic functions are not currently supported
    type y = x      // 编译错误：type declarations inside generic functions are not currently supported
}

func main() {
    F[int]()
}
</code></pre>
<p><a href="https://github.com/golang/go/issues/47631">Go 1.20改进了语言前端的实现</a>，通过unified IR实现了对在泛型函数/方法中进行类型声明(包括定义type alias)的支持。</p>
<p>同时，Go 1.20在<a href="https://go.dev/ref/spec#Type_parameter_declarations">spec</a>中还明确了<a href="https://github.com/golang/go/issues/40882">哪些使用了递归方式声明的类型形参列表是不合法的</a>：</p>
<pre><code>type T1[P T1[P]] …                    // 不合法: 形参列表中作为约束的T1引用了自己
type T2[P interface{ T2[int] }] …     // 不合法: 形参列表中作为约束的T2引用了自己
type T3[P interface{ m(T3[int])}] …   // 不合法: 形参列表中作为约束的T3引用了自己

type T4[P T5[P]] …                    // 不合法: 形参列表中，T4引用了T5 并且
type T5[P T4[P]] …                    //          T5引用了T4

type T6[P int] struct{ f *T6[P] }     // 正确: 虽然引用了T6，但这个引用发生在结构体定义中而不是形参列表中
</code></pre>
<h4>4) 构建自举源码的Go编译器的版本选择</h4>
<p>Go从Go 1.5版本开始实现自举，即使用Go实现Go，那么自举后的Go项目是谁来编译的呢？最初对应编译Go 1.5版本的Go编译器版本为Go 1.4。</p>
<p>以前从源码构建Go发行版，当未设置GOROOT_BOOTSTRAP时，编译脚本会默认使用Go 1.4，但如果有更高版本的Go编译器存在，会使用更高版本的编译器。</p>
<p>Go 1.18和Go 1.19会首先寻找是否有go 1.17版本，如果没有再使用go 1.4。</p>
<p>Go 1.20会寻找当前Go 1.17的最后一个版本Go 1.17.13，如果没有，则使用Go 1.4。</p>
<p>将来，Go核心团队计划一年升级一次构建自举源码的Go编译器的版本，例如：Go 1.22版本将使用Go 1.20版本的编译器。</p>
<h4>5) cgo</h4>
<p>Go命令现在在没有C工具链的系统上会默认禁用了cgo。更具体来说，当CGO_ENABLED环境变量未设置，CC环境变量未设置以及PATH环境变量中没有找到默认的C编译器（通常是clang或gcc）时，CGO_ENABLED会被默认设置为0。</p>
<h3>3. 其他工具</h3>
<h4>1) 支持采集应用执行的代码盖率</h4>
<p>在前瞻一文中，我提到过Go 1.20将对代码覆盖率的支持扩展到了应用整体层面，而不再仅仅是unit test。这里使用一个例子来看一下，究竟如何采集应用代码的执行覆盖率。我们以gitlab.com/esr/loccount这个代码统计工具为例，先修改一下Makefile，在go build后面加上-cover选项，然后编译loccount，并对其自身进行代码统计：</p>
<pre><code>// /home/tonybai/go/src/gitlab.com/loccount
$make
$mkdir mycovdata
$GOCOVERDIR=./mycovdata loccount .
all          SLOC=4279    (100.00%) LLOC=1213    in 110 files
Go           SLOC=1724    (40.29%)  LLOC=835     in 3 files
asciidoc     SLOC=752     (17.57%)  LLOC=0       in 5 files
C            SLOC=278     (6.50%)   LLOC=8       in 2 files
Python       SLOC=156     (3.65%)   LLOC=0       in 2 files
... ...
</code></pre>
<p>上面执行loccount之前，我们建立了一个mycovdata目录，并设置GOCOVERDIR的值为mycovdata目录的路径。在这样的上下文下，执行loccount后，mycovdata目录下会生成一些覆盖率统计数据文件：</p>
<pre><code>$ls mycovdata
covcounters.4ec45ce64f965e77563ecf011e110d4f.926594.1675678144659536943  covmeta.4ec45ce64f965e77563ecf011e110d4f
</code></pre>
<p>怎么查看loccount的执行覆盖率呢？我们使用go tool covdata来查看：</p>
<pre><code>$go tool covdata percent -i=mycovdata
    loccount    coverage: 69.6% of statements
</code></pre>
<p>当然, covdata子命令还支持其他一些功能，大家可以自行查看manual挖掘。</p>
<h4>2) vet</h4>
<p>Go 1.20版本中，go工具链的vet子命令增加了两个十分实用的检测：</p>
<ul>
<li>对loopclosure这一检测策略进行了增强</li>
</ul>
<p>具体可参见https://github.com/golang/tools/tree/master/go/analysis/passes/loopclosure代码</p>
<ul>
<li>增加对2006-02-01的时间格式的检查</li>
</ul>
<p>注意我们使用time.Format或Parse时，最常使用的是2006-01-02这样的格式，即ISO 8601标准的时间格式，但一些代码中总是出现2006-02-01，十分容易导致错误。这个版本中，go vet将会对此种情况进行检查。</p>
<h2>三. 运行时与标准库</h2>
<h3>1. 运行时(runtime)</h3>
<p>Go 1.20运行时的调整并不大，仅对GC的内部数据结构进行了微调，这个调整可以获得最多2%的内存开销下降以及cpu性能提升。</p>
<h3>2. 标准库</h3>
<p>标准库肯定是变化最多的那部分。前瞻一文中对下面变化也做了详细介绍，这里不赘述了，大家可以翻看那篇文章细读：</p>
<ul>
<li>支持wrap multiple errors</li>
<li>time包新增DateTime、DateOnly和TimeOnly三个layout格式常量</li>
<li>新增arena包<br />
&#8230; &#8230;</li>
</ul>
<p>标准库变化很多，这里不能一一罗列，再补充一些我认为重要的，其他的变化大家可以到<a href="https://go.dev/doc/go1.20">Go 1.20 Release Notes</a>去看：</p>
<h4>1) arena包</h4>
<p>前瞻一文已经对arena包做了简要描述，对于arena包的使用以及最佳适用场合的探索还在进行中。著名持续性能剖析工具<a href="https://pyroscope.io/">pyroscope</a>的官方博客文章<a href="https://pyroscope.io/blog/go-1-20-memory-arenas/">《Go 1.20 arenas实践：arena vs. 传统内存管理》</a>对于arena实验特性的使用给出了几点好的建议，比如：</p>
<ul>
<li>只在关键的代码路径中使用arena，不要到处使用它们</li>
<li>在使用arena之前和之后对你的代码进行profiling，以确保你在能提供最大好处的地方添加arena。</li>
<li>密切关注arena上创建的对象的生命周期。确保你不会把它们泄露给你程序中的其他组件，因为那里的对象可能会超过arena的寿命。</li>
<li>使用defer a.Free()来确保你不会忘记释放内存。</li>
<li>如果你想在arena被释放后使用对象，使用arena.Clone()将其克隆回heap中。</li>
</ul>
<p>pyroscope的开发人员认为arena是一个强大的工具，也支持标准库中保留arena这个特性，但也建议将arena和reflect、unsafe、cgo等一样纳入“不推荐”使用的包行列。这点我也是赞同的。我也在考虑如何基于arena改进我们产品的协议解析器的性能，有成果后，我也会将实践过程分享出来的。</p>
<h4>2) 新增crypto/ecdh包</h4>
<p>密码学包(crypto)的主要maintainer <a href="https://filippo.io/">Filippo Valsorda</a>从google离职后，<a href="https://words.filippo.io/full-time-maintainer/">成为了一名专职开源项目维护者</a>。这似乎让其更有精力和动力对crypto包进行更好的规划、设计和实现了。<a href="https://github.com/golang/go/issues/52221">crypto/ecdh包就是在他的提议下加入到Go标准库中的</a>。</p>
<p>相对于标准库之前存在的crypto/elliptic等包，crypto/ecdh包的API更为高级，Go官方推荐使用ecdh的高级API，这样大家以后可以不必再与低级的密码学函数斗争了。</p>
<h4>3) HTTP ResponseController</h4>
<p>以前HTTP handler的超时都是http服务器全局指定一个的：包括ReadTimeout和WriteTimeout。但有些时候，如果能在某个请求范围内支持这些超时（以及可能的其他选项）将非常有用。Damien Neil就创建了这个<a href="https://github.com/golang/go/issues/54136">增加ResponseController的提案</a>，下面是一个在HandlerFunc中使用ResponseController的例子：</p>
<pre><code>http.HandleFunc("/foo", func(w http.ResponseWriter, r *http.Request) {
  ctl := http.NewResponseController(w, r)
  ctl.SetWriteDeadline(time.Now().Add(1 * time.Minute)) // 仅为这个请求设置deadline
  fmt.Fprintln(w, "Hello, world.") // 这个写入的timeout为1-minute
})
</code></pre>
<h4>4) context包增加WithCancelCause函数</h4>
<p>context包新增了一个WithCancelCause函数，与WithCancel不同，通过WithCancelCause返回的Context，我们可以得到cancel的原因，比如下面示例：</p>
<pre><code>// https://github.com/bigwhite/experiments/blob/master/go1.20-examples/library/context.go

func main() {
    myError := fmt.Errorf("%s", "myError")
    ctx, cancel := context.WithCancelCause(context.Background())
    cancel(myError)
    fmt.Println(ctx.Err())          // context.Canceled
    fmt.Println(context.Cause(ctx)) // myError
}
</code></pre>
<p>我们看到通过context.Cause可以得到Context在cancel时传入的错误原因。</p>
<h2>四. 移植性</h2>
<p>Go对新cpu体系结构和OS的支持向来是走在前面的。Go 1.20还新增了对freebsd在risc-v上的实验性支持，其环境变量为GOOS=freebsd, GOARCH=riscv64。但Go 1.20也将成为对下面平台提供支持的最后一个Go版本：</p>
<ul>
<li>Windows 7, 8, Server 2008和Server 2012</li>
<li>MacOS 10.13 High Sierra和10.14 (我的安装了10.14的mac os又要在go 1.21不被支持了^_^) </li>
</ul>
<p>近期Go团队又有了新提案：<a href="https://github.com/golang/go/issues/58141">支持WASI(GOOS=wasi GOARCH=wasm)</a>，WASI是啥，它是WebAssembly一套与引擎无关(engine-indepent)的、面向非Web系统的WASM API标准，是WebAssembly脱离浏览器的必经之路！一旦生成满足WASI的WASM程序，该程序就可以在任何支持WASI或兼容的runtime上运行。不出意外，该提案将在Go 1.21或Go 1.22版本落地。</p>
<p>本文中的示例代码可以在<a href="https://github.com/bigwhite/experiments/blob/master/go1.20-examples">这里</a>下载。</p>
<hr />
<p><a href="https://wx.zsxq.com/dweb2/index/group/51284458844544">“Gopher部落”知识星球</a>旨在打造一个精品Go学习和进阶社群！高品质首发Go技术文章，“三天”首发阅读权，每年两期Go语言发展现状分析，每天提前1小时阅读到新鲜的Gopher日报，网课、技术专栏、图书内容前瞻，六小时内必答保证等满足你关于Go语言生态的所有需求！2023年，Gopher部落将进一步聚焦于如何编写雅、地道、可读、可测试的Go代码，关注代码质量并深入理解Go核心技术，并继续加强与星友的互动。欢迎大家加入！</p>
<p><img src="http://image.tonybai.com/img/tonybai/gopher-tribe-zsxq-small-card.png" alt="img{512x368}" /><br />
<img src="http://image.tonybai.com/img/tonybai/go-programming-from-beginner-to-master-qr.png" alt="img{512x368}" /></p>
<p><img src="http://image.tonybai.com/img/tonybai/go-first-course-banner.png" alt="img{512x368}" /><br />
<img src="http://image.tonybai.com/img/tonybai/imooc-go-column-pgo-with-qr.jpg" alt="img{512x368}" /></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>微博2：https://weibo.com/u/6484441286</li>
<li>博客：tonybai.com</li>
<li>github: https://github.com/bigwhite</li>
</ul>
<p><img src="http://image.tonybai.com/img/tonybai/iamtonybai-wechat-qr.png" alt="" /></p>
<p>商务合作方式：撰稿、出书、培训、在线课程、合伙创业、咨询、广告合作。</p>
<p style='text-align:left'>&copy; 2023, <a href='https://tonybai.com'>bigwhite</a>. 版权所有. </p>
]]></content:encoded>
			<wfw:commentRss>https://tonybai.com/2023/02/08/some-changes-in-go-1-20/feed/</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>Go程序员拥抱C语言简明指南</title>
		<link>https://tonybai.com/2022/05/16/the-short-guide-of-embracing-c-lang-for-gopher/</link>
		<comments>https://tonybai.com/2022/05/16/the-short-guide-of-embracing-c-lang-for-gopher/#comments</comments>
		<pubDate>Sun, 15 May 2022 23:11:16 +0000</pubDate>
		<dc:creator>bigwhite</dc:creator>
				<category><![CDATA[技术志]]></category>
		<category><![CDATA[ANSI-C]]></category>
		<category><![CDATA[archive]]></category>
		<category><![CDATA[break]]></category>
		<category><![CDATA[Build]]></category>
		<category><![CDATA[C]]></category>
		<category><![CDATA[C11]]></category>
		<category><![CDATA[C18]]></category>
		<category><![CDATA[C99]]></category>
		<category><![CDATA[Cgo]]></category>
		<category><![CDATA[Clang]]></category>
		<category><![CDATA[clang-format]]></category>
		<category><![CDATA[CMake]]></category>
		<category><![CDATA[Configure]]></category>
		<category><![CDATA[Cpp]]></category>
		<category><![CDATA[C标准库]]></category>
		<category><![CDATA[C语言]]></category>
		<category><![CDATA[DennisRitchie]]></category>
		<category><![CDATA[error]]></category>
		<category><![CDATA[fallthrough]]></category>
		<category><![CDATA[GC]]></category>
		<category><![CDATA[Go]]></category>
		<category><![CDATA[gofmt]]></category>
		<category><![CDATA[Golang]]></category>
		<category><![CDATA[gomodule]]></category>
		<category><![CDATA[Gopher]]></category>
		<category><![CDATA[Go语言第一课]]></category>
		<category><![CDATA[iso]]></category>
		<category><![CDATA[K&R]]></category>
		<category><![CDATA[KenThompson]]></category>
		<category><![CDATA[LeetCode]]></category>
		<category><![CDATA[libc]]></category>
		<category><![CDATA[Lint]]></category>
		<category><![CDATA[Linux]]></category>
		<category><![CDATA[loccount]]></category>
		<category><![CDATA[macos]]></category>
		<category><![CDATA[Make]]></category>
		<category><![CDATA[Makefile]]></category>
		<category><![CDATA[RobPike]]></category>
		<category><![CDATA[Rust]]></category>
		<category><![CDATA[soname]]></category>
		<category><![CDATA[switch-case]]></category>
		<category><![CDATA[TIOBE]]></category>
		<category><![CDATA[Unix]]></category>
		<category><![CDATA[utf-8]]></category>
		<category><![CDATA[Windows]]></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=3535</guid>
		<description><![CDATA[本文永久链接 &#8211; https://tonybai.com/2022/05/16/the-short-guide-of-embracing-c-lang-for-gopher 本文是为于航老师的极客时间专栏《深入C语言和程序运行原理》写的加餐文章《Tony Bai：Go程序员拥抱C语言简明指南》，这里分享给大家，尤其是那些想学习C语言的Gopher们。 你好，我是Tony Bai。 也许有同学对我比较熟悉，看过我在极客时间上的专栏《Tony Bai ·Go语言第一课》，或者是关注了我的博客。那么，作为一个Gopher，我怎么跑到这个C语言专栏做分享了呢？其实，在学习Go语言并成为一名Go程序员之前，我也曾是一名地地道道的C语言程序员。 大学毕业后，我就开始从事C语言后端服务开发工作，在电信增值领域摸爬滚打了十多年。不信的话，你可以去翻翻我的博客，数一数我发的C语言相关文章是不是比关于Go的还多。一直到近几年，我才将工作中的主力语言从C切换到了Go。不过这并不是C语言的问题，主要原因是我转换赛道了。我目前在智能网联汽车领域从事面向云原生平台的先行研发，而在云原生方面，新生代的Go语言有着更好的生态。 不过作为资深C程序员，C语言已经在我身上打下了深深的烙印。虽然Go是我现在工作中的主力语言，但我仍然会每天阅读一些C开源项目的源码，每周还会写下数百行的C代码。在一些工作场景中，特别是在我参与先行研发一些车端中间件时，C语言有着资源占用小、性能高的优势，这一点是Go目前还无法匹敌的。 正因为我有着C程序员和Go程序员的双重身份，接到这个加餐邀请时，我就想到了一个很适合聊的话题——在 Gopher（泛指Go程序员）与C语言之间“牵线搭桥”。在这门课的评论区里，我看到一些同学说，“正是因为学了Go，所以我想学好C”。如果你也对Go比较熟悉，那么恭喜你，这篇加餐简直是为你量身定制的：一个熟悉Go的程序员在学习C时需要注意的问题，还有可能会遇到的坑，我都替你总结好了。 当然，我知道还有一些对Go了解不多的同学，看到这里也别急着退出去。因为C和Go这两门语言的比较，本身就是一个很有意思的话题。今天的加餐，会涉及这两门语言的异同点，通过对C与Go语言特性的比较，你就能更好地理解“C 语言为什么设计成现在这样”。 一. C语言是现代IT工业的根基 在比较C和Go之前，先说说我推荐Gopher学C的最重要原因吧：用一句话总结，C语言在IT工业中的根基地位，是Go和其他语言目前都无法动摇的。 C语言是由美国贝尔实验室的丹尼斯·里奇（Dennis Ritchie）以Unix发明人肯·汤普森（Ken Thompson）设计的B语言为基础而创建的高级编程语言。诞生于上个世纪（精确来说是1972年）的它，到今年（2022年）已到了“知天命”的半百年纪。 年纪大、设计久远一直是“C语言过时论”兴起的根源，但如果你相信这一论断，那就大错特错了。下面，我来为你分析下个中缘由。 首先，我们说说C语言本身：C语言一直在演进，从未停下过脚步。 虽然C语言之父丹尼斯·里奇不幸于2011年永远地离开了我们，但C语言早已成为ANSI（美国国家标准学会）标准以及ISO/IEC（国际标准化组织和国际电工委员会）标准，因此其演进也早已由标准委员会负责。我们来简单回顾一下C语言标准的演进过程： 1989年，ANSI发布了首个C语言标准，被称为C89，又称ANSI C。次年，ISO和IEC把ANSI C89标准定为C语言的国际标准（ISO/IEC 9899:1990），又称C90，它也是C语言的第一个官方版本； 1999年，ISO和IEC发布了C99标准(ISO/IEC 9899:1999)，它是C语言的第二个官方版本； 2011年，ISO和IEC发布了C11标准(ISO/IEC 9899:2011)，它是C语言的第三个官方版本； 2018年，ISO和IEC发布了C18标准(ISO/IEC 9899:2018)，它是C语言的第四个官方版本。 目前，ISO/IEC标准化委员会正在致力于C2x标准的改进与制定，预计它会在2023年发布。 其次，时至今日，C语言的流行度仍然非常高。 著名编程语言排行榜TIOBE的数据显示，各大编程语言年度平均排名的总位次，C语言多年来高居第一，如下图（图片来自TIOBE）所示： 这说明，无论是在过去还是现在，C语言都是一门被广泛应用的工业级编程语言。 最后，也是最重要的一点是：C语言是现代IT工业的根基，我们说C永远不会退出IT行业舞台也不为过。 如今，无论是普通消费者端的Windows、macOS、Android、苹果iOS，还是服务器端的Linux、Unix等操作系统，亦或是各个工业嵌入式领域的操作系统，其内核实现语言都是C语言。互联网时代所使用的主流Web服务器，比如 Nginx、Apache，以及主流数据库，比如MySQL、Oracle、PostgreSQL等，也都是使用C语言开发的杰作。可以说，现代人类每天都在跟由C语言实现的系统亲密接触，并且已经离不开这些系统了。回到我们程序员的日常，Git、SVN等我们时刻在用的源码版本控制软件也都是由C语言实现的。 可以说，C语言在IT工业中的根基地位，不光Go语言替代不了，C++、Rust等系统编程语言也无法动摇，而且不仅短期如此，长期来看也是如此。 总之，C语言具有紧凑、高效、移植性好、对内存的精细控制等优秀特性，这使得我们在任何时候学习它都不会过时。不过，我在这里推荐Gopher去了解和系统学习C语言，其实还有另一个原因。我们继续往下看。 二. C与Go的相通之处：Gopher拥抱C语言的“先天优势” 众所周知，Go 是在C语言的基础上衍生而来的，二者之间有很多相通之处，因此 Gopher 在学习C语言时是有“先天优势”的。接下来，我们具体看看C和Go的相通之处有哪些。 1. 简单且语法同源 Go语言以简单著称，而作为Go先祖的C语言，入门门槛同样不高：Go有25个关键字，C有32个关键字（C89标准），简洁程度在伯仲之间。C语言曾长期作为高校计算机编程教育的首选编程语言，这与C的简单也不无关系。 和Go不同的是，C语言是一个小内核、大外延的编程语言，其简单主要体现在小内核上了。这个“小内核”包括C基本语法与其标准库，我们可以快速掌握它。但需要注意的是，与Go语言“开箱即用、内容丰富”的标准库不同，C标准库非常小（在C11标准之前甚至连thread库都不包含），所以掌握“小内核”后，在LeetCode平台上刷题是没有任何问题的，但要写出某一领域的工业级生产程序，我们还有很多外延知识技能要学习，比如并发原语、操作系统的系统调用，以及进程间通信等。 C语言的这种简单很容易获得Gopher们的认同感。当年Go语言之父们在设计Go语言时，也是主要借鉴了C语言的语法。当然，这与他们深厚的C语言背景不无关系：肯·汤普森（Ken [...]]]></description>
			<content:encoded><![CDATA[<p><img src="https://tonybai.com/wp-content/uploads/the-short-guide-of-embracing-c-lang-for-gopher-1.png" alt="" /></p>
<p><a href="https://tonybai.com/2022/05/16/the-short-guide-of-embracing-c-lang-for-gopher">本文永久链接</a> &#8211; https://tonybai.com/2022/05/16/the-short-guide-of-embracing-c-lang-for-gopher</p>
<p>本文是为于航老师的极客时间专栏<a href="http://gk.link/a/11osT">《深入C语言和程序运行原理》</a>写的加餐文章<a href="https://time.geekbang.org/column/article/500145">《Tony Bai：Go程序员拥抱C语言简明指南》</a>，这里分享给大家，尤其是那些想学习C语言的Gopher们。</p>
<hr />
<p>你好，我是Tony Bai。</p>
<p>也许有同学对我比较熟悉，看过我在极客时间上的专栏<a href="http://gk.link/a/10AVZ">《Tony Bai ·Go语言第一课》</a>，或者是关注了<a href="https://tonybai.com">我的博客</a>。那么，作为一个Gopher，我怎么跑到这个C语言专栏做分享了呢？其实，在学习Go语言并成为一名Go程序员之前，我也曾是一名地地道道的C语言程序员。</p>
<p>大学毕业后，我就开始从事C语言后端服务开发工作，在电信增值领域摸爬滚打了十多年。不信的话，你可以去翻翻<a href="https://tonybai.com/tag/c">我的博客</a>，数一数我发的C语言相关文章是不是比关于Go的还多。一直到近几年，我才将工作中的主力语言从C切换到了Go。不过这并不是C语言的问题，主要原因是我转换赛道了。我目前在智能网联汽车领域从事面向云原生平台的先行研发，而在云原生方面，新生代的Go语言有着更好的生态。</p>
<p>不过作为资深C程序员，C语言已经在我身上打下了深深的烙印。虽然Go是我现在工作中的主力语言，但我仍然会每天阅读一些C开源项目的源码，每周还会写下数百行的C代码。在一些工作场景中，特别是在我参与先行研发一些车端中间件时，C语言有着资源占用小、性能高的优势，这一点是Go目前还无法匹敌的。</p>
<p>正因为我有着C程序员和Go程序员的双重身份，接到这个加餐邀请时，我就想到了一个很适合聊的话题——在 Gopher（泛指Go程序员）与C语言之间“牵线搭桥”。在这门课的评论区里，我看到一些同学说，“正是因为学了Go，所以我想学好C”。如果你也对Go比较熟悉，那么恭喜你，这篇加餐简直是为你量身定制的：一个熟悉Go的程序员在学习C时需要注意的问题，还有可能会遇到的坑，我都替你总结好了。</p>
<p><strong>当然，我知道还有一些对Go了解不多的同学，看到这里也别急着退出去。</strong>因为C和Go这两门语言的比较，本身就是一个很有意思的话题。今天的加餐，会涉及这两门语言的异同点，通过对C与Go语言特性的比较，你就能更好地理解“C 语言为什么设计成现在这样”。</p>
<h2>一. C语言是现代IT工业的根基</h2>
<p>在比较C和Go之前，先说说我推荐Gopher学C的最重要原因吧：用一句话总结，<strong>C语言在IT工业中的根基地位，是Go和其他语言目前都无法动摇的</strong>。</p>
<p>C语言是由美国贝尔实验室的丹尼斯·里奇（Dennis Ritchie）以Unix发明人肯·汤普森（Ken Thompson）设计的B语言为基础而创建的高级编程语言。诞生于上个世纪（精确来说是1972年）的它，到今年（2022年）已到了“知天命”的半百年纪。 年纪大、设计久远一直是“C语言过时论”兴起的根源，但如果你相信这一论断，那就大错特错了。下面，我来为你分析下个中缘由。</p>
<p><img src="https://tonybai.com/wp-content/uploads/the-short-guide-of-embracing-c-lang-for-gopher-3.jpeg" alt="" /></p>
<p>首先，我们说说C语言本身：<strong>C语言一直在演进，从未停下过脚步</strong>。</p>
<p>虽然C语言之父丹尼斯·里奇不幸于2011年永远地离开了我们，但C语言早已成为ANSI（美国国家标准学会）标准以及ISO/IEC（国际标准化组织和国际电工委员会）标准，因此其演进也早已由标准委员会负责。我们来简单回顾一下C语言标准的演进过程：</p>
<ul>
<li>1989年，ANSI发布了首个C语言标准，被称为C89，又称ANSI C。次年，ISO和IEC把ANSI C89标准定为C语言的国际标准（ISO/IEC 9899:1990），又称C90，它也是C语言的第一个官方版本；</li>
<li>1999年，ISO和IEC发布了<a href="https://www.iso.org/standard/29237.html">C99标准(ISO/IEC 9899:1999)</a>，它是C语言的第二个官方版本；</li>
<li>2011年，ISO和IEC发布了<a href="https://www.iso.org/standard/57853.html">C11标准(ISO/IEC 9899:2011)</a>，它是C语言的第三个官方版本；</li>
<li>2018年，ISO和IEC发布了<a href="https://www.iso.org/standard/74528.html">C18标准(ISO/IEC 9899:2018)</a>，它是C语言的第四个官方版本。<br />
目前，ISO/IEC标准化委员会正在致力于C2x标准的改进与制定，预计它会在2023年发布。</li>
</ul>
<p>其次，<strong>时至今日，C语言的流行度仍然非常高</strong>。</p>
<p>著名编程语言排行榜TIOBE的数据显示，各大编程语言年度平均排名的总位次，C语言多年来高居第一，如下图（图片来自<a href="https://www.tiobe.com/tiobe-index">TIOBE</a>）所示：</p>
<p><img src="https://tonybai.com/wp-content/uploads/the-short-guide-of-embracing-c-lang-for-gopher-2.png" alt="" /></p>
<p>这说明，无论是在过去还是现在，C语言都是一门被广泛应用的工业级编程语言。</p>
<p>最后，也是最重要的一点是：<strong>C语言是现代IT工业的根基</strong>，我们说C永远不会退出IT行业舞台也不为过。</p>
<p>如今，无论是普通消费者端的Windows、macOS、Android、苹果iOS，还是服务器端的Linux、Unix等操作系统，亦或是各个工业嵌入式领域的操作系统，其内核实现语言都是C语言。互联网时代所使用的主流Web服务器，比如 Nginx、Apache，以及主流数据库，比如MySQL、Oracle、PostgreSQL等，也都是使用C语言开发的杰作。可以说，现代人类每天都在跟由C语言实现的系统亲密接触，并且已经离不开这些系统了。回到我们程序员的日常，Git、SVN等我们时刻在用的源码版本控制软件也都是由C语言实现的。</p>
<p>可以说，C语言在IT工业中的根基地位，不光Go语言替代不了，C++、Rust等系统编程语言也无法动摇，而且不仅短期如此，长期来看也是如此。</p>
<p>总之，C语言具有紧凑、高效、移植性好、对内存的精细控制等优秀特性，这使得我们在任何时候学习它都不会过时。不过，我在这里推荐Gopher去了解和系统学习C语言，其实还有另一个原因。我们继续往下看。</p>
<h2>二. C与Go的相通之处：Gopher拥抱C语言的“先天优势”</h2>
<p>众所周知，Go 是在C语言的基础上衍生而来的，二者之间有很多相通之处，因此 Gopher 在学习C语言时是有“先天优势”的。接下来，我们具体看看C和Go的相通之处有哪些。</p>
<h3>1. 简单且语法同源</h3>
<p>Go语言以简单著称，而作为<strong>Go先祖</strong>的C语言，入门门槛同样不高：Go有25个关键字，C有32个关键字（C89标准），简洁程度在伯仲之间。C语言曾长期作为高校计算机编程教育的首选编程语言，这与C的简单也不无关系。</p>
<p>和Go不同的是，C语言是一个<strong>小内核、大外延</strong>的编程语言，其简单主要体现在小内核上了。这个“小内核”包括C基本语法与其标准库，我们可以快速掌握它。但需要注意的是，与Go语言“开箱即用、内容丰富”的标准库不同，<a href="https://en.wikipedia.org/wiki/C_standard_library">C标准库</a>非常小（在C11标准之前甚至连thread库都不包含），所以掌握“小内核”后，在LeetCode平台上刷题是没有任何问题的，但要写出某一领域的工业级生产程序，我们还有很多外延知识技能要学习，比如并发原语、操作系统的系统调用，以及进程间通信等。</p>
<p>C语言的这种简单很容易获得Gopher们的认同感。当年Go语言之父们在设计Go语言时，也是主要借鉴了C语言的语法。当然，这与他们深厚的C语言背景不无关系：肯·汤普森（Ken Thompson）是Unix之父，与丹尼斯·里奇共同设计了C语言；罗博·派克（Rob Pike）是贝尔实验室的资深研究员，参与了Unix系统的演进、Plan9操作系统的开发，还是UTF-8编码的发明人；罗伯特·格瑞史莫（Robert Griesemer）也是用C语言手写Java虚拟机的大神级人物。</p>
<p>Go的第一版编译器就是由肯·汤普森（Ken Thompson）用C语言实现的。并且，Go语言的早期版本中，C代码的比例还不小。以Go语言发布的第一个版本，<a href="https://github.com/golang/go/releases/tag/go1">Go 1.0版本</a>为例，我们通过<a href="https://gitlab.com/esr/loccount">loccount工具</a>对其进行分析，会得到下面的结果：</p>
<pre><code>$loccount .
all          SLOC=460992  (100.00%) LLOC=193045  in 2746 files
Go           SLOC=256321  (55.60%)  LLOC=109763  in 1983 files
C            SLOC=148001  (32.10%)  LLOC=73458   in 368 files
HTML         SLOC=25080   (5.44%)   LLOC=0       in 57 files
asm          SLOC=10109   (2.19%)   LLOC=0       in 133 files
... ...
</code></pre>
<p>这里我们看到，在1.0版本中，C语言代码行数占据了32.10%的份额，这一份额直至Go 1.5版本实现自举后，才下降为不到1%。</p>
<p>我当初对Go“一见钟情”，其中一个主要原因就是Go与C语言的<strong>语法同源。</strong>相对应地，相信这种同源的语法也会让Gopher们喜欢上C语言。</p>
<h3>2. 静态编译且基础范式相同</h3>
<p>除了语法同源，C语言与Go语言的另一个相同点是，它们都是静态编译型语言。这意味着它们都有如下的语法特性：</p>
<ul>
<li>变量与函数都要先声明后才能使用；</li>
<li>所有分配的内存块都要有对应的类型信息，并且在确定其类型信息后才能操作；</li>
<li>源码需要先编译链接后才能运行。</li>
</ul>
<p>相似的编程逻辑与构建过程，让学习C语言的Gopher可以做到无缝衔接。</p>
<p>除此之外，Go 和C的基础编程范式都是命令式编程（imperative programming），即面向算法过程，由程序员通过编程告诉计算机应采取的动作。然后，计算机按程序指令执行一系列流程，生成特定的结果，就像菜谱指定了厨师做蛋糕时应遵循的一系列步骤一样。</p>
<p>从Go看 C，没有面向对象，没有函数式编程，没有泛型（Go 1.18已加入），满眼都是类型与函数，可以说是相当亲切了。</p>
<h3>3. 错误处理机制如出一辙</h3>
<p>对于后端编程语言来说，错误处理机制十分重要。如果两种语言的错误处理机制不同，那么这两种语言的代码整体语法风格很可能大不相同。</p>
<p>在C语言中，我们通常用一个类型为整型的函数返回值作为错误状态标识，函数调用者基于值比较的方式，对这一代表错误状态的返回值进行检视。通常，当这个返回值为0时，代表函数调用成功；当这个返回值为其他值时，代表函数调用出现错误。函数调用者需根据该返回值所代表的错误状态，来决定后续执行哪条错误处理路径上的代码。</p>
<p>C语言这种简单的<strong>基于错误值比较</strong>的错误处理机制，让每个开发人员必须显式地去关注和处理每个错误。经过显式错误处理的代码会更为健壮，也会让开发人员对这些代码更有信心。另外，这些错误就是普通的值，我们不需要额外的语言机制去处理它们，只需利用已有的语言机制，像处理其他普通类型值那样去处理错误就可以了。这让代码更容易调试，我们也更容易针对每个错误处理的决策分支进行测试覆盖。</p>
<p>C语言错误处理机制的这种简单与显式，跟Go语言的设计哲学十分契合，于是Go语言设计者决定继承这种错误处理机制。因此，当Gopher们来到C语言的世界时，无需对自己的错误处理思维做出很大的改变，就可以很容易地适应C语言的风格。</p>
<h2>三. 知己知彼，来看看C与Go的差异</h2>
<p>虽说 Gopher 学习C语言有“先天优势”，但是不经过脚踏实地的学习与实践就想掌握和精通C语言，也是不可能的。而且，C 和Go还是有很大差异的，Gopher 们只有清楚这些差异，做到“知己知彼”，才能在学习过程中分清轻重，有的放矢。俗话说，“磨刀不误砍柴功”，下面我们就一起看看C与Go有哪些不同。</p>
<h3>1. 设计哲学</h3>
<p>在人类自然语言学界，有一个很著名的假说——“<a href="https://en.wikipedia.org/wiki/Linguistic_relativity">萨丕尔-沃夫假说</a>”。这个假说的内容是这样的：<strong>语言影响或决定人类的思维方式</strong>。对我来说，<strong>编程语言也不仅仅是一门工具，它还影响着程序员的思维方式</strong>。每次开始学习一门新的编程语言时，我都会先了解这门编程语言的设计哲学。</p>
<p>每种编程语言都有自己的设计哲学，即便这门语言的设计者没有将其显式地总结出来，它也真真切切地存在，并影响着这门语言的后续演进，以及这门语言程序员的思维方式。我在<a href="http://gk.link/a/10AVZ">《Tony Bai · Go语言第一课》</a>专栏里，将Go语言的设计哲学总结成了5点，分别是<strong>简单、显式、组合、并发和面向工程</strong>。</p>
<p>那么C语言的设计哲学又是什么呢？从表面上看，简单紧凑、性能至上、极致资源、全面移植，这些都可以作为C的设计哲学，但我倾向于一种更有人文气息的说法：<strong>满足和相信程序员</strong>。</p>
<p>在这样的设计哲学下，一方面，C语言提供了几乎所有可以帮助程序员表达自己意图的语法手段，比如宏、指针与指针运算、位操作、pragma指示符、goto语句，以及跳转能力更为强大的longjmp等；另一方面，C语言对程序员的行为并没有做特别严格的限定与约束，C程序员可以利用语言提供的这些语法手段，进行天马行空的发挥：访问硬件、利用指针访问内存中的任一字节、操控任意字节中的每个位（bit）等。总之，C语言假定程序员知道他们在做什么，并选择相信程序员。</p>
<p>C语言给了程序员足够的自由，可以说，在C语言世界，你几乎可以“为所欲为”。但这种哲学也是有代价的，那就是你可能会犯一些莫名其妙的错误，比如悬挂指针，而这些错误很少或不可能在其他语言中出现。</p>
<p>这里再用一个比喻来更为形象地表达下：从Go世界到C世界，就好比在动物园中饲养已久的动物被放归到野生自然保护区，有了更多自由，但周围也暗藏着很多未曾遇到过的危险。因此，学习C语言的Gopher们要有足够的心理准备。</p>
<h3>2. 内存管理</h3>
<p>接下来我们来看C与Go在内存管理方面的不同。我把这一点放在第二位，是因为这两种语言在内存管理上有很大的差异，而且这一差异会给程序员的日常编码带来巨大影响。</p>
<p>我们知道，Go是带有垃圾回收机制（俗称GC）的静态编程语言。使用Go编程时，内存申请与释放，在栈上还是在堆上分配，以及新内存块的清零等等，这一切都是自动的，且对程序员透明。</p>
<p>但在C语言中，上面说的这些都是程序员的责任。手工内存管理在带来灵活性的同时，也带来了极大的风险，其中最常见的就是内存泄露（memory leak）与悬挂指针（dangling pointer）问题。</p>
<p>内存泄露主要指的是<strong>程序员手工在堆上分配的内存在使用后没有被释放（free），进而导致的堆内存持续增加</strong>。而悬挂指针的意思是<strong>指针指向了非法的内存地址</strong>，未初始化的指针、指针所指对象已经被释放等，都是导致悬挂指针的主要原因。针对悬挂指针进行解引用（dereference）操作将会导致运行时错误，从而导致程序异常退出的严重后果。</p>
<p>Go语言带有GC，而C语言不带GC，这都是由各自语言设计哲学所决定的。GC是不符合C语言的设计哲学的，因为一旦有了GC，程序员就远离了机器，程序员直面机器的需求就无法得到满足了。并且，一旦有了GC，无论是在性能上还是在资源占用上，都不可能做到极致了。</p>
<p>在C中，手工管理内存到底是一种什么感觉呢？作为一名有着十多年C开发经验的资深C程序员，我只能告诉你：<strong>与内存斗，其乐无穷</strong>！这是在带GC的编程语言中无法体会到的。</p>
<h3>3. 语法形式</h3>
<p>虽然C语言是Go的先祖，并且Go也继承了很多C语言的语法元素，但在变量/函数声明、行尾分号、代码块是否用括号括起、标识符作用域，以及控制语句语义等方面，二者仍有较大差异。因此，对Go已经很熟悉的程序员在初学C时，受之前编码习惯的影响，往往会踩一些“坑”。基于此，我总结了Gopher学习C语言时需要特别注意的几点，接下来我们具体看看。</p>
<p><strong>第一，注意声明变量时类型与变量名的顺序</strong></p>
<p>前面说过，Go与C都是静态编译型语言，这就要求我们在使用任何变量之前，需要先声明这个变量。但Go采用的变量声明语法颇似Pascal语言，即<strong>变量名在前，变量类型在后</strong>，这与C语言恰好相反，如下所示：</p>
<pre><code>Go:

var a, b int
var p, q *int

vs.

C：
int a, b;
int *p, *q;
</code></pre>
<p>此外，Go支持短变量声明，并且由于短变量声明更短小，无需显式提供变量类型，Go编译器会根据赋值操作符后面的初始化表达式的结果，自动为变量赋予适当类型。因此，它成为了Gopher们喜爱和重度使用的语法。但短声明在C中却不是合法的语法元素：</p>
<pre><code>int main() {
    a := 5; //  error: expected expression
    printf("a = %d\n", a);
}
</code></pre>
<p>不过，和上面的变量类型与变量名声明的顺序问题一样，C编译器会发现并告知我们这个问题，并不会给程序带来实质性的伤害。</p>
<p><strong>第二，注意函数声明无需关键字前缀</strong></p>
<p>无论是C语言还是Go语言，函数都是基本功能逻辑单元，我们也可以说<strong>C程序就是一组函数的集合</strong>。实际上，我们日常的C代码编写大多集中在实现某个函数上。</p>
<p>和变量一样，函数在两种语言中都需要先声明才能使用。Go语言使用func关键字作为<strong>函数声明的前缀</strong>，并且函数返回值列表放在函数声明的最后。但在C语言中，函数声明无需任何关键字作为前缀，函数只支持单一返回值，并且返回值类型放在函数名的前面，如下所示：</p>
<pre><code>Go：
func Add(a, b int) int {
    return a+b
}

vs.

C：
int Add(int a, int b) {
    return a+b;
}
</code></pre>
<p><strong>第三，记得加上代码行结尾的分号</strong></p>
<p>我们日常编写Go代码时，<strong>极少手写分号</strong>。这是因为，Go设计者当初为了简化代码编写，提高代码可读性，选择了<strong>由编译器在词法分析阶段自动在适当位置插入分号的技术路线</strong>。如果你是一个被Go编译器惯坏了的Gopher，来到C语言的世界后，一定不要忘记代码行尾的分号。比如上面例子中的C语言Add函数实现，在return语句后面记得要手动加上分号。</p>
<p><strong>第四，补上“省略”的括号</strong></p>
<p>同样是出于简化代码、增加可读性的考虑，Go设计者最初就取消掉了条件分支语句（if）、选择分支语句（switch）和循环控制语句（for）中条件表达式外围的小括号：</p>
<pre><code>// Go代码
func f() int {
    return 5
}
func main() {
    a := 1
    if a == 1 { // 无需小括号包裹条件表达式
        fmt.Println(a)
    }

    switch b := f(); b { // 无需小括号包裹条件表达式
    case 4:
        fmt.Println("b = 4")
    case 5:
        fmt.Println("b = 5")
    default:
        fmt.Println("b = n/a")
    }

    for i := 1; i &lt; 10; i++ { // 无需小括号包裹循环语句的循环表达式
        a += i
    }
    fmt.Println(a)
}
</code></pre>
<p>这一点恰恰与C语言“背道而驰”。因此，我们在使用C语言编写代码时，务必要想着补上这些括号：</p>
<pre><code>// C代码
int f() {
        return 5;
}

int main() {
    int a = 1;
    if (a == 1) { // 需用小括号包裹条件表达式
        printf("%d\n", a);
    }

    int b = f();
    switch (b) { // 需用小括号包裹条件表达式
    case 4:
        printf("b = 4\n");
        break;
    case 5:
        printf("b = 5\n");
        break;
    default:
        printf("b = n/a\n");
    }

    int i = 0;
    for (i = 1; i &lt; 10; i++) { // 需用小括号包裹循环语句的循环表达式
        a += i;
    }
    printf("%d\n", a);
}
</code></pre>
<p><strong>第五，留意C与Go导出符号的不同机制</strong></p>
<p>C语言通过头文件来声明对外可见的符号，所以我们不用管符号是不是首字母大写的。但在Go中，只有首字母大写的包级变量、常量、类型、函数、方法才是可导出的，即对外部包可见。反之，首字母小写的则为包私有的，仅在包内使用。Gopher一旦习惯了这样的规则，在切换到C语言时，就会产生“心理后遗症”：遇到在其他头文件中定义的首字母小写的函数时，总以为不能直接使用。</p>
<p><strong>第六，记得在switch case语句中添加break</strong></p>
<p>C 语言与Go语言在选择分支语句的语义方面有所不同：C语言的 case 语句中，如果没有显式加入break语句，那么代码将向下自动掉落执行。而Go在最初设计时就重新规定了switch case的语义，默认不自动掉落（fallthrough），除非开发者显式使用fallthrough关键字。</p>
<p>适应了Go的switch case语句的语义后再回来写C代码，就会存在潜在的“风险”。我们来看一个例子：</p>
<pre><code>// C代码：
int main() {
    int a = 1;
    switch(a) {
        case 1:printf("a = 1\n");
        case 2:printf("a = 2\n");
        case 3:printf("a = 3\n");
        default:printf("a = ?\n");
    }
}
</code></pre>
<p>这段代码是按Go语义编写的switch case，编译运行后得到的结果如下：</p>
<pre><code>a = 1
a = 2
a = 3
a = ?
</code></pre>
<p>这显然不符合我们输出“a = 1”的预期。对于初学C的Gopher而言，这个问题影响还是蛮大的，因为这样编写的代码在C编译器眼中是完全合法的，但所代表的语义却完全不是开发人员想要的。这样的程序一旦流入到生产环境，其缺陷可能会引发生产故障。</p>
<p>一些Clint 工具可以检测出这样的问题，因此对于写C代码的Gopher，我建议在提交代码前使用lint工具对代码做一下检查。</p>
<h3>4. 构建机制</h3>
<p>Go与C都是静态编译型语言，它们的源码需要经过编译器和链接器处理，这个过程称为<strong>构建(build)</strong>，构建后得到的可执行文件才是最终交付给用户的成果物。</p>
<p>和Go语言略有不同的是，C语言的构建还有一个预处理（pre-processing）阶段，预处理环节的输出才是C编译器的真正输入。C语言中的宏就是在预处理阶段展开的。不过，Go没有预处理阶段。</p>
<p>C语言的编译单元是一个C源文件（.c），每个编译单元在编译过程中会对应生成一个目标文件（.o/.obj），最后链接器将这些目标文件链接在一起，形成可执行文件。</p>
<p>而Go则是以一个包（package）为编译单元的，每个包内的源文件生成一个.o文件，一个包的所有.o文件聚合（archive）成一个.a文件，链接器将这些目标文件链接在一起形成可执行文件。</p>
<p>Go语言提供了统一的Go命令行工具链，且Go编译器原生支持增量构建，源码构建过程不需要Gopher手工做什么配置。但在C语言的世界中，用于构建C程序的工具有很多，主流的包括gcc/clang，以及微软平台的C编译器。这些编译器原生不支持增量构建，为了提升工程级构建的效率，避免每次都进行全量构建，我们通常会使用第三方的构建管理工具，比如make（Makefile）或CMake。考虑移植性时，我们还会使用到configure文件，用于在目标机器上收集和设置编译器所需的环境信息。</p>
<h3>5. 依赖管理</h3>
<p>我在前面提过，C语言仅提供了一个“小内核”。像依赖管理这类的事情，C语言本身并没有提供跟Go中的Go Module类似的，统一且相对完善的解决方案。在C语言的世界中，我们依然要靠外部工具（比如CMake）来管理第三方的依赖。</p>
<p>C语言的第三方依赖通常以静态库（.a）或动态共享库（.so）的形式存在。如果你的应用要使用静态链接，那就必须在系统中为C编译器提供第三方依赖的静态库文件。但在实际工作中，完全采用静态链接有时是会遇到麻烦的。这是因为，很多操作系统在默认安装时是不带开发包的，也就是说，像 libc、libpthread 这样的系统库只提供了动态共享库版本（如/lib下提供了libc的共享库libc.so.6），其静态库版本是需要自行下载、编译和安装的（如libc的静态库libc.a在安装后是放在/usr/lib下面的)。所以<strong>多数情况下，我们是将****静态、动态****两种链接方式混合在一起使用的</strong>，比如像libc这样的系统库多采用动态链接。</p>
<p>动态共享库通常是有版本的，并且按照一定规则安装到系统中。举个例子，一个名为libfoo的动态共享库，在安装的目录下文件集合通常是这样：</p>
<pre><code>2022-03-10 12:28 libfoo.so -&gt; libfoo.so.0.0.0*
2022-03-10 12:28 libfoo.so.0 -&gt; libfoo.so.0.0.0*
2022-03-10 12:28 libfoo.so.0.0.0*
</code></pre>
<p>按惯例，每个动态共享库都有多个名字属性，包括real name、soname和linker name。下面我们来分别看下。</p>
<ul>
<li>real name：实际包含共享库代码的那个文件的名字(如上面例子中的libfoo.so.0.0.0)。动态共享库的真实版本信息就在real name中，显然real name中的版本号符合<a href="https://semver.org/">语义版本规范</a>，即major.minor.patch。当两个版本的major号一致，说明是向后兼容的两个版本；</li>
<li>soname：shared object name的缩写，也是这三个名字中最重要的一个。无论是在编译阶段还是在运行阶段，系统链接器都是通过动态共享库的soname（如上面例子中的libfoo.so.0）来唯一识别共享库的。我们看到的soname实际上是仅包含major号的共享库名字；</li>
<li>linker name：编译阶段提供给编译器的名字（如上面例子中的libfoo.so）。如果你构建的共享库的real name跟上面例子中libfoo.so.0.0.0类似，带有版本号，那么你在编译器命令中直接使用-L path -lfoo是无法让链接器找到对应的共享库文件的，除非你为libfoo.so.0.0.0提供了一个linker name（如libfoo.so，一个指向libfoo.so.0.0.0的符号链接）。linker name一般在共享库安装时手工创建。<br />
动态共享库有了这三个名称属性，依赖管理就有了依据。但由于在链接的时候使用的是linker name，而linker name并不带有版本号，真实版本与主机环境有关，因此要实现C应用的可重现构建还是比较难。在实践中，我们通常会使用专门的构建主机，项目组将该主机上的依赖管理起来，进而保证每次构建所使用的依赖版本是可控的。同时，应用部署的目标主机上的依赖版本也应该得到管理，避免运行时出现动态共享库版本不匹配的问题。</li>
</ul>
<h3>6. 代码风格</h3>
<p>Go语言是历史上首次实现了代码风格全社区统一的编程语言。它基本上消除了开发人员在代码风格上的无休止的、始终无法达成一致的争论，以及不同代码风格带来的阅读、维护他人代码时的低效。gofmt工具格式化出来的代码风格已经成为Go开发者的一种共识，融入到Go语言的开发文化当中了。所以，如果你让某个Go开发者说说gofmt后的代码风格是什么样的，多数Go开发者可能说不出，因为代码会被gofmt自动变成那种风格，大家已经不再关心风格了。</p>
<p>而在C语言的世界，代码风格仍存争议。但经过多年的演进，以及像Go这样新兴语言的不断“教育”，C社区也在尝试进行这方面的改进，涌现出了像<a href="https://clang.llvm.org/docs/ClangFormat.html">clang-format</a>这样的工具。目前，虽然还没有在全社区达成一致的代码风格（由于历史原因，这很难做到），但已经可以减少很多不必要的争论。</p>
<p>对于正在学习C语言，并进行C编码实践的Gopher，我的建议是：<strong>不要拘泥于使用什么代码风格，先用clang-format，并确定一套风格模板就好</strong>。</p>
<h2>四. 小结</h2>
<p>作为一名对Go跟随和研究了近十年的程序员，我深刻体会到，Go的简单性、性能和生产力使它成为了创建面向用户的应用程序和服务的理想语言。快速的迭代让团队能够快速地作出反应，以满足用户不断变化的需求，让团队可以将更多精力集中在保持灵活性上。</p>
<p>但Go也有缺点，比如缺少对内存以及一些低级操作的精确控制，而C语言恰好可以弥补这个缺陷。C 语言提供的更精细的控制允许更多的精确性，使得C成为低级操作的理想语言。这些低级操作不太可能发生变化，并且C相比Go还提高了性能。所以，如果你是一个有性能与低级操作需求的 Gopher ，就有充分的理由来学习C语言。</p>
<p>C 的优势体现在最接近底层机器的地方，而Go的优势在离用户较近的地方能得到最大发挥。当然，这并不是说两者都不能在对方的空间里工作，但这样做会增加“摩擦”。当你的需求从追求灵活性转变为注重效率时，用C重写库或服务的理由就更充分了。</p>
<p>总之，虽然Go和C的设计有很大的不同，但它们也有很多相似性，具备发挥兼容优势的基础。并且，当我们同时使用这二者时，就可以既有很大的灵活性，又有很好的性能，可以说是相得益彰！</p>
<h2>五. 写在最后</h2>
<p>今天的加餐中，我主要是基于C与Go的比较来讲解的，对于Go语言的特性并没有作详细展开。如果你还想进一步了解Go语言的设计哲学、语法特性、程序设计相关知识，欢迎来学习我在极客时间上的专栏<a href="http://gk.link/a/10AVZ">《Tony Bai ·Go语言第一课》</a>。在这门课里，我会用我十年Gopher的经验，带给你一条系统、完整的Go语言入门路径。</p>
<p>感谢你看到这里，如果今天的内容让你有所收获，欢迎把它分享给你的朋友。</p>
<hr />
<p><a href="https://wx.zsxq.com/dweb2/index/group/51284458844544">“Gopher部落”知识星球</a>旨在打造一个精品Go学习和进阶社群！高品质首发Go技术文章，“三天”首发阅读权，每年两期Go语言发展现状分析，每天提前1小时阅读到新鲜的Gopher日报，网课、技术专栏、图书内容前瞻，六小时内必答保证等满足你关于Go语言生态的所有需求！2022年，Gopher部落全面改版，将持续分享Go语言与Go应用领域的知识、技巧与实践，并增加诸多互动形式。欢迎大家加入！</p>
<p><img src="http://image.tonybai.com/img/tonybai/gopher-tribe-zsxq-small-card.png" alt="img{512x368}" /><br />
<img src="http://image.tonybai.com/img/tonybai/go-programming-from-beginner-to-master-qr.png" alt="img{512x368}" /></p>
<p><img src="http://image.tonybai.com/img/tonybai/go-first-course-banner.png" alt="img{512x368}" /><br />
<img src="http://image.tonybai.com/img/tonybai/imooc-go-column-pgo-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><img src="http://image.tonybai.com/img/tonybai/iamtonybai-wechat-qr.png" alt="" /></p>
<p>商务合作方式：撰稿、出书、培训、在线课程、合伙创业、咨询、广告合作。</p>
<p style='text-align:left'>&copy; 2022, <a href='https://tonybai.com'>bigwhite</a>. 版权所有. </p>
]]></content:encoded>
			<wfw:commentRss>https://tonybai.com/2022/05/16/the-short-guide-of-embracing-c-lang-for-gopher/feed/</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>“Go语言第一课”结课了</title>
		<link>https://tonybai.com/2022/02/17/go-first-course-close/</link>
		<comments>https://tonybai.com/2022/02/17/go-first-course-close/#comments</comments>
		<pubDate>Thu, 17 Feb 2022 13:57:00 +0000</pubDate>
		<dc:creator>bigwhite</dc:creator>
				<category><![CDATA[技术志]]></category>
		<category><![CDATA[github]]></category>
		<category><![CDATA[Go]]></category>
		<category><![CDATA[go-module]]></category>
		<category><![CDATA[go1.18]]></category>
		<category><![CDATA[Golang]]></category>
		<category><![CDATA[GOPATH]]></category>
		<category><![CDATA[Go语言第一课]]></category>
		<category><![CDATA[Interface]]></category>
		<category><![CDATA[map]]></category>
		<category><![CDATA[Slice]]></category>
		<category><![CDATA[String]]></category>
		<category><![CDATA[TIOBE]]></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=3417</guid>
		<description><![CDATA[本文永久链接 &#8211; https://tonybai.com/2022/02/17/go-first-course-close 就在家家户户刚刚过完虎年元宵佳节之际，我的Go语言专栏：《Tony Bai·Go语言第一课》也迎来了它的最后一讲结术语。 这门专栏的撰写开始于2021年5月中旬，翻看我用于管理专栏原始文稿的github仓库的commit log记录，这一有纪念价值的日子被精确定位在5月16日： 从那时开始，我便进入了专栏的节奏。从2021年5月到2022年2月，9个月的时间洋洋洒洒写下了20多万字(估计值)，写作过程的艰辛只有写过极客时间专栏的作者们才会知道。每天睡眠4-5个小时是我的常态。这也算是对我个人极限的一种挑战了:)。 专栏于2021年10月13日正式上线！上线后，当我看到有那么订阅学习专栏、认真完成课后思考题以及在留言区留言的童鞋，我顿感之前的努力与付出都没有白费。 写结束语之前，我认真回顾了一下这门课的内容，当初设定的目标，包括覆盖了绝大多数Go语言的语法点等都基本实现。此外，从大家的留言反馈情况来看，彻底抛弃GOPATH，并将对Go module构建模式、Go项目布局的讲解前置到入门篇中是无比正确的决定。另外专栏对一些语法概念，比如切片、字符串、map、接口类型等进行了超出入门范畴的原理性地讲解也得到了来自学员的肯定，这也算是这个入门课的吸睛之处。 不过课程依然存在遗憾，其中最令我感到不安的是对指针这个概念的讲解的缺失。在规划课程之初，我没有意识到很多来自动态语言的童鞋完全没有对指针这个概念的认知，我的这个疏忽导致给一些学员的后续学习带去了困惑。为了弥补这个遗憾，我会在后面以加餐的形式补充对Go指针基础的讲解。 2022年3月份，Go 1.18版本将携着泛型语法正式发布。对于定位为“Go语言第一课”的本专栏来说，不能缺少对泛型语法的系统讲解，并且Go泛型很可能是Go语法特性的最后一次较大更新了。虽然通过加餐聊过泛型，但那些还是较为粗线条的，我将在后续补充泛型篇，系统全面介绍Go泛型语法的细节，专栏也要做到“与时俱进”！ Go语言第一课专栏上线以来得到了广大童鞋的点赞，这让我尤其开心。有些童鞋在结束语的留言中还期望我能后续能再出进阶或深度Go专栏： 这真的让我受宠若惊！不过，是否能出其他极客专栏，暂时还无法给大家承诺，还需要给我时间复复盘、充充电，再策划策划^_^。 撰写结束语时，恰逢著名编程语言排名指数TIOBE发布2022年2月编程语言排名情况，如下图： 在这期排名中，Go上升到第11位，相较于2021年年底各大编程语言的最终排名以及2021年2月份的同比排名都上升了2位。Go语言位次的提升在我的预料之中。TIOBE在1月份发布的2021年年终编程语言排行榜配文中也认为：除了Swift和Go之外，尚不会有新的编程语言能迅速进入前3名甚至前5名，这也在一定程度上证明了对Go发展趋势的看好。 在本专栏的第一讲“前世今生：你不得不了解的Go的历史和现状”一文中，我曾提到过：绝大多数主流编程语言将在其诞生后的第15至第20年间大步前进。按照这个编程语言的一般规律，已经迈过开源第12个年头的Go很可能将进入自己的黄金5-10年。而2022年很大可能会成为Go语言黄金5-10年的起点，并且其标志只能是Go泛型语法的落地。 按照Go语言的调性，在语法层面上，Go在加入泛型后很难再有大的改变了，错误处理是最后一个硬骨头，也许在泛型引入后，Go核心团队能有新的解决思路。剩下的就是对Go编译器、运行时层、标准库以及工具链的不断的打磨与优化了。到时候，我们就坐收这些优化所带来的红利即可。 学习Go语言10+年的我，很庆幸也很骄傲当初做出了正确的选择。在Go即将迎来黄金十年的历史时刻，希望各位Gopher都能在Go语言之路上走的更远并兑现个人价值。 《Go语言第一课》的结束不是Go语言学习的终点，而是深入和实践Go的起点！ 我爱发短信：企业级短信平台定制开发专家 https://tonybai.com/。smspush : 可部署在企业内部的定制化短信平台，三网覆盖，不惧大并发接入，可定制扩展； 短信内容你来定，不再受约束, 接口丰富，支持长短信，签名可选。2020年4月8日，中国三大电信运营商联合发布《5G消息白皮书》，51短信平台也会全新升级到“51商用消息平台”，全面支持5G RCS消息。 著名云主机服务厂商DigitalOcean发布最新的主机计划，入门级Droplet配置升级为：1 core CPU、1G内存、25G高速SSD，价格5$/月。有使用DigitalOcean需求的朋友，可以打开这个链接地址：https://m.do.co/c/bff6eed92687 开启你的DO主机之路。 Gopher Daily(Gopher每日新闻)归档仓库 &#8211; https://github.com/bigwhite/gopherdaily 我的联系方式： 微博：https://weibo.com/bigwhite20xx 微信公众号：iamtonybai 博客：tonybai.com github: https://github.com/bigwhite “Gopher部落”知识星球：https://public.zsxq.com/groups/51284458844544 商务合作方式：撰稿、出书、培训、在线课程、合伙创业、咨询、广告合作。 &#169; 2022, bigwhite. 版权所有.]]></description>
			<content:encoded><![CDATA[<p><img src="https://tonybai.com/wp-content/uploads/go-first-course-close-1.png" alt="" /></p>
<p><a href="https://tonybai.com/2022/02/17/go-first-course-close">本文永久链接</a> &#8211; https://tonybai.com/2022/02/17/go-first-course-close</p>
<p>就在家家户户刚刚过完虎年元宵佳节之际，我的Go语言专栏：<a href="http://gk.link/a/10AVZ">《Tony Bai·Go语言第一课》</a>也迎来了它的最后一讲<a href="https://time.geekbang.org/column/article/486536"><strong>结术语</strong></a>。</p>
<p>这门专栏的撰写开始于2021年5月中旬，翻看我用于管理专栏原始文稿的github仓库的commit log记录，这一有纪念价值的日子被精确定位在5月16日：</p>
<p><img src="https://tonybai.com/wp-content/uploads/go-first-course-close-2.png" alt="" /></p>
<p>从那时开始，我便进入了专栏的节奏。从2021年5月到2022年2月，9个月的时间洋洋洒洒写下了20多万字(估计值)，写作过程的艰辛只有写过极客时间专栏的作者们才会知道。每天睡眠4-5个小时是我的常态。这也算是对我个人极限的一种挑战了:)。</p>
<p>专栏于2021年10月13日<a href="https://mp.weixin.qq.com/s/xg_jnbRPqaolNksNLjStRw">正式上线</a>！上线后，当我看到有那么订阅学习专栏、认真完成课后思考题以及在留言区留言的童鞋，<strong>我顿感之前的努力与付出都没有白费</strong>。</p>
<p>写结束语之前，我认真回顾了一下这门课的内容，当初设定的目标，包括覆盖了绝大多数Go语言的语法点等都基本实现。此外，从大家的留言反馈情况来看，彻底抛弃GOPATH，并将对Go module构建模式、Go项目布局的讲解前置到入门篇中是无比正确的决定。另外专栏对一些语法概念，比如切片、字符串、map、接口类型等进行了超出入门范畴的原理性地讲解也得到了来自学员的肯定，这也算是这个入门课的吸睛之处。</p>
<p>不过课程依然存在遗憾，其中最令我感到不安的是对指针这个概念的讲解的缺失。在规划课程之初，我没有意识到很多来自动态语言的童鞋完全没有对指针这个概念的认知，我的这个疏忽导致给一些学员的后续学习带去了困惑。为了弥补这个遗憾，我会在后面以加餐的形式补充对Go指针基础的讲解。</p>
<p>2022年3月份，<a href="https://go.dev/blog/go1.18beta2">Go 1.18版本将携着泛型语法正式发布</a>。对于定位为“Go语言第一课”的本专栏来说，不能缺少对泛型语法的系统讲解，并且Go泛型很可能是Go语法特性的最后一次较大更新了。虽然通过加餐聊过泛型，但那些还是较为粗线条的，我将在后续<strong>补充泛型篇</strong>，系统全面介绍Go泛型语法的细节，专栏也要做到“与时俱进”！</p>
<p>Go语言第一课专栏上线以来得到了广大童鞋的点赞，这让我尤其开心。有些童鞋在结束语的留言中还期望我能后续能再出进阶或深度Go专栏：</p>
<p><img src="https://tonybai.com/wp-content/uploads/go-first-course-close-3.png" alt="" /><br />
<img src="https://tonybai.com/wp-content/uploads/go-first-course-close-4.png" alt="" /><br />
<img src="https://tonybai.com/wp-content/uploads/go-first-course-close-5.png" alt="" /><br />
<img src="https://tonybai.com/wp-content/uploads/go-first-course-close-6.png" alt="" /></p>
<p>这真的让我受宠若惊！不过，是否能出其他极客专栏，暂时还无法给大家承诺，还需要给我时间<strong>复复盘、充充电，再策划策划^_^</strong>。</p>
<p>撰写结束语时，恰逢著名编程语言排名指数<a href="https://www.tiobe.com/tiobe-index/">TIOBE</a>发布2022年2月编程语言排名情况，如下图：</p>
<p><img src="https://tonybai.com/wp-content/uploads/go-first-course-close-7.png" alt="" /></p>
<p>在这期排名中，Go上升到第11位，相较于2021年年底各大编程语言的最终排名以及2021年2月份的同比排名都上升了2位。Go语言位次的提升在我的预料之中。TIOBE在1月份发布的<a href="https://mp.weixin.qq.com/s/5g7T7VP8Xj-IrJhZKt9Ovw">2021年年终编程语言排行榜</a>配文中也认为：除了Swift和Go之外，尚不会有新的编程语言能迅速进入前3名甚至前5名，这也在一定程度上证明了对Go发展趋势的看好。</p>
<p>在本专栏的第一讲<a href="https://time.geekbang.org/column/article/426282">“前世今生：你不得不了解的Go的历史和现状”</a>一文中，我曾提到过：<strong>绝大多数主流编程语言将在其诞生后的第15至第20年间大步前进</strong>。按照这个编程语言的一般规律，已经迈过开源第12个年头的Go很可能将进入自己的黄金5-10年。而2022年很大可能会成为Go语言黄金5-10年的起点，并且其标志只能是Go泛型语法的落地。</p>
<p>按照Go语言的调性，在语法层面上，Go在加入泛型后很难再有大的改变了，错误处理是最后一个硬骨头，也许在泛型引入后，Go核心团队能有新的解决思路。剩下的就是对Go编译器、运行时层、标准库以及工具链的不断的打磨与优化了。到时候，我们就坐收这些优化所带来的红利即可。</p>
<p>学习Go语言10+年的我，很庆幸也很骄傲当初做出了正确的选择。在Go即将迎来黄金十年的历史时刻，希望各位Gopher都能在Go语言之路上走的更远并兑现个人价值。</p>
<p><strong>《Go语言第一课》的结束不是Go语言学习的终点，而是深入和实践Go的起点！</strong></p>
<hr />
<p><img src="http://image.tonybai.com/img/tonybai/go-first-course-banner.png" alt="img{512x368}" /><br />
<img src="http://image.tonybai.com/img/tonybai/gopher-tribe-zsxq-small-card.png" alt="img{512x368}" /><br />
<img src="http://image.tonybai.com/img/tonybai/imooc-go-column-pgo-with-qr.jpg" alt="img{512x368}" /><br />
<img src="http://image.tonybai.com/img/tonybai/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><img src="http://image.tonybai.com/img/tonybai/iamtonybai-wechat-qr.png" alt="" /></p>
<p>商务合作方式：撰稿、出书、培训、在线课程、合伙创业、咨询、广告合作。</p>
<p style='text-align:left'>&copy; 2022, <a href='https://tonybai.com'>bigwhite</a>. 版权所有. </p>
]]></content:encoded>
			<wfw:commentRss>https://tonybai.com/2022/02/17/go-first-course-close/feed/</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>切换到Go 1.18后的第一件事：将interface{}全部替换为any</title>
		<link>https://tonybai.com/2021/12/18/replace-empty-interface-with-any-first-after-switching-to-go-1-18/</link>
		<comments>https://tonybai.com/2021/12/18/replace-empty-interface-with-any-first-after-switching-to-go-1-18/#comments</comments>
		<pubDate>Sat, 18 Dec 2021 13:12:35 +0000</pubDate>
		<dc:creator>bigwhite</dc:creator>
				<category><![CDATA[技术志]]></category>
		<category><![CDATA[any]]></category>
		<category><![CDATA[build-constraints]]></category>
		<category><![CDATA[find]]></category>
		<category><![CDATA[generics]]></category>
		<category><![CDATA[Go]]></category>
		<category><![CDATA[go1.17]]></category>
		<category><![CDATA[go1.18]]></category>
		<category><![CDATA[go1.9]]></category>
		<category><![CDATA[gofmt]]></category>
		<category><![CDATA[Golang]]></category>
		<category><![CDATA[Interface]]></category>
		<category><![CDATA[RussCox]]></category>
		<category><![CDATA[typealias]]></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=3385</guid>
		<description><![CDATA[本文永久链接 &#8211; https://tonybai.com/2021/12/18/replace-empty-interface-with-any-first-after-switching-to-go-1-18 伴随着Go 1.18 beta1版本的发布，很多Gopher已经迫不及待地下载该版本并体验其中的新特性了！ Go 1.18 beta1到手后，你想做的第一件事是什么呢？ 说到这里，很多人会问：这是什么梗？ 这个梗来自于Russ Cox在2021年12月1日对Go语言项目的一次commit： 从commit log可以看出，这次change主要是将Go语言项目src目录下代码中的所有interface{}都替换为any。只要学过Go的小伙伴儿们都知道: interface{}在Go中被称为“空接口(empty interface)”，所有类型都实现了空接口interface{}，任意类型T的实例都可以赋值给空接口类型变量： var t T // T可以是任意Go类型 var i interface{} = t var j interface{} = &#38;t 那么为什么Go团队要在Go 1.18 beta1发布之前，将interface{}全部替换为any呢？any又是啥？我们翻看Go 1.18 beta1代码后，在builtin/builtin.go中找到了any类型的定义： // $GOROOT/src/builtin/builtin.go // any is an alias for interface{} and is equivalent to interface{} in all ways. type any [...]]]></description>
			<content:encoded><![CDATA[<p><img src="https://tonybai.com/wp-content/uploads/replace-empty-interface-with-any-first-after-switching-to-go-1-18-1.png" alt="" /></p>
<p><a href="https://tonybai.com/2021/12/18/replace-empty-interface-with-any-first-after-switching-to-go-1-18">本文永久链接</a> &#8211; https://tonybai.com/2021/12/18/replace-empty-interface-with-any-first-after-switching-to-go-1-18</p>
<p>伴随着<a href="https://mp.weixin.qq.com/s/Zthy6IAxGC10Q7q2N3gqMQ">Go 1.18 beta1版本的发布</a>，很多Gopher已经迫不及待地下载该版本并体验其中的新特性了！</p>
<p><img src="https://tonybai.com/wp-content/uploads/replace-empty-interface-with-any-first-after-switching-to-go-1-18-2.png" alt="" /></p>
<p>Go 1.18 beta1到手后，<strong>你想做的第一件事是什么呢</strong>？ 说到这里，很多人会问：<strong>这是什么梗</strong>？</p>
<p>这个梗来自于Russ Cox在2021年12月1日<a href="https://github.com/golang/go/commit/2580d0e08d5e9f979b943758d3c49877fb2324cb">对Go语言项目的一次commit</a>：</p>
<p><img src="https://tonybai.com/wp-content/uploads/replace-empty-interface-with-any-first-after-switching-to-go-1-18-3.jpeg" alt="" /></p>
<p>从commit log可以看出，这次change主要是<strong>将Go语言项目src目录下代码中的所有interface{}都替换为any</strong>。只要学过Go的小伙伴儿们都知道: interface{}在Go中被称为“空接口(empty interface)”，所有类型都实现了空接口interface{}，任意类型T的实例都可以赋值给空接口类型变量：</p>
<pre><code>var t T // T可以是任意Go类型
var i interface{} = t
var j interface{} = &amp;t
</code></pre>
<p>那么为什么Go团队要在Go 1.18 beta1发布之前，将interface{}全部替换为any呢？any又是啥？我们翻看Go 1.18 beta1代码后，在builtin/builtin.go中找到了any类型的定义：</p>
<pre><code>// $GOROOT/src/builtin/builtin.go

// any is an alias for interface{} and is equivalent to interface{} in all ways.
type any = interface{}
</code></pre>
<p>我们看到<strong>any就是一个interface{}的type alias，它与interface{}完全等价</strong>。那为啥要增加any，换掉interface{}呢？我觉得主要还是考虑到<a href="https://mp.weixin.qq.com/s/_4p1wyo3eKEaCU_9GNdkLA">Go 1.18加入泛型</a>后的影响，看下面两个使用了泛型语法的函数声明：</p>
<pre><code>func f[T1 any, T2 comparable, T3 any](t1 T1, t2 T2) T3 { }
func f[T1 interface{}, T2 comparable, T3 interface{}](t1 T1, t2 T2) T3 { }
</code></pre>
<p>Go泛型增加了type parameter，如果在类型参数声明区域继续使用interface{}，我们看到，函数声明部分就会显得十分冗长，给开发者的感官体验上就不那么舒服。另外interface类型在Go 1.18引入泛型后，身兼另外一个职责：<strong>定义类型参数的约束(constraints)</strong>，使用any这样的名字与新职责更匹配。于是Go 1.18就引入了interface{}的type alias，并做了全局替换。</p>
<p>当然这种事情有人爱，就有人反对：</p>
<p><img src="https://tonybai.com/wp-content/uploads/replace-empty-interface-with-any-first-after-switching-to-go-1-18-4.png" alt="" /></p>
<p>不过，我们也看到多数gopher还是喜欢any而不喜欢interface{}的“冗长”的。</p>
<p>既然Go语言项目自身都这么做了，作为Gopher而言，我们有义务响应号召，在切换到Go 1.18开始就着手将代码中的interface{}统统换成any。那怎么换呢？简单的很！<a href="https://www.imooc.com/read/87/article/2376">gofmt大法搞定一切</a>！下面是具体步骤：</p>
<ul>
<li>查看当前项目下的interface{}使用情况</li>
</ul>
<pre><code>$find . -name "*.go"|xargs grep "interface{}"

// 如要排除掉vendor
$find . -name "*.go"|grep -v vendor|xargs grep "interface{}"
</code></pre>
<ul>
<li>查看此次替换会影响到的源文件列表</li>
</ul>
<pre><code>$gofmt -l -r 'interface{} -&gt; any' .

// 如要排除掉vendor
$gofmt -l -r 'interface{} -&gt; any' .|grep -v vendor
</code></pre>
<ul>
<li>实施全局替换</li>
</ul>
<pre><code>$gofmt -w -r 'interface{} -&gt; any' .

// 如要排除掉vendor目录
$find . -name "*.go"|grep -v vendor|xargs gofmt -w -r 'interface{} -&gt; any'
</code></pre>
<blockquote>
<p>注意：gofmt不会替换注释中的interface{}</p>
</blockquote>
<p>最后，可以使用下面名了检查替换情况：</p>
<pre><code>$find . -name "*.go"|xargs grep "interface{}"
</code></pre>
<p>一段时间后&#8230;..</p>
<p><strong>你可能觉得你有些“冲动”了</strong>！虽然Go 1.18支持any，但Go 1.17及之前的版本不支持啊，团队内部除非步调一致的全部升级到go 1.18，否则其他组员可能就无法编译你提交的用any换掉interface{}的代码了！怎么办？</p>
<p>考虑到兼容Go 1.18之前直至Go 1.9版本，我们可以用条件编译来解决这个问题。看下面例子：</p>
<pre><code>// https://github.com/bigwhite/experiments/tree/master/emptyinterface2any 

$tree emptyinterface2any
emptyinterface2any
├── any.go
├── demo
├── go.mod
├── main.go
├── pkg1
│   ├── any.go
│   └── pkg1.go
└── pkg2
    ├── any.go
    └── pkg2.go
</code></pre>
<p>这个emptyinterface2any demo项目中，所有interface{}都换成了any。我们用go 1.18构建这个demo自然没有问题。但是如果用Go 1.17或之前的版本，那么就会得到“any未定义”的错误。为了兼容老版本，我们在每个包的下面都加入一个any.go文件：</p>
<pre><code>// https://github.com/bigwhite/experiments/tree/master/emptyinterface2any/any.go

// +build !go1.18
//go:build !go1.18

package main

type any = interface{}
</code></pre>
<p>我们看到在这个文件中，我们加入了<a href="https://pkg.go.dev/cmd/go#hdr-Build_constraints">编译约束指示信息</a>，通过这些信息告诉编译器：这个源文件仅在Go 1.18版本之前的版本构建时才参与编译，Go 1.18编译时，不参与编译。这样当使用Go 1.17及之前的版本编译时，该文件参与编译，相当于我们自定义了一个any别名类型。</p>
<p>这个方案适用于[Go 1.9，Go 1.17]范围内的Go版本，因为type alias语法是在<a href="https://tonybai.com/2017/07/14/some-changes-in-go-1-9/">Go 1.9版本</a>中引入的。</p>
<p>这下你可以无后顾之忧的提交你的代码了，虽然增加any.go并用编译约束的方式麻烦点^_^。不过这也是临时的，一旦全部迁移到Go 1.18以及后续版本，这些临时措施就可以撤掉了(删除所有any.go)。</p>
<hr />
<p><a href="https://mp.weixin.qq.com/s/jUqAL7hf2GmMun64BJufEA">“Gopher部落”知识星球</a>正式转正（从试运营星球变成了正式星球）！“gopher部落”旨在打造一个精品Go学习和进阶社群！高品质首发Go技术文章，“三天”首发阅读权，每年两期Go语言发展现状分析，每天提前1小时阅读到新鲜的Gopher日报，网课、技术专栏、图书内容前瞻，六小时内必答保证等满足你关于Go语言生态的所有需求！部落目前虽小，但持续力很强，欢迎大家加入！</p>
<p><img src="http://image.tonybai.com/img/tonybai/gopher-tribe-zsxq-small-card.png" alt="img{512x368}" /></p>
<p><img src="http://image.tonybai.com/img/tonybai/go-first-course-banner.png" alt="img{512x368}" /><br />
<img src="http://image.tonybai.com/img/tonybai/imooc-go-column-pgo-with-qr.jpg" alt="img{512x368}" /><br />
<img src="http://image.tonybai.com/img/tonybai/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><img src="http://image.tonybai.com/img/tonybai/iamtonybai-wechat-qr.png" alt="" /></p>
<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/12/18/replace-empty-interface-with-any-first-after-switching-to-go-1-18/feed/</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>TB一周萃选[第2期]</title>
		<link>https://tonybai.com/2017/12/22/2nd-issue-of-the-tech-weekly-carefully-chosen-by-tonybai/</link>
		<comments>https://tonybai.com/2017/12/22/2nd-issue-of-the-tech-weekly-carefully-chosen-by-tonybai/#comments</comments>
		<pubDate>Fri, 22 Dec 2017 15:27:32 +0000</pubDate>
		<dc:creator>bigwhite</dc:creator>
				<category><![CDATA[技术志]]></category>
		<category><![CDATA[135editor]]></category>
		<category><![CDATA[bleve]]></category>
		<category><![CDATA[blogging]]></category>
		<category><![CDATA[C]]></category>
		<category><![CDATA[CloudNativeCon]]></category>
		<category><![CDATA[conduit]]></category>
		<category><![CDATA[container]]></category>
		<category><![CDATA[CSDN]]></category>
		<category><![CDATA[docker]]></category>
		<category><![CDATA[envoy]]></category>
		<category><![CDATA[Go]]></category>
		<category><![CDATA[Golang]]></category>
		<category><![CDATA[gonum]]></category>
		<category><![CDATA[Google]]></category>
		<category><![CDATA[gorgonia]]></category>
		<category><![CDATA[growth-hacker]]></category>
		<category><![CDATA[IBM]]></category>
		<category><![CDATA[image]]></category>
		<category><![CDATA[istio]]></category>
		<category><![CDATA[k8s]]></category>
		<category><![CDATA[katacontainer]]></category>
		<category><![CDATA[KubeCon]]></category>
		<category><![CDATA[Kubernetes]]></category>
		<category><![CDATA[linkerd]]></category>
		<category><![CDATA[mesos]]></category>
		<category><![CDATA[rkt]]></category>
		<category><![CDATA[ServiceMesh]]></category>
		<category><![CDATA[solr]]></category>
		<category><![CDATA[swarm]]></category>
		<category><![CDATA[TB一周萃选]]></category>
		<category><![CDATA[Wechat]]></category>
		<category><![CDATA[wukong]]></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">http://tonybai.com/?p=2500</guid>
		<description><![CDATA[本文是首发于个人微信公众号的文章TB一周萃选[第2期]的归档。 封面 “我天性不宜交际。 在多数场合，我不是觉得对方乏味，就是害怕对方觉得我乏味。可是我既不愿忍受对方的乏味，也不愿费劲使自己显得有趣，那都太累了。 我独处时最轻松，因为我不觉得自己乏味，即使乏味，也自己承受，不累及他人，无需感到不安。” ——周国平 本周日晚上就是平安夜了！ 圣诞节，是西方最重要的节日之一，也是一个公历纪年的最后一个节日。对于中华大地的人们来说，圣诞节这个洋节日影响力倒不是那么大，不过它却是一个重要的日子，它提醒着大家：这一年要结束了！该总结的总结，该计划的也要开始计划了。 圣诞节是一个美丽的节日。在西方，绿色的挂满彩饰的圣诞树、创意十足的圣诞贺卡、白胡子红袍子的慈祥的圣诞老人、装满礼物的圣诞袜以及美味的圣诞大餐构成了圣诞节永恒不变的节日主题。不过中国人的过法与西方完全不同，尤其是年轻人。他们喜欢成双成对地在商业街以休闲购物的方式过圣诞节，这不仅是商业元素的引导，可能也是荷尔蒙的需要。对于渐渐步入中年的我而言，家庭的分量更重。守在孩子和老婆身边，更能带来心灵上的温暖。 一、一周文章精粹 1、七牛CEO许式伟：”我与Go语言的这十年” 许式伟是大中华地区Go首席布道者（至少，我还不知道谁使用Go和大力推广Go早过许总^_^），并且身体力行、率先垂范地在自己的项目中、在自己的公司产品全面使用Go技术栈。在这篇文章中，许总回顾了Go语言10年来的成长以及他个人使用和推广Go语言的历程。许总对Go有着深刻的理解和洞察力，在这篇文章的结尾处许总再次给出了自己对Go语言未来十年的预测，这里笔者表示不能同意再多了^0^。这里将一段文字摘录如下： 下一个十年会怎样？我知道有一些人很期望 Go 语言特性的迭代。但是如果你抱有这种想法可能会失望，因为下一个十年 Go 不会发生太大的变化。对远期需求变化的预测和把控能力，是 Go 的最大魅力之一。这一点上能够和 Go 相比的是 C 语言（C 语言不同版本的规范差异极少），但因为 Go 要解决的问题更多，做到这一点实际上也更难。下一个十年 Go 仍然会继续深耕服务端开发的生态，同时积极探索其他潜在的应用市场。 原文链接：“我与Go语言的这十年” 图：Go语言的十年 2、追求极简：Docker镜像构建演化史 这是笔者在CSDN《程序员杂志》2017.12上投稿的一篇文章。这两年容器技术飞速发展，除了Docker之外，又有Rkt、kata container等容器引擎或runtime的出现。但Docker依然是容器领域使用最为广泛的主流技术。对于已经接纳和使用Docker技术在日常开发工作中的开发者而言，构建Docker镜像已经是家常便饭。但如何更高效地构建以及构建出Size更小的镜像却是很多Docker技术初学者心中常见的疑问，甚至是一些老手都未曾细致考量过的问题。这篇文章将从一个Docker用户角度来阐述Docker镜像构建的演化史，希望能起到一定的解惑作用。 原文链接：“Docker镜像构建演化史” 3、Service Mesh时代的选边与站队 2017年KubeCon&#38;CloudNativeCon Austin大会上，作为代表下一代微服务解决方案设计理念的Service Mesh成为“热词”而被众人追捧。国内的ServiceMesh也是刚刚起步，方兴未艾。这篇“Service Mesh时代的选边与站队 ”就是发表在国内ServiceMesh社区上的一篇文章。文章脉络大致如下： Service Mesh的地位与生态格局 大公司间关于Service Mesh的布局与斗争策略 istio尚未发布1.0时，最早提出Service Mesh概念的小公司buoyant的努力喘息 Service Mesh的2018 原文链接：“Service Mesh 时代的选边与站队” 4、全文检索数据库Bleve简介 去年年末在做一个全文检索查询功能时曾用过陈辉的wukong引擎，不过wukong引擎由于作者的日理万机，无闲打理，已经不再维护。而在Go语言实现的全文检索工具领域，国外社区更流行的是Bleve。这篇文章介绍了作者所在公司为何用bleve替换solr，并对bleve中概念、使用方法进行了介绍，算是Bleve的入门文章。不过对于中文分词和全文检索的支持好坏，还需验证。 原文链接：“Go实现的全文检索数据库Bleve简介” [...]]]></description>
			<content:encoded><![CDATA[<p>本文是首发于<a href="https://mp.weixin.qq.com/mp/qrcode?scene=10000005&amp;size=102&amp;__biz=MzIyNzM0MDk0Mg==&amp;mid=2247483848&amp;idx=1&amp;sn=a3cd9182a2b2d3716623cc2c43d59f37&amp;send_time=">个人微信公众号</a>的文章<strong>TB一周萃选[第2期]</strong>的归档。</p>
<p><img src="http://tonybai.com/wp-content/uploads/weekly-issues/2nd-issue/gopher-christmas.jpg" alt="img{512x368}" /><br />
封面</p>
<blockquote>
<p>“我天性不宜交际。</p>
<p>在多数场合，我不是觉得对方乏味，就是害怕对方觉得我乏味。可是我既不愿忍受对方的乏味，也不愿费劲使自己显得有趣，那都太累了。</p>
<p>我独处时最轻松，因为我不觉得自己乏味，即使乏味，也自己承受，不累及他人，无需感到不安。”        ——周国平</p>
</blockquote>
<p>本周日晚上就是平安夜了！</p>
<p><a href="https://en.wikipedia.org/wiki/Christmas">圣诞节</a>，是西方最重要的节日之一，也是一个公历纪年的最后一个节日。对于中华大地的人们来说，圣诞节这个洋节日影响力倒不是那么大，不过它却是一个重要的日子，它提醒着大家：<strong>这一年要结束了！该总结的总结，该计划的也要开始计划了</strong>。</p>
<p><img src="http://tonybai.com/wp-content/uploads/weekly-issues/2nd-issue/christmas-day.jpg" alt="img{512x368}" /></p>
<p>圣诞节是一个美丽的节日。在西方，绿色的挂满彩饰的圣诞树、创意十足的圣诞贺卡、白胡子红袍子的慈祥的圣诞老人、装满礼物的圣诞袜以及美味的圣诞大餐构成了圣诞节永恒不变的节日主题。不过中国人的过法与西方完全不同，尤其是年轻人。他们喜欢成双成对地在商业街以休闲购物的方式过圣诞节，这不仅是商业元素的引导，可能也是荷尔蒙的需要。对于渐渐步入中年的我而言，家庭的分量更重。守在孩子和老婆身边，更能带来心灵上的温暖。</p>
<p><img src="http://tonybai.com/wp-content/uploads/weekly-issues/2nd-issue/christmas-postcard-1907.jpg" alt="img{512x368}" /></p>
<h2>一、一周文章精粹</h2>
<h3>1、<a href="https://www.qiniu.com/">七牛</a>CEO<a href="https://weibo.com/xushiweizh">许式伟</a>：”我与<a href="http://tonybai.com/tag/go">Go语言</a>的这十年”</h3>
<p>许式伟是大中华地区Go首席布道者（至少，我还不知道谁使用Go和大力推广Go早过许总^_^），并且身体力行、率先垂范地在自己的项目中、在自己的公司产品全面使用Go技术栈。在这篇文章中，许总回顾了<a href="http://tonybai.com/2017/09/24/go-ten-years-and-climbing/">Go语言10年</a>来的成长以及他个人使用和推广Go语言的历程。许总对Go有着深刻的理解和洞察力，在这篇文章的结尾处许总再次给出了自己对Go语言未来十年的预测，这里笔者表示不能同意再多了^0^。这里将一段文字摘录如下：</p>
<blockquote>
<p>下一个十年会怎样？我知道有一些人很期望 Go 语言特性的迭代。但是如果你抱有这种想法可能会失望，因为下一个十年 Go 不会发生太大的变化。对远期需求变化的预测和把控能力，是 Go 的最大魅力之一。这一点上能够和 Go 相比的是 C 语言（C 语言不同版本的规范差异极少），但因为 Go 要解决的问题更多，做到这一点实际上也更难。下一个十年 Go 仍然会继续深耕服务端开发的生态，同时积极探索其他潜在的应用市场。</p>
</blockquote>
<p>原文链接：<a href="https://mp.weixin.qq.com/s?__biz=MjM5OTcxMzE0MQ==&amp;mid=2653370520&amp;idx=1&amp;sn=69827cc58f3bee76abb9778a8c286915&amp;key=aa4d734c2f5165c43f84c9affec15b08721124970a2831fb8f1fd0bd8e4130234c7a6e9cb300e3a5dccca45b88ba7be73a852e515e8a57c68450ff21b0d47141c160f7a1554c9b532ed449f0fcec8148&amp;ascene=0&amp;uin=MTYwMzM0NjYyMQ%3D%3D&amp;devicetype=iMac+MacBookAir6%2C2+OSX+OSX+10.9.2+build(13C64)&amp;version=11020201&amp;lang=zh_CN&amp;pass_ticket=J6dBgepwYkSeUbwD7vdoXH7qZWH3o0gvnsMESYbiL1opRfDiLSA8owEztxcczj4v">“我与Go语言的这十年”</a></p>
<p><img src="http://tonybai.com/wp-content/uploads/weekly-issues/2nd-issue/gophers10th.jpg" alt="img{512x368}" /><br />
图：Go语言的十年</p>
<h3>2、追求极简：Docker镜像构建演化史</h3>
<p>这是笔者在CSDN《程序员杂志》2017.12上投稿的一篇文章。这两年容器技术飞速发展，除了<a href="http://tonybai.com/tag/docker">Docker</a>之外，又有<a href="https://github.com/rkt/rkt">Rkt</a>、<a href="https://katacontainers.io/">kata container</a>等容器引擎或runtime的出现。但Docker依然是容器领域使用最为广泛的主流技术。对于已经接纳和使用Docker技术在日常开发工作中的开发者而言，构建Docker镜像已经是家常便饭。但如何更高效地构建以及构建出Size更小的镜像却是很多Docker技术初学者心中常见的疑问，甚至是一些老手都未曾细致考量过的问题。这篇文章将从一个Docker用户角度来阐述Docker镜像构建的演化史，希望能起到一定的解惑作用。</p>
<p>原文链接：<a href="http://tonybai.com/2017/12/21/the-concise-history-of-docker-image-building/">“Docker镜像构建演化史”</a></p>
<p><img src="http://tonybai.com/wp-content/uploads/docker-image-history-4-2.png" alt="img{512x368}" /></p>
<h3>3、Service Mesh时代的选边与站队</h3>
<p>2017年<a href="https://kccncna17.sched.com/">KubeCon&amp;CloudNativeCon Austin大会</a>上，作为代表下一代微服务解决方案设计理念的<a href="https://buoyant.io/2017/04/25/whats-a-service-mesh-and-why-do-i-need-one/">Service Mesh</a>成为“热词”而被众人追捧。国内的ServiceMesh也是刚刚起步，方兴未艾。这篇“Service Mesh时代的选边与站队 ”就是发表在国内<a href="http://www.servicemesh.cn/">ServiceMesh社区</a>上的一篇文章。文章脉络大致如下：</p>
<ul>
<li>Service Mesh的地位与生态格局</li>
<li>大公司间关于Service Mesh的布局与斗争策略</li>
<li>istio尚未发布1.0时，最早提出Service Mesh概念的小公司<a href="https://buoyant.io">buoyant</a>的努力喘息</li>
<li>Service Mesh的2018</li>
</ul>
<p>原文链接：<a href="https://mp.weixin.qq.com/s/hHzDa1T_UKPB97ttFRaDCQ">“Service Mesh 时代的选边与站队”</a></p>
<p><img src="http://tonybai.com/wp-content/uploads/weekly-issues/2nd-issue/servicemesh-google-products.png" alt="img{512x368}" /></p>
<p><img src="http://tonybai.com/wp-content/uploads/weekly-issues/2nd-issue/istio-arch.png" alt="img{512x368}" /></p>
<h3>4、全文检索数据库Bleve简介</h3>
<p>去年年末在做一个全文检索查询功能时曾用过陈辉的<a href="http://tonybai.com/2016/12/06/an-intro-to-wukong-fulltext-search-engine/">wukong引擎</a>，不过wukong引擎由于作者的日理万机，无闲打理，已经不再维护。而在Go语言实现的全文检索工具领域，国外社区更流行的是<a href="https://github.com/blevesearch/bleve">Bleve</a>。这篇文章介绍了作者所在公司为何用bleve替换solr，并对bleve中概念、使用方法进行了介绍，算是Bleve的入门文章。不过对于中文分词和全文检索的支持好坏，还需验证。</p>
<p>原文链接：<a href="https://medium.com/wireless-registry-engineering/short-introduction-to-bleve-5de4bbf16657">“Go实现的全文检索数据库Bleve简介”</a></p>
<h3>5、十年专业写博经验谈</h3>
<p>Andrew Chen是硅谷的一位企业家，创业顾问，“<a href="http://andrewchen.co/how-to-be-a-growth-hacker-an-airbnbcraigslist-case-study/">Growth Hacker is the new VP of Marketing</a>”一文作者，目前就职于uber。他还是一位拥有10年写博经验的博主。在“十年专业写博经验谈”一文中，他总结了10年来写博的经验教训，并逐条给出详细的亲历讲解。</p>
<p>原文链接：<a href="http://andrewchen.co/professional-blogging">“10 years of professional blogging – what I’ve learned”</a></p>
<h3>6、Go数据科学Data Sheet</h3>
<p>Go语言在数据科学领域算得上是一个年轻，但却极具潜力的选手。近一年来，Go语言在大数据领域已经有了<a href="https://github.com/gonum/gonum">gonum</a>、<a href="https://github.com/gorgonia/gorgonia">gorgonia</a>等用于数值计算和数据分析的library。gorgonia项目的作者Chewxy这篇”Data Science In Go: A Cheat Sheet”就是使用gonum和gorgonia进行数据科学计算和统计计算的速查手册。</p>
<p>原文链接：<a href="https://www.cheatography.com/chewxy/cheat-sheets/data-science-in-go-a/">“Data Science In Go: A Cheat Sheet”</a></p>
<p><img src="http://tonybai.com/wp-content/uploads/weekly-issues/2nd-issue/go-data-science.jpg" alt="img{512x368}" /></p>
<h2>二、一周资料分享</h2>
<p><a href="https://blog.golang.org/8years">Go正式发布8年</a>后，市面上关于Go语言入门的书籍和课程资料已经出现很多了，无论免费的还是收费。和其他语言的技术资料一样，很多资料质量良莠不齐。hackr.io针对Go语言的教程发起了社区投票，在这里我们可以看到社区对这些资料的质量甄别，同时这也是一份很好的Go书籍资料集合。这个投票是open的，你也可以提交list上尚没有的gobook，并根据你的阅读体验贡献你的vote。</p>
<p>原文地址：<a href="https://hackr.io/tutorials/learn-golang">“Best Community up-voted Go programming resources” </a></p>
<h2>三、一周工具推荐</h2>
<h3>1、135editor</h3>
<p>之前将blog内容同步到微信公众号的时候，多为简单的复制粘贴，导致很多朋友抱怨公众号文章格式太粗糙，尤其是贴代码部分。自从有了做“TB一周萃选”这个weekly issue后，我就在市面上搜寻好用的微信公号文章编辑器。之前用的是微信编辑器(www.wxbj.cn)，简洁易用。但不知何故，该站点现在似乎变成了“易企秀”。于是我将编辑器换成了<a href="http://www.135editor.com/">135editor</a>，这个似乎更加强大，就是左栏下方的广告推广多了一些。</p>
<p><strong>135editor</strong>还支持在绑定公众号后的素材库同步，省了一步copy的动作。</p>
<h2>四、一周书籍推荐</h2>
<h3>1、Kubernetes Handbook</h3>
<p><a href="http://tonybai.com/tag/kubernetes">Kubernetes</a>赢得了与mesos、docker swarm的关于容器管理和服务编排引擎的“战争”，<a href="https://techcrunch.com/2017/12/18/as-kubernetes-surged-in-popularity-in-2017-it-created-a-vibrant-ecosystem/">成为这个领域当之无愧的领头羊</a>。越来越多的公司开始试用Kubernetes，这里推荐一个有关于Kubernetes的开源书《Kubernetes Handbook》，是由talkingdata的jimmy song编写整理的。该书的最大特点就是全面，从K8s的基本概念、运维手段到k8s的领域应用，并且有详细的实践操作讲解。</p>
<p>书籍链接：<a href="https://jimmysong.io/kubernetes-handbook/">《Kubernetes Handbook》</a></p>
<hr />
<p>我的联系方式：</p>
<p>微博：http://weibo.com/bigwhite20xx<br />
微信公众号：iamtonybai<br />
博客：tonybai.com<br />
github: https://github.com/bigwhite</p>
<p>微信赞赏：<br />
<img src="http://tonybai.com/wp-content/uploads/wechat-zanshang-code-512x512.jpg" alt="img{512x368}" /></p>
<p>商务合作方式：撰稿、出书、培训、在线课程、合伙创业、咨询、广告合作。</p>
<p style='text-align:left'>&copy; 2017, <a href='https://tonybai.com'>bigwhite</a>. 版权所有. </p>
]]></content:encoded>
			<wfw:commentRss>https://tonybai.com/2017/12/22/2nd-issue-of-the-tech-weekly-carefully-chosen-by-tonybai/feed/</wfw:commentRss>
		<slash:comments>1</slash:comments>
		</item>
		<item>
		<title>godep的一个“坑”</title>
		<link>https://tonybai.com/2014/10/30/a-hole-of-godep/</link>
		<comments>https://tonybai.com/2014/10/30/a-hole-of-godep/#comments</comments>
		<pubDate>Thu, 30 Oct 2014 14:38:09 +0000</pubDate>
		<dc:creator>bigwhite</dc:creator>
				<category><![CDATA[技术志]]></category>
		<category><![CDATA[Buildc]]></category>
		<category><![CDATA[Git]]></category>
		<category><![CDATA[github]]></category>
		<category><![CDATA[Go]]></category>
		<category><![CDATA[godep]]></category>
		<category><![CDATA[Golang]]></category>
		<category><![CDATA[Google]]></category>
		<category><![CDATA[GOPATH]]></category>
		<category><![CDATA[Opensource]]></category>
		<category><![CDATA[Subversion]]></category>
		<category><![CDATA[开源]]></category>
		<category><![CDATA[构建]]></category>
		<category><![CDATA[编译]]></category>

		<guid isPermaLink="false">http://tonybai.com/?p=1595</guid>
		<description><![CDATA[很多人学习和使用Golang一段时间后，都会被golang的第三方包依赖版本搞得有些烦躁，golang设计者最初过于乐观的设计使得今天大 家不得不各自想办法解决这个问题。godep就是综合了多年第三方包依赖问题的解决方案后的一个趋向统一的方案，至少是在go get的设计没有进化前的一个比较不错的方案。 今天试用了一把godep，不过&#8220;体验&#8221;并不理想，这缘于我遇到了godep的一个&#8220;坑&#8221;，不过是那种你在正式项目中不一定遇到的&#8220;坑&#8221;，这里来说到说到。 按照godep官方使用说明的第一步，先下载godep： $ go get github.com/tools/godep $godep Godep is a tool for managing Go package dependencies. Usage: &#160;&#160;&#160; godep command [arguments] The commands are: &#160;&#160;&#160; save&#160;&#160;&#160;&#160; list and copy dependencies into Godeps &#160;&#160;&#160; go&#160;&#160;&#160;&#160;&#160;&#160; run the go tool in a sandbox &#160;&#160;&#160; get&#160;&#160;&#160;&#160;&#160; download and install packages with specified dependencies &#160;&#160;&#160; path&#160;&#160;&#160;&#160; [...]]]></description>
			<content:encoded><![CDATA[<p>很多人学习和使用<a href="http://tonybai.com/tag/golang">Golang</a>一段时间后，都会被<a href="http://golang.org">golang</a>的第三方包依赖版本搞得有些烦躁，golang设计者最初过于乐观的设计使得今天大 家不得不各自想办法解决这个问题。<a href="https://github.com/tools/godep">godep</a>就是综合了多年第三方包依赖问题的解决方案后的一个趋向统一的方案，至少是在go get的设计没有进化前的一个比较不错的方案。</p>
<p>今天试用了一把godep，不过&ldquo;体验&rdquo;并不理想，这缘于我遇到了godep的一个&ldquo;坑&rdquo;，不过是那种你在正式项目中不一定遇到的&ldquo;坑&rdquo;，这里来说到说到。</p>
<p>按照godep官方使用说明的第一步，先下载godep：</p>
<p><font face="Courier New">$ go get github.com/tools/godep</font><br />
	<font face="Courier New">$godep<br />
	Godep is a tool for managing Go package dependencies.</font></p>
<p><font face="Courier New">Usage:</font></p>
<p><font face="Courier New">&nbsp;&nbsp;&nbsp; godep command [arguments]</font></p>
<p><font face="Courier New">The commands are:</font></p>
<p><font face="Courier New">&nbsp;&nbsp;&nbsp; save&nbsp;&nbsp;&nbsp;&nbsp; list and copy dependencies into Godeps<br />
	&nbsp;&nbsp;&nbsp; go&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; run the go tool in a sandbox<br />
	&nbsp;&nbsp;&nbsp; get&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; download and install packages with specified dependencies<br />
	&nbsp;&nbsp;&nbsp; path&nbsp;&nbsp;&nbsp;&nbsp; print sandbox path for use in a GOPATH<br />
	&nbsp;&nbsp;&nbsp; restore&nbsp; check out listed dependency versions in GOPATH<br />
	&nbsp;&nbsp;&nbsp; update&nbsp;&nbsp; use different revision of selected packages</font></p>
<p><font face="Courier New">Use &quot;godep help [command]&quot; for more information about a command.</font></p>
<p>确认正确下载后，我们来准备一个测试例子，目录如下：</p>
<p><font face="Courier New">$GOPATH/<br />
	&nbsp;&nbsp;&nbsp; src/<br />
	&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; tonybai.com/<br />
	&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; foolib/<br />
	&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; foo.go<br />
	&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; fooapp/<br />
	&nbsp;&nbsp;&nbsp; &nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;&nbsp; &nbsp; &nbsp;&nbsp; main.go</font>&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp;<br />
	&nbsp;&nbsp;&nbsp;<br />
	<font face="Courier New">//foo.go<br />
	package foo</font></p>
<p><font face="Courier New">func Add(a, b int) int {<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; return a + b<br />
	}</font></p>
<p><font face="Courier New">//main.go<br />
	package main</font></p>
<p><font face="Courier New">import (<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; &quot;fmt&quot;<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; foo &quot;tonybai.com/foolib&quot;<br />
	)</font></p>
<p><font face="Courier New">func main() {<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; fmt.Println(foo.Add(1, 3))<br />
	}</font></p>
<p>在<font face="Courier New">fooapp</font>下，编译执行程序：</p>
<p><font face="Courier New">$go run main.go<br />
	4</font></p>
<p>接下来godep登场，根据godep文档中得步骤，接下来我们应该在一个构建依赖关系完整的项目中执行godep save以保存依赖关系以及依赖的当前版本第三方包：</p>
<p><font face="Courier New">$godep save<br />
	godep: directory &quot;/Users/tony/Test/GoToolsProjects/src&quot; is not using a known version control system<br />
	godep: error loading dependencies</font></p>
<p>出错了！godep提示$GOPATH/src目录没有使用任何版本控制系统(<font face="Courier New">not using a known version control system</font>)。 奇怪啊！这个错误什么意思呢？难道使用godep还需要将$GOPATH/src整体作为一个Project纳入git or subversion repository中？无奈之下，我只能先这么做，再作观察。我在$GOPATH下执行git init，建立一个local git repository，然后将src add到这个repository中。</p>
<p>回到fooapp下，再次执行godep save，居然依旧是同样地错误结果。于是到godep的issues中去查，看看是否有人和我遇到了同样地问题！godep的<a href="https://github.com/tools/godep/issues/116">#116 issue</a>中提到的问题恰恰和我的一致，不过这个issue一 直是open状态，也没有人comments。接着翻看一下godep的源码，godep依赖一些第三方包，save这个命令在分析版本控制工具库时也是 调用了多层外部包实现的，短时间内无法定位问题。</p>
<p>静想一下，godep是管理第三方包依赖关系的，而第三方包多是go get下载的，是不是foolib要放到repository中才行呢？于是尝试在foolib中建立git repository并做一次commit。第三次在fooapp下执行godep save，错误依旧！</p>
<p>难道fooapp也必须放在repository中？试试吧。在fooapp下init一个git repository，将fooapp下的main.go提交到repository中。再执行godep save：</p>
<p><font face="Courier New">$godep save<br />
	$ls -l<br />
	total 8<br />
	drwxr-xr-x&nbsp; 5 tony&nbsp; staff&nbsp; 170 10 30 22:01 Godeps/<br />
	-rw-r&#8211;r&#8211;&nbsp; 1 tony&nbsp; staff&nbsp; 103 10 30 21:44 main.go</font></p>
<p>这回成功了！godep save在fooapp下建立了Godeps目录，其结构如下：</p>
<p><font face="Courier New">$ls -R<br />
	Godeps.json&nbsp;&nbsp;&nbsp; Readme&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; _workspace/</font></p>
<p><font face="Courier New">./_workspace:<br />
	src/</font></p>
<p><font face="Courier New">./_workspace/src:<br />
	tonybai.com/</font></p>
<p><font face="Courier New">./_workspace/src/tonybai.com:<br />
	foolib/</font></p>
<p><font face="Courier New">./_workspace/src/tonybai.com/foolib:<br />
	foolib.go</font></p>
<p>godep将当前版本的foolib copy到Godeps/_workspace下了。</p>
<p>Godeps.json记录了fooapp对foolib的依赖关系：</p>
<p><font face="Courier New">{<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; &quot;ImportPath&quot;: &quot;fooapp&quot;,<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; &quot;GoVersion&quot;: &quot;go1.3&quot;,<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; &quot;Deps&quot;: [<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; {<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; &quot;ImportPath&quot;: &quot;tonybai.com/foolib&quot;,<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; &quot;Rev&quot;: &quot;<b>20a9c2a682537813d37847f2f270bf929672cc84</b>&quot;<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; }<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; ]<br />
	}</font></p>
<p>godep记录了foolib的当前revision number，这个number恰是我最新一次commit的hash code：</p>
<p><font face="Courier New">~/Test/GoToolsProjects/src/tonybai.com/foolib]$git log<br />
	commit <b>20a9c2a682537813d37847f2f270bf929672cc84</b><br />
	Author: Tony Bai &lt;bigwhite.cn@gmail.com&gt;<br />
	Date:&nbsp;&nbsp; Thu Oct 30 22:00:25 2014 +0800</font></p>
<p><font face="Courier New">&nbsp;&nbsp;&nbsp; init</font></p>
<p>到这里让我觉得godep的设计思路有些与我的<a href="http://github.com/bigwhite/buildc">buildc</a>（<a href="http://tonybai.com/2011/12/08/buildc-a-building-assistant-tool-for-c-app/">C程序辅助构建工具</a>）的思路有些类似，只是godep做得更彻底：</p>
<p>&nbsp;&nbsp;&nbsp; 1、godep将项目依赖统统放到项目的私有_workspace下，而buildc是共享的，通过project下的版本号配置区分依赖<br />
	&nbsp;&nbsp;&nbsp; 2、godep将依赖管理到revision(修订号)级别，buildc只是根据version来区分依赖。</p>
<p>godep的辅助构建原理（<font face="Courier New">godep go build main.go</font>）通过一条命令即可看出来：</p>
<p><font face="Courier New">$godep go env<br />
	GOARCH=&quot;amd64&quot;<br />
	GOBIN=&quot;/usr/local/go/bin&quot;<br />
	GOCHAR=&quot;6&quot;<br />
	GOEXE=&quot;&quot;<br />
	GOHOSTARCH=&quot;amd64&quot;<br />
	GOHOSTOS=&quot;darwin&quot;<br />
	GOOS=&quot;darwin&quot;<br />
	<strong>GOPATH=&quot;/Users/tony/Test/GoToolsProjects/src/fooapp/Godeps/_workspace:/Users/tony/Test/GoToolsProjects&quot;</strong></font></p>
<p>godep临时将_workspace放在GOPATH列表的前面，这样gc在编译时就会按顺序先在_workspace下面找依赖包，这样fooapp的私有依赖就会理所当然的被gc用到，即便在其他GOPATH路径下有同名包（可能是不同版本的）。</p>
<p>显然这也算是godep的一个小bug吧（或者是godep依赖的包的bug，目前不确认），毕竟提示的路径是不正确的，不应该提示<font face="Courier New">&quot;/Users/tony/Test/GoToolsProjects/src&quot; is not using a known version control system，而应该是</font><font face="Courier New">&quot;/Users/tony/Test/GoToolsProjects/src/tonybai.com/foolib或</font><font face="Courier New">&quot;/Users/tony/Test/GoToolsProjects/src/fooapp没有版本控制系统的repository留存。</font></p>
<p><font face="Courier New">另外觉得</font>godep的author应该把这个&ldquo;坑&rdquo;作为一个使用godep的前提进行说明，并在github主页给出明确展示，即便这个&ldquo;坑&rdquo;多数人可能不会遇到。</p>
<p style='text-align:left'>&copy; 2014, <a href='https://tonybai.com'>bigwhite</a>. 版权所有. </p>
]]></content:encoded>
			<wfw:commentRss>https://tonybai.com/2014/10/30/a-hole-of-godep/feed/</wfw:commentRss>
		<slash:comments>7</slash:comments>
		</item>
		<item>
		<title>Golang测试技术</title>
		<link>https://tonybai.com/2014/10/22/golang-testing-techniques/</link>
		<comments>https://tonybai.com/2014/10/22/golang-testing-techniques/#comments</comments>
		<pubDate>Wed, 22 Oct 2014 07:57:21 +0000</pubDate>
		<dc:creator>bigwhite</dc:creator>
				<category><![CDATA[技术志]]></category>
		<category><![CDATA[Concurrency]]></category>
		<category><![CDATA[Go]]></category>
		<category><![CDATA[Golang]]></category>
		<category><![CDATA[GopherCon]]></category>
		<category><![CDATA[http]]></category>
		<category><![CDATA[Mock]]></category>
		<category><![CDATA[Package]]></category>
		<category><![CDATA[Unittest]]></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=1579</guid>
		<description><![CDATA[本篇文章内容来源于Golang核心开发组成员Andrew Gerrand在Google I/O 2014的一次主题分享&#8220;Testing Techniques&#8221;，即介绍使用Golang开发 时会使用到的测试技术（主要针对单元测试），包括基本技术、高级技术（并发测试、mock/fake、竞争条件测试、并发测试、内/外部测 试、vet工具等）等，感觉总结的很全面，这里整理记录下来，希望能给大家带来帮助。原Slide访问需要自己搭梯子。另外这里也要吐槽一 下：Golang官方站的slide都是以一种特有的golang artical的格式放出的（用这个工具http://go-talks.appspot.com/可以在线观看），没法像pdf那样下载，在国内使用和传播极其不便。 一、基础测试技术 1、测试Go代码 Go语言内置测试框架。 内置的测试框架通过testing包以及go test命令来提供测试功能。 下面是一个完整的测试strings.Index函数的完整测试文件： //strings_test.go (这里样例代码放入strings_test.go文件中) package strings_test import ( &#160;&#160;&#160; &#34;strings&#34; &#160;&#160;&#160; &#34;testing&#34; ) func TestIndex(t *testing.T) { &#160;&#160;&#160; const s, sep, want = &#34;chicken&#34;, &#34;ken&#34;, 4 &#160;&#160;&#160; got := strings.Index(s, sep) &#160;&#160;&#160; if got != want { &#160;&#160;&#160;&#160;&#160;&#160;&#160; t.Errorf(&#34;Index(%q,%q) = %v; want [...]]]></description>
			<content:encoded><![CDATA[<p>本篇文章内容来源于<a href="http://golang.org">Golang</a>核心开发组成员<a href="http://nf.wh3rd.net/">Andrew Gerrand</a>在Google I/O 2014的一次主题分享&ldquo;<a href="https://talks.golang.org/2014/testing.slide#1">Testing Techniques</a>&rdquo;，即介绍使用Golang开发 时会使用到的测试技术（主要针对<a href="http://tonybai.com/2005/11/08/the-design-and-implementation-of-c-unittest-framework/"><b>单元测试</b></a>），包括基本技术、高级技术（并发测试、<a href="http://tonybai.com/2010/10/29/lcut-add-mock-support/">mock</a>/fake、竞争条件测试、并发测试、内/外部测 试、vet工具等）等，感觉总结的很全面，这里整理记录下来，希望能给大家带来帮助。原Slide访问需要自己搭梯子。另外这里也要吐槽一 下：Golang官方站的slide都是以一种特有的<a href="http://godoc.org/code.google.com/p/go.tools/present">golang artical</a>的格式放出的（用这个工具http://go-talks.appspot.com/可以在线观看），没法像pdf那样下载，在国内使用和传播极其不便。</p>
<p><b>一、基础测试技术</b></p>
<p><b>1、测试Go代码</b></p>
<p>Go语言内置测试框架。</p>
<p>内置的测试框架通过<font face="Courier New">testing</font>包以及<font face="Courier New">go test</font>命令来提供测试功能。</p>
<p>下面是一个完整的测试<font face="Courier New">strings.Index</font>函数的完整测试文件：</p>
<p><font face="Courier New">//strings_test.go (这里样例代码放入strings_test.go文件中)</font><br />
	<font face="Courier New">package strings_test</font></p>
<p><font face="Courier New">import (<br />
	&nbsp;&nbsp;&nbsp; &quot;strings&quot;<br />
	&nbsp;&nbsp;&nbsp; &quot;testing&quot;<br />
	)</font></p>
<p><font face="Courier New">func TestIndex(t *testing.T) {<br />
	&nbsp;&nbsp;&nbsp; const s, sep, want = &quot;chicken&quot;, &quot;ken&quot;, 4<br />
	&nbsp;&nbsp;&nbsp; got := strings.Index(s, sep)<br />
	&nbsp;&nbsp;&nbsp; if got != want {<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; t.Errorf(&quot;Index(%q,%q) = %v; want %v&quot;, s, sep, got, want)//<u>注意原slide中</u><u>的got和want写反了</u><br />
	&nbsp;&nbsp;&nbsp; }<br />
	}</font></p>
<p><font face="Courier New">$go test -v strings_test.go<br />
	=== RUN TestIndex<br />
	&#8212; PASS: TestIndex (0.00 seconds)<br />
	PASS<br />
	ok&nbsp; &nbsp;&nbsp;&nbsp; command-line-arguments&nbsp;&nbsp;&nbsp; 0.007s</font></p>
<p>go test的-v选项是表示输出详细的执行信息。</p>
<p>将代码中的want常量值修改为3，我们制造一个无法通过的测试：</p>
<p><font face="Courier New">$go test -v strings_test.go<br />
	=== RUN TestIndex<br />
	&#8212; FAIL: TestIndex (0.00 seconds)<br />
	&nbsp;&nbsp;&nbsp; strings_test.go:12: Index(&quot;chicken&quot;,&quot;ken&quot;) = 4; want 3<br />
	FAIL<br />
	exit status 1<br />
	FAIL&nbsp;&nbsp;&nbsp; command-line-arguments&nbsp;&nbsp;&nbsp; 0.008s</font></p>
<p><b>2、表驱动测试</b></p>
<p>Golang的struct字面值(struct literals)语法让我们可以轻松写出表驱动测试。</p>
<p><font face="Courier New">package strings_test</font></p>
<p><font face="Courier New">import (<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; &quot;strings&quot;<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; &quot;testing&quot;<br />
	)</font></p>
<p><font face="Courier New">func TestIndex(t *testing.T) {<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; var tests = []struct {<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; s&nbsp;&nbsp; string<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; sep string<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; out int<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; }{<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; {&quot;&quot;, &quot;&quot;, 0},<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; {&quot;&quot;, &quot;a&quot;, -1},<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; {&quot;fo&quot;, &quot;foo&quot;, -1},<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; {&quot;foo&quot;, &quot;foo&quot;, 0},<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; {&quot;oofofoofooo&quot;, &quot;f&quot;, 2},<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; // etc<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; }<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; for _, test := range tests {<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; actual := strings.Index(test.s, test.sep)<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; if actual != test.out {<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; t.Errorf(&quot;Index(%q,%q) = %v; want %v&quot;,<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; test.s, test.sep, actual, test.out)<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; }<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; }<br />
	}</font></p>
<p><font face="Courier New">$go test -v strings_test.go<br />
	=== RUN TestIndex<br />
	&#8212; PASS: TestIndex (0.00 seconds)<br />
	PASS<br />
	ok&nbsp; &nbsp;&nbsp;&nbsp; command-line-arguments&nbsp;&nbsp;&nbsp; 0.007s</font></p>
<p><b>3、T结构</b></p>
<p><font face="Courier New">*testing.T</font>参数用于错误报告：</p>
<p><font face="Courier New">t.Errorf(&quot;got bar = %v, want %v&quot;, got, want)<br />
	t.Fatalf(&quot;Frobnicate(%v) returned error: %v&quot;, arg, err)<br />
	t.Logf(&quot;iteration %v&quot;, i)</font></p>
<p>也可以用于enable并行测试(parallet test)：<br />
	<font face="Courier New">t.Parallel()</font></p>
<p>控制一个测试是否运行：</p>
<p><font face="Courier New">if runtime.GOARCH == &quot;arm&quot; {<br />
	&nbsp;&nbsp;&nbsp; t.Skip(&quot;this doesn&#39;t work on ARM&quot;)<br />
	}</font></p>
<p><b>4、运行测试</b></p>
<p>我们用<font face="Courier New">go test</font>命令来运行特定包的测试。</p>
<p>默认执行当前路径下包的测试代码。</p>
<p><font face="Courier New">$ go test<br />
	PASS</font></p>
<p><font face="Courier New">$ go test -v<br />
	=== RUN TestIndex<br />
	&#8212; PASS: TestIndex (0.00 seconds)<br />
	PASS</font></p>
<p><font face="Courier New">要运行工程下的所有测试，我们执行如下命令：</font></p>
<p><font face="Courier New">$ go test github.com/nf/&#8230;</font></p>
<p><font face="Courier New">标准库的测试：<br />
	$ go test std</font></p>
<p>注：假设<font face="Courier New">strings_test.go</font>的当前目录为<font face="Courier New">testgo</font>，在testgo目录下执行<font face="Courier New">go test</font>都是OK的。但如果我们切换到testgo的上一级目录执行go test，我们会得到什么结果呢？</p>
<p><font face="Courier New">$go test testgo<br />
	can&#39;t load package: package testgo: cannot find package &quot;testgo&quot; in any of:<br />
	&nbsp;&nbsp;&nbsp; /usr/local/go/src/pkg/testgo (from $GOROOT)<br />
	&nbsp;&nbsp;&nbsp; /Users/tony/Test/GoToolsProjects/src/testgo (from $GOPATH)</font></p>
<p>提示找不到testgo这个包，go test后面接着的应该是一个包名，go test会在GOROOT和GOPATH下查找这个包并执行包的测试。</p>
<p><b>5、测试覆盖率</b></p>
<p>go tool命令可以报告测试覆盖率统计。</p>
<p>我们在testgo下执行go test -cover，结果如下：</p>
<p><font face="Courier New">go build _/Users/tony/Test/Go/testgo: no buildable Go source files in /Users/tony/Test/Go/testgo<br />
	FAIL&nbsp;&nbsp;&nbsp; _/Users/tony/Test/Go/testgo [build failed]</font></p>
<p>显然通过cover参数选项计算测试覆盖率不仅需要测试代码，还要有被测对象（一般是函数）的源码文件。</p>
<p>我们将目录切换到$GOROOT/src/pkg/strings下，执行<font face="Courier New">go test -cover</font>：</p>
<p><font face="Courier New">$go test -v -cover<br />
	=== RUN TestReader<br />
	&#8212; PASS: TestReader (0.00 seconds)<br />
	&#8230; &#8230;<br />
	=== RUN: ExampleTrimPrefix<br />
	&#8212; PASS: ExampleTrimPrefix (1.75us)<br />
	PASS<br />
	coverage: 96.9% of statements<br />
	ok&nbsp; &nbsp;&nbsp;&nbsp; strings&nbsp;&nbsp;&nbsp; 0.612s</font></p>
<p>go test可以生成覆盖率的profile文件，这个文件可以被go tool cover工具解析。</p>
<p>在$GOROOT/src/pkg/strings下面执行：</p>
<p><font face="Courier New">$ go test -coverprofile=cover.out</font></p>
<p>会再当前目录下生成cover.out文件。</p>
<p>查看cover.out文件，有两种方法：</p>
<p>a) <font face="Courier New">cover -func=cover.out</font></p>
<p><font face="Courier New">$sudo go tool cover -func=cover.out<br />
	strings/reader.go:24:&nbsp;&nbsp;&nbsp; Len&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; 66.7%<br />
	strings/reader.go:31:&nbsp;&nbsp;&nbsp; Read&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; 100.0%<br />
	strings/reader.go:44:&nbsp;&nbsp;&nbsp; ReadAt&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; 100.0%<br />
	strings/reader.go:59:&nbsp;&nbsp;&nbsp; ReadByte&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; 100.0%<br />
	strings/reader.go:69:&nbsp;&nbsp;&nbsp; UnreadByte&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; 100.0%<br />
	&#8230; &#8230;<br />
	strings/strings.go:638:&nbsp;&nbsp;&nbsp; Replace&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; 100.0%<br />
	strings/strings.go:674:&nbsp;&nbsp;&nbsp; EqualFold&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; 100.0%<br />
	total:&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; (statements)&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; 96.9%</font></p>
<p>b) 可视化查看</p>
<p>执行go tool cover -html=cover.out命令，会在/tmp目录下生成目录coverxxxxxxx，比如/tmp/cover404256298。目录下有一个 coverage.html文件。用浏览器打开coverage.html，即可以可视化的查看代码的测试覆盖情况。</p>
<pre style="margin-top: 0px; margin-bottom: 0px; padding: 0px; font-family: 'Droid Sans Mono', 'Courier New', monospace; font-size: 18px; line-height: 24px; letter-spacing: -1px; color: rgb(0, 0, 0);">&nbsp;</pre>
<p>关于go tool的cover命令，我的go version go1.3 darwin/amd64默认并不自带，需要通过go get下载。</p>
<p><font face="Courier New">$sudo GOPATH=/Users/tony/Test/GoToolsProjects go get code.google.com/p/go.tools/cmd/cover</font></p>
<p>下载后，cover安装在<font face="Courier New">$GOROOT/pkg/tool/darwin_amd64</font>下面。</p>
<p><b>二、高级测试技术</b></p>
<p><b>1、一个例子程序</b></p>
<p>outyet是一个web服务，用于宣告某个特定Go版本是否已经打标签发布了。其获取方法：</p>
<p><font face="Courier New">go get github.com/golang/example/outyet</font></p>
<p>注：<br />
	go get执行后，<font face="Courier New">cd $GOPATH/src/github.com/golang/example/outyet</font>下，执行<font face="Courier New">go run main.go</font>。然后用浏览器打开<font face="Courier New">http://localhost:8080</font>即可访问该Web服务了。</p>
<p><b>2、测试Http客户端和服务端</b></p>
<p>net/http/httptest包提供了许多帮助函数，用于测试那些发送或处理Http请求的代码。</p>
<p><b>3、httptest.Server</b></p>
<p>httptest.Server在本地回环网口的一个系统选择的端口上listen。它常用于端到端的HTTP测试。</p>
<p><font face="Courier New">type Server struct {<br />
	&nbsp;&nbsp;&nbsp; URL&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; string // base URL of form http://ipaddr:port with no trailing slash<br />
	&nbsp;&nbsp;&nbsp; Listener net.Listener</font></p>
<p><font face="Courier New">&nbsp;&nbsp;&nbsp; // TLS is the optional TLS configuration, populated with a new config<br />
	&nbsp;&nbsp;&nbsp; // after TLS is started. If set on an unstarted server before StartTLS<br />
	&nbsp;&nbsp;&nbsp; // is called, existing fields are copied into the new config.<br />
	&nbsp;&nbsp;&nbsp; TLS *tls.Config</font></p>
<p><font face="Courier New">&nbsp;&nbsp;&nbsp; // Config may be changed after calling NewUnstartedServer and<br />
	&nbsp;&nbsp;&nbsp; // before Start or StartTLS.<br />
	&nbsp;&nbsp;&nbsp; Config *http.Server<br />
	}</font></p>
<p><font face="Courier New">func NewServer(handler http.Handler) *Server</font></p>
<p><font face="Courier New">func (*Server) Close() error</font></p>
<p><b>4、httptest.Server实战</b></p>
<p>下面代码创建了一个临时Http Server，返回简单的Hello应答：</p>
<p><font face="Courier New">&nbsp;&nbsp;&nbsp; ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; fmt.Fprintln(w, &quot;Hello, client&quot;)<br />
	&nbsp;&nbsp;&nbsp; }))<br />
	&nbsp;&nbsp;&nbsp; defer ts.Close()</font></p>
<p><font face="Courier New">&nbsp;&nbsp;&nbsp; res, err := http.Get(ts.URL)<br />
	&nbsp;&nbsp;&nbsp; if err != nil {<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; log.Fatal(err)<br />
	&nbsp;&nbsp;&nbsp; }</font></p>
<p><font face="Courier New">&nbsp;&nbsp;&nbsp; greeting, err := ioutil.ReadAll(res.Body)<br />
	&nbsp;&nbsp;&nbsp; res.Body.Close()<br />
	&nbsp;&nbsp;&nbsp; if err != nil {<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; log.Fatal(err)<br />
	&nbsp;&nbsp;&nbsp; }</font></p>
<p><font face="Courier New">&nbsp;&nbsp;&nbsp; fmt.Printf(&quot;%s&quot;, greeting)</font></p>
<p><b>5、httptest.ResponseRecorder</b></p>
<p>httptest.ResponseRecorder是http.ResponseWriter的一个实现，用来记录变化，用在测试的后续检视中。</p>
<p><font face="Courier New">type ResponseRecorder struct {<br />
	&nbsp;&nbsp;&nbsp; Code&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; int&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; // the HTTP response code from WriteHeader<br />
	&nbsp;&nbsp;&nbsp; HeaderMap http.Header&nbsp;&nbsp; // the HTTP response headers<br />
	&nbsp;&nbsp;&nbsp; Body&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; *bytes.Buffer // if non-nil, the bytes.Buffer to append written data to<br />
	&nbsp;&nbsp;&nbsp; Flushed&nbsp;&nbsp; bool<br />
	}</font></p>
<p><b>6、httptest.ResponseRecorder实战</b></p>
<p>向一个HTTP handler中传入一个ResponseRecorder，通过它我们可以来检视生成的应答。</p>
<p><font face="Courier New">&nbsp;&nbsp;&nbsp; handler := func(w http.ResponseWriter, r *http.Request) {<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; http.Error(w, &quot;something failed&quot;, http.StatusInternalServerError)<br />
	&nbsp;&nbsp;&nbsp; }</font></p>
<p><font face="Courier New">&nbsp;&nbsp;&nbsp; req, err := http.NewRequest(&quot;GET&quot;, &quot;http://example.com/foo&quot;, nil)<br />
	&nbsp;&nbsp;&nbsp; if err != nil {<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; log.Fatal(err)<br />
	&nbsp;&nbsp;&nbsp; }</font></p>
<p><font face="Courier New">&nbsp;&nbsp;&nbsp; w := httptest.NewRecorder()<br />
	&nbsp;&nbsp;&nbsp; handler(w, req)</font></p>
<p><font face="Courier New">&nbsp;&nbsp;&nbsp; fmt.Printf(&quot;%d &#8211; %s&quot;, w.Code, w.Body.String())</font></p>
<p><b>7、竞争检测(race detection)</b></p>
<p>当两个goroutine并发访问同一个变量，且至少一个goroutine对变量进行写操作时，就会发生数据竞争（data race）。</p>
<p>为了协助诊断这种bug，Go提供了一个内置的数据竞争检测工具。</p>
<p>通过传入-race选项，go tool就可以启动竞争检测。</p>
<p><font face="Courier New">$ go test -race mypkg&nbsp;&nbsp;&nbsp; // to test the package<br />
	$ go run -race mysrc.go&nbsp; // to run the source file<br />
	$ go build -race mycmd&nbsp;&nbsp; // to build the command<br />
	$ go install -race mypkg // to install the package</font></p>
<p>注：一个数据竞争检测的例子</p>
<p>例子代码：</p>
<p><font face="Courier New">//testrace.go</font></p>
<p><font face="Courier New">package main</font></p>
<p><font face="Courier New">import &quot;fmt&quot;<br />
	import &quot;time&quot;</font></p>
<p><font face="Courier New">func main() {<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; var i int = 0<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; go func() {<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; for {<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; i++<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; fmt.Println(&quot;subroutine: i = &quot;, i)<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; time.Sleep(1 * time.Second)<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; }<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; }()</font></p>
<p><font face="Courier New">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; for {<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; i++<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; fmt.Println(&quot;mainroutine: i = &quot;, i)<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; time.Sleep(1 * time.Second)<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; }<br />
	}</font></p>
<p><font face="Courier New">$go run -race testrace.go<br />
	mainroutine: i =&nbsp; 1<br />
	==================<br />
	<b>WARNING: DATA RACE</b><br />
	Read by goroutine 5:<br />
	&nbsp; main.func&middot;001()<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; /Users/tony/Test/Go/testrace.go:10 +0&#215;49</font></p>
<p><font face="Courier New">Previous write by main goroutine:<br />
	&nbsp; main.main()<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; /Users/tony/Test/Go/testrace.go:17 +0xd5</font></p>
<p><font face="Courier New">Goroutine 5 (running) created at:<br />
	&nbsp; main.main()<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; /Users/tony/Test/Go/testrace.go:14 +0xaf<br />
	==================<br />
	subroutine: i =&nbsp; 2<br />
	mainroutine: i =&nbsp; 3<br />
	subroutine: i =&nbsp; 4<br />
	mainroutine: i =&nbsp; 5<br />
	subroutine: i =&nbsp; 6<br />
	mainroutine: i =&nbsp; 7<br />
	subroutine: i =&nbsp; 8</font></p>
<p><b>8、测试并发</b><b>（testing with concurrency)</b></p>
<p>当测试并发代码时，总会有一种使用sleep的冲动。大多时间里，使用sleep既简单又有效。</p>
<p>但大多数时间不是&rdquo;总是&ldquo;。</p>
<p>我们可以使用Go的并发原语让那些奇怪不靠谱的sleep驱动的测试更加值得信赖。</p>
<p><b>9、</b><b>使用静态分析工具vet查找错误</b></p>
<p>vet工具用于检测代码中程序员犯的常见错误：<br />
	&nbsp;&nbsp;&nbsp; &#8211; 错误的printf格式<br />
	&nbsp;&nbsp;&nbsp; &#8211; 错误的构建tag<br />
	&nbsp;&nbsp;&nbsp; &#8211; 在闭包中使用错误的range循环变量<br />
	&nbsp;&nbsp;&nbsp; &#8211; 无用的赋值操作<br />
	&nbsp;&nbsp;&nbsp; &#8211; 无法到达的代码<br />
	&nbsp;&nbsp;&nbsp; &#8211; 错误使用mutex<br />
	&nbsp;&nbsp;&nbsp; 等等。</p>
<p>使用方法：<br />
	<font face="Courier New">&nbsp;&nbsp;&nbsp; go vet [package]</font></p>
<p><b>10、</b><b>从内部测试</b></p>
<p>golang中大多数测试代码都是被测试包的源码的一部分。这意味着测试代码可以访问包种未导出的符号以及内部逻辑。就像我们之前看到的那样。</p>
<p>注：比如$GOROOT/src/pkg/path/path_test.go与path.go都在path这个包下。</p>
<p><b>11、从外部测试</b></p>
<p>有些时候，你需要从被测包的外部对被测包进行测试，比如测试代码在package foo_test下，而不是在package foo下。</p>
<p>这样可以打破依赖循环，比如：</p>
<p>&nbsp;&nbsp;&nbsp; &#8211; testing包使用fmt<br />
	&nbsp;&nbsp;&nbsp; &#8211; fmt包的测试代码还必须导入testing包<br />
	&nbsp;&nbsp;&nbsp; &#8211; 于是，fmt包的测试代码放在fmt_test包下，这样既可以导入testing包，也可以同时导入fmt包。</p>
<p><b>12、Mocks和fakes</b></p>
<p>通过在代码中使用interface，Go可以避免使用mock和fake测试机制。</p>
<p>例如，如果你正在编写一个文件格式解析器，不要这样设计函数：</p>
<p><font face="Courier New">func Parser(f *os.File) error</font></p>
<p>作为替代，你可以编写一个接受interface类型的函数:</p>
<p><font face="Courier New">func Parser(r io.Reader) error</font></p>
<p>和<font face="Courier New">bytes.Buffer、strings.Reader</font>一样，*os.File也实现了<font face="Courier New">io.Reader</font>接口。</p>
<p><b>13、子进程测试</b></p>
<p>有些时候，你需要测试的是一个进程的行为，而不仅仅是一个函数。例如：</p>
<p><font face="Courier New">func Crasher() {<br />
	&nbsp;&nbsp;&nbsp; fmt.Println(&quot;Going down in flames!&quot;)<br />
	&nbsp;&nbsp;&nbsp; os.Exit(1)<br />
	}</font></p>
<p>为了测试上面的代码，我们将测试程序本身作为一个子进程进行测试：</p>
<p><font face="Courier New">func TestCrasher(t *testing.T) {<br />
	&nbsp;&nbsp;&nbsp; if os.Getenv(&quot;BE_CRASHER&quot;) == &quot;1&quot; {<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; Crasher()<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; return<br />
	&nbsp;&nbsp;&nbsp; }<br />
	&nbsp;&nbsp;&nbsp; cmd := exec.Command(os.Args[0], &quot;-test.run=TestCrasher&quot;)<br />
	&nbsp;&nbsp;&nbsp; cmd.Env = append(os.Environ(), &quot;BE_CRASHER=1&quot;)<br />
	&nbsp;&nbsp;&nbsp; err := cmd.Run()<br />
	&nbsp;&nbsp;&nbsp; if e, ok := err.(*exec.ExitError); ok &amp;&amp; !e.Success() {<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; return<br />
	&nbsp;&nbsp;&nbsp; }<br />
	&nbsp;&nbsp;&nbsp; t.Fatalf(&quot;process ran with err %v, want exit status 1&quot;, err)<br />
	}</font></p>
<p style='text-align:left'>&copy; 2014, <a href='https://tonybai.com'>bigwhite</a>. 版权所有. </p>
]]></content:encoded>
			<wfw:commentRss>https://tonybai.com/2014/10/22/golang-testing-techniques/feed/</wfw:commentRss>
		<slash:comments>8</slash:comments>
		</item>
	</channel>
</rss>
