<?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/%e7%bc%96%e7%a8%8b/feed/" rel="self" type="application/rss+xml" />
	<link>https://tonybai.com</link>
	<description>一个程序员的心路历程</description>
	<lastBuildDate>Thu, 09 Apr 2026 00:20:15 +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>AI 是让你忘掉如何编程的最快方式</title>
		<link>https://tonybai.com/2026/01/01/ai-is-the-fastest-way-to-forget-how-to-code/</link>
		<comments>https://tonybai.com/2026/01/01/ai-is-the-fastest-way-to-forget-how-to-code/#comments</comments>
		<pubDate>Thu, 01 Jan 2026 00:26:30 +0000</pubDate>
		<dc:creator>bigwhite</dc:creator>
				<category><![CDATA[技术志]]></category>
		<category><![CDATA[AI]]></category>
		<category><![CDATA[ArchitectureReview]]></category>
		<category><![CDATA[Author]]></category>
		<category><![CDATA[ClaudeCode]]></category>
		<category><![CDATA[consumer]]></category>
		<category><![CDATA[Copilot]]></category>
		<category><![CDATA[CopyAndPaste]]></category>
		<category><![CDATA[Cursor]]></category>
		<category><![CDATA[EdgeCases]]></category>
		<category><![CDATA[Go]]></category>
		<category><![CDATA[Golang]]></category>
		<category><![CDATA[GPSEffect]]></category>
		<category><![CDATA[GPS效应]]></category>
		<category><![CDATA[MentalModels]]></category>
		<category><![CDATA[Reviewer]]></category>
		<category><![CDATA[RubberDuck]]></category>
		<category><![CDATA[SDD]]></category>
		<category><![CDATA[SoftwareEngineer]]></category>
		<category><![CDATA[SpecDrivenDevelopment]]></category>
		<category><![CDATA[Teacher]]></category>
		<category><![CDATA[Tradeoffs]]></category>
		<category><![CDATA[Typist]]></category>
		<category><![CDATA[UnitTests]]></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=5643</guid>
		<description><![CDATA[本文永久链接 &#8211; https://tonybai.com/2026/01/01/ai-is-the-fastest-way-to-forget-how-to-code 大家好，我是Tony Bai。 在 Copilot、Cursor、Claude Code等普及的这两年，编程似乎变得前所未有的轻松。 Tab 键一按，十行代码倾泻而出；回车一敲，整个函数自动补全；一个Prompt发出，一个项目的框架代码便完成了。那种多巴胺分泌的快感是真实的，效率提升的数据也是真实的。我们仿佛一夜之间都变成了“十倍工程师”。 但在这种虚幻的快感背后，一种隐秘的焦虑正在资深开发者群体中蔓延：离开 AI 提示词，你还能流畅地写出一个复杂的递归，或者手撸一个带有完整错误处理的 HTTP Client 吗？ 最近，我在技术社区看到一段发人深省的论述，它像一盆冷水，浇在了在这个狂热的 AI 时代： “AI is the fastest way to forget how to code and how to think.” （AI 是让你忘掉如何编程、忘掉如何思考的最快方式。） 这句话听起来很刺耳，但很真实。 如果我们习惯了让 AI 替我们思考，我们的大脑正在经历一场无声的“认知肌肉萎缩”。在 AI 时代，写下每一行代码依然重要。这不是一种复古的情怀，而是关乎我们职业生存的“认知保留”。 警惕“GPS 效应”：你是在驾驶，还是在被运送？ 心理学中有一个著名的“GPS 效应”：习惯了使用导航的人，海马体（负责空间记忆的脑区）活跃度会降低，久而久之，他们会逐渐丧失方向感，甚至在自家小区门口也会迷路。 编程也是一样。 学习和成长的本质，发生在“挣扎”的过程中。 当你为了设计一个类结构而绞尽脑汁，当你为了修复一个“竞态条件”而彻夜排查，你的大脑正在构建复杂的神经连接，正在建立对系统的“心智模型”。 如果你跳过了这个“挣扎”的过程，直接向 AI 索要答案： AI 变成了“代笔者（Author）”：它替你构建了心智模型。 你变成了“消费者（Consumer）”：你只负责 Copy [...]]]></description>
			<content:encoded><![CDATA[<p><img src="https://tonybai.com/wp-content/uploads/2025/ai-is-the-fastest-way-to-forget-how-to-code-1.png" alt="" /></p>
<p><a href="https://tonybai.com/2026/01/01/ai-is-the-fastest-way-to-forget-how-to-code">本文永久链接</a> &#8211; https://tonybai.com/2026/01/01/ai-is-the-fastest-way-to-forget-how-to-code</p>
<p>大家好，我是Tony Bai。</p>
<p>在 Copilot、Cursor、Claude Code等普及的这两年，编程似乎变得前所未有的轻松。</p>
<p>Tab 键一按，十行代码倾泻而出；回车一敲，整个函数自动补全；一个Prompt发出，一个项目的框架代码便完成了。那种多巴胺分泌的快感是真实的，效率提升的数据也是真实的。我们仿佛一夜之间都变成了“十倍工程师”。</p>
<p>但在这种虚幻的快感背后，一种隐秘的焦虑正在资深开发者群体中蔓延：<strong>离开 AI 提示词，你还能流畅地写出一个复杂的递归，或者手撸一个带有完整错误处理的 HTTP Client 吗？</strong></p>
<p>最近，我在技术社区看到一段发人深省的论述，它像一盆冷水，浇在了在这个狂热的 AI 时代：</p>
<p><img src="https://tonybai.com/wp-content/uploads/2025/ai-is-the-fastest-way-to-forget-how-to-code-2.png" alt="" /></p>
<blockquote>
<p><strong>“AI is the fastest way to forget how to code and how to think.”</strong><br />
  <strong>（AI 是让你忘掉如何编程、忘掉如何思考的最快方式。）</strong></p>
</blockquote>
<p>这句话听起来很刺耳，但很真实。</p>
<p>如果我们习惯了让 AI 替我们思考，我们的大脑正在经历一场无声的“认知肌肉萎缩”。在 AI 时代，<strong>写下每一行代码依然重要</strong>。这不是一种复古的情怀，而是关乎我们职业生存的<strong>“认知保留”</strong>。</p>
<p><img src="https://tonybai.com/wp-content/uploads/2025/paid/google-adk-in-action-qr.png" alt="" /></p>
<h2>警惕“GPS 效应”：你是在驾驶，还是在被运送？</h2>
<p>心理学中有一个著名的<strong>“GPS 效应”</strong>：习惯了使用导航的人，海马体（负责空间记忆的脑区）活跃度会降低，久而久之，他们会逐渐丧失方向感，甚至在自家小区门口也会迷路。</p>
<p>编程也是一样。</p>
<p><strong>学习和成长的本质，发生在“挣扎”的过程中。</strong></p>
<p>当你为了设计一个类结构而绞尽脑汁，当你为了修复一个“竞态条件”而彻夜排查，你的大脑正在构建复杂的神经连接，正在建立对系统的<strong>“心智模型”</strong>。</p>
<p>如果你跳过了这个“挣扎”的过程，直接向 AI 索要答案：</p>
<ul>
<li><strong>AI 变成了“代笔者（Author）”</strong>：它替你构建了心智模型。</li>
<li><strong>你变成了“消费者（Consumer）”</strong>：你只负责 Copy &amp; Paste。</li>
</ul>
<p>结果是：代码虽然跑通了，但你对系统组件之间的连接、潜在的边缘情况（Edge Cases）一无所知。你不再是代码的<strong>“作者”</strong>，你只是代码的<strong>“搬运工”</strong>。</p>
<p>一旦 AI 遇到它没见过的深水区，或者系统出现了一个隐蔽的 Bug，你会发现自己束手无策——因为你从未真正拥有过这段代码。</p>
<h2>重构契约：把 AI 当做“磨刀石”，而非“枪手”</h2>
<p>那么，我们要因噎废食，扔掉 AI 吗？当然不。</p>
<p>关键在于<strong>重构你与 AI 的协作契约</strong>。</p>
<p>核心原则只有一条：</p>
<p><strong>Use AI as a Reviewer, a Rubber Duck, a Teacher. Not as an Author.</strong><br />
（把它当作审查者、橡胶鸭、导师。绝不要把它当作代笔者。）</p>
<p>如果 AI 在替你思考，你在退步；如果 AI 在<strong>逼迫</strong>你思考得更深，你在进步。</p>
<p>以下是基于这个原则的 4 个<strong>深度思考工作流</strong>：</p>
<h3>1. 解释意图，而非索要实现</h3>
<p>不要直接丢一句“帮我写个鉴权中间件”。</p>
<p><strong>试着这样做：</strong> 你自己写出核心逻辑，然后对 AI 说：</p>
<blockquote>
<p>“这是我写的鉴权逻辑。请解释我为什么在这里使用了 Context 传递用户信息？这种写法符合 Go 语言的惯用范式吗？有没有更好的风格？”</p>
</blockquote>
<p><strong>收益：</strong> 强迫自己理清思路，利用 AI 验证你的设计直觉。</p>
<h3>2. 索要权衡(trade off)，而非标准答案</h3>
<p>不要问“在这个场景下我该用 Redis 还是 Memcached？”</p>
<p><strong>试着这样做：</strong></p>
<blockquote>
<p>“我倾向于使用 Redis，因为我们需要持久化。但在这个高并发场景下，使用 Redis 会带来哪些潜在的性能瓶颈或运维风险？请列出 Trade-offs。”</p>
</blockquote>
<p><strong>收益：</strong> AI 不再是给你喂饭，而是在陪你进行架构评审（Architecture Review）。</p>
<h3>3. 寻找盲区，挑战假设</h3>
<p>当你写完一段代码，觉得完美无缺时，把它扔给 AI：</p>
<blockquote>
<p>“这段代码在什么极端输入下会崩溃（Edge Cases）？我是否遗漏了某些并发安全问题？请像一个最挑剔的 Tech Lead 一样 Review 它。”</p>
</blockquote>
<p><strong>收益：</strong> 利用 AI 广博的知识库，填补你的认知盲区。</p>
<h3>4. 生成测试，而非生产代码</h3>
<p>这是一个最高阶的玩法。<strong>你自己写业务代码，让 AI 写测试用例。</strong></p>
<blockquote>
<p>“这是我实现的订单状态机。请为它编写一套覆盖率 100% 的单元测试，特别是针对状态回滚的异常场景。”</p>
</blockquote>
<p><strong>收益：</strong> 如果 AI 生成的测试跑通了，说明你的逻辑是自洽的；如果跑不通，或者 AI 根本理解不了你的代码，说明<strong>你</strong>没想清楚。</p>
<h2>小结：不要温和地走进那个良夜</h2>
<p>在 AI 时代，能够熟练调用 API 生成代码的人多如牛毛。</p>
<p>但能够<strong>独立构建复杂系统心智模型</strong>，并能驾驭 AI 进行<strong>深度架构推演</strong>的人，将变得极度稀缺。</p>
<p><strong>Writing code matters.</strong></p>
<p>写代码的过程，强迫你思考，强迫你大脑建立连接，强迫你理解系统是如何像齿轮一样咬合的。</p>
<p>请继续亲自写下那些核心的、关键的代码。</p>
<p>把 AI 当作你的<strong>磨刀石</strong>，让你的思维在与它的碰撞中变得更加锋利，而不是让它锈蚀你的大脑。</p>
<hr />
<p><strong>深度实战：构建“以人为本”的 AI 工作流</strong></p>
<p>道理大家都懂，但在高压的项目交付期，我们很容易滑向“让 AI 全自动生成”的舒适区。</p>
<p>如何建立一套<strong>强制性</strong>的工作流，既利用 AI 的效率，又保留人类的深度思考？</p>
<ul>
<li>如何在 Spec 文档中通过<strong>“伪代码”</strong>保留思考过程？</li>
<li>如何配置 <strong>Claude Code</strong>，让它默认扮演 Reviewer 而不是 Coder？</li>
<li>如何利用 <strong>SDD (Spec-Driven Development)</strong> 迫使自己在 Coding 前先进行完整的思维推演？</li>
</ul>
<p>如果你想掌握这套<strong>“不降智、反内卷”</strong>的高阶开发心法，欢迎关注我的极客时间专栏《<a href="http://gk.link/a/12EPd">AI原生开发工作流实战</a>》。</p>
<p>在这个专栏里，我不教你如何偷懒，我教你如何进化。我们将一起探索，如何在 AI 的加持下，成为更强大的<strong>Software Engineer</strong>，而不是更快的<strong>Typist</strong>。</p>
<p><strong>扫描下方卡片，开启你的认知升级之旅。</strong></p>
<p><img src="https://tonybai.com/wp-content/uploads/2025/ai-native-dev-workflow-qr.png" alt="" /></p>
<hr />
<p>你的Go技能，是否也卡在了“熟练”到“精通”的瓶颈期？</p>
<ul>
<li>想写出更地道、更健壮的Go代码，却总在细节上踩坑？</li>
<li>渴望提升软件设计能力，驾驭复杂Go项目却缺乏章法？</li>
<li>想打造生产级的Go服务，却在工程化实践中屡屡受挫？</li>
</ul>
<p>继《<a href="http://gk.link/a/10AVZ">Go语言第一课</a>》后，我的《<a href="http://gk.link/a/12yGY">Go语言进阶课</a>》终于在极客时间与大家见面了！</p>
<p>我的全新极客时间专栏 《<a href="http://gk.link/a/12yGY">Tony Bai·Go语言进阶课</a>》就是为这样的你量身打造！30+讲硬核内容，带你夯实语法认知，提升设计思维，锻造工程实践能力，更有实战项目串讲。</p>
<p>目标只有一个：助你完成从“Go熟练工”到“Go专家”的蜕变！ 现在就加入，让你的Go技能再上一个新台阶！</p>
<p><img src="https://tonybai.com/wp-content/uploads/course-card/iamtonybai-banner-2.gif" alt="" /></p>
<hr />
<p>商务合作方式：撰稿、出书、培训、在线课程、合伙创业、咨询、广告合作。如有需求，请扫描下方公众号二维码，与我私信联系。</p>
<p><img src="http://image.tonybai.com/img/tonybai/iamtonybai-wechat-qr.png" alt="" /></p>
<p style='text-align:left'>&copy; 2026, <a href='https://tonybai.com'>bigwhite</a>. 版权所有. </p>
]]></content:encoded>
			<wfw:commentRss>https://tonybai.com/2026/01/01/ai-is-the-fastest-way-to-forget-how-to-code/feed/</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>Rust 布道者Jon Gjengset深度访谈：在 AI 时代，我们该如何思考编程、职业与未来？</title>
		<link>https://tonybai.com/2025/10/30/jon-gjengset-rust-ai-future/</link>
		<comments>https://tonybai.com/2025/10/30/jon-gjengset-rust-ai-future/#comments</comments>
		<pubDate>Thu, 30 Oct 2025 10:06:12 +0000</pubDate>
		<dc:creator>bigwhite</dc:creator>
				<category><![CDATA[技术志]]></category>
		<category><![CDATA[AgenticAI]]></category>
		<category><![CDATA[AI]]></category>
		<category><![CDATA[AI时代]]></category>
		<category><![CDATA[Amazon]]></category>
		<category><![CDATA[AWS]]></category>
		<category><![CDATA[bluesky]]></category>
		<category><![CDATA[BorrowChecker]]></category>
		<category><![CDATA[Channel]]></category>
		<category><![CDATA[GC]]></category>
		<category><![CDATA[Go]]></category>
		<category><![CDATA[Golang]]></category>
		<category><![CDATA[Gopher]]></category>
		<category><![CDATA[Helsing]]></category>
		<category><![CDATA[Java]]></category>
		<category><![CDATA[JonGjengset]]></category>
		<category><![CDATA[Kotlin]]></category>
		<category><![CDATA[LLM]]></category>
		<category><![CDATA[MIT]]></category>
		<category><![CDATA[RAII]]></category>
		<category><![CDATA[Rust]]></category>
		<category><![CDATA[RustforRustaceans]]></category>
		<category><![CDATA[Rust布道者]]></category>
		<category><![CDATA[Stackoverflow]]></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[编译时]]></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=5332</guid>
		<description><![CDATA[本文永久链接 &#8211; https://tonybai.com/2025/10/30/jon-gjengset-rust-ai-future 大家好，我是Tony Bai。 他是 MIT 的博士，Rust 社区的知名布道者，《Rust for Rustaceans》作者，前亚马逊首席工程师，现欧洲顶尖 AI 防务公司 Helsing 的首席工程师。Jon Gjengset 的履历，本身就是一部现代软件工程师的精英成长史。 在一场深度访谈中，Gjengset 以其一贯的冷静与深刻，系统性地阐述了他对 Rust 语言的哲学、AI 带来的冲击、工程师的职业发展，乃至在美欧之间做出的人生选择。这既是一场关于技术的对话，更是一次关于如何在日益复杂的软件世界中，保持清醒思考和持续成长的思想盛宴。 Rust 的“预先头疼”哲学 连续九年被评为 Stack Overflow“最受喜爱”的语言，但实际使用率却仍在缓慢爬坡——Rust 的这种“叫好不叫座”现象背后，隐藏着其核心的设计哲学。Gjengset 将其精辟地概括为：“Rust 让你预先头疼 (gives you the headache up front)。” “你终究需要修复这些 bug。问题只在于，你愿意在编译时修复它们，还是在六个月后，当你的生产系统崩溃时再修复？” 这正是 Rust 与 Go、Java 等 GC 语言在开发者体验上的根本分歧。Rust 通过其著名的借用检查器 (Borrow Checker)，在编译期强制开发者思考清楚数据的生命周期和所有权，以换取运行时的极致安全和性能。 这个陡峭的学习曲线，也正是 Rust 最大的“护城河”。Gjengset 认为，学习 Rust 的过程，本质上就是在你的大脑中，强制安装并训练一个强大的静态分析器。这个“脑内借用检查器”一旦形成，其价值将溢出到你使用的所有语言中。 AI [...]]]></description>
			<content:encoded><![CDATA[<p><img src="https://tonybai.com/wp-content/uploads/2025/jon-gjengset-rust-ai-future-1.png" alt="" /></p>
<p><a href="https://tonybai.com/2025/10/30/jon-gjengset-rust-ai-future">本文永久链接</a> &#8211; https://tonybai.com/2025/10/30/jon-gjengset-rust-ai-future</p>
<p>大家好，我是Tony Bai。</p>
<p>他是 MIT 的博士，Rust 社区的知名布道者，《<a href="https://book.douban.com/subject/35520588/">Rust for Rustaceans</a>》作者，前亚马逊首席工程师，现欧洲顶尖 AI 防务公司 Helsing 的首席工程师。Jon Gjengset 的履历，本身就是一部现代软件工程师的精英成长史。</p>
<p><img src="https://tonybai.com/wp-content/uploads/2025/jon-gjengset-rust-ai-future-2.jpg" alt="" /></p>
<p>在一场<a href="https://www.youtube.com/watch?v=nOSxuaDgl3s">深度访谈</a>中，Gjengset 以其一贯的冷静与深刻，系统性地阐述了他对 Rust 语言的哲学、AI 带来的冲击、工程师的职业发展，乃至在美欧之间做出的人生选择。这既是一场关于技术的对话，更是一次关于如何在日益复杂的软件世界中，保持清醒思考和持续成长的思想盛宴。</p>
<p><img src="https://tonybai.com/wp-content/uploads/2025/paid/the-ultimate-guide-to-go-module-qr.png" alt="" /></p>
<h2>Rust 的“预先头疼”哲学</h2>
<p>连续九年被评为 Stack Overflow“最受喜爱”的语言，但实际使用率却仍在缓慢爬坡——Rust 的这种“叫好不叫座”现象背后，隐藏着其核心的设计哲学。Gjengset 将其精辟地概括为：<strong>“Rust 让你预先头疼 (gives you the headache up front)。”</strong></p>
<blockquote>
<p>“你终究需要修复这些 bug。问题只在于，你愿意在<strong>编译时</strong>修复它们，还是在<strong>六个月后</strong>，当你的生产系统崩溃时再修复？”</p>
</blockquote>
<p>这正是 Rust 与 Go、Java 等 GC 语言在开发者体验上的根本分歧。Rust 通过其著名的<strong>借用检查器 (Borrow Checker)</strong>，在编译期强制开发者思考清楚数据的生命周期和所有权，以换取运行时的极致安全和性能。</p>
<p>这个陡峭的学习曲线，也正是 Rust 最大的“护城河”。Gjengset 认为，学习 Rust 的过程，本质上就是在你的大脑中，强制安装并训练一个强大的静态分析器。这个“脑内借用检查器”一旦形成，其价值将溢出到你使用的所有语言中。</p>
<h2>AI 时代的“悲观”乐观主义</h2>
<p>当被问及 AI 是否会取代程序员时，Gjengset 展现了一种独特的“悲观的乐观主义”。</p>
<ul>
<li>悲观之处：“AI 被过度炒作了，因为它无法真正理解”</li>
</ul>
<p>他认为，当前由 LLM 驱动的 AI，其核心能力是<strong>模式复制与推断</strong>，而非真正的<strong>理解与推理 (understanding and reasoning)</strong>。</p>
<blockquote>
<p>“它们在预测行星的位置上表现出色，但它们无法推导出驱动其运动的底层物理原理。”</p>
</blockquote>
<p>他将这一观点延伸到编程领域：AI 擅长编写那些有大量现有范例可供学习的代码，但对于那些需要深刻理解类型系统、并发模型或创造全新抽象的创新性任务，AI 依然力不从心。</p>
<ul>
<li>乐观之处：“它只是更好的电锯”</li>
</ul>
<p>Gjengset 引用了一位开发者在 BlueSky 上的比喻，来阐述他对 AI 工具角色的看法：</p>
<blockquote>
<p>“因为 Agentic AI 的出现而辞去软件工程师的工作，就像因为电锯的发明而辞去木匠的工作一样，毫无道理。”</p>
</blockquote>
<p>AI 并非替代品，而是一个<strong>强大的工具</strong>，一个“加速器”。它将开发者从重复、繁琐的“模板式”工作中解放出来，让我们有更多时间去从事更高层次的、更具创造性的工作。</p>
<h2>工程师的职业选择 —— 从 AWS 到欧洲独角兽</h2>
<p>Gjengset 的职业路径，本身就是一部关于工程师如何在巨头与创业公司之间做出选择的生动教材。</p>
<ul>
<li>在亚马逊：自下而上的变革</li>
</ul>
<p>在 AWS，他的职责是构建和维护 Rust 的内部构建系统。他强调，Rust 在亚马逊的普及，并非一次自上而下的行政命令，而是一场<strong>由一线团队驱动的、自下而上的变革</strong>。团队选择 Rust 的核心驱动力，是为了解决 Java/Kotlin 在 GC 停顿下难以优化的<strong>尾部延迟 (tail latency)</strong> 问题。</p>
<ul>
<li>离开美国，回归欧洲：一次关于“社会”的选择</li>
</ul>
<p>2023 年，Gjengset 做出了一个令许多人意外的决定：离开美国，搬回欧洲。他坦言，这并非一个纯粹的职业选择，而是一个更深层次的、关于<strong>“社会” (society)</strong> 的选择。他的选择，为所有面临跨国职业机会的工程师提供了一个深刻的参考：<strong>职业选择，最终是个人价值观的体现。</strong></p>
<h2>对 Go 的犀利‘判词’——一场关于权衡的对话</h2>
<p>Gjengset 的故事与 Go 有着不解之缘——他最初的博士论文项目原型，正是用 Go 编写的。这段经历，让他对 Go 与 Rust 的哲学差异，有了最为直观和深刻的体悟。</p>
<h3>核心批评：“Go 忽略了自 70 年代以来的编程语言研究”</h3>
<p>当被问及“Rust 在哪些方面比 Go 更好”时，Gjengset 的回答直截了当，甚至有些“刺耳”：</p>
<blockquote>
<p>“哦，Rust 比 Go 更好，因为它有<strong>类型系统</strong>。这太简单了。Go 在被创造时，选择性地忽略了自 1970 年代以来几乎所有的编程语言研究成果。而 Rust 则决定从这些创新中学习。最终，你得到了一门更复杂，但写起来也<strong>有趣得多、表达力强得多</strong>的语言。对我来说，这就是最大的区别，也是我不想再用 Go 的原因。”</p>
</blockquote>
<p>这句犀利的批评，直指 Go 语言设计的核心权衡：<strong>Go 为了追求极致的“简单”，在语言的“表达力”上做出了巨大的妥协。</strong></p>
<p>Gjengset 认为，Rust 强大的类型系统（如 enum、模式匹配、Trait 系统）不仅仅是为了内存安全，更是为了让开发者能够在<strong>编译期</strong>，就对程序的行为建立起更强大的<strong>保证 (Guarantees)</strong>。他举例说，在 Rust 中可以利用类型系统创建 CoordinateInFrameA 这样的类型，从而在编译期就杜绝坐标系混用的错误，而这在 Go 中难以轻易实现。</p>
<h3>Go 的“nil 指针” vs. Rust 的“编译期保证”</h3>
<p>在向一个 Go 团队“推销”Rust 时，Gjengset 会说什么？</p>
<blockquote>
<p>“你的应用在运行时因为一个错误而崩溃，这感觉很糟糕吧？在 Rust 中，这种事发生的概率要小得多。”</p>
</blockquote>
<p>他认为，Go 开发者引以为傲的“我没有空指针异常”，其实只是将问题转化为了“nil 指针异常”。虽然 Go 通过 if err != nil 强制处理错误，但大量的业务逻辑错误，依然只能在运行时暴露。而 Rust 通过其类型系统和所有权模型，能将更多类别的错误扼杀在编译阶段。</p>
<h3>“脑内借用检查器”对 Gopher 的价值</h3>
<p>Gjengset 提出的一个极具启发性的观点是，学习 Rust 的思维模式，可以反哺我们的 Go 编程实践。一个内化了“借用检查”思想的 Gopher，会对以下问题更加敏感：</p>
<ul>
<li><strong>理解 Go 的<a href="https://tonybai.com/2021/05/24/understand-go-escape-analysis-by-example">逃逸分析</a></strong>：当你的“脑内借用检查器”告诉你“从函数返回一个局部变量的引用是不合法的”时，在 Go 的世界里，这意味着“哦，这个变量会<strong>逃逸</strong>到堆上，我应该思考一下这带来的性能影响”。</li>
<li><strong>编写更健壮的并发代码</strong>：虽然 Go 的 channel 提供了强大的并发同步机制，但对于通过指针共享数据等场景，一个关于数据所有权和生命周期的清晰心智模型，能帮助你从根本上避免数据竞争。</li>
</ul>
<h2>小结：给开发者的忠告 —— 跨越语言的智慧</h2>
<p>在访谈的最后，Gjengset 还分享了他对 C++ 等语言的看法，这些看法充满了辨证的智慧。</p>
<ul>
<li><strong>对 C++ 团队</strong>：“你已经通过 RAII 获得了部分内存安全，但你的<strong>并发安全</strong>呢？Rust 可以在编译期静态地排除数据竞争。”</li>
<li><strong>对所有开发者</strong>：不要害怕借用检查器。它虽然让你“预先头疼”，但它正在你的大脑中构建一个关于数据流的强大心智模型，这个模型将使你在使用<strong>任何</strong>语言时，都成为一个更优秀的程序员。</li>
</ul>
<p>Jon Gjengset 的这场访谈，远不止是一次对 Rust 的“布道”。它是一次关于<strong>工程权衡、技术信仰、职业战略和个人价值观</strong>的深度剖析。对于 Gopher 而言，这场来自 Rust 阵营的“他山之石”，深刻地揭示了 Go 在诞生之初所做出的核心权衡：<strong>用语言表达力的“舍”，换取工程效率和心智负担的“得”。</strong></p>
<p>理解这场对话，将使我们对自己手中的工具，有更清醒的认知和更深刻的敬畏。</p>
<p>资料链接：https://www.youtube.com/watch?v=nOSxuaDgl3s</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><strong>想系统学习Go，构建扎实的知识体系？</strong></p>
<p>我的新书《<a href="https://book.douban.com/subject/37499496/">Go语言第一课</a>》是你的首选。源自2.4万人好评的极客时间专栏，内容全面升级，同步至Go 1.24。首发期有专属五折优惠，不到40元即可入手，扫码即可拥有这本300页的Go语言入门宝典，即刻开启你的Go语言高效学习之旅！</p>
<p><img src="https://tonybai.com/wp-content/uploads/2025/go-primer-published-4.png" 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/30/jon-gjengset-rust-ai-future/feed/</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>智能时代临近：我眼中AI编程的现在与未来</title>
		<link>https://tonybai.com/2024/10/14/programming-in-ai-era/</link>
		<comments>https://tonybai.com/2024/10/14/programming-in-ai-era/#comments</comments>
		<pubDate>Mon, 14 Oct 2024 13:04:30 +0000</pubDate>
		<dc:creator>bigwhite</dc:creator>
				<category><![CDATA[思考控]]></category>
		<category><![CDATA[技术志]]></category>
		<category><![CDATA[Agent]]></category>
		<category><![CDATA[AI]]></category>
		<category><![CDATA[AR]]></category>
		<category><![CDATA[ChatGPT]]></category>
		<category><![CDATA[Claude]]></category>
		<category><![CDATA[Go]]></category>
		<category><![CDATA[Golang]]></category>
		<category><![CDATA[IDE]]></category>
		<category><![CDATA[livereload]]></category>
		<category><![CDATA[LLM]]></category>
		<category><![CDATA[matrix]]></category>
		<category><![CDATA[openai]]></category>
		<category><![CDATA[programming]]></category>
		<category><![CDATA[Prompt]]></category>
		<category><![CDATA[PromptEngineering]]></category>
		<category><![CDATA[Rust]]></category>
		<category><![CDATA[SaaS]]></category>
		<category><![CDATA[VR]]></category>
		<category><![CDATA[人工智能]]></category>
		<category><![CDATA[创新]]></category>
		<category><![CDATA[多模态]]></category>
		<category><![CDATA[大模型]]></category>
		<category><![CDATA[奥特曼]]></category>
		<category><![CDATA[尼奥]]></category>
		<category><![CDATA[异常处理]]></category>
		<category><![CDATA[强化学习]]></category>
		<category><![CDATA[快速思考]]></category>
		<category><![CDATA[思维链]]></category>
		<category><![CDATA[慢速思考]]></category>
		<category><![CDATA[推理层]]></category>
		<category><![CDATA[提示工程]]></category>
		<category><![CDATA[救世主]]></category>
		<category><![CDATA[智能时代]]></category>
		<category><![CDATA[服务即软件]]></category>
		<category><![CDATA[本质]]></category>
		<category><![CDATA[机器人]]></category>
		<category><![CDATA[架构师]]></category>
		<category><![CDATA[端到端]]></category>
		<category><![CDATA[红杉]]></category>
		<category><![CDATA[编程]]></category>
		<category><![CDATA[脑机接口]]></category>
		<category><![CDATA[设计师]]></category>
		<category><![CDATA[需求]]></category>
		<category><![CDATA[黑客帝国]]></category>

		<guid isPermaLink="false">https://tonybai.com/?p=4344</guid>
		<description><![CDATA[本文永久链接 &#8211; https://tonybai.com/2024/10/14/programming-in-ai-era 自2022年末ChatGPT发布以来，人工智能（AI）正在深刻地改变软件开发的格局。从简单的代码补全到复杂的逻辑生成，AI正逐渐成为程序员不可或缺的助手。最近，OpenAI首席执行官山姆·奥特曼在其个人博客中发表的文章《智能时代》(The Intelligence Age)更让我们深切体会到，超级智能似乎离我们越来越近了。 正如100年前的打孔卡编程方式与现今编程方式的天壤之别，如今的我们也难以完全预见超级AI时代的编程模式。尽管现阶段的大语言模型（如ChatGPT、Claude等）在AI辅助编程方面已经展现出强大的能力，并显著提升了开发效率，但它们仍面临诸多挑战。不过，与打孔卡时代的程序员相比，我们这一代程序员是幸运的，因为我们已经嗅到了超级AI的气息。 当前AI辅助编程的现状 目前，AI辅助编程主要有三种模式： IDE模式：通过使用工具（如Cursor等）智能分析代码上下文，仅需简单的TAB键操作即可生成代码片段甚至是完整代码，显著提高编程效率。 Prompt模式：开发者提供描述性的prompt，AI据此生成代码块，然后开发者将其整合到项目中。这种模式要求开发者对prompt撰写有较高的理解与能力。 Agent模式：在这种模式下，AI作为自主的编程助手，理解开发者的意图并主动规划(强化学习增强的思维链等)和执行任务。开发者可以与AI对话，提出问题或请求功能，而AI则基于上下文自动生成代码、测试用例，甚至进行调试。Agent模式更接近于超级AI的初级模拟，试图通过自然语言交互与上下文理解，模拟人类思维，自主规划并处理复杂编程任务。 虽然IDE和Agent模式本质上都是Prompt模式的变种，但Agent模式更像是对超级AI的初级尝试，使开发者能够更专注于高层设计，将重复性任务交给AI处理。 不过，这三种模式都属于初级辅助模式，虽然已经能显著提升开发效率。这些模式的辅助效能还与多种因素相关，比如： 人类提示工程(Prompt engineering )水平：开发者如何有效地与AI沟通需求，直接影响输出质量。 AI对不同编程语言的掌握和擅长程度：这与AI训练时使用的语料丰富程度和训练方法密切相关。日常实践中事实也证明，像Rust这样语法复杂的语言，AI生成的代码可能更容易出现编译错误。相比之下，Go语言生成的代码往往更容易直接运行。 编程任务的特性：不同类型的编程任务可能更适合不同的AI辅助方式。 注：随着AI在推理方面的提升(乃至形成独立的推理层)，“过提示工程”可能不仅无法提高推理性能，还有可能妨碍模型工作。也就是说对于推理能力越来越强的大模型，反倒是提示词越简洁越好，因为思维链都隐藏到了模型内部，如果再用思维链提示反而会适得其反。 当前AI的局限性与未来展望 当前的AI系统更像是一个知识数据库，主要基于已有的知识进行推理，与现实世界的互动能力仍然有限，如缺乏访问互联网和本地系统的能力。这种限制导致AI只能生成代码，却无法验证其逻辑是否正确或者能否编译运行。此外，AI与人类的交互手段仍相对初级，大多局限于文本、图片或语音的形式，这些方式在面对复杂需求时显得笨拙。 那么未来理想的AI辅助编程模式应该是什么样的呢？我认为应是端到端编程，即通过多种交互手段（自然语言、语音、图片以及将来的未知方式等）输入需求，AI直接输出已部署完毕且可正确运行的完整程序。在超级AI时代，这种编程模式将成为现实，届时AI与程序员的交互方式将迎来革命性变化。 我们可以将当前阶段称为”AI的过渡时代“。正如OpenAI的Sam Altman所预言那样，真正的智能时代可能还需要几千天才能到来。在这个超级AI出现的时代，端到端的编程模式可能才会真正实现。 根据Sequoia Capital的最新研究报告，AI技术正在从”快速思考”(System 1)向”慢速思考”(System 2)演进。System 1指的是快速、直觉性的反应，而System 2则涉及更深层次的推理和问题解决能力。这种演进正在推动一种新的”推理层”的发展，这可能是通向真正智能时代的关键一步： 来自Sequoia Capital的最新研究报告 超级AI时代的编程模式可能包括： 脑机接口：通过思维直接传达编程意图。 AR手势交互：在虚拟空间中操控代码组件，如钢铁侠电影中的场景。 多模态融合交互：结合语音、手势、眼动跟踪等多种方式。 自适应自然语言处理：AI能够理解和解析非结构化的自然语言，转换为代码逻辑。 这些技术的发展可能会让未来的编程体验更像是与高度智能的助手协作，而非单纯的工具使用。如今脑机接口、AR增强现界等技术也在快速演进，很可能与超级AI带来的智能时代同时到来。 程序员角色的转变 在超级AI时代，程序员的角色将发生显著的变化。程序员基本上不再编码，而是更多地转变为”系统架构师”、”AI协作者”和”创新推动者”。他们的工作会更多地涉及高层次的问题解决、创新思考和跨学科合作。技术知识仍然重要，但更重要的是理解业务需求、系统设计、伦理考量和用户体验等更广泛的技能。 Sequoia Capital的报告指出，随着AI技术的进步，软件开发正在从”软件即服务”(SaaS, Software as a Service)模式转向”服务即软件(Service as a Software”模式。这意味着AI应用不仅仅是提供软件工具，而是直接提供完整的服务解决方案。这种转变将极大地扩展AI应用的市场，从软件市场扩展到更广阔的服务市场： 来自Sequoia Capital的最新研究报告 注：怎么理解“服务即软件”（Service as a [...]]]></description>
			<content:encoded><![CDATA[<p><img src="https://tonybai.com/wp-content/uploads/programming-in-ai-era-1.png" alt="" /></p>
<p><a href="https://tonybai.com/2024/10/14/programming-in-ai-era">本文永久链接</a> &#8211; https://tonybai.com/2024/10/14/programming-in-ai-era</p>
<p>自2022年末ChatGPT发布以来，人工智能（AI）正在深刻地改变软件开发的格局。从简单的代码补全到复杂的逻辑生成，AI正逐渐成为程序员不可或缺的助手。最近，OpenAI首席执行官山姆·奥特曼在其个人博客中发表的文章<a href="https://ia.samaltman.com/">《智能时代》(The Intelligence Age)</a>更让我们深切体会到，超级智能似乎离我们越来越近了。</p>
<p>正如100年前的打孔卡编程方式与现今编程方式的天壤之别，如今的我们也难以完全预见超级AI时代的编程模式。尽管现阶段的大语言模型（如<a href="https://chatgpt.com/">ChatGPT</a>、<a href="https://claude.ai/">Claude</a>等）在AI辅助编程方面已经展现出强大的能力，并显著提升了开发效率，但它们仍面临诸多挑战。不过，与打孔卡时代的程序员相比，我们这一代程序员是幸运的，因为我们已经嗅到了超级AI的气息。</p>
<h2>当前AI辅助编程的现状</h2>
<p>目前，AI辅助编程主要有三种模式：</p>
<ol>
<li>
<p><strong>IDE模式</strong>：通过使用工具（如<a href="https://www.cursor.com/">Cursor</a>等）智能分析代码上下文，仅需简单的TAB键操作即可生成代码片段甚至是完整代码，显著提高编程效率。</p>
</li>
<li>
<p><strong>Prompt模式</strong>：开发者提供描述性的prompt，AI据此生成代码块，然后开发者将其整合到项目中。这种模式要求开发者对prompt撰写有较高的理解与能力。</p>
</li>
<li>
<p><strong>Agent模式</strong>：在这种模式下，AI作为自主的编程助手，理解开发者的意图并主动规划(强化学习增强的思维链等)和执行任务。开发者可以与AI对话，提出问题或请求功能，而AI则基于上下文自动生成代码、测试用例，甚至进行调试。Agent模式更接近于超级AI的初级模拟，试图通过自然语言交互与上下文理解，模拟人类思维，自主规划并处理复杂编程任务。</p>
</li>
</ol>
<p>虽然IDE和Agent模式本质上都是Prompt模式的变种，但Agent模式更像是对超级AI的初级尝试，使开发者能够更专注于高层设计，将重复性任务交给AI处理。</p>
<p>不过，这三种模式都属于初级辅助模式，虽然已经能显著提升开发效率。这些模式的辅助效能还与多种因素相关，比如：</p>
<ul>
<li>人类<a href="https://en.wikipedia.org/wiki/Prompt_engineering">提示工程(Prompt engineering )</a>水平：开发者如何有效地与AI沟通需求，直接影响输出质量。</li>
<li><a href="https://mp.weixin.qq.com/s/K8pao4_-YU77j7Ld1p7VLQ">AI对不同编程语言的掌握和擅长程度</a>：这与AI训练时使用的语料丰富程度和训练方法密切相关。日常实践中事实也证明，像<a href="https://tonybai.com/tag/rust">Rust</a>这样语法复杂的语言，AI生成的代码可能更容易出现编译错误。相比之下，<a href="https://tonybai.com/tag/go">Go语言</a>生成的代码往往更容易直接运行。</li>
<li>编程任务的特性：不同类型的编程任务可能更适合不同的AI辅助方式。</li>
</ul>
<blockquote>
<p>注：随着AI在推理方面的提升(乃至形成独立的推理层)，<a href="https://openai.com/index/learning-to-reason-with-llms/">“过提示工程”可能不仅无法提高推理性能，还有可能妨碍模型工作</a>。也就是说对于推理能力越来越强的大模型，反倒是提示词越简洁越好，因为思维链都隐藏到了模型内部，如果再用思维链提示反而会适得其反。</p>
</blockquote>
<h2>当前AI的局限性与未来展望</h2>
<p>当前的AI系统更像是一个知识数据库，主要基于已有的知识进行推理，与现实世界的互动能力仍然有限，如缺乏访问互联网和本地系统的能力。这种限制导致AI只能生成代码，却无法验证其逻辑是否正确或者能否编译运行。此外，AI与人类的交互手段仍相对初级，大多局限于文本、图片或语音的形式，这些方式在面对复杂需求时显得笨拙。</p>
<p>那么未来理想的AI辅助编程模式应该是什么样的呢？我认为应是<strong>端到端编程</strong>，即通过多种交互手段（自然语言、语音、图片以及将来的未知方式等）输入需求，AI直接输出<strong>已部署完毕且可正确运行的完整程序</strong>。在超级AI时代，这种编程模式将成为现实，届时AI与程序员的交互方式将迎来革命性变化。</p>
<p>我们可以将当前阶段称为”<strong>AI的过渡时代</strong>“。正如OpenAI的Sam Altman所预言那样，真正的智能时代可能还需要几千天才能到来。在这个超级AI出现的时代，端到端的编程模式可能才会真正实现。</p>
<p>根据<a href="https://www.sequoiacap.com/article/generative-ais-act-o1/">Sequoia Capital的最新研究报告</a>，AI技术正在从”快速思考”(System 1)向”慢速思考”(System 2)演进。System 1指的是快速、直觉性的反应，而System 2则涉及更深层次的推理和问题解决能力。这种演进正在推动一种新的”推理层”的发展，这可能是通向真正智能时代的关键一步：</p>
<p><img src="https://tonybai.com/wp-content/uploads/programming-in-ai-era-2.png" alt="" /><br />
<center>来自Sequoia Capital的最新研究报告</center></p>
<p>超级AI时代的编程模式可能包括：</p>
<ul>
<li><strong>脑机接口</strong>：通过思维直接传达编程意图。</li>
<li><strong>AR手势交互</strong>：在虚拟空间中操控代码组件，如钢铁侠电影中的场景。</li>
<li><strong>多模态融合交互</strong>：结合语音、手势、眼动跟踪等多种方式。</li>
<li><strong>自适应自然语言处理</strong>：AI能够理解和解析非结构化的自然语言，转换为代码逻辑。</li>
</ul>
<p>这些技术的发展可能会让未来的编程体验更像是与高度智能的助手协作，而非单纯的工具使用。如今脑机接口、AR增强现界等技术也在快速演进，很可能与超级AI带来的智能时代同时到来。</p>
<h2>程序员角色的转变</h2>
<p>在超级AI时代，程序员的角色将发生显著的变化。程序员基本上不再编码，而是更多地转变为”系统架构师”、”AI协作者”和”创新推动者”。他们的工作会更多地涉及高层次的问题解决、创新思考和跨学科合作。技术知识仍然重要，但更重要的是理解业务需求、系统设计、伦理考量和用户体验等更广泛的技能。</p>
<p>Sequoia Capital的报告指出，随着AI技术的进步，软件开发正在从”软件即服务”(SaaS, Software as a Service)模式转向”服务即软件(Service as a Software”模式。这意味着AI应用不仅仅是提供软件工具，而是直接提供完整的服务解决方案。这种转变将极大地扩展AI应用的市场，从软件市场扩展到更广阔的服务市场：</p>
<p><img src="https://tonybai.com/wp-content/uploads/programming-in-ai-era-3.png" alt="" /><br />
<center>来自Sequoia Capital的最新研究报告</center></p>
<blockquote>
<p>注：怎么理解“服务即软件”（Service as a Software）呢？想象一下，之前你的公司购买了一个人力资源管理的SaaS服务，这种购买仅仅让你能够使用其功能，但谁来操作这些功能呢？你的公司依然需要雇佣专门的HR人员来通过Web、GUI客户端或App进行管理。而“服务即软件”则将这两方面“打包”在一起。你无需再招聘专员来操作，只需提出你的需求即可。这种模式有点类似于现代的HR劳务外包，但不同的是，在智能时代，这种外包的真正执行者不再是“人”，而是AI应用和支持AI运行的算力。这样一来，你可以更高效地满足业务需求，而无需担心人力资源的管理和操作。</p>
</blockquote>
<p>随着超级AI的出现，我们还可能会看到AI系统不仅能辅助编程，还能自主编写、维护和优化代码，即AI的自主性。然而，这种高度自治的系统也可能面临复杂的自我管理问题。</p>
<p>借鉴《黑客帝国》中的概念，未来的AI系统可能会像一个巨大的自维护程序，但仍需要”异常处理程序”来解决一些无法自动修复的关键问题。在这个场景中，人类程序员可能扮演类似”尼奥”的角色，成为系统无法自行解决问题时的最后求助对象。</p>
<p><img src="https://tonybai.com/wp-content/uploads/programming-in-ai-era-4.png" alt="" /></p>
<p>这种人机协作模式可能类似于现代软件系统中的”live reload”概念：当AI遇到无法自动解决的问题时，它会寻求人类的帮助，重新加载并修复系统，从而保持整个生态系统的稳定运行。</p>
<h2>小结</h2>
<p>AI辅助编程技术正处于一个激动人心的过渡时期，距离完全自主的端到端编程还有一定距离。然而，随着技术进步和新型人机交互方式的到来，编程的本质将发生革命性的变化。未来的编程将是人类与AI共同塑造的领域，一个充满无限可能的智能时代。</p>
<hr />
<p><a href="https://public.zsxq.com/groups/51284458844544">Gopher部落知识星球</a>在2024年将继续致力于打造一个高品质的Go语言学习和交流平台。我们将继续提供优质的Go技术文章首发和阅读体验。同时，我们也会加强代码质量和最佳实践的分享，包括如何编写简洁、可读、可测试的Go代码。此外，我们还会加强星友之间的交流和互动。欢迎大家踊跃提问，分享心得，讨论技术。我会在第一时间进行解答和交流。我衷心希望Gopher部落可以成为大家学习、进步、交流的港湾。让我相聚在Gopher部落，享受coding的快乐! 欢迎大家踊跃加入！</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://gopherdaily.tonybai.com</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>
<li>Gopher Daily归档 &#8211; https://github.com/bigwhite/gopherdaily</li>
<li>Gopher Daily Feed订阅 &#8211; https://gopherdaily.tonybai.com/feed</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; 2024, <a href='https://tonybai.com'>bigwhite</a>. 版权所有. </p>
]]></content:encoded>
			<wfw:commentRss>https://tonybai.com/2024/10/14/programming-in-ai-era/feed/</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>十分钟入门Go语言</title>
		<link>https://tonybai.com/2023/02/23/learn-go-in-10-min/</link>
		<comments>https://tonybai.com/2023/02/23/learn-go-in-10-min/#comments</comments>
		<pubDate>Thu, 23 Feb 2023 14:16:52 +0000</pubDate>
		<dc:creator>bigwhite</dc:creator>
				<category><![CDATA[技术志]]></category>
		<category><![CDATA[Array]]></category>
		<category><![CDATA[As]]></category>
		<category><![CDATA[Bool]]></category>
		<category><![CDATA[byte]]></category>
		<category><![CDATA[cap]]></category>
		<category><![CDATA[Channel]]></category>
		<category><![CDATA[Cillum]]></category>
		<category><![CDATA[closure]]></category>
		<category><![CDATA[Concurrency]]></category>
		<category><![CDATA[Const]]></category>
		<category><![CDATA[defer]]></category>
		<category><![CDATA[docker]]></category>
		<category><![CDATA[error]]></category>
		<category><![CDATA[float64]]></category>
		<category><![CDATA[for]]></category>
		<category><![CDATA[generics]]></category>
		<category><![CDATA[Go]]></category>
		<category><![CDATA[go.mod]]></category>
		<category><![CDATA[go1.11]]></category>
		<category><![CDATA[go1.18]]></category>
		<category><![CDATA[Golang]]></category>
		<category><![CDATA[GOPATH]]></category>
		<category><![CDATA[GOPROXY]]></category>
		<category><![CDATA[goroutine]]></category>
		<category><![CDATA[Go语义第一课]]></category>
		<category><![CDATA[if]]></category>
		<category><![CDATA[import]]></category>
		<category><![CDATA[InfluxDB]]></category>
		<category><![CDATA[int]]></category>
		<category><![CDATA[Interface]]></category>
		<category><![CDATA[IO]]></category>
		<category><![CDATA[Is]]></category>
		<category><![CDATA[KenThompson]]></category>
		<category><![CDATA[Kubernetes]]></category>
		<category><![CDATA[len]]></category>
		<category><![CDATA[macos]]></category>
		<category><![CDATA[map]]></category>
		<category><![CDATA[Package]]></category>
		<category><![CDATA[pprof]]></category>
		<category><![CDATA[prometheus]]></category>
		<category><![CDATA[range]]></category>
		<category><![CDATA[reader]]></category>
		<category><![CDATA[receiver]]></category>
		<category><![CDATA[RobertGriesemer]]></category>
		<category><![CDATA[RobPike]]></category>
		<category><![CDATA[runtime]]></category>
		<category><![CDATA[RussCox]]></category>
		<category><![CDATA[select]]></category>
		<category><![CDATA[semver]]></category>
		<category><![CDATA[Slice]]></category>
		<category><![CDATA[String]]></category>
		<category><![CDATA[switch]]></category>
		<category><![CDATA[type]]></category>
		<category><![CDATA[typeparameter]]></category>
		<category><![CDATA[var]]></category>
		<category><![CDATA[variables]]></category>
		<category><![CDATA[Windows]]></category>
		<category><![CDATA[writer]]></category>
		<category><![CDATA[依赖管理]]></category>
		<category><![CDATA[函数]]></category>
		<category><![CDATA[切片]]></category>
		<category><![CDATA[变量]]></category>
		<category><![CDATA[常量]]></category>
		<category><![CDATA[并发]]></category>
		<category><![CDATA[开源]]></category>
		<category><![CDATA[指针]]></category>
		<category><![CDATA[接口]]></category>
		<category><![CDATA[控制语句]]></category>
		<category><![CDATA[数组]]></category>
		<category><![CDATA[方法]]></category>
		<category><![CDATA[极客时间]]></category>
		<category><![CDATA[泛型]]></category>
		<category><![CDATA[程序员]]></category>
		<category><![CDATA[类型]]></category>
		<category><![CDATA[类型断言]]></category>
		<category><![CDATA[类型约束]]></category>
		<category><![CDATA[结构体]]></category>
		<category><![CDATA[编程]]></category>
		<category><![CDATA[编程语言]]></category>
		<category><![CDATA[编译器]]></category>
		<category><![CDATA[语义版本]]></category>
		<category><![CDATA[运行时]]></category>
		<category><![CDATA[闭包]]></category>

		<guid isPermaLink="false">https://tonybai.com/?p=3808</guid>
		<description><![CDATA[本文永久链接 &#8211; https://tonybai.com/2023/02/23/learn-go-in-10-min 本文旨在带大家快速入门Go语言，期望小伙伴们在花费十分钟左右通读全文后能对Go语言有一个初步的认知，为后续进一步深入学习Go奠定基础。 本文假设你完全没有接触过Go，你可能是一名精通其他编程语言的程序员，也可能是毫无编程经验、刚刚想转行为码农的热血青年。 编程简介 编程就是生产可在计算机上执行的程序的过程(如下图)。在这个过程中，程序员是“劳动力”，编程语言是工具，可执行的程序是生产结果。而Go语言就是程序员在编程生产过程中使用的一种优秀生产工具。 作为“劳动力”的程序员在这个过程中要做的就是使用某种编程语言作为生产工具，将事先设计好的执行逻辑组织和表达出来，这与一个作家将其大脑中设计好的故事情节用人类语言组织和书写在纸上的过程颇为类似(如下图)。 通过这个类比来看，学习一门编程语言，就好比学习一门人类语言，其词汇和语法将是我们的主要学习内容，本文就将围绕Go语言的主要“词汇”和语法形式进行快速说明。 Go简介 Go语言是由Google公司的三位大神级程序员Robert Griesemer、Rob Pike和Ken Thompson在2007年共同开发的一种新的后端编程语言，2009年，Go语言宣布开源。 Go语言的特点是简单易学、静态类型、编译速度快，运行效率高，代码简洁，并且原生支持并发编程。它还支持自动内存管理，可以让开发者更加专注于编程本身，而不用担心内存泄漏的问题。此外，Go语言还支持多核处理器，可以更好地利用多核处理器的优势，提高程序的运行效率。 经过十多年的发展，Go语言现在已经成为一种流行的编程语言，它可以用于开发各种应用程序，包括Web应用、网络服务、系统管理工具、移动应用、游戏开发、数据库管理等。Go语言常用于构建大型分布式系统，以及构建高性能的服务器端应用程序。Go为当前的云原生计算时代开发了一批“杀手级”应用，包括Docker、Kubernetes、Prometheus、InfluxDB、Cilium等。 安装Go Go是静态语言，需要先编译，再执行，因此在开发Go程序之前，我们首先需要安装Go编译器以及相关工具链。安装的步骤很简单： 从Go官网下载最新版本的Go语言安装包 &#8211; https://go.dev/dl/ 解压安装包，并将其复制到您想要安装的位置，例如：/usr/local/go；如果是Windows、MacOS平台，也可以下载图形化安装的安装包； 设置环境变量，将Go语言的安装路径添加到PATH变量中； 打开终端，输入go version，检查Go语言是否安装成功。如输出类似下面的内容，则表明安装成功！ $go version go version go1.20 darwin/amd64 注：位于中国大陆的开发者们还需要一个额外的设置：export GOPROXY=&#8217;https://goproxy.cn&#8217;或将这个设置置于shell配置文件(比如.bashrc)中并使之生效。 第一个Go程序：Hello World 建立一个新目录，并在其中创建新文件helloworld.go，用任意编辑器打开helloworld.go，输入下面Go源码： //helloworld.go package main import "fmt" func main() { fmt.Println("Hello, World!") } Go支持直接运行某个源文件： $go run helloworld.go Hello, World! 但通常我们会先编译这个源文件(helloworld.go)，生成可执行的二进制程序(./helloworld)，然后再运行它： $go [...]]]></description>
			<content:encoded><![CDATA[<p><img src="https://tonybai.com/wp-content/uploads/learn-go-in-10-min-1.png" alt="" /></p>
<p><a href="https://tonybai.com/2023/02/23/learn-go-in-10-min">本文永久链接</a> &#8211; https://tonybai.com/2023/02/23/learn-go-in-10-min</p>
<p>本文旨在带大家快速入门Go语言，期望小伙伴们在花费十分钟左右通读全文后能对Go语言有一个初步的认知，为后续进一步深入学习Go奠定基础。</p>
<p>本文假设你完全没有接触过Go，你可能是一名精通其他编程语言的程序员，也可能是毫无编程经验、刚刚想转行为码农的热血青年。</p>
<h2>编程简介</h2>
<p>编程就是<strong>生产可在计算机上执行的程序的过程(如下图)</strong>。在这个过程中，程序员是“劳动力”，编程语言是工具，可执行的程序是生产结果。而Go语言就是程序员在编程生产过程中使用的一种优秀生产工具。</p>
<p><img src="https://tonybai.com/wp-content/uploads/learn-go-in-10-min-2.png" alt="" /></p>
<p>作为“劳动力”的程序员在这个过程中要做的就是使用某种编程语言作为生产工具，将事先设计好的执行逻辑组织和表达出来，这与一个作家将其大脑中设计好的故事情节用人类语言组织和书写在纸上的过程颇为类似(如下图)。</p>
<p><img src="https://tonybai.com/wp-content/uploads/learn-go-in-10-min-3.png" alt="" /></p>
<p>通过这个类比来看，学习一门编程语言，就好比学习一门人类语言，其词汇和语法将是我们的主要学习内容，本文就将围绕Go语言的主要“词汇”和语法形式进行快速说明。</p>
<h2>Go简介</h2>
<p>Go语言是由Google公司的三位大神级程序员Robert Griesemer、Rob Pike和Ken Thompson在2007年共同开发的一种新的后端编程语言，2009年，Go语言宣布开源。</p>
<p>Go语言的特点是简单易学、静态类型、编译速度快，运行效率高，代码简洁，并且原生支持并发编程。它还支持自动内存管理，可以让开发者更加专注于编程本身，而不用担心内存泄漏的问题。此外，Go语言还支持多核处理器，可以更好地利用多核处理器的优势，提高程序的运行效率。</p>
<p><a href="https://tonybai.com/2022/11/11/go-opensource-13-years">经过十多年的发展</a>，Go语言现在已经成为一种流行的编程语言，它可以用于开发各种应用程序，包括Web应用、网络服务、系统管理工具、移动应用、游戏开发、数据库管理等。Go语言常用于构建大型分布式系统，以及构建高性能的服务器端应用程序。Go为当前的云原生计算时代开发了一批“杀手级”应用，包括Docker、Kubernetes、Prometheus、InfluxDB、<a href="https://cilium.io">Cilium</a>等。</p>
<h2>安装Go</h2>
<p>Go是静态语言，需要先编译，再执行，因此在开发Go程序之前，我们首先需要安装Go编译器以及相关工具链。安装的步骤很简单：</p>
<ul>
<li>从<a href="https://go.dev/dl">Go官网下载</a>最新版本的Go语言安装包 &#8211; https://go.dev/dl/</li>
<li>解压安装包，并将其复制到您想要安装的位置，例如：/usr/local/go；如果是Windows、MacOS平台，也可以下载图形化安装的安装包；</li>
<li>设置环境变量，将Go语言的安装路径添加到PATH变量中；</li>
<li>打开终端，输入go version，检查Go语言是否安装成功。如输出类似下面的内容，则表明安装成功！</li>
</ul>
<pre><code>$go version
go version go1.20 darwin/amd64
</code></pre>
<blockquote>
<p>注：位于中国大陆的开发者们还需要一个额外的设置：export GOPROXY=&#8217;https://goproxy.cn&#8217;或将这个设置置于shell配置文件(比如.bashrc)中并使之生效。</p>
</blockquote>
<h2>第一个Go程序：Hello World</h2>
<p>建立一个新目录，并在其中创建新文件helloworld.go，用任意编辑器打开helloworld.go，输入下面Go源码：</p>
<pre><code>//helloworld.go

package main

import "fmt"

func main() {
    fmt.Println("Hello, World!")
}
</code></pre>
<p>Go支持直接运行某个源文件：</p>
<pre><code>$go run helloworld.go
Hello, World!
</code></pre>
<p>但通常我们会先编译这个源文件(helloworld.go)，生成可执行的二进制程序(./helloworld)，然后再运行它：</p>
<pre><code>$go build -o helloworld helloworld.go
$./helloworld
Hello, World!
</code></pre>
<h2>Go包(package)</h2>
<p>Go包是Go语言中的一种封装技术，它可以将一组Go语言源文件组织成一个可重用的单元，以便在其他Go程序中使用。同属于一个Go包的所有源文件放在一个目录下，并且按惯例该目录的名字与包名相同。以Go标准库的io包为例，其包内的源文件列表如下：</p>
<pre><code>// $GOROOT/src/io目录下的文件列表：
io.go
multi.go
pipe.go
</code></pre>
<p>Go包也是Go编译的基本单元，Go编译器可以将包编译为可执行文件(如何该包为main包，且包含main函数实现)，也可以编译为可重用的库文件(.a)。</p>
<h3>包声明</h3>
<p>Go包的声明通常是在每个Go源文件的开头，使用关键字package进行声明，例如：</p>
<pre><code>// mypackage.go
package mypackage

... ...
</code></pre>
<p>package的名字按惯例通常为全小写的单个单词或缩略词，比如io、net、os、fmt、strconv、bytes等。</p>
<h3>导入Go包</h3>
<p>如果要复用已有的Go包，我们需要在源码中导入该包。要导入Go包，可以使用import关键字，例如：</p>
<pre><code>import "fmt"                    // 导入标准库的fmt包

import "github.com/spf13/pflag" // 导入spf13开源的pflag包

import _ "net/http/pprof"       // 导入标准库net/http/pprof包，
                                // 但不显式使用该包中的类型、变量、函数等标识符

import myfmt "fmt"              // 将导入的包重命名为myfmt
</code></pre>
<h2>Go模块</h2>
<p>Go模块(module)是Go语言在<a href="https://tonybai.com/2018/11/19/some-changes-in-go-1-11">1.11版本</a>中引入的新特性，Go module是一组相关的Go package的集合，这个包集合被当做一个独立的单元进行统一版本管理。Go module这种新的<strong>依赖管理机制</strong>可以让开发者更轻松地管理Go语言项目的依赖关系，并且可以更好地支持多版本的依赖管理。在具有实用价值的Go项目中，我们都会使用Go module进行依赖管理。Go module有版本之分，Go module的版本依赖关系是建立在对<a href="https://semver.org/">语义版本(semver)</a>严格遵守的前提下的。</p>
<p>Go使用go.mod文件来精确记录依赖关系要求，下面是go.mod中依赖关系的操作方法：</p>
<pre><code>$go mod init demo // 创建一个module root为demo的go.mod
$go mod init github.com/bigwhite/mymodule // 创建一个module root为github.com/bigwhite/mymodule的go.mod

$go get github.com/bigwhite/foo@latest  // 向go.mod中添加一个依赖包github.com/bigwhite/foo的最新版本
$go get github.com/bigwhite/foo         // 与上面命令等价
$go get github.com/bigwhite/foo@v1.2.3  // 显式指定要获取v1.2.3版本

$go mod tidy   // 自动添加缺失的依赖包和清理不用的依赖包
$go mod verify // 确认所有依赖都有效
</code></pre>
<h2>Go最小项目结构</h2>
<p>Go官方并没有规定Go项目的标准结构布局，下面是Go核心团队技术负责人Russ Cox推荐的Go最小项目结构：</p>
<pre><code>// 在Go项目仓库根路径下

- go.mod
- LICENSE
- README
- xx.go
- yy.go
... ...
</code></pre>
<p>或</p>
<pre><code>// 在Go项目仓库根路径下

- go.mod
- LICENSE
- README
- package1/
    - package1.go
- package2/
    - package2.go
... ...
</code></pre>
<h2>变量</h2>
<p>Go语言有两种变量声明方式：</p>
<ul>
<li>使用var关键字</li>
</ul>
<p>使用var关键字进行声明的方式适合所有场合。</p>
<pre><code>var a int     // 声明一个int型变量a，初值为0
var b int = 5 // 声明一个int型变量b，初值为5
var c = 6     // Go会根据右值自动为变量c的赋予默认类型，默认的整型为int

var (         // 我们可以将变量声明统一放置在一个var块中，这与上面的声明方式等价
    a int
    b int = 5
    c = 6
)
</code></pre>
<blockquote>
<p>注：Go变量声明采用变量在前，类型在后的方式，这与C、C++、Java等静态编程语言有较大不同。</p>
</blockquote>
<ul>
<li>使用短声明方式声明变量</li>
</ul>
<pre><code>a := 5       // 声明一个变量a，Go会根据右值自动为变量a的赋予默认类型，默认的整型为int
s := "hello" // 声明一个变量s，Go会根据右值自动为变量s的赋予默认类型，默认的字符串类型为string
</code></pre>
<blockquote>
<p>注：这种声明方式仅限于在函数或方法内使用，不能用于声明包级变量或全局变量。</p>
</blockquote>
<h2>常量</h2>
<p>Go语言的常量使用const关键字进行声明：</p>
<pre><code>const a int       // 声明一个int型常量a，其值为0
const b int = 5   // 声明一个int型常量b，其值为5
const c = 6       // 声明一个常量c，Go会根据右值自动为常量c的赋予默认类型，默认的整型为int
const s = "hello" // 声明一个常量s，Go会根据右值自动为常量s的赋予默认类型，默认的字符串类型为string

const (           // 我们可以将常量声明统一放置在一个const块中，这与上面的声明方式等价
    a int
    b int = 5
    c = 6
    s = "hello"
)
</code></pre>
<h2>类型</h2>
<p>Go原生内置了多种基本类型与复合类型。</p>
<h3>基本类型</h3>
<p>Go原生支持的基本类型包括布尔型、数值类型（整型、浮点型、复数类型）、字符串类型，下面是一些示例：</p>
<pre><code>bool  // 布尔类型，默认值false

uint     // 架构相关的无符号整型，64位平台上其长度为8字节
int      // 架构相关的有符号整型，64位平台上其长度为8字节
uintptr  // 架构相关的用于表示指针值的类型，它是一个无符号的整数，大到足以存储一个任意类型的指针的值

uint8    // 架构无关的8位无符号整型
uint16   // 架构无关的16位无符号整型
uint32   // 架构无关的32位无符号整型
uint64   // 架构无关的64位无符号整型

int8     // 架构无关的8位有符号整型
int16    // 架构无关的16位有符号整型
int32    // 架构无关的32位有符号整型
int64    // 架构无关的64位有符号整型

byte     // uint8类型的别名
rune     // int32类型的别名，用于表示一个unicode字符(码点)

float32     // 单精度浮点类型，满足IEEE-754规范
float64     // 双精度浮点类型，满足IEEE-754规范

complex64   // 复数类型，其实部和虚部均为float32浮点类型
complex128  // 复数类型，其实部和虚部均为float64浮点类型

string      // 字符串类型，默认值为""
</code></pre>
<blockquote>
<p>我们可以使用预定义函数complex来构造复数类型，比如：complex(1.0, -1.4)构造的复数为1 &#8211; 1.4i。</p>
</blockquote>
<h3>复合类型</h3>
<p>Go原生支持的复合类型包括数组（array）、切片(slice)、结构体(struct)、指针(pointer)、函数(function)、接口(interface)、map、channel。</p>
<h4>数组类型</h4>
<p><img src="https://tonybai.com/wp-content/uploads/learn-go-in-10-min-4.png" alt="" /></p>
<p>数组类型是一组同构类型元素组成的连续体，它具有固定的长度(length)，不能动态伸缩：</p>
<pre><code>[8]int      // 一个元素类型为int、长度为16的数组类型
[32]byte    // 一个元素类型为byte、长度为32的数组类型
[2]string   // 一个元素类型为string、长度为2的数组类型
[N]T        // 一个元素类型为T、长度为N的数组类型
</code></pre>
<p>通过预定义函数len可以得到数组的长度：</p>
<pre><code>var a = [8]int{11, 12, 13, 14, 15, 16, 17, 18}
println(len(a)) // 8
</code></pre>
<p>通过数组下标(从0开始)可以直接访问到数组中的任意元素：</p>
<pre><code>println(a[0]) // 11
println(a[2]) // 13
println(a[7]) // 18
</code></pre>
<p>Go支持声明多维数组，即数组的元素类型依然为数组类型：</p>
<pre><code>[2][3][5]float64  // 一个多维数组类型，等价于[2]([3]([5]float64))
</code></pre>
<h4>切片类型</h4>
<p><img src="https://tonybai.com/wp-content/uploads/learn-go-in-10-min-5.png" alt="" /></p>
<p>切片类型与数组类型类似，也是同构类型元素的连续体。不同的是切片类型的长度可变，我们在声明切片类型时无需传入长度属性：</p>
<pre><code>[]int       // 一个元素类型为int的切片类型
[]string    // 一个元素类型为string的切片类型
[]T         // 一个元素类型为T的切片类型
[][][]float64 // 多维切片类型，等价于[]([]([]float64))
</code></pre>
<p>通过预定义函数len可以得到切片的当前长度：</p>
<pre><code>var sl = []int{11, 12} // 一个元素类型为int的切片，其长度(len)为2, 其值为[11 12]
println(len(sl)) // 2
</code></pre>
<p>切片还有一个属性，那就是容量，通过预定义函数cap可以获得其容量值：</p>
<pre><code>println(cap(sl)) // 2
</code></pre>
<p>和数组不同，切片可以动态伸缩，Go会根据元素的数量动态对切片容量进行扩展。我们可以通过append函数向切片追加元素：</p>
<pre><code>sl = append(sl, 13)     // 向sl中追加新元素，操作后sl为[11 12 13]
sl = append(sl, 14)     // 向sl中追加新元素，操作后sl为[11 12 13 14]
sl = append(sl, 15)     // 向sl中追加新元素，操作后sl为[11 12 13 14 15]
println(len(sl), cap(sl)) // 5 8 追加后切片容量自动扩展为8
</code></pre>
<p>和数组一样，切片也是使用下标直接访问其中的元素：</p>
<pre><code>println(sl[0]) // 11
println(sl[2]) // 13
println(sl[4]) // 15
</code></pre>
<h4>结构体类型</h4>
<p>Go的结构体类型是一种异构类型字段的聚合体，它提供了一种通用的、对实体对象进行聚合抽象的能力。下面是一个包含三个字段的结构体类型：</p>
<pre><code>struct {
    name string
    age  int
    gender string
}
</code></pre>
<p>我们通常会给这样的一个结构体类型起一个名字，比如下面的Person：</p>
<pre><code>type Person struct {
    name string
    age  int
    gender string
}
</code></pre>
<p>下面声明了一个Person类型的变量：</p>
<pre><code>var p = Person {
    name: "tony bai",
    age: 20,
    gender: "male",
}
</code></pre>
<p>我们可以通过p.FieldName来访问结构体中的字段：</p>
<pre><code>println(p.name) // tony bai
p.age = 21
</code></pre>
<p>结构体类型T的定义中可以包含类型为&#42;T的字段成员，但不能递归包含T类型的字段成员：</p>
<pre><code>type T struct {
    ... ...
    p *T    // ok
    t T     // 错误：递归定义
}
</code></pre>
<p>Go结构体亦可以在定义中嵌入其他类型：</p>
<pre><code>type F struct {
    ... ...
}

type MyInt int

type T struct {
    MyInt
    F
    ... ...
}
</code></pre>
<p>嵌入类型的名字将作为字段名：</p>
<pre><code>var t = T {
    MyInt: 5,
    F: F {
        ... ...
    },
}

println(t.MyInt) // 5
</code></pre>
<p>Go支持不包含任何字段的空结构体：</p>
<pre><code>struct{}
type Empty struct{}        // 一个空结构体类型
</code></pre>
<p>空结构体类型的大小为0，这在很多场景下很有用(省去了内存分配的开销)：</p>
<pre><code>var t = Empty{}
println(unsafe.Sizeof(t)) // 0
</code></pre>
<h4>指针类型</h4>
<p>int类型对应的指针类型为&#42;int，推而广之T类型对应的指针类型为&#42;T。和非指针类型不同，指针类型变量存储的是内存单元的地址，&#42;T指针类型变量的大小与T类型大小无关，而是和系统地址的表示长度有关。</p>
<pre><code>*int     // 一个int指针类型
*[4]byte // 一个[4]byte数组指针类型

var a = 6
var p *T // 声明一个T类型指针变量p，默认值为nil
p = &amp;a   // 用变量a的内存地址给指针变量p赋值
*p = 7   // 指针解引用，通过指针p将变量a的值由6改为7

n := new(int)  // 预定义函数返回一个*int类型指针
arr := new([4]int)  // 使用预定义函数new分配一个[4]int数组并返回一个*[4]int类型指针
</code></pre>
<h4>map类型</h4>
<p>map是Go语言提供的一种抽象数据类型，它表示一组无序的键值对，下面定义了一组map类型：</p>
<pre><code>map[string]int                // 一个key类型为string，value类型为int的map类型
map[*T]struct{ x, y float64 } // 一个key类型为*T，value类型为struct{ x, y float64 }的map类型
map[string]interface{}        // 一个key类型为string，value类型为interface{}的map类型
</code></pre>
<p>我们可以用map字面量或make来创建一个map类型实例：</p>
<pre><code>var m = map[string]int{}      // 声明一个map[string]int类型变量并初始化
var m1 = make(map[string]int) // 与上面的声明等价
var m2 = make(map[string]int, 100) // 声明一个map[string]int类型变量并初始化，其初始容量建议为100
</code></pre>
<p>操作map变量的方法也很简单：</p>
<pre><code>m["key1"] = 5  // 添加/设置一个键值对
v, ok := m["key1"]  // 获取“key1”这个键的值，如果存在，则其值存储在v中，ok为true
delete(m, "key1") // 从m这个map中删除“key1”这个键以及其对应的值
</code></pre>
<h4>其他类型</h4>
<p>函数、接口、channel类型在后面有详细说明。</p>
<h3>自定义类型</h3>
<p>使用type关键字可以实现自定义类型：</p>
<pre><code>type T1 int         // 定义一个新类型T1，其底层类型(underlying type)为int
type T2 string      // 定义一个新类型T2，其底层类型为string
type T3 struct{     // 定义一个新类型T3，其底层类型为一个结构体类型
    x, y int
    z string
}
type T4 []float64   // 定义一个新类型T4，其底层类型为[]float64切片类型
type T5 T4          // 定义一个新类型T5，其底层类型为[]float64切片类型
</code></pre>
<p>Go也支持为类型定义别名(alias)，其形式如下；</p>
<pre><code>type T1 = int       // 定义int的类型别名为T1，T1与int等价
type T2 = string    // 定义string的类型别名为T2，T2与string等价
type T3 = T2        // 定义T的类型别名为T3，T3与T2等价，也与string等价
</code></pre>
<h3>类型转换</h3>
<p>Go不支持隐式自动转型，如果要进行类型转换操作，我们必须显式进行，即便两个类型的底层类型相同也需如此：</p>
<pre><code>type T1 int
type T2 int
var t1 T1
var n int = 5
t1 = T1(n)      // 显式将int类型变量转换为T1类型
var t2 T2
t2 = T2(t1)     // 显式将T1类型变量转换为T2类型
</code></pre>
<p>Go很多原生类型支持相互转换：</p>
<pre><code>// 数值类型的相互转换

var a int16 = 16
b := int32(a)
c := uint16(a)
f := float64(a)

// 切片与数组的转换(Go 1.17版本及后续版本支持)

var a [3]int = [3]int([]int{1,2,3}) // 切片转换为数组
var pa *[3]int = (*[3]int)([]int{1,2,3}) // 切片转换为数组指针
sl := a[:] // 数组转换为切片

// 字符串与切片的相互转换

var sl = []byte{'h', 'e','l', 'l', 'o'}
var s = string(sl) // s为hello
var sl1 = []byte(s) // sl1为['h' 'e' 'l' 'l' 'o']
string([]rune{0x767d, 0x9d6c, 0x7fd4})  // []rune切片到string的转换
</code></pre>
<h2>控制语句</h2>
<p>Go提供了常见的控制语句，包括条件分支(if)、循环语句(for)和选择分支语句(switch)。</p>
<h3>条件分支语句</h3>
<pre><code>// if ...

if a == 1 {
    ... ...
}

// if - else if - else

if a == 1 {

} else if b == 2 {

} else {

}

// 带有条件语句自用变量
if a := 1; a != 0 {

}

// if语句嵌套

if a == 1 {
    if b == 2 {

    } else if c == 3 {

    } else {

    }
}
</code></pre>
<h3>循环语句</h3>
<pre><code>// 经典循环

for i := 0; i &lt; 10; i++ {
    ...
}

// 模拟while ... do

for i &lt; 10 {

}

// 无限循环

for {

}

// for range

var s = "hello"
for i, c := range s {

}

var sl = []int{... ...}
for i, v := range sl {

}

var m = map[string]int{}
for k, v := range m {

}

var c = make(chan int, 100)
for v := range c {

}
</code></pre>
<h3>选择分支语句</h3>
<pre><code>var n = 5
switch n {
    case 0, 1, 2, 3:
        s1()
    case 4, 5, 6, 7:
        s2()
    default: // 默认分支
        s3()
}

switch n {
    case 0, 1:
        fallthrough  // 显式告知执行下面分支的动作
    case 2, 3:
        s1()
    case 4, 5, 6, 7:
        s2()
    default:
        s3()
}

switch x := f(); {
    case x &lt; 0:
        return -x
    default:
        return x
}

switch {
    case x &lt; y:
        f1()
    case x &lt; z:
        f2()
    case x == 4:
        f3()
}
</code></pre>
<h2>函数</h2>
<p>Go使用func关键字来声明一个函数：</p>
<pre><code>func greet(name string) string {
    return fmt.Sprintf("Hello %s", name)
}
</code></pre>
<p>函数由函数名、可选的参数列表和返回值列表组成。Go函数支持返回多个返回值，并且我们通常将表示错误值的返回类型放在返回值列表的最后面：</p>
<pre><code>func Atoi(s string) (int, error) {
    ... ...
    return n, nil
}
</code></pre>
<p>在Go中函数是一等公民，因此函数自身也可以作为参数或返回值：</p>
<pre><code>func MultiplyN(n int) func(x int) int {
  return func(x int) int {
    return x * n
  }
}
</code></pre>
<p>像上面MultiplyN函数中定义的匿名函数func(x int) int，它的实现中引用了它的外围函数MultiplyN的参数n，这样的匿名函数也被称为<strong>闭包(closure)</strong>。</p>
<p>说到函数，我们就不能不提defer。在某函数F调用的前面加上defer，该函数F的执行将被“延后”至其调用者A结束之后：</p>
<pre><code>func F() {
    fmt.Println("call F")
}

func A() {
    fmt.Println("call A")
    defer F()
    fmt.Println("exit A")
}

func main() {
    A()
}
</code></pre>
<p>上面示例输出：</p>
<pre><code>call A
exit A
call F
</code></pre>
<p>在一个函数中可以多次使用defer：</p>
<pre><code>func B() {
    defer F()
    defer G()
    defer H()
}
</code></pre>
<p>被defer修饰的函数将按照“先入后出”的顺序在B函数结束后被调用，上面B函数执行后将输出：</p>
<pre><code>call H
call G
call F
</code></pre>
<h2>方法</h2>
<p>方法是带有receiver的函数。下面是Point类型的一个方法Length：</p>
<pre><code>type Point struct {
    x, y float64
}

func (p Point) Length() float64 {
    return math.Sqrt(p.x * p.x + p.y * p.y)
}
</code></pre>
<p>而在func关键字与函数名之间的部分便是receiver。这个receiver也是Length方法与Point类型之间纽带。我们可以通过Point类型变量来调用Length方法：</p>
<pre><code>var p = Point{3,4}
fmt.Println(p.Length())
</code></pre>
<p>亦可以将方法当作函数来用：</p>
<pre><code>var p = Point{3,4}
fmt.Println(Point.Length(p)) // 这种用法也被称为方法表达式(method expression)
</code></pre>
<h2>接口</h2>
<p>接口是一组方法的集合，它代表一个“契约”，下面是一个由三个方法组成的方法集合的接口类型：</p>
<pre><code>type MyInterface interface {
    M1(int) int
    M2(string) error
    M3()
}
</code></pre>
<p>Go推崇<strong>面向接口编程</strong>，因为通过接口我们可以很容易构建<strong>低耦合</strong>的应用。</p>
<p>Go还支持在接口类型(如I)中嵌套其他接口类型(如io.Writer、sync.Locker)，其结果就是新接口类型I的方法集合为其方法集合与嵌入的接口类型Writer和Locker的方法集合的并集：</p>
<pre><code>type I interface { // 一个嵌入了其他接口类型的接口类型
   io.Writer
   sync.Locker
}
</code></pre>
<h3>接口实现</h3>
<p>如果一个类型T实现了某个接口类型MyInterface方法集合中的所有方法，那么我们说该类型T实现了接口MyInterface，于是T类型的变量t可以赋值给接口类型MyInterface的变量i，此时变量i的<strong>动态类型</strong>为T：</p>
<pre><code>var t T
var i MyInterface = t // ok
</code></pre>
<p>通过上述变量i可以调用T的方法：</p>
<pre><code>i.M1(5)
i.M2("demo")
i.M3()
</code></pre>
<p>方法集合为空的接口类型interface{}被称为“空接口类型”，空白的“契约”意味着任何类型都实现了该空接口类型，即任何变量都可以赋值给interface{}类型的变量：</p>
<pre><code>var i interface{} = 5 // ok
i = "demo"            // ok
i = T{}               // ok
i = &amp;T{}              // ok
i = []T{}             // ok
</code></pre>
<blockquote>
<p>注：Go 1.18中引入的新预定义标识符any与interface{}是等价类型。</p>
</blockquote>
<h3>接口的类型断言</h3>
<p>Go支持通过类型断言从接口变量中提取其动态类型的值：</p>
<pre><code>v, ok := i.(T) // 类型断言
</code></pre>
<p>如果接口变量i的动态类型确为T，那么v将被赋予该动态类型的值，ok为true；否则，v为T类型的零值，ok为false。</p>
<p>类型断言也支持下面这种语法形式：</p>
<pre><code>v := i.(T)
</code></pre>
<p>但在这种形式下，一旦接口变量i之前被赋予的值不是T类型的值，那么这个语句将抛出panic。</p>
<h3>接口类型的type switch</h3>
<p>“type switch”这是一种特殊的switch语句用法，仅用于接口类型变量：</p>
<pre><code>func main() {
    var x interface{} = 13
    switch x.(type) {
    case nil:
        println("x is nil")
    case int:
        println("the type of x is int") // 执行这一分支case
    case string:
        println("the type of x is string")
    case bool:
        println("the type of x is string")
    default:
        println("don't support the type")
    }
}
</code></pre>
<p>switch关键字后面跟着的表达式为x.(type)，这种表达式形式是switch语句专有的，而且也只能在switch语句中使用。这个表达式中的x必须是一个接口类型变量，表达式的求值结果是这个接口类型变量对应的动态类型。</p>
<p>上述例子中switch后面的表达式也可由x.(type)换成了v := x.(type)。v中将存储变量x的动态类型对应的值信息：</p>
<pre><code>var x interface{} = 13
switch x.(type) {
    case nil:
        println("v is nil")
    case int:
        println("the type of v is int, v =", v) // 执行这一分支case，v = 13
    ... ...
}
</code></pre>
<h2>泛型</h2>
<p><a href="https://tonybai.com/2022/04/20/some-changes-in-go-1-18">Go从1.18版本开始支持泛型</a>。Go泛型的基本语法是类型参数(type parameter)，Go泛型方案的实质是对类型参数的支持，包括：</p>
<ul>
<li>泛型函数（generic function）：带有类型参数的函数；</li>
<li>泛型类型（generic type）：带有类型参数的自定义类型；</li>
<li>泛型方法（generic method）：泛型类型的方法。</li>
</ul>
<h3>泛型函数</h3>
<p>下面是一个泛型函数max的定义：</p>
<pre><code>type ordered interface {
    ~int | ~int8 | ~int16 | ~int32 | ~int64 |
        ~uint | ~uint8 | ~uint16 | ~uint32 | ~uint64 | ~uintptr |
        ~float32 | ~float64 |
        ~string
}

func max[T ordered](sl []T) T {
    ... ...
}
</code></pre>
<p>与普通Go函数相比，max函数在函数名称与函数参数列表之间多了一段由方括号括起的代码：[T ordered]；max参数列表中的参数类型以及返回值列表中的返回值类型都是T，而不是某个具体的类型。</p>
<p>max函数中多出的[T ordered]就是Go泛型的类型参数列表（type parameters list），示例中这个列表中仅有一个类型参数T，ordered为类型参数的类型约束（type constraint）。</p>
<p>我们可以像普通函数一样调用泛型函数，我们可以显式指定类型实参：</p>
<pre><code>var m int = max[int]([]int{1, 2, -4, -6, 7, 0})  // 显式指定类型实参为int
fmt.Println(m) // 输出：7
</code></pre>
<p>Go也支持自动推断出类型实参：</p>
<pre><code>var m int = max([]int{1, 2, -4, -6, 7, 0}) // 自动推断T为int
fmt.Println(m) // 输出：7
</code></pre>
<h3>泛型类型</h3>
<p>所谓泛型类型，就是在类型声明中带有类型参数的Go类型：</p>
<pre><code>type Set[T comparable] map[T]string

type element[T any] struct {
    next *element[T]
    val  T
}

type Map[K, V any] struct {
  root    *node[K, V]
  compare func(K, K) int
}
</code></pre>
<p>以泛型类型Set为例，其使用方法如下：</p>
<pre><code>var s = Set[string]{}
s["key1"] = "value1"
println(s["key1"]) // value1
</code></pre>
<h3>泛型方法</h3>
<p>Go类型可以拥有自己的方法（method），泛型类型也不例外，为泛型类型定义的方法称为泛型方法（generic method）。</p>
<pre><code>type Set[T comparable] map[T]string

func (s Set[T]) Insert(key T, val string) {
    s[key] = val
}

func (s Set[T]) Get(key T) (string, error) {
    val, ok := s[key]
    if !ok {
        return "", errors.New("not found")
    }
    return val, nil
}

func main() {
    var s = Set[string]{
        "key": "value1",
    }
    s.Insert("key2", "value2")
    v, err := s.Get("key2")
    fmt.Println(v, err) // value2 &lt;nil&gt;
}
</code></pre>
<h3>类型约束</h3>
<p>Go通过类型约束(constraint)对泛型函数的类型参数以及泛型函数中的实现代码设置限制。Go使用扩展语法后的interface类型来定义约束。</p>
<p>下面是使用常规接口类型作为约束的例子：</p>
<pre><code>type Stringer interface {
    String() string
}

func Stringify[T fmt.Stringer](s []T) (ret []string) { // 通过Stringer约束了T的实参只能是实现了Stringer接口的类型
    for _, v := range s {
        ret = append(ret, v.String())
    }
    return ret
}
</code></pre>
<p>Go接口类型声明语法做了扩展，支持在接口类型中放入类型元素（type element）信息：</p>
<pre><code>type ordered interface {
    ~int | ~int8 | ~int16 | ~int32 | ~int64 |
        ~uint | ~uint8 | ~uint16 | ~uint32 | ~uint64 | ~uintptr |
        ~float32 | ~float64 | ~string
}

func Less[T ordered](a, b T) bool {
    return a &lt; b
}

type Person struct {
    name string
    age  int
}

func main() {
    println(Less(1, 2)) // true
    println(Less(Person{"tony", 11}, Person{"tom", 23})) // Person不满足ordered的约束，会导致编译错误
}
</code></pre>
<h2>并发</h2>
<p>Go语言原生支持并发，Go并没有使用操作系统线程作为并发的基本执行单元，而是实现了goroutine这一由Go运行时（runtime）负责调度的、轻量的用户级线程，为并发程序设计提供原生支持。</p>
<h3>goroutine</h3>
<p>通过go关键字+函数/方法的方式，我们便可以创建一个goroutine。创建后，新goroutine将拥有独立的代码执行流，并与创建它的goroutine一起被Go运行时调度。</p>
<pre><code>go fmt.Println("I am a goroutine")

// $GOROOT/src/net/http/server.go
c := srv.newConn(rw)
go c.serve(connCtx)
</code></pre>
<p>goroutine的执行函数返回后，goroutine便退出。如果是主goroutine(执行main.main的goroutine)退出，那么整个Go应用进程将会退出，程序生命周期结束。</p>
<h3>channel</h3>
<p>Go提供了原生的用于goroutine之间通信的机制channel，channel的定义与操作方式如下：</p>
<pre><code>// channel类型
chan T          // 一个元素类型为T的channel类型
chan&lt;- float64  // 一个元素类型为float64的只发送channel类型
&lt;-chan int      // 一个元素类型为int的只接收channel类型

var c chan int             // 声明一个元素类型为int的channel类型的变量，初值为nil
c1 := make(chan int)       // 声明一个元素类型为int的无缓冲的channel类型的变量
c2 := make(chan int, 100)  // 声明一个元素类型为int的带缓冲的channel类型的变量，缓冲大小为100
close(c)                   // 关闭一个channel
</code></pre>
<p>下面是两个goroutine基于channel通信的例子：</p>
<pre><code>func main() {
    var c = make(chan int)
    go func(a, b int) {
        c &lt;- a + b
    }(3,4)
    println(&lt;-c) // 7
}
</code></pre>
<p>当涉及同时对多个channel进行操作时，Go提供了select机制。通过select，我们可以同时在多个channel上进行发送/接收操作：</p>
<pre><code>select {
case x := &lt;-ch1:     // 从channel ch1接收数据
  ... ...

case y, ok := &lt;-ch2: // 从channel ch2接收数据，并根据ok值判断ch2是否已经关闭
  ... ...

case ch3 &lt;- z:       // 将z值发送到channel ch3中:
  ... ...

default:             // 当上面case中的channel通信均无法实施时，执行该默认分支
}
</code></pre>
<h2>错误处理</h2>
<p>Go提供了简单的、基于错误值比较的错误处理机制，这种机制让每个开发人员必须显式地去关注和处理每个错误。</p>
<h3>error类型</h3>
<p>Go用error这个接口类型表示错误，并且按惯例，我们通常将error类型返回值放在返回值列表的末尾。</p>
<pre><code>// $GOROOT/src/builtin/builtin.go
type error interface {
    Error() string
}
</code></pre>
<p>任何实现了error的Error方法的类型的实例，都可以作为错误值赋值给error接口变量。</p>
<p>Go提供了便捷的构造错误值的方法：</p>
<pre><code>err := errors.New("your first demo error")
errWithCtx = fmt.Errorf("index %d is out of bounds", i)
</code></pre>
<h3>错误处理形式</h3>
<p>Go最常见的错误处理形式如下：</p>
<pre><code>err := doSomething()
if err != nil {
    ... ...
    return err
}
</code></pre>
<p>通常我们会定义一些“哨兵”错误值来辅助错误处理方检视（inspect）错误值并做出错误处理分支的决策：</p>
<pre><code>// $GOROOT/src/bufio/bufio.go
var (
    ErrInvalidUnreadByte = errors.New("bufio: invalid use of UnreadByte")
    ErrInvalidUnreadRune = errors.New("bufio: invalid use of UnreadRune")
    ErrBufferFull        = errors.New("bufio: buffer full")
    ErrNegativeCount     = errors.New("bufio: negative count")
)

func doSomething() {
    ... ...
    data, err := b.Peek(1)
    if err != nil {
        switch err {
        case bufio.ErrNegativeCount:
            // ... ...
            return
        case bufio.ErrBufferFull:
            // ... ...
            return
        case bufio.ErrInvalidUnreadByte:
            // ... ...
            return
        default:
            // ... ...
            return
        }
    }
    ... ...
}
</code></pre>
<h3>Is和As</h3>
<p>从Go 1.13版本开始，标准库errors包提供了Is函数用于错误处理方对错误值的检视。Is函数类似于把一个error类型变量与“哨兵”错误值进行比较：</p>
<pre><code>// 类似 if err == ErrOutOfBounds{ … }
if errors.Is(err, ErrOutOfBounds) {
    // 越界的错误处理
}
</code></pre>
<p>不同的是，如果error类型变量的底层错误值是一个包装错误（Wrapped Error），errors.Is方法会沿着该包装错误所在错误链（Error Chain)，与链上所有被包装的错误（Wrapped Error）进行比较，直至找到一个匹配的错误为止。</p>
<p>标准库errors包还提供了As函数给错误处理方检视错误值。As函数类似于通过类型断言判断一个error类型变量是否为特定的自定义错误类型：</p>
<pre><code>// 类似 if e, ok := err.(*MyError); ok { … }
var e *MyError
if errors.As(err, &amp;e) {
    // 如果err类型为*MyError，变量e将被设置为对应的错误值
}
</code></pre>
<p>如果error类型变量的动态错误值是一个包装错误，errors.As函数会沿着该包装错误所在错误链，与链上所有被包装的错误的类型进行比较，直至找到一个匹配的错误类型，就像errors.Is函数那样。</p>
<h2>小结</h2>
<p>读到这里，你已经对Go语言有了入门级的认知，但要想成为一名Gopher(对Go开发人员的称呼)，还需要更进一步的学习与实践。我的极客时间专栏<a href="http://gk.link/a/10AVZ">《Go语言第一课》</a>是一个很好的起点，欢迎大家订阅学习^_^。</p>
<p>BTW，本文部分内容由<a href="https://chat.openai.com/">ChatGPT</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/23/learn-go-in-10-min/feed/</wfw:commentRss>
		<slash:comments>5</slash:comments>
		</item>
		<item>
		<title>2023年的Rust与Go[译]</title>
		<link>https://tonybai.com/2023/02/22/rust-vs-go-in-2023/</link>
		<comments>https://tonybai.com/2023/02/22/rust-vs-go-in-2023/#comments</comments>
		<pubDate>Wed, 22 Feb 2023 13:49:14 +0000</pubDate>
		<dc:creator>bigwhite</dc:creator>
				<category><![CDATA[技术志]]></category>
		<category><![CDATA[benchmark]]></category>
		<category><![CDATA[Channel]]></category>
		<category><![CDATA[Compile]]></category>
		<category><![CDATA[Cpp]]></category>
		<category><![CDATA[DevOps]]></category>
		<category><![CDATA[GC]]></category>
		<category><![CDATA[Go]]></category>
		<category><![CDATA[gofmt]]></category>
		<category><![CDATA[Golang]]></category>
		<category><![CDATA[goroutine]]></category>
		<category><![CDATA[Java]]></category>
		<category><![CDATA[Mutex]]></category>
		<category><![CDATA[programming]]></category>
		<category><![CDATA[Python]]></category>
		<category><![CDATA[RobPike]]></category>
		<category><![CDATA[Ruby]]></category>
		<category><![CDATA[Rust]]></category>
		<category><![CDATA[rustfmt]]></category>
		<category><![CDATA[scale]]></category>
		<category><![CDATA[内存安全]]></category>
		<category><![CDATA[垃圾回收]]></category>
		<category><![CDATA[基准测试]]></category>
		<category><![CDATA[多范式]]></category>
		<category><![CDATA[安全]]></category>
		<category><![CDATA[并发]]></category>
		<category><![CDATA[微服务]]></category>
		<category><![CDATA[性能]]></category>
		<category><![CDATA[抽象]]></category>
		<category><![CDATA[机器]]></category>
		<category><![CDATA[类型安全]]></category>
		<category><![CDATA[系统编程]]></category>
		<category><![CDATA[线程]]></category>
		<category><![CDATA[编程]]></category>
		<category><![CDATA[编译]]></category>
		<category><![CDATA[编译型语言]]></category>
		<category><![CDATA[语法]]></category>
		<category><![CDATA[通用语言]]></category>
		<category><![CDATA[静态语言]]></category>
		<category><![CDATA[面向对象]]></category>

		<guid isPermaLink="false">https://tonybai.com/?p=3804</guid>
		<description><![CDATA[本文永久链接 &#8211; https://tonybai.com/2023/02/22/rust-vs-go-in-2023 本文译自《Rust vs Go in 2023》。 注：从2022年下半年开始，我们研发团队的产品研发不再局限于云端，车端也是将来的一个重要方向。于是我除了继续对Go语言保持常规的高度关注之外，也逐步开始留意Rust语言的发展。 Rust和Go哪个更好？Go还是Rust？在2023年，你应该为你的下一个项目选择哪种语言，为什么？两者在性能、简单性、安全性、功能、规模和并发性等方面如何比较？它们的共同点是什么，它们有哪些根本性的不同？让我们在这个友好而公平的Rust和Go的比较中找到答案。 Rust和Go都很棒 首先，我必须要说的是，Go和Rust都是绝对优秀的编程语言。它们都是现代的、强大的、被广泛采用的编程语言，并且都提供出色的性能。 你可能读过一些说Go比Rust好的文章，或者相反。但这真的没有意义；每一种编程语言都代表了一系列的权衡和取舍。每种语言都有自己的优化重点，所以你对语言的选择应该由适合你的东西和你想用它解决的问题决定。 在这篇文章中，我将尝试告诉你何时使用Go是理想选择以及何时使用Rust更佳。我也会试着介绍一下这两种语言的本质（如果你愿意的话，就是Go和Rust的道）。 虽然它们在语法和风格上有很大不同，但Rust和Go都是构建软件的一流工具。接下来，让我们仔细看看这两种语言。 Go和Rust的相似之处 Rust和Go有很多共同点，这也是你经常听到它们一起被提及的原因之一。两种语言的共同目标是什么呢？ Rust是一种低级静态类型的多范式编程语言，专注于安全和性能。 &#8211; Gints Dreimanis Go是一种开源的编程语言，可以轻松构建简单、可靠、高效的软件。 &#8211; go.dev 内存安全 Go和Rust都属于现代编程语言，它们的首要任务是内存安全。经过几十年对C和C++等旧语言的使用，我们可以清楚地看到，导致错误和安全漏洞的最大原因之一是不安全地或不正确地访问内存。 Rust和Go以不同的方式处理这个问题，但它们的目标都是在管理内存方面比其他语言更聪明、更安全，并帮助你写出正确和高性能的程序。 快速、紧凑的可执行文件 Go和Rust都是编译型语言，这意味着你的程序被直接翻译成可执行的机器码，因此你可以以单一二进制文件形式来部署你的程序；与Python和Ruby等解释型语言不同，你不需要将解释器和大量的库和依赖关系与你的程序一起分发，这是一个很大的优点。这也使得Rust和Go的程序与解释型语言相比都非常快。 通用语言 Rust和Go都是强大的、可扩展的通用编程语言，你可以用它们来开发各种现代软件，从网络应用到分布式微服务，或者从嵌入式微控制器到移动应用程序。 两者都有优秀的标准库、繁荣的第三方生态系统以及巨大的商业支持和庞大的用户基础。它们都已经存在了很多年，并将在未来几年内继续被广泛使用。今天学习Go或Rust将是对你时间和精力的合理投资。 务实的编程风格 Go和Rust都不是以函数式编程为主的语言（例如像Scala或Elixir），也不是完全面向对象的语言（像Java和C#）。相反，虽然Go和Rust都有与函数式和面向对象编程相关的特性，但它们是务实的语言，旨在以最合适的方式解决问题，而不是强迫你采用特定的做事方式。 如果你喜欢函数式编程风格，你会在Rust中发现更多对这种风格的支持，因为Rust在语法特性数量上要比Go更多。 我们可以讨论什么是“面向对象”语言，但可以说C++、Java或C#用户所期望的面向对象编程风格在Go或Rust中都不存在。 &#8211; Jack Mott 规模化的开发 Rust和Go都有一些有用的特性，使它们适合于大规模的编程，不管是指大型团队，还是大型代码库，或者两者兼具。 例如，C语言的程序员们多年来一直在争论将括号放在哪里，以及代码应该用制表符还是空格缩进，而Rust和Go通过使用标准的格式化工具（Go为gofmt，Rust为rustfmt）使用规范的风格自动重写你的代码，完全消除了这些问题。 这并不是说这种特殊的风格本身有多好：而是Rust和Go的程序员都喜欢这种标准化。 gofmt的风格是没有人喜欢的，但gofmt却是所有人的最爱。 &#8211; Rob Pike 两种语言的另一个高分领域是构建管道(pipeline)。两种语言都有优秀的、内置的、高性能的标准构建和依赖管理工具；不再需要与复杂的第三方构建系统搏斗，也不再需要每隔几年就学习一个新的系统。 对于早期职业生涯以Java和Ruby为背景的我而言，构建Go和Rust代码感觉就像从我的肩上卸下了一个不可能的重担。当我在谷歌工作时，遇到用Go编写的服务是一种解脱，因为我知道它很容易构建和运行。Rust也是如此，尽管我只在较小规模的Rust项目上工作过。我希望可无限配置的构建系统的时代已经过去了，所有语言都会有自己专门的构建工具，开箱即可使用。- 山姆-罗斯 Rust还是Go？ 综上可知，这两种语言都设计得很好、很强大，那么你可能会想知道那些关于两门语言的“圣战”究竟是怎么回事（我也是）。为什么人们对“Go vs.Rust”如此大惊小怪，在社交媒体上大打出手，并且写长篇博文说只有傻瓜才会使用Rust，或者Go不是真正的编程语言，或者其他什么。 这可能会让他们感觉好些，但这并不能完全帮助你，因为你正试图决定在你的项目中使用哪种语言，或者你应该学习哪种语言来推动你的编程生涯。一个明智的人不会根据谁喊得声最大来做出重要的选择。 现在让我们继续我们成熟的讨论，看看在某些领域，一个有理智的人可能更喜欢哪一种语言。 [...]]]></description>
			<content:encoded><![CDATA[<p><img src="https://tonybai.com/wp-content/uploads/rust-vs-go-in-2023-1.png" alt="" /></p>
<p><a href="https://tonybai.com/2023/02/22/rust-vs-go-in-2023">本文永久链接</a> &#8211; https://tonybai.com/2023/02/22/rust-vs-go-in-2023</p>
<p>本文译自<a href="https://bitfieldconsulting.com/golang/rust-vs-go">《Rust vs Go in 2023》</a>。</p>
<blockquote>
<p>注：从2022年下半年开始，我们研发团队的产品研发不再局限于云端，车端也是将来的一个重要方向。于是我除了继续对Go语言保持常规的高度关注之外，也逐步开始留意Rust语言的发展。</p>
</blockquote>
<hr />
<p>Rust和Go哪个更好？Go还是Rust？在2023年，你应该为你的下一个项目选择哪种语言，为什么？两者在性能、简单性、安全性、功能、规模和并发性等方面如何比较？它们的共同点是什么，它们有哪些根本性的不同？让我们在这个友好而公平的Rust和Go的比较中找到答案。</p>
<h2>Rust和Go都很棒</h2>
<p>首先，我必须要说的是，<strong>Go和Rust都是绝对优秀的编程语言</strong>。它们都是现代的、强大的、被广泛采用的编程语言，并且都提供出色的性能。</p>
<p>你可能读过一些说Go比Rust好的文章，或者相反。但这真的没有意义；每一种编程语言都代表了一系列的权衡和取舍。每种语言都有自己的优化重点，所以你对语言的选择应该由适合你的东西和你想用它解决的问题决定。</p>
<p>在这篇文章中，我将尝试告诉你何时使用Go是理想选择以及何时使用Rust更佳。我也会试着介绍一下这两种语言的本质（如果你愿意的话，就是<a href="https://tonybai.com/2022/09/25/the-tao-of-go">Go和Rust的道</a>）。</p>
<p>虽然它们在语法和风格上有很大不同，但Rust和Go都是构建软件的一流工具。接下来，让我们仔细看看这两种语言。</p>
<h2>Go和Rust的相似之处</h2>
<p>Rust和Go有很多共同点，这也是你经常听到它们一起被提及的原因之一。两种语言的共同目标是什么呢？</p>
<blockquote>
<p>Rust是一种低级静态类型的多范式编程语言，专注于安全和性能。 &#8211; <a href="https://serokell.io/blog/rust-guide">Gints Dreimanis</a></p>
<p>Go是一种开源的编程语言，可以轻松构建简单、可靠、高效的软件。 &#8211; <a href="https://go.dev">go.dev</a></p>
</blockquote>
<h3>内存安全</h3>
<p>Go和Rust都属于现代编程语言，它们的首要任务是内存安全。经过几十年对C和C++等旧语言的使用，我们可以清楚地看到，导致错误和安全漏洞的最大原因之一是不安全地或不正确地访问内存。</p>
<p>Rust和Go以不同的方式处理这个问题，但它们的目标都是在管理内存方面比其他语言更聪明、更安全，并帮助你写出<a href="https://bitfieldconsulting.com/golang/crisp-code">正确</a>和<a href="https://bitfieldconsulting.com/golang/slower">高性能</a>的程序。</p>
<h3>快速、紧凑的可执行文件</h3>
<p>Go和Rust都是编译型语言，这意味着你的程序被直接翻译成可执行的机器码，因此你可以以单一二进制文件形式来部署你的程序；与<a href="https://bitfieldconsulting.com/golang/go-vs-python">Python</a>和Ruby等解释型语言不同，你不需要将解释器和大量的库和依赖关系与你的程序一起分发，这是一个很大的优点。这也使得Rust和Go的程序与解释型语言相比都非常快。</p>
<h3>通用语言</h3>
<p>Rust和Go都是强大的、可扩展的通用编程语言，你可以用它们来开发各种现代软件，从网络应用到分布式微服务，或者从嵌入式微控制器到移动应用程序。</p>
<p>两者都有优秀的标准库、繁荣的第三方生态系统以及巨大的商业支持和庞大的用户基础。它们都已经存在了很多年，并将在未来几年内继续被广泛使用。今天学习Go或Rust将是对你时间和精力的合理投资。</p>
<h3>务实的编程风格</h3>
<p>Go和Rust都不是<a href="https://bitfieldconsulting.com/golang/functional">以函数式编程为主的语言</a>（例如像Scala或Elixir），也不是完全面向对象的语言（像Java和C#）。相反，虽然Go和Rust都有与函数式和面向对象编程相关的特性，但它们是务实的语言，旨在以最合适的方式解决问题，而不是强迫你采用特定的做事方式。</p>
<p>如果你喜欢函数式编程风格，你会在Rust中发现更多对这种风格的支持，因为Rust在语法特性数量上要比Go更多。</p>
<blockquote>
<p>我们可以讨论什么是“面向对象”语言，但可以说C++、Java或C#用户所期望的面向对象编程风格在Go或Rust中都不存在。 &#8211; Jack Mott</p>
</blockquote>
<h3>规模化的开发</h3>
<p>Rust和Go都有一些有用的特性，使它们适合于大规模的编程，不管是指大型团队，还是大型代码库，或者两者兼具。</p>
<p>例如，C语言的程序员们多年来一直在争论将括号放在哪里，以及代码应该用制表符还是空格缩进，而Rust和Go通过使用标准的格式化工具（Go为gofmt，Rust为rustfmt）使用规范的风格自动重写你的代码，完全消除了这些问题。</p>
<p>这并不是说这种特殊的风格本身有多好：而是Rust和Go的程序员都喜欢这种<strong>标准化</strong>。</p>
<blockquote>
<p>gofmt的风格是没有人喜欢的，但gofmt却是所有人的最爱。 &#8211; <a href="https://www.youtube.com/watch?v=PAAkCSZUG1c&amp;t=8m43s">Rob Pike</a></p>
</blockquote>
<p>两种语言的另一个高分领域是<strong>构建管道(pipeline)</strong>。两种语言都有优秀的、内置的、高性能的标准构建和依赖管理工具；不再需要与复杂的第三方构建系统搏斗，也不再需要每隔几年就学习一个新的系统。</p>
<blockquote>
<p>对于早期职业生涯以Java和Ruby为背景的我而言，构建Go和Rust代码感觉就像从我的肩上卸下了一个不可能的重担。当我在谷歌工作时，遇到用Go编写的服务是一种解脱，因为我知道它很容易构建和运行。Rust也是如此，尽管我只在较小规模的Rust项目上工作过。我希望可无限配置的构建系统的时代已经过去了，所有语言都会有自己专门的构建工具，开箱即可使用。- <a href="https://samwho.dev/">山姆-罗斯</a></p>
</blockquote>
<h2>Rust还是Go？</h2>
<p>综上可知，这两种语言都设计得很好、很强大，那么你可能会想知道那些关于两门语言的“圣战”究竟是怎么回事（我也是）。为什么人们对“Go vs.Rust”如此大惊小怪，在社交媒体上大打出手，并且写长篇博文说只有傻瓜才会使用Rust，或者Go不是真正的编程语言，或者其他什么。</p>
<p>这可能会让他们感觉好些，但这并不能完全帮助你，因为你正试图决定在你的项目中使用哪种语言，或者你应该学习哪种语言来推动你的编程生涯。一个明智的人不会根据谁喊得声最大来做出重要的选择。</p>
<p>现在让我们继续我们成熟的讨论，看看在某些领域，一个有理智的人可能更喜欢哪一种语言。</p>
<h2>Go与Rust的性能对比</h2>
<p>我们已经说过，Go和Rust都能生产出高性能的程序，因为它们被编译成了本地机器代码，而不必通过解释器或虚拟机。</p>
<p>然而，Rust的性能尤其突出。它可以与C和C++相媲美，这两种语言通常被认为是性能最高的编译语言，但与这些老语言不同的是，Rust还提供了内存安全和并发安全，并且基本上不会给执行速度上带去没有任何开销。Rust还允许你创建复杂的抽象，而不需要在运行时付出任何性能上的代价。</p>
<p>相比之下，尽管Go程序的性能也非常好，但Go主要是为开发速度（包括编译）而设计的，而不是执行速度。Go程序员<a href="https://bitfieldconsulting.com/golang/slower">更倾向于清晰的代码而不是快速的代码</a>。</p>
<p>Go编译器也不会花很多时间去尝试生成最有效的机器代码；它更关心的是快速编译大量代码。所以Rust通常会在运行时基准测试中击败Go。</p>
<p>Rust的运行时性能也是一致和可预测的，因为它不使用垃圾收集。Go的垃圾收集器非常高效，并且经过优化，使其“STW(停止世界)”的停顿时间尽可能短（每一个新的Go版本都会越来越短）。但是垃圾收集不可避免地在程序的行为方式中引入了一些不可预测的因素，这在某些应用中可能是一个严重的问题，例如嵌入式系统。</p>
<p>因为Rust旨在让程序员完全控制底层硬件，所以有可能将Rust程序优化到相当接近机器的最大理论性能。这使得Rust在执行速度胜过所有其他考虑因素的领域是一个很好的选择，比如游戏编程、操作系统内核、网络浏览器组件和实时控制系统。</p>
<h2>简单性</h2>
<p>如果没有人能够弄清楚如何使用一种编程语言，那么这种语言有多快也无所谓。Go语言是为了应对C++等语言不断增长的复杂性而特意设计的；它的语法非常少，关键字也非常少，事实上，功能特性也很少。</p>
<p>这意味着<a href="http://gk.link/a/10AVZ">学习Go语言</a>不需要很长时间，就可以用它来编写有用的程序。</p>
<blockquote>
<p>Go是非常容易学习的。我知道这是一个经常被吹捧的好处，但我真的很惊讶于我能够如此迅速地提高工作效率。多亏了这个语言、文档和工具，我在两天后就写出了有趣的、可提交的代码。 &#8211; <a href="https://medium.com/better-programming/early-impressions-of-go-from-a-rust-programmer-f4fd1074c410">一个Rust程序员对Go的早期印象</a></p>
</blockquote>
<p>这里的关键词是<strong>简单性</strong>。当然，简单并不等同于容易，但是小而简单的语言比大而复杂的语言更容易学习。Go语言没有提供那么多不同的方法来做一件事情，所以所有写得好的Go代码往往看起来都一样。快速学习一个不熟悉的服务并理解它在做什么很容易。</p>
<pre><code>fmt.Println("Gopher's Diner Breakfast Menu")
for dish, price := range menu {
    fmt.Println(dish, price)
}
</code></pre>
<p>在我的<a href="https://bitfieldconsulting.com/code-club">代码俱乐部视频系列</a>中，我正是这样做的：从GitHub上半随机地挑选Go项目，并与一群Go初学者一起探索它们，看看我们能理解多少的代码。结果总是比我们预期的要多。</p>
<p>虽然核心语言很小，但Go的标准库却非常强大。这意味着你的学习曲线也需要包括你需要的标准库的部分，而不仅仅是Go语法。</p>
<p>另一方面，将功能从语言中转移到标准库中，意味着你可以只专注于学习与你现在相关的库。</p>
<p>Go也是为大规模的软件开发而设计的，支持有大型代码库的大型团队。在这种情况下，新的开发人员能够尽快上手是非常重要的。出于这个原因，Go社区十分看重：<a href="https://bitfieldconsulting.com/golang/commandments">简单、明显、常规、直接的程序</a>。</p>
<blockquote>
<p>使用Go，你可以快速完成工作。Go是我所使用过的生产力最高的语言之一。它的口号是：今天解决实际问题。 &#8211; <a href="https://endler.dev/2017/go-vs-rust/">马蒂亚斯-恩德勒</a></p>
</blockquote>
<h2>特性</h2>
<blockquote>
<p>Rust比其他几种编程语言支持更多的复杂语法特性，因此，你可以用它实现更多。 &#8211; <a href="https://devathon.com/blog/rust-vs-go-which-programming-language-to-choose/">devathon</a></p>
</blockquote>
<p>Rust是专门设计用来帮助程序员用最少的代码做最多的事情，它包括很多强大而有用的功能特性。例如，Rust的match功能可以让你以十分简洁地方式写出灵活的、富有表现力的逻辑：</p>
<pre><code>fn is_prime(n: u64) -&gt; bool {
    match n {
        0...1 =&gt; false,
        _ =&gt; !(2..n).any(|d| n % d == 0),
    }
}
</code></pre>
<p>因为Rust做了很多事情，这意味着有很多东西需要学习，特别是在开始的时候。但这没关系：在C++或Java中也有很多东西要学，而且你不会得到Rust的高级特性，比如内存安全。</p>
<p>批评Rust是一种复杂的语言忽略了一点：它被设计成具有表现力，这意味着有很多功能，而在许多情况下，这正是你想要的编程语言。</p>
<p>当然，Rust有一个学习曲线，但一旦你开始使用它，你就会好起来。</p>
<blockquote>
<p>对于那些准备接受更复杂的语法和语义（以及可能更高的可读性成本）以换取最大可能的性能的程序员来说，Rust将与C++和D语言争夺思想份额。 &#8211; <a href="https://dave.cheney.net/2015/07/02/why-go-and-rust-are-not-competitors">戴夫-切尼</a></p>
</blockquote>
<p>虽然Rust采用了Go的一些特性，而Go也在采用Rust的一些特性（尤其是<a href="https://bitfieldconsulting.com/golang/generics">泛型</a>），但可以说Rust的特性很重，而Go的特性相对较轻。</p>
<h2>并发</h2>
<p>大多数语言都对并发编程（同时做多件事情）有某种形式的支持，但Go从一开始就是为这项工作而设计的。Go不使用操作系统的线程，而是提供了一个轻量级的替代方案：<strong>goroutine</strong>。</p>
<p>每个goroutine是一个独立执行的Go函数，Go调度器会将其映射到其控制下的一个操作系统线程中。这意味着调度器可以非常有效地管理大量并发的goroutine，只使用有限的操作系统线程。</p>
<p>因此，你可以在一个程序中运行数百万个并发的goroutine，而不会产生严重的性能问题。这使得Go成为高规模并发应用程序的完美选择，如网络服务器和微服务。</p>
<p>Go还具有快速、安全、高效的功能特性，可以使用channel让goroutines进行通信和共享数据。Go的并发支持感觉设计得很好，使用起来也很愉快。</p>
<p>一般来说，对并发程序进行推断是很难的，而且在任何语言中建立可靠、正确的并发程序都是一个挑战。但由于它从一开始就内置于语言中，而不是事后才想到的，Go中的并发编程是最简单、最完整的。</p>
<blockquote>
<p>Go语言可以很容易地建立一个很好的多因素的应用程序，充分利用并发性，同时作为一组微服务进行部署。Rust也可以做这些事情，但可以说它更难。 在某些方面，Rust对防止与内存有关的安全漏洞的痴迷意味着程序员必须不遗余力地执行那些在其他语言（包括Go）中会更简单的任务。 &#8211; <a href="https://sdtimes.com/softwaredev/the-developers-dilemma-choosing-between-go-and-rust/">Sonya Koptyev</a></p>
</blockquote>
<p>相比之下，Rust中的并发故事是非常新的，而且还在稳定中，但它正处于非常积极的开发中，所以请关注这个领域。例如，Rust的<a href="https://github.com/rayon-rs/rayon">rayon库</a>提供了一种非常优雅和轻量级的方式来将顺序计算转化为并行计算。</p>
<blockquote>
<p>拥有goroutines和使用channel的轻量级语法真的很好。这真的显示了语法的力量，这些小细节使并发编程比其他语言感觉好得多 &#8211; <a href="https://medium.com/better-programming/early-impressions-of-go-from-a-rust-programmer-f4fd1074c410">一个Rust程序员对Go的早期印象</a></p>
</blockquote>
<p>虽然在Rust中实现并发程序可能不那么简单，但还是有可能的，而且这些程序可以利用Rust的安全保证。</p>
<p>一个很好的例子是标准库的Mutex类：在Go中，你可以忘记在访问某些东西之前获得一个Mutex锁，但Rust不会让你这样做。</p>
<blockquote>
<p>Go专注于将并发性作为一个一等公民的概念。这并不是说你不能在Rust中找到Go的面向actor的并发性，但这是留给程序员的一个练习。 &#8211; <a href="https://dave.cheney.net/2015/07/02/why-go-and-rust-are-not-competitors">Dave Cheney</a></p>
</blockquote>
<h2>安全</h2>
<p>我们在前面看到，Go和Rust都以不同的方式来防止一大类与内存管理有关的常见编程错误。但是Rust尤其努力确保你不会做一些你不想做的不安全的事情。</p>
<blockquote>
<p>Rust的编译器非常严格和学究派，它检查你使用的每个变量和你引用的每个内存地址。它避免了可能的数据竞争条件，并告知你未定义的行为。并发和内存安全问题在Rust的安全子集中根本不可能发生。 &#8211; <a href="https://bitbucket.org/blog/why-rust">为什么是Rust？</a></p>
</blockquote>
<p>这将使Rust编程成为与几乎所有其他语言不同的体验，而且一开始可能是一种挑战。但对很多人来说，这种辛苦是值得的。</p>
<blockquote>
<p>对我来说，Rust的关键优势是一种感觉，即编译器是我的后盾，不会让它可能检测到的任何错误通过（说真的，有时感觉就像魔法一样）。 &#8211; Grzegorz Nosek</p>
</blockquote>
<p>包括Go在内的许多语言都有帮助程序员避免错误的设施，但Rust将这一点提高到了一个新的水平，因此可能不正确的程序甚至不会被编译。</p>
<blockquote>
<p>有了Rust，库程序员有很多工具来防止他/她的用户犯错。Rust让我们有能力说，我们拥有一块特定的数据；其他东西不可能声称拥有，所以我们知道没有其他东西能够修改它。我想不出以前有什么时候我被赋予过这么多工具来防止意外的误用。这是一种奇妙的感觉。 &#8211; <a href="https://samwho.dev/">山姆-罗斯</a></p>
</blockquote>
<p>“与借用检查器(borrow checker)斗争”是Rust程序员新手的常见综合症，但在大多数情况下，它所发现的问题是你的代码中真正的bug（或至少是潜在的bug）。它可能会迫使你从根本上重构你的程序，以避免遇到这些问题；而当正确性和可靠性是你的首要任务时，这是件好事。</p>
<p>一个不改变你编程方式的语言有什么意义呢？当你用其他语言工作时，Rust所教授的关于安全的课程也是有用的。</p>
<blockquote>
<p>如果你选择了Rust，通常你需要该语言提供的保证：针对空指针和数据竞争的安全，可预测的运行时行为，以及对硬件的完全控制。如果你不需要这些功能，Rust可能是你下一个项目的糟糕选择。这是因为这些保证是有代价的：入门时间。你需要戒掉坏习惯，学习新概念。有可能的是，当你开始的时候，你会经常和借用检查器斗争。 &#8211; <a href="https://endler.dev/2017/go-vs-rust/">Matthias Endler</a></p>
</blockquote>
<p>你觉得Rust的编程模型有多大的挑战性，可能取决于你以前有哪些其他语言的经验。Python或Ruby程序员可能会发现它的限制性；其他人会很高兴。</p>
<blockquote>
<p>如果你是一个花了几周的时间来追寻内存安全漏洞的C/C++程序员，你会非常欣赏Rust。”与借用检查器斗争”变成了”编译器可以检测到这个？酷！” -Grzegorz Nosek</p>
</blockquote>
<h2>规模化</h2>
<blockquote>
<p>今天的服务器程序由数千万行代码组成，由数百甚至数千名程序员进行构建，而且每天都在更新。Go的设计和开发是为了使在这种环境中工作更有成效。Go的设计考虑包括严格的依赖性管理，随着系统的发展，软件架构的适应性，以及组件之间的健壮性。 &#8211; <a href="https://talks.golang.org/2012/splash.article">Rob Pike</a></p>
</blockquote>
<p>当你一个人或在小团队中处理问题时，选择简单的语言还是功能丰富的语言是一个偏好的问题。但是当软件越来越大，越来越复杂，团队越来越大时，差异就开始显现出来了。</p>
<p>对于大型应用程序和分布式系统来说，执行速度不如开发速度重要：像Go这样刻意简化的语言可以减少新开发人员的启动时间，并使他们更容易处理大型代码库的工作。</p>
<blockquote>
<p>有了Go，作为初级开发者更容易提高工作效率，而作为中级开发者则更难引入会导致后续问题的脆弱抽象。由于这些原因，Rust在企业软件开发方面不如Go有说服力。 &#8211; <a href="https://kristoff.it/blog/why-go-and-not-rust">Loris Cro</a></p>
</blockquote>
<p>当涉及到大型的软件开发时，清晰的比聪明的好。Go的局限性实际上使它比Rust等更复杂和强大的语言更适合企业和大机构。</p>
<h2>Rust和Go的不同点</h2>
<p>虽然Rust和Go都是流行的、现代的、广泛使用的语言，但它们并不是真正的竞争对手，因为它们故意针对的是完全不同的使用情况。</p>
<p><a href="https://tonybai.com/2022/09/25/the-tao-of-go">Go的整个编程方法</a>与Rust的完全不同，每一种语言都适合一些人，同时也会刺激另一些人。这完全没问题，如果Rust和Go都能以或多或少相同的方式做同样的事情，我们就不会真的需要两种不同的语言。</p>
<p>那么，我们是否可以通过发现Rust和Go所采取的截然不同的方法来了解它们各自的本性呢？让我们拭目以待。</p>
<h3>垃圾回收</h3>
<p>“要不要垃圾回收”是一个没有正确答案的问题。垃圾回收，以及一般的自动内存管理，使得开发可靠、高效的程序变得快速和容易，对于一些人来说，这至关重要。</p>
<p>但也有人说，垃圾回收及其性能开销和停顿，使程序在运行时表现得不可预测，并引入了不可接受的延迟。争论还在继续。</p>
<blockquote>
<p>Go是一种与Rust非常不同的语言。虽然两者都可以被模糊地描述为系统语言或C语言的替代品，但它们有不同的目标和应用、语言设计的风格以及优先级。垃圾回收是一个真正巨大的区别。Go中的GC使语言更简单，更小，更容易推理。在Rust中没有GC会让它变得非常快（尤其是当你需要保证延迟，而不仅仅是高吞吐量的时候），并且可以实现Go中不可能实现的功能和编程模式（或者至少是在不牺牲性能的情况下）。 &#8211; <a href="https://medium.com/better-programming/early-impressions-of-go-from-a-rust-programmer-f4fd1074c410">PingCAP</a></p>
</blockquote>
<h3>接近机器</h3>
<p>计算机编程的历史是一个越来越复杂的抽象的故事，它让程序员在解决问题时不用太担心底层机器的实际运作。</p>
<p>这使得程序更容易编写，也许更容易移植。但是对于许多程序来说，对硬件的访问以及对程序执行方式的精确控制更为重要。</p>
<p>Rust的目标是让程序员“更接近机器”，有更多的控制权，但Go抽象了架构细节，让程序员更接近问题。</p>
<blockquote>
<p>两种语言都有不同的适用范围。Go在编写微服务和典型的”DevOps”任务方面表现出色，但它不是一种系统编程语言。Rust对于那些看重并发性、安全性和性能的任务中更强；但它的学习曲线比Go更陡峭。 &#8211; <a href="https://endler.dev/2017/go-vs-rust/">Matthias Endler</a></p>
</blockquote>
<h3>必须运行更快</h3>
<p>许多人同意，对于大多数程序来说，<a href="https://bitfieldconsulting.com/golang/slower">性能不如可读性重要</a>。但当性能确实重要时，它真的很重要。Rust做了一些设计上的权衡，以达到尽可能好的执行速度。</p>
<p>相比之下，Go更关注简单性，它愿意为此牺牲一些（运行时）性能。但是Go的构建速度是无可匹敌的，这对于大型代码库来说是非常重要的。</p>
<blockquote>
<p>Rust比Go快。在基准测试中，Rust更快，在某些情况下，甚至是数量级的快。但在你选择用Rust写所有东西之前，考虑一下Go在许多基准测试中并不落后于它，而且它仍然比Java、C#、JavaScript、Python等快得多。如果你需要的是顶级的性能，那么选择这两种语言中的任何一种，你都会在游戏中领先。如果你正在构建一个处理高负载的网络服务，你希望能够在纵向和横向上进行扩展，那么这两种语言都会非常适合你。- <a href="https://codeburst.io/should-i-rust-or-should-i-go-59a298e00ea9">安德鲁-拉德</a></p>
</blockquote>
<h3>正确性</h3>
<p>另一方面，如果一个程序不需要正常工作的话，它可以任意地快。大多数代码不是为长期而写的，但有些程序能在生产中运行多长时间往往是令人惊讶的：在某些情况下，可以保持几十年。</p>
<p>在这种情况下，值得在开发中多花一点时间，以确保程序的正确性、可靠性，并在未来不需要大量的维护。</p>
<p>Go和Rust都旨在帮助你编写正确的程序，但方式不同。例如，Go提供了一个极好的内置测试框架，而Rust则专注于使用其借用检查器消除运行时的错误。</p>
<blockquote>
<p>我认为。Go适用于明天必须交付的代码，而Rust适用于必须在未来五年内保持运行不动的代码。 &#8211; Grzegorz Nosek</p>
</blockquote>
<p>虽然Go和Rust对于任何严肃的项目来说都是很好的选择，但是让自己尽可能地了解每种语言及其特点是一个好主意。</p>
<p>归根结底，别人怎么想并不重要：只有你能决定哪种语言适合你和你的团队。</p>
<blockquote>
<p>如果你想加快开发速度，也许是因为你有许多不同的服务需要编写，或者你有一个庞大的开发团队，那么Go是你的首选语言。Go把并发性作为第一等公民给你，并且不容忍不安全的内存访问（Rust也是如此），但不强迫你管理每一个细节。Go是快速和强大的，但它避免了使开发者陷入困境，而是专注于简单性和统一性。如果在另一方面，拧出每一盎司的性能是必要的，那么Rust应该是你的选择。 &#8211; <a href="https://codeburst.io/should-i-rust-or-should-i-go-59a298e00ea9">安德鲁-拉德</a></p>
</blockquote>
<h2>结论</h2>
<p>我希望这篇文章能让你相信Rust和Go都值得你认真考虑。如果可能的话，你应该争取在这两种语言中至少获得一定程度的经验，因为它们对你的任何技术职业都会有极大的帮助，甚至如果你仅把编程作为一种业余爱好的话。</p>
<p>如果你只有时间投资学习一门语言，在你将Go和Rust用于各种不同类型的大小程序之前，不要做出最终决定。</p>
<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/02/22/rust-vs-go-in-2023/feed/</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>让reviewdog支持gitlab-push-commit，守住代码质量下限</title>
		<link>https://tonybai.com/2022/09/08/make-reviewdog-support-gitlab-push-commit-to-preserve-the-code-quality-floor/</link>
		<comments>https://tonybai.com/2022/09/08/make-reviewdog-support-gitlab-push-commit-to-preserve-the-code-quality-floor/#comments</comments>
		<pubDate>Thu, 08 Sep 2022 13:37:30 +0000</pubDate>
		<dc:creator>bigwhite</dc:creator>
				<category><![CDATA[技术志]]></category>
		<category><![CDATA[.gitlab-ci.yml]]></category>
		<category><![CDATA[.reviewdog.yml]]></category>
		<category><![CDATA[Codereview]]></category>
		<category><![CDATA[Compiler]]></category>
		<category><![CDATA[diff]]></category>
		<category><![CDATA[fortran]]></category>
		<category><![CDATA[fuzzing]]></category>
		<category><![CDATA[gerrit]]></category>
		<category><![CDATA[Git]]></category>
		<category><![CDATA[git-commit]]></category>
		<category><![CDATA[git-diff]]></category>
		<category><![CDATA[git-push]]></category>
		<category><![CDATA[github]]></category>
		<category><![CDATA[gitlab]]></category>
		<category><![CDATA[gitlab-runner]]></category>
		<category><![CDATA[Go]]></category>
		<category><![CDATA[Golang]]></category>
		<category><![CDATA[golangci-lint]]></category>
		<category><![CDATA[govet]]></category>
		<category><![CDATA[Make]]></category>
		<category><![CDATA[pre-commit-hook]]></category>
		<category><![CDATA[reviewdog]]></category>
		<category><![CDATA[Test]]></category>
		<category><![CDATA[token]]></category>
		<category><![CDATA[Ubuntu]]></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=3654</guid>
		<description><![CDATA[本文永久链接 &#8211; https://tonybai.com/2022/09/08/make-reviewdog-support-gitlab-push-commit-to-preserve-the-code-quality-floor 一. 代码质量保证的手段 从世界上首款计算机高级程序设计语言Fortran自上世纪50年代诞生以来，编程这个行当已经走过了近70年。虽然年头已不少，但不可否认的一点是：软件生产依然无法像硬件那样标准化，同一个小功能，N个程序员的有N种实现方法。 那么如何保证生产出的软件的质量符合我们的要求呢？不同领域的程序员都在进行着努力，比如：做编译器的让编译器更加严格，努力将内存安全问题彻底消除(如Rust)；做工具链的为程序员提供了内置于语言的各种单测、集成测试、接口测试、fuzzing test等工具(如Go工具链)，让程序员可以更容易地对自己所写的代码进行全方位的测试，以期找出更多的代码中的潜在问题&#8230; 当然，还有一种主观的代码质量保证方法目前依旧是主流，它就是是同行的代码评审(code review, cr)。 代码评审的方法主要有两种，一种是大家坐到一个会议室中，对某个人的某段代码“发表大论”；另外一种则是利用像gerrit这样的工具，在线对其他人的某次提交的代码或某PR的代码进行“评头论足”。 不过无论哪种，最初的时候大家都会细无巨细地从语法层面看到代码结构设计，再到业务逻辑层面，但这样做的弊端也是很显而易见，那就是效率低下，不聚焦(focus)。 于是人们想到了：能否利用工具来尽可能地发现语法层面的问题，这样代码评审时，人类专家便可以聚焦代码结构设计与业务逻辑层面的问题，分工明确后，效率自然提升(如下图)： 注：目前绝大多数工具链仅能自动帮助程序员解决语法层面的问题。将来，随着工具的日益强大，工具可以不断升级关注层次，逐渐进化到具备发现代码结构设计问题，甚至可以发现业务层面逻辑问题的能力。 于是就有了reviewdog这样的可以调用各种linter工具对代码进行自动扫描并将问题以comment的形式自动提交的代码仓库的工具。 到这里很多朋友会问，即便让工具来关注语法层面的问题，为何要用reviewdog这样的工具，git的pre-commit hook、git server hooks、利用Make等工具做开发阶段检查等手段也能检查代码中的语法问题，它们不再香了吗？ 下面简单看看这些方法的“问题”(我们假设大家都已经在使用git作为代码版本管理工具)： git pre-commit-hook git pre-commit hook是一个客户端的git hook，它是放在开发人员本地代码copy中的.git/hooks目录下的钩子，当开发人员在本地执行git commit时会被唤起执行。pre-commot hook的问题就在于我们没法在中心代码仓库对pre-commit hook的脚本内容做统一管理和维护。这个更适合开发人员根据自己的喜好、代码素养在自己的开发环境下部署。 此外，有些代码并不一定是在开发者自己的开发机上提交的，换环境后，pre-commit hook就不在生效。 利用Make等工具做本地检查 利用make工具，我们可以在本地build代码之前对代码做lint等各种静态检查，但和pre-commit-hook一样，虽然Makefile可以提交代码仓库，但真正用于检查代码的工具依旧是在开发人员本地，难于对工具版本，设定的检查规则进行统一管理维护，可能导致不同开发人员环境有不一致的情况。另外同样的情况，有些代码并不一定是在开发者自己的开发机上提交的，换环境后，Make工具依赖的代码检查工具可能并不存在，检查环节就无法有效实施。 git server hooks git支持server hooks，gitlab自12.8版本也开始支持server hooks(替换之前的custom hooks)。 Git server支持以下钩子： pre-receive post-receive update 我倒是没有深研究过这些server hooks是否能满足我们的功能要求，但就git server hooks的部署特点就决定了，它不适合，因为它要在gitlab的server上执行，这就意味着我们需要的所有静态代码检查工具都要部署和配置在与gitlab server同一个环境中，这耦合性太强，根本不便于我们对这些静态代码检查工具的管理与日常维护。 而像reviewdog这样的工具将与ci工具(比如gitlab-ci)集成，运行在slave/worker/runner的机器上，而这些机器上的环境便很容易统一的定制与管理。 好了，下面进入reviewdog时间！ 注：我们以代码仓库为gitlab为例，我曾做过小调查，目前企业内部基本都在使用gitlab搭建私有git仓库，除了那些自实现code仓库平台的大厂。 二. [...]]]></description>
			<content:encoded><![CDATA[<p><img src="https://tonybai.com/wp-content/uploads/make-reviewdog-support-gitlab-push-commit-to-preserve-the-code-quality-floor-1.png" alt="" /></p>
<p><a href="https://tonybai.com/2022/09/08/make-reviewdog-support-gitlab-push-commit-to-preserve-the-code-quality-floor">本文永久链接</a> &#8211; https://tonybai.com/2022/09/08/make-reviewdog-support-gitlab-push-commit-to-preserve-the-code-quality-floor</p>
<h3>一. 代码质量保证的手段</h3>
<p>从世界上首款计算机高级程序设计语言<a href="https://fortran-lang.org/en/">Fortran</a>自上世纪50年代诞生以来，编程这个行当已经走过了近70年。虽然年头已不少，但不可否认的一点是：<strong>软件生产依然无法像硬件那样标准化，同一个小功能，N个程序员的有N种实现方法</strong>。</p>
<p>那么如何保证生产出的软件的质量符合我们的要求呢？不同领域的程序员都在进行着努力，比如：做编译器的让编译器更加严格，努力将内存安全问题彻底消除(如<a href="https://tonybai.com/2021/03/15/rust-vs-go-why-they-are-better-together">Rust</a>)；做工具链的为程序员提供了内置于语言的各种单测、集成测试、接口测试、fuzzing test等工具(如Go工具链)，让程序员可以更容易地对自己所写的代码进行全方位的测试，以期找出更多的代码中的潜在问题&#8230;</p>
<p>当然，还有一种主观的代码质量保证方法目前依旧是主流，它就是是<a href="https://tonybai.com/2013/07/08/code-review-from-rule-of-man-to-rule-of-law/"><strong>同行的代码评审(code review, cr)</strong></a>。</p>
<p>代码评审的方法主要有两种，一种是大家坐到一个会议室中，对某个人的某段代码“发表大论”；另外一种则是利用像<a href="https://www.gerritcodereview.com">gerrit</a>这样的工具，在线对其他人的某次提交的代码或某PR的代码进行“评头论足”。</p>
<p>不过无论哪种，最初的时候大家都会细无巨细地从语法层面看到代码结构设计，再到业务逻辑层面，但这样做的弊端也是很显而易见，那就是<strong>效率低下，不聚焦(focus)</strong>。</p>
<p>于是人们想到了：能否利用工具来尽可能地发现语法层面的问题，这样代码评审时，人类专家便可以聚焦代码结构设计与业务逻辑层面的问题，分工明确后，效率自然提升(如下图)：</p>
<p><img src="https://tonybai.com/wp-content/uploads/make-reviewdog-support-gitlab-push-commit-to-preserve-the-code-quality-floor-2.png" alt="" /></p>
<blockquote>
<p>注：目前绝大多数工具链仅能自动帮助程序员解决语法层面的问题。将来，随着工具的日益强大，工具可以不断升级关注层次，逐渐进化到具备发现代码结构设计问题，甚至可以发现业务层面逻辑问题的能力。</p>
</blockquote>
<p>于是就有了<a href="https://github.com/reviewdog/reviewdog">reviewdog</a>这样的可以调用各种linter工具对代码进行自动扫描并将问题以comment的形式自动提交的代码仓库的工具。</p>
<p>到这里很多朋友会问，即便让工具来关注语法层面的问题，为何要用reviewdog这样的工具，git的pre-commit hook、git server hooks、利用Make等工具做开发阶段检查等手段也能检查代码中的语法问题，它们不再香了吗？</p>
<p>下面简单看看这些方法的“问题”(我们假设大家都已经在使用git作为代码版本管理工具)：</p>
<ul>
<li>git pre-commit-hook  </li>
</ul>
<p>git pre-commit hook是一个客户端的git hook，它是放在开发人员本地代码copy中的.git/hooks目录下的钩子，当开发人员在本地执行git commit时会被唤起执行。pre-commot hook的问题就在于我们没法在中心代码仓库对pre-commit hook的脚本内容做统一管理和维护。这个更适合开发人员根据自己的喜好、代码素养在自己的开发环境下部署。</p>
<p>此外，有些代码并不一定是在开发者自己的开发机上提交的，换环境后，pre-commit hook就不在生效。</p>
<ul>
<li>利用Make等工具做本地检查</li>
</ul>
<p>利用make工具，我们可以在本地build代码之前对代码做lint等各种静态检查，但和pre-commit-hook一样，虽然Makefile可以提交代码仓库，但真正用于检查代码的工具依旧是在开发人员本地，难于对工具版本，设定的检查规则进行统一管理维护，可能导致不同开发人员环境有不一致的情况。另外同样的情况，有些代码并不一定是在开发者自己的开发机上提交的，换环境后，Make工具依赖的代码检查工具可能并不存在，检查环节就无法有效实施。</p>
<ul>
<li>git server hooks</li>
</ul>
<p>git支持<a href="https://git-scm.com/book/en/v2/Customizing-Git-Git-Hooks#_server_side_hooks">server hooks</a>，<a href="https://docs.gitlab.com/ee/administration/server_hooks.html">gitlab自12.8版本也开始支持server hooks</a>(替换之前的custom hooks)。</p>
<p>Git server支持以下钩子：</p>
<ul>
<li>pre-receive</li>
<li>post-receive</li>
<li>update</li>
</ul>
<p>我倒是没有深研究过这些server hooks是否能满足我们的功能要求，但就git server hooks的部署特点就决定了，它不适合，因为它要在gitlab的server上执行，这就意味着我们需要的所有静态代码检查工具都要部署和配置在与gitlab server同一个环境中，这耦合性太强，根本不便于我们对这些静态代码检查工具的管理与日常维护。</p>
<p>而像reviewdog这样的工具将与ci工具(比如gitlab-ci)集成，运行在slave/worker/runner的机器上，而这些机器上的环境便很容易统一的定制与管理。</p>
<p>好了，下面进入reviewdog时间！</p>
<blockquote>
<p>注：我们以代码仓库为gitlab为例，我曾做过小调查，目前企业内部基本都在使用gitlab搭建私有git仓库，除了那些自实现code仓库平台的大厂。</p>
</blockquote>
<h3>二. reviewdog是什么</h3>
<p>reviewdog是一个什么样的工具呢？我们来看看下面这幅示意图：</p>
<p><img src="https://tonybai.com/wp-content/uploads/make-reviewdog-support-gitlab-push-commit-to-preserve-the-code-quality-floor-3.png" alt="" /></p>
<p>我们看到，这是一幅基于gitlab的ci执行流程图，在这个流程中，reviewdog运行在gitlab-runner节点，也就是负责真正执行ci job的节点上。每当开发人员执行一次git push，将commit同步到代码仓库，一次ci job将被触发，在承载该ci job的gitlab-runner节点上，reviewdog被唤起，它做了三件事：</p>
<ul>
<li>调用静态代码检查工具对最新pull下来的代码进行检查；</li>
<li>将代码检查结果(第几行有问题)与commit diff的结果进行比对，得到交集(即commit diff中变更(add和update)的代码行与代码检查结果的行一致的，放入交集中)；</li>
<li>将交集中代码检查结果信息以gitlab commit comment的形式post到gitlab仓库中</li>
</ul>
<p>这样开发人员就可以通过commit页面看到这些comments，并应对这些comment，必要情况下，会修复这些问题。</p>
<p>我们看到reviewdog和其他工具相比，最大的不同就是可以找出commit diff与lint结果中的交集，并与代码仓库交互，将这些交集中的结果以comments的形式放入commit页面，<strong>就像同行代码评审时，同行直接在你的commit页面添加comment一样</strong>。</p>
<p>然而当前版本的reviewdog还不支持直接在gitlab-push-commit上做检查与提交comment，可能是这样的场景较为少见，因为目前开源项目更多采用基于pr(pull request)的工作流，所以reviewdog内置了诸如github-pr-check、github-pr-review、gitlab-mr-commit等工作流的代码review。而像我们使用的基于gitlab-push-commit可能并不多见（当然我们内部使用这种也是有特定上下文的）。</p>
<p>那么如何让reviewdog支持gitlab-push-commit，即对push动作中的commit进行静态代码检查并将结果以comment的形式放入commit页面呢？我们只能<a href="https://github.com/bigwhite/reviewdog">fork reviewdog项目</a>，并在<a href="https://github.com/bigwhite/reviewdog">fork后的项目</a>中自行添加对gitlab-push-commit模式的支持。</p>
<h3>三. 改造reviewdog以支持gitlab-push-commit模式</h3>
<p>reviewdog就是一个命令行工具，通常就是一次性执行，因此它的代码结构较为清晰。我们可以简单围绕它支持的几种reporter模式来搞清楚如何增加对gitlab-push-commit模式的支持。</p>
<p>这里说明一下gitlab-push-commit模式的含义，首先该模式适用于开发人员通过git push推送代码到gitlab时触发的ci job。在该ci job中，reviewdog会运行配置的静态代码分析工具(比如golangci-lint等)对最新的代码进行扫描，并得到问题集合；然后获取最新的commit的sha值(CI_COMMIT_SHA)以及push之前的latest commit的sha值(CI_COMMIT_BEFORE_SHA)，并比较这两个版本间的diff。最后通过文件名与行号将问题集合与diff集合中的“交集”找出来，并将结果以comment形式通过gitlab client api提交到的此次push的最新的那个commit的页面。</p>
<p>目前该模式尚存在一个“瑕疵”，那就是如果一个push中有多个commit，那么gitlab-push-commit模式不会针对每个commit做diff和comment，而只是会用push中的latest commit与push之前的最新commit做比较。</p>
<p>定义清除gitlab-push-commit模式含义后，我们就可以“照葫芦画瓢”的为reviewdog增加该模式的支持了！</p>
<p>在main.go中，我们主要是在run函数中增加一个reporter case分支：</p>
<pre><code>// https://github.com/bigwhite/reviewdog/blob/master/cmd/reviewdog/main.go
func run(r io.Reader, w io.Writer, opt *option) error {
... ...

case "gitlab-push-commit":
    build, cli, err := gitlabBuildWithClient(opt.reporter)
    if err != nil {
        return err
    }
    log.Printf("reviewdog: [gitlab-push-commit-report] gitlabBuildWithClient ok\n")

    gc, err := gitlabservice.NewGitLabPushCommitsCommenter(cli, build.Owner, build.Repo, build.SHA)
    if err != nil {
        return err
    }
    log.Printf("reviewdog: [gitlab-push-commit-report] NewGitLabPushCommitsCommenter ok\n")

    cs = reviewdog.MultiCommentService(gc, cs)
    ds, err = gitlabservice.NewGitLabPushCommitsDiff(cli, build.Owner, build.Repo, build.SHA, build.BeforeSHA)
    if err != nil {
        return err
    }
    log.Printf("reviewdog: [gitlab-push-commit-report] NewGitLabPushCommitsDiff ok\n")
... ...

}
</code></pre>
<p>在这个case中，我们主要是为后面的project.Run或reviewdog.Run方法准备gitlab client对象、PushCommitsCommenter对象(位于service/gitlab/gitlab_push_commits.go中)、PushCommitsDiff对象(位于service/gitlab/gitlab_push_commits_diff.go中)等。</p>
<p>gitlab_push_commits.go和gitlab_push_commits_diff.go是新增的两个go源文件，也是参考了同目录下的gitlab_mr_commit.go和gitlab_mr_diff.go改写而成的。具体代码这里就不列出来了，大家有兴趣可以自行阅读。</p>
<h3>四. 部署gitlab-runner验证新版reviewdog</h3>
<p>下面我们就来验证一下上述改造后的reviewdog。</p>
<h4>1. 安装gitlab-runner</h4>
<p>我们先在gitlab上建立一个实验项目，然后为该项目配置ci。如果你的gitlab还没有注册gitlab-runner，可以按下面步骤安装和注册runner节点(可以在顶层group下面建立，这样runner可以在group内共享：settings => CI/CD => Runners => Show runner installation instructions 有部署runner的详细命令说明)：</p>
<pre><code>//假设我们有一个ubuntu 20.04的主机，我们可以按下面命令安装和注册一个gitlab-runner：

sudo curl -L --output /usr/local/bin/gitlab-runner https://gitlab-runner-downloads.s3.amazonaws.com/latest/binaries/gitlab-runner-linux-amd64

# Give it permissions to execute
sudo chmod +x /usr/local/bin/gitlab-runner

# Create a GitLab CI user
sudo useradd --comment 'GitLab Runner' --create-home gitlab-runner --shell /bin/bash

# Install and run as service
sudo gitlab-runner install --user=gitlab-runner --working-directory=/home/gitlab-runner
sudo gitlab-runner start

# 注册该runner
sudo gitlab-runner register --url http://{gitlab-server-ip-addr}/ --registration-token {registration token}
</code></pre>
<p>上面命令会在/etc/gitlab-runner下面建立一个runner自用配置文件：config.toml：</p>
<pre><code>//  /etc/gitlab-runner/config.toml

concurrent = 1
check_interval = 0

[session_server]
  session_timeout = 1800

[[runners]]
  name = "runner for ard group"
  url = "http://gitlab_ip_addr/"
  id = 1
  token = "{registration token}"
  token_obtained_at = 2022-09-01T11:03:43Z
  token_expires_at = 0001-01-01T00:00:00Z
  executor = "shell"
  shell = "bash"
  environment = ["PATH=/home/tonybai/.bin/go1.18/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/usr/games:/usr/local/games:/snap/bin"]
  [runners.custom_build_dir]
  [runners.cache]
    [runners.cache.s3]
    [runners.cache.gcs]
    [runners.cache.azure]
</code></pre>
<p>这里我选择了shell executor，即基于主机shell执行ci job中的命令。runners下的environment可以设置shell的环境变量，这里的设置将覆盖对应账号(比如gitlab-runner)下的环境变量值。</p>
<p>gitlab-runner部署成功后，我们在group的runners下面便可以看到下面的available runners：</p>
<p><img src="https://tonybai.com/wp-content/uploads/make-reviewdog-support-gitlab-push-commit-to-preserve-the-code-quality-floor-4.png" alt="" /></p>
<blockquote>
<p>注：在创建runner时，我为该runner设置了两个tag：ard和ci。</p>
<p>注：确保runner执行的命令在主机的PATH下面可以找到。</p>
</blockquote>
<h4>2. 创建personal access token</h4>
<p>reviewdog需要通过gitlab client API访问gitlab仓库获取信息并提交comments，这就需要我们为runner执行的命令提供access token。</p>
<p>gitlab有多种access token，比如：personal access token、project access token等。我们创建personal access token，我也测试过project access token，使用<a href="https://docs.gitlab.com/ee/user/project/settings/project_access_tokens.html">project access token</a>可以成功提交comment，但是notify mail十有八九无法发送出来。</p>
<p>access token要保存好，因为它只显示一次。</p>
<p>我们将personal access token配置到实验项目的variable中(Settings => CI/CD => variables)，variable的key为REVIEWDOG_GITLAB_API_TOKEN，值为刚刚创建的token。</p>
<p>后续每次CI job执行，该variable会作为预定义的环境变量对job生效。我们的reviewdog便可以使用该token访问gitlab。</p>
<h4>3. 配置实验项目的ci pipeline</h4>
<p>我们可以通过代码的形式配置实验项目的ci pipeline，我们在项目根目录下建立.gitlab-ci.yml文件，其内容如下：</p>
<pre><code>// .gitlab-ci.yml

build-job:
  tags:
      - ard
  stage: build
  script:
    - export CI_REPO_OWNER=ard/incubators
    - export CI_REPO_NAME=learn-gitlab
    - reviewdog -reporter=gitlab-push-commit
  only:
    - master
    - pushes
</code></pre>
<p>.gitlab-ci.yml的具体字段含义可以参考gitlab文档。在这个配置中，值得注意的有几点：</p>
<ul>
<li>使用tags关联runner(这里用ard这个tag)；</li>
<li>script部分是job具体执行的命令列表，这里先设置CI_REPO_OWNER和CI_REPO_NAME两个环境变量，供reviewdog使用；然后执行reviewdog；</li>
<li>only部分描述仅针对master分支的push事件触发ci job。</li>
</ul>
<h4>4. 配置.reviewdog.yml</h4>
<p>最后，我们来配置一下适合实验项目的reviewdog的配置文件。我们同样在项目根目录下建立.reviewdog.yml文件，其内容如下：</p>
<pre><code>runner:
  golangci:
    cmd: golangci-lint run --max-same-issues=0 --out-format=line-number ./...
    errorformat:
      - '%E%f:%l:%c: %m'
      - '%E%f:%l: %m'
      - '%C%.%#'
    level: warning
</code></pre>
<p>在这里我们看到，我们使用golangci-lint这个静态检查工具对实验项目的代码进行检查。这里的&#8211;max-same-issues=0的含义是不限制相同错误的数量。至于.reviewdog.yml的具体格式，reviewdog项目自身的<a href="https://github.com/bigwhite/reviewdog/blob/master/.reviewdog.yml">.reviewdog.yml</a>很具参考价值，大家需要时可以仔细研究。</p>
<h4>5. 推送代码并验证reviewdog的执行结果</h4>
<p>我们可以故意在代码中写下有问题的一些代码，这些问题要保证可以被golangci-lint工具扫描出来，比如：</p>
<pre><code>package main

type Foo struct {
    A int
    B string
    C bool
}

func Demo1() error {
    return nil
}

func Demo2() error {
    return nil
}

func Demo3() error {
    return nil
}

func main() {
    f := &amp;Foo{1, "tony", false}
    _ = f
    Demo2()
    Demo1()
    Demo3()
}
</code></pre>
<p>这里并没有对Demo函数调用进行错误处理，golangci-lint中的errcheck可以检测出这个问题。提交并push这些代码到仓库，稍等片刻，我们便可收到notify mail，打开commit页面，便会看到下面这样的commit comments：</p>
<p><img src="https://tonybai.com/wp-content/uploads/make-reviewdog-support-gitlab-push-commit-to-preserve-the-code-quality-floor-5.png" alt="" /></p>
<p>看到这样的结果，说明reviewdog按预期工作了！</p>
<h3>五. 小结</h3>
<p>本文介绍了如何基于reviewdog对push提交的commit进行静态代码检查并像一个“同行”一样在commit中提交评论的方法。</p>
<p>这样做的目的就是希望通过工具提升代码评审的效率，同时也守住代码质量的下限。</p>
<p>就像本文开始所说的那样，随着检查工具能力的增强，这样的基于reviewdog自动检查代码的方案在保证代码质量方面还可以继续提升。</p>
<p>Go开源了go/ast等工具链，有能力的童鞋可以基于go/ast自行开发具有“特定目的”的检查工具并集成到reviewdog中，这将使得检查更有针对性和有效性。</p>
<p>本文涉及源码在<a href="https://github.com/bigwhite/reviewdog">这里</a>下载 &#8211; https://github.com/bigwhite/reviewdog/</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>博客：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; 2022, <a href='https://tonybai.com'>bigwhite</a>. 版权所有. </p>
]]></content:encoded>
			<wfw:commentRss>https://tonybai.com/2022/09/08/make-reviewdog-support-gitlab-push-commit-to-preserve-the-code-quality-floor/feed/</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>Golang Channel用法简编</title>
		<link>https://tonybai.com/2014/09/29/a-channel-compendium-for-golang/</link>
		<comments>https://tonybai.com/2014/09/29/a-channel-compendium-for-golang/#comments</comments>
		<pubDate>Mon, 29 Sep 2014 09:02:43 +0000</pubDate>
		<dc:creator>bigwhite</dc:creator>
				<category><![CDATA[技术志]]></category>
		<category><![CDATA[Actor]]></category>
		<category><![CDATA[BestPractice]]></category>
		<category><![CDATA[Blog]]></category>
		<category><![CDATA[Blogger]]></category>
		<category><![CDATA[Channel]]></category>
		<category><![CDATA[Concurrent]]></category>
		<category><![CDATA[container]]></category>
		<category><![CDATA[CSP]]></category>
		<category><![CDATA[docker]]></category>
		<category><![CDATA[github]]></category>
		<category><![CDATA[Go]]></category>
		<category><![CDATA[Golang]]></category>
		<category><![CDATA[Google]]></category>
		<category><![CDATA[GopherCon]]></category>
		<category><![CDATA[LXC]]></category>
		<category><![CDATA[Opensource]]></category>
		<category><![CDATA[Programmer]]></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>
		<category><![CDATA[程序员]]></category>
		<category><![CDATA[编程]]></category>

		<guid isPermaLink="false">http://tonybai.com/?p=1551</guid>
		<description><![CDATA[在进入正式内容前，我这里先顺便转发一则消息，那就是Golang 1.3.2已经正式发布了。国内的golangtc已经镜像了golang.org的安装包下载页面，国内go程序员与爱好者们可以到&#34;Golang中 国&#34;，即golangtc.com去下载go 1.3.2版本。 Go这门语言也许你还不甚了解，甚至是完全不知道，这也有情可原，毕竟Go在TIOBE编程语言排行榜上位列30开外。但近期使用Golang 实现的一杀手级应用 Docker你却不该不知道。docker目前火得是一塌糊涂啊。你去国内外各大技术站点用眼轻瞥一下，如 果没有涉及到&#8220;docker&#8221;字样新闻的站点建 议你以后就不要再去访问了^_^。Docker是啥、怎么用以及基础实践可以参加国内一位仁兄的经验之作：《 Docker &#8211; 从入门到实践》。 据我了解，目前国内试水Go语言开发后台系统的大公司与初创公司日益增多，比如七牛、京东、小米，盛大，金山，东软，搜狗等，在这里我们可以看到一些公司的Go语言应用列表，并且目前这个列表似乎依旧在丰富中。国内Go语言的推广与布道也再稳步推进中，不过目前来看多以Go入 门与基础为主题，Go idioms、tips或Best Practice的Share并不多见，想必国内的先行者、布道师们还在韬光养晦，积攒经验，等到时机来临再厚积薄发。另外国内似乎还没有一个针对Go的 布道平台，比如Golang技术大会之类的的平台。 在国外，虽然Go也刚刚起步，但在Golang share的广度和深度方面显然更进一步。Go的国际会议目前还不多，除了Golang老东家Google在自己的各种大会上留给Golang展示自己的 机会外，由 Gopher Academy 发起的GopherCon 会议也于今年第一次举行，并放出诸多高质量资料，在这里可以下载。欧洲的Go语言大会.dotgo也即将开幕，估计后续这两个大会将撑起Golang技术分享 的旗帜。 言归正传，这里要写的东西并非原创，自己的Go仅仅算是入门级别，工程经验、Best Practice等还谈不上有多少，因此这里主要是针对GopherCon2014上的&#8220;舶来品&#8221;的学习心得。来自CloudFlare的工程师John Graham-Cumming谈了关于 Channel的实践经验，这里针对其分享的内容，记录一些学习体会和理解，并结合一些外延知识，也可以算是一种学习笔记吧，仅供参考。 一、Golang并发基础理论 Golang在并发设计方面参考了C.A.R Hoare的CSP，即Communicating Sequential Processes并发模型理论。但就像John Graham-Cumming所说的那样，多数Golang程序员或爱好者仅仅停留在&#8220;知道&#8221;这一层次，理解CSP理论的并不多，毕竟多数程序员是搞工程 的。不过要想系统学习CSP的人可以从这里下载到CSP论文的最新版本。 维基百科中概要罗列了CSP模型与另外一种并发模型Actor模型的区别： Actor模型广义上讲与CSP模型很相似。但两种模型就提供的原语而言，又有一些根本上的不同之处： &#160;&#160;&#160; &#8211; CSP模型处理过程是匿名的，而Actor模型中的Actor则具有身份标识。 &#160;&#160;&#160; &#8211; CSP模型的消息传递在收发消息进程间包含了一个交会点，即发送方只能在接收方准备好接收消息时才能发送消息。相反，actor模型中的消息传递是异步 的，即消息的发送和接收无需在同一时间进行，发送方可以在接收方准备好接收消息前将消息发送出去。这两种方案可以认为是彼此对偶的。在某种意义下，基于交 会点的系统可以通过构造带缓冲的通信的方式来模拟异步消息系统。而异步系统可以通过构造带消息/应答协议的方式来同步发送方和接收方来模拟交会点似的通信 方式。 &#160;&#160;&#160; &#8211; CSP使用显式的Channel用于消息传递，而Actor模型则将消息发送给命名的目的Actor。这两种方法可以被认为是对偶的。某种意义下，进程可 以从一个实际上拥有身份标识的channel接收消息，而通过将actors构造成类Channel的行为模式也可以打破actors之间的名字耦合。 二、Go Channel基本操作语法 Go Channel的基本操作语法如下： [...]]]></description>
			<content:encoded><![CDATA[<p>在进入正式内容前，我这里先顺便转发一则消息，那就是Golang 1.3.2已经正式发布了。国内的<a href="http://golangtc.com">golangtc</a>已经镜像了golang.org的安装包下载页面，国内go程序员与爱好者们可以到&quot;Golang中 国&quot;，即golangtc.com去下载go 1.3.2版本。</p>
<p><a href="http://golang.org">Go</a>这门语言也许你还不甚了解，甚至是完全不知道，这也有情可原，毕竟Go在<a href="http://www.tiobe.com">TIOBE</a>编程语言排行榜上位列30开外。但近期使用Golang 实现的一杀手级应用<a href="http://docker.com"> <b>Docker</b></a>你却不该不知道。docker目前火得是一塌糊涂啊。你去国内外各大技术站点用眼轻瞥一下，如 果没有涉及到&ldquo;docker&rdquo;字样新闻的站点建 议你以后就不要再去访问了^_^。Docker是啥、怎么用以及基础实践可以参加国内一位仁兄的经验之作：《 <a href="https://www.gitbook.io/book/yeasy/docker_practice">Docker &#8211; 从入门到实践</a>》。</p>
<p>据我了解，目前国内试水Go语言开发后台系统的大公司与初创公司日益增多，比如七牛、京东、小米，盛大，金山，东软，搜狗等，在<a href="https://github.com/qiniu/go/issues/15#issuecomment-55568731">这里</a>我们可以看到一些公司的Go语言应用列表，并且目前这个列表似乎依旧在丰富中。国内Go语言的推广与布道也再稳步推进中，不过目前来看多以Go入 门与基础为主题，Go idioms、tips或Best Practice的Share并不多见，想必国内的先行者、布道师们还在韬光养晦，积攒经验，等到时机来临再厚积薄发。另外国内似乎还没有一个针对Go的 布道平台，比如Golang技术大会之类的的平台。</p>
<p>在国外，虽然Go也刚刚起步，但在Golang share的广度和深度方面显然更进一步。Go的国际会议目前还不多，除了Golang老东家Google在自己的各种大会上留给Golang展示自己的 机会外，由 <span style="font-family: Ubuntu, Tahoma, sans-serif; font-size: 14px; line-height: 20px;"><a href="http://gopheracademy.com/">Gopher Academy</a> 发起的</span><a href="http://www.gophercon.com">GopherCon</a> 会议也于今年第一次举行，并放出诸多高质量资料，在<a href="https://github.com/gophercon/2014-talks">这里</a>可以下载。欧洲的Go语言大会<a href="http://www.dotgo.eu/">.dotgo</a>也即将开幕，估计后续这两个大会将撑起Golang技术分享 的旗帜。</p>
<p>言归正传，这里要写的东西并非原创，自己的Go仅仅算是入门级别，工程经验、Best Practice等还谈不上有多少，因此这里主要是针对GopherCon2014上的&ldquo;舶来品&rdquo;的学习心得。来自CloudFlare的工程师John Graham-Cumming谈了关于 Channel的实践经验，这里针对其分享的内容，记录一些学习体会和理解，并结合一些外延知识，也可以算是一种学习笔记吧，仅供参考。</p>
<p><b>一、Golang并发基础理论</b></p>
<p>Golang在并发设计方面参考了C.A.R Hoare的CSP，即Communicating Sequential Processes并发模型理论。但就像John Graham-Cumming所说的那样，多数Golang程序员或爱好者仅仅停留在&ldquo;知道&rdquo;这一层次，理解CSP理论的并不多，毕竟多数程序员是搞工程 的。不过要想系统学习CSP的人可以从<a href="http://www.usingcsp.com">这里</a>下载到CSP论文的最新版本。</p>
<p><a href="http://en.wikipedia.org/wiki/Communicating_sequential_processes">维基百科</a>中概要罗列了CSP模型与另外一种并发模型Actor模型的区别：</p>
<p>Actor模型广义上讲与CSP模型很相似。但两种模型就提供的原语而言，又有一些根本上的不同之处：<br />
	&nbsp;&nbsp;&nbsp; &#8211; CSP模型处理过程是匿名的，而Actor模型中的Actor则具有身份标识。<br />
	&nbsp;&nbsp;&nbsp; &#8211; CSP模型的消息传递在收发消息进程间包含了一个交会点，即发送方只能在接收方准备好接收消息时才能发送消息。相反，actor模型中的消息传递是异步 的，即消息的发送和接收无需在同一时间进行，发送方可以在接收方准备好接收消息前将消息发送出去。这两种方案可以认为是彼此对偶的。在某种意义下，基于交 会点的系统可以通过构造带缓冲的通信的方式来模拟异步消息系统。而异步系统可以通过构造带消息/应答协议的方式来同步发送方和接收方来模拟交会点似的通信 方式。<br />
	&nbsp;&nbsp;&nbsp; &#8211; CSP使用显式的Channel用于消息传递，而Actor模型则将消息发送给命名的目的Actor。这两种方法可以被认为是对偶的。某种意义下，进程可 以从一个实际上拥有身份标识的channel接收消息，而通过将actors构造成类Channel的行为模式也可以打破actors之间的名字耦合。</p>
<p><b>二、Go Channel基本操作语法</b></p>
<p>Go Channel的基本操作语法如下：</p>
<p><font face="Courier New">c := make(chan bool) //创建一个无缓冲的bool型Channel <br />
	c &lt;- x&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; //向一个Channel发送一个值<br />
	&lt;- c&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; //从一个Channel中接收一个值<br />
	x = &lt;- c&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; //从Channel c接收一个值并将其存储到x中<br />
	x, ok = &lt;- c&nbsp; //从Channel接收一个值，如果channel关闭了或没有数据，那么ok将被置为false</font></p>
<p><i>不带缓冲的Channel</i>兼具通信和同步两种特性，颇受青睐。</p>
<p><b>三、Channel用作信号(Signal)的场景</b></p>
<p>1、等待一个事件(Event)</p>
<p><font face="Courier New">等待一个事件，有时候通过close一个Channel就足够了。例如：</font></p>
<p><font face="Courier New">//testwaitevent1.go<br />
	package main</font></p>
<p><font face="Courier New">import &quot;fmt&quot;</font></p>
<p><font face="Courier New">func main() {<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; fmt.Println(&quot;Begin doing something!&quot;)<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; c := make(chan bool)<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; fmt.Println(&quot;Doing something&#8230;&quot;)<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <i>close(c)</i><br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; }()<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <i>&lt;-c</i><br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; fmt.Println(&quot;Done!&quot;)<br />
	}</font></p>
<p>这里main goroutine通过&quot;<font face="Courier New">&lt;-c</font>&quot;来等待sub goroutine中的&ldquo;完成事件&rdquo;，sub goroutine通过close channel促发这一事件。当然也可以通过向Channel写入一个bool值的方式来作为事件通知。main goroutine在channel c上没有任何数据可读的情况下会阻塞等待。</p>
<p>关于输出结果：</p>
<p>根据《<a href="http://golang.org/ref/mem">Go memory model</a>》中关于close channel与recv from channel的order的定义：<span style="color: rgb(34, 34, 34); font-family: Helvetica, Arial, sans-serif; font-size: 16px; font-style: italic; line-height: normal;"><font face="Courier New">The closing of a channel happens before a receive that returns a zero value because the channel is closed.</font></span></p>
<p>我们可以很容易判断出上面程序的输出结果：</p>
<p><font face="Courier New">Begin doing something!<br />
	Doing something&#8230;<br />
	Done!</font></p>
<p>如果将<font face="Courier New">close(c)</font>换成<font face="Courier New">c&lt;-true</font>，则根据《Go memory model》中的定义：<font face="Courier New"><span style="color: rgb(34, 34, 34); font-size: 16px; font-style: italic; line-height: normal;">A receive from an unbuffered channel happens before the send on that channel completes.</span></font><br />
	&quot;<font face="Courier New">&lt;-c</font>&quot;要先于&quot;<font face="Courier New">c&lt;-true</font>&quot;完成，但也不影响日志的输出顺序，输出结果仍为上面三行。</p>
<p>2、协同多个Goroutines</p>
<p>同上，close channel还可以用于协同多个Goroutines，比如下面这个例子，我们创建了100个Worker Goroutine，这些Goroutine在被创建出来后都阻塞在&quot;<font face="Courier New">&lt;-start&quot;</font>上，直到我们在main goroutine中给出<u>开工</u>的信号：&quot;<font face="Courier New">close(start)&quot;</font>，这些goroutines才开始真正的并发运行起来。</p>
<p><font face="Courier New">//testwaitevent2.go<br />
	package main</font></p>
<p><font face="Courier New">import &quot;fmt&quot;</font></p>
<p><font face="Courier New">func worker(start chan bool, index int) {<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; &lt;-start<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; fmt.Println(&quot;This is Worker:&quot;, index)<br />
	}</font></p>
<p><font face="Courier New">func main() {<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; start := make(chan bool)<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; for i := 1; i &lt;= 100; i++ {<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; go worker(start, i)<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; }<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; close(start)<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; select {} //deadlock we expected<br />
	}</font></p>
<p>3、Select</p>
<p>【select的基本操作】<br />
	select是Go语言特有的操作，使用select我们可以同时在多个channel上进行发送/接收操作。下面是select的基本操作。</p>
<p><font face="Courier New">select {<br />
	case x := &lt;- somechan:<br />
	&nbsp;&nbsp;&nbsp; // &#8230; 使用x进行一些操作</font></p>
<p><font face="Courier New">case y, ok := &lt;- someOtherchan:<br />
	&nbsp;&nbsp;&nbsp; // &#8230; 使用y进行一些操作，<br />
	&nbsp;&nbsp;&nbsp; // </font><font face="Courier New"><font face="Courier New">检查ok值判断someOtherchan是否已经关闭</font></font></p>
<p><font face="Courier New">case outputChan &lt;- z:<br />
	&nbsp;&nbsp;&nbsp; // &#8230; z值被成功发送到Channel上时</font></p>
<p><font face="Courier New">default:<br />
	&nbsp;&nbsp;&nbsp; // &#8230; 上面case均无法通信时，执行此分支<br />
	}</font></p>
<p>【惯用法：for/select】</p>
<p>我们在使用select时很少只是对其进行一次evaluation，我们常常将其与for {}结合在一起使用，并选择适当时机从for{}中退出。</p>
<p><font face="Courier New">for {<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; select {<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; case x := &lt;- somechan:<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; // &#8230; 使用x进行一些操作</font></p>
<p><font face="Courier New">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; case y, ok := &lt;- someOtherchan:<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; // &#8230; 使用y进行一些操作，<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; // 检查ok值判断someOtherchan是否已经关闭</font></p>
<p><font face="Courier New">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; case outputChan &lt;- z:<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; // &#8230; z值被成功发送到Channel上时</font></p>
<p><font face="Courier New">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; default:<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; // &#8230; 上面case均无法通信时，执行此分支<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; }<br />
	} </font></p>
<p>【终结workers】</p>
<p>下面是一个常见的终结sub worker goroutines的方法，每个worker goroutine通过select监视一个die channel来及时获取main goroutine的退出通知。</p>
<p><font face="Courier New">//testterminateworker1.go</font><br />
	<font face="Courier New">package main</font></p>
<p><font face="Courier New">import (<br />
	&nbsp;&nbsp;&nbsp; &quot;fmt&quot;<br />
	&nbsp;&nbsp;&nbsp; &quot;time&quot;<br />
	)</font></p>
<p><font face="Courier New">func worker(die chan bool, index int) {<br />
	&nbsp;&nbsp;&nbsp; fmt.Println(&quot;Begin: This is Worker:&quot;, index)<br />
	&nbsp;&nbsp;&nbsp; for {<br />
	&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; select {<br />
	&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; //case xx：<br />
	&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; //做事的分支<br />
	&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; case &lt;-die:<br />
	&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; fmt.Println(&quot;Done: This is Worker:&quot;, index)<br />
	&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; return<br />
	&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; }<br />
	&nbsp;&nbsp;&nbsp; }<br />
	}</font></p>
<p><font face="Courier New">func main() {<br />
	&nbsp;&nbsp;&nbsp; die := make(chan bool)</font></p>
<p><font face="Courier New">&nbsp;&nbsp;&nbsp; for i := 1; i &lt;= 100; i++ {<br />
	&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; go worker(die, i)<br />
	&nbsp;&nbsp;&nbsp; }</font></p>
<p><font face="Courier New">&nbsp;&nbsp;&nbsp; time.Sleep(time.Second * 5)<br />
	&nbsp;&nbsp;&nbsp; close(die)<br />
	&nbsp;&nbsp;&nbsp; select {} </font><font face="Courier New"><font face="Courier New">//deadlock we expected</font><br />
	}</font></p>
<p>【终结验证】</p>
<p>有时候终结一个worker后，main goroutine想确认worker routine是否真正退出了，可采用下面这种方法：</p>
<p><font face="Courier New">//testterminateworker2.go<br />
	package main</font></p>
<p><font face="Courier New">import (<br />
	&nbsp;&nbsp;&nbsp; &quot;fmt&quot;<br />
	&nbsp;&nbsp;&nbsp; //&quot;time&quot;<br />
	)</font></p>
<p><font face="Courier New">func worker(die chan bool) {<br />
	&nbsp;&nbsp;&nbsp; fmt.Println(&quot;Begin: This is Worker&quot;)<br />
	&nbsp;&nbsp;&nbsp; for {<br />
	&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; select {<br />
	&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; //case xx：<br />
	&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; //做事的分支<br />
	&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; case &lt;-die:<br />
	&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; fmt.Println(&quot;Done: This is Worker&quot;)<br />
	&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; die &lt;- true<br />
	&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; return<br />
	&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; }<br />
	&nbsp;&nbsp;&nbsp; }<br />
	}</font></p>
<p><font face="Courier New">func main() {<br />
	&nbsp;&nbsp;&nbsp; die := make(chan bool)</font></p>
<p><font face="Courier New">&nbsp;&nbsp;&nbsp; go worker(die)</font></p>
<p><font face="Courier New">&nbsp;&nbsp;&nbsp; die &lt;- true<br />
	&nbsp;&nbsp;&nbsp; &lt;-die<br />
	&nbsp;&nbsp;&nbsp; fmt.Println(&quot;Worker goroutine has been terminated&quot;)<br />
	}</font></p>
<p>【关闭的Channel永远不会阻塞】</p>
<p>下面演示在一个已经关闭了的channel上读写的结果：</p>
<p><font face="Courier New">//testoperateonclosedchannel.go<br />
	package main</font></p>
<p><font face="Courier New">import &quot;fmt&quot;</font></p>
<p><font face="Courier New">func main() {<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; cb := make(chan bool)<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; close(cb)<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; x := &lt;-cb<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; fmt.Printf(&quot;%#v\n&quot;, x)</font></p>
<p><font face="Courier New">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; x, ok := &lt;-cb<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; fmt.Printf(&quot;%#v %#v\n&quot;, x, ok)</font></p>
<p><font face="Courier New">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; ci := make(chan int)<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; close(ci)<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; y := &lt;-ci<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; fmt.Printf(&quot;%#v\n&quot;, y)</font></p>
<p><font face="Courier New">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; cb &lt;- true<br />
	}</font></p>
<p><font face="Courier New">$go run </font><font face="Courier New"><font face="Courier New">testoperateonclosedchannel.go</font><br />
	false<br />
	false false<br />
	0<br />
	panic: runtime error: send on closed channel</font></p>
<p>可以看到在一个已经close的unbuffered channel上执行读操作，回返回channel对应类型的零值，比如bool型channel返回false，int型channel返回0。但向close的channel写则会触发panic。不过无论读写都不会导致阻塞。</p>
<p>【关闭带缓存的channel】</p>
<p>将unbuffered channel换成buffered channel会怎样？我们看下面例子：</p>
<p><font face="Courier New">//testclosedbufferedchannel.go<br />
	package main</font></p>
<p><font face="Courier New">import &quot;fmt&quot;</font></p>
<p><font face="Courier New">func main() {<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; c := make(chan int, 3)<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; c &lt;- 15<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; c &lt;- 34<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; c &lt;- 65<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; close(c)<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; fmt.Printf(&quot;%d\n&quot;, &lt;-c)<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; fmt.Printf(&quot;%d\n&quot;, &lt;-c)<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; fmt.Printf(&quot;%d\n&quot;, &lt;-c)<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; fmt.Printf(&quot;%d\n&quot;, &lt;-c)</font></p>
<p><font face="Courier New">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; c &lt;- 1<br />
	}</font></p>
<p><font face="Courier New">$go run testclosedbufferedchannel.go<br />
	15<br />
	34<br />
	65<br />
	0<br />
	panic: runtime error: send on closed channel</font></p>
<p>可以看出带缓冲的channel略有不同。尽管已经close了，但我们依旧可以从中读出关闭前写入的3个值。第四次读取时，则会返回该channel类型的零值。向这类channel写入操作也会触发panic。</p>
<p>【range】</p>
<p>Golang中的range常常和channel并肩作战，它被用来从channel中读取所有值。下面是一个简单的实例：</p>
<p><font face="Courier New">//testrange.go<br />
	package main</font></p>
<p><font face="Courier New">import &quot;fmt&quot;</font></p>
<p><font face="Courier New">func generator(strings chan string) {<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; strings &lt;- &quot;Five hour&#39;s New York jet lag&quot;<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; strings &lt;- &quot;and Cayce Pollard wakes in Camden Town&quot;<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; strings &lt;- &quot;to the dire and ever-decreasing circles&quot;<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; strings &lt;- &quot;of disrupted circadian rhythm.&quot;<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; close(strings)<br />
	}</font></p>
<p><font face="Courier New">func main() {<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; strings := make(chan string)<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; go generator(strings)<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; for s := range strings {<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; fmt.Printf(&quot;%s\n&quot;, s)<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; }<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; fmt.Printf(&quot;\n&quot;)<br />
	}</font></p>
<p><b>四、隐藏状态</b></p>
<p>下面通过一个例子来演示一下channel如何用来隐藏状态：</p>
<p>1、例子：唯一的ID服务</p>
<p><font face="Courier New">//testuniqueid.go<br />
	package main</font></p>
<p><font face="Courier New">import &quot;fmt&quot;</font></p>
<p><font face="Courier New">func newUniqueIDService() &lt;-chan string {<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; id := make(chan string)<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; var counter int64 = 0<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; id &lt;- fmt.Sprintf(&quot;%x&quot;, counter)<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; counter += 1<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 />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; return id<br />
	}<br />
	func main() {<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; id := newUniqueIDService()<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; for i := 0; i &lt; 10; i++ {<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; fmt.Println(&lt;-id)<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; }<br />
	}</font></p>
<p><font face="Courier New">$ go run testuniqueid.go<br />
	0<br />
	1<br />
	2<br />
	3<br />
	4<br />
	5<br />
	6<br />
	7<br />
	8<br />
	9</font></p>
<p>newUniqueIDService通过一个channel与main goroutine关联，main goroutine无需知道uniqueid实现的细节以及当前状态，只需通过channel获得最新id即可。</p>
<p><b>五、默认情况</b></p>
<p>我想这里John Graham-Cumming主要是想告诉我们select的default分支的实践用法。</p>
<p>1、select&nbsp; for non-blocking receive</p>
<p><font face="Courier New">idle:= make(chan []byte, 5) //用一个带缓冲的channel构造一个简单的队列</font></p>
<p><font face="Courier New">select {<br />
	case b = &lt;-idle:  //尝试从idle队列中读取<br />
	&nbsp;&nbsp;&nbsp; &#8230;<br />
	default:&nbsp; //队列空，分配一个新的buffer<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; makes += 1<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; b = make([]byte, size)<br />
	}</font></p>
<p>2、select for non-blocking send</p>
<p><font face="Courier New">idle:= make(chan []byte, 5) </font><font face="Courier New"><font face="Courier New">//用一个带缓冲的channel构造一个简单的队列</font></font></p>
<p><font face="Courier New">select {<br />
	case idle &lt;- b: //尝试向队列中插入一个buffer<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; //&#8230;<br />
	default: //队列满？</font></p>
<p><font face="Courier New">}</font></p>
<p><b>六、Nil Channels</b></p>
<p>1、nil channels阻塞</p>
<p>对一个没有初始化的channel进行读写操作都将发生阻塞，例子如下：</p>
<p><font face="Courier New">package main</font></p>
<p><font face="Courier New">func main() {<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; var c chan int<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; &lt;-c<br />
	}</font></p>
<p><font face="Courier New">$go run testnilchannel.go<br />
	fatal error: all goroutines are asleep &#8211; deadlock!</font></p>
<p><font face="Courier New">package main</font></p>
<p><font face="Courier New">func main() {<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; var c chan int<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; c &lt;- 1<br />
	}</font></p>
<p><font face="Courier New">$go run testnilchannel.go<br />
	fatal error: all goroutines are asleep &#8211; deadlock!</font></p>
<p>2、nil channel在select中很有用</p>
<p>看下面这个例子：</p>
<p><font face="Courier New">//testnilchannel_bad.go<br />
	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 c1, c2 chan int = make(chan int), make(chan int)<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; time.Sleep(time.Second * 5)<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; c1 &lt;- 5<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; close(c1)<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; }()</font></p>
<p><font face="Courier New">&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; time.Sleep(time.Second * 7)<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; c2 &lt;- 7<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; close(c2)<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; select {<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; case x := &lt;-c1:<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(x)<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; case x := &lt;-c2:<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(x)<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 />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; fmt.Println(&quot;over&quot;)<br />
	}</font></p>
<p>我们原本期望程序交替输出5和7两个数字，但实际的输出结果却是：</p>
<p><font face="Courier New">5<br />
	0<br />
	0<br />
	0<br />
	&#8230; &#8230; 0死循环</font></p>
<p>再仔细分析代码，原来select每次按case顺序evaluate：<br />
	&nbsp;&nbsp;&nbsp; &#8211; 前5s，select一直阻塞；<br />
	&nbsp;&nbsp;&nbsp; &#8211; 第5s，c1返回一个5后被close了，&ldquo;case x := &lt;-c1&rdquo;这个分支返回，select输出5，并重新select<br />
	&nbsp;&nbsp;&nbsp; &#8211; 下一轮select又从&ldquo;case x := &lt;-c1&rdquo;这个分支开始evaluate，由于c1被close，按照前面的知识，close的channel不会阻塞，我们会读出这个 channel对应类型的零值，这里就是0；select再次输出0；这时即便c2有值返回，程序也不会走到c2这个分支<br />
	&nbsp;&nbsp;&nbsp; &#8211; 依次类推，程序无限循环的输出0</p>
<p>我们利用nil channel来改进这个程序，以实现我们的意图，代码如下：</p>
<p><font face="Courier New">//testnilchannel.go<br />
	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 c1, c2 chan int = make(chan int), make(chan int)<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; time.Sleep(time.Second * 5)<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; c1 &lt;- 5<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; close(c1)<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; }()</font></p>
<p><font face="Courier New">&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; time.Sleep(time.Second * 7)<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; c2 &lt;- 7<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; close(c2)<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; select {<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; case x, ok := &lt;-c1:<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; if !ok {<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;&nbsp;&nbsp;&nbsp; c1 = nil<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; } else {<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;&nbsp;&nbsp;&nbsp; fmt.Println(x)<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&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; case x, ok := &lt;-c2:<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; if !ok {<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;&nbsp;&nbsp;&nbsp; c2 = nil<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; } else {<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;&nbsp;&nbsp;&nbsp; fmt.Println(x)<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&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; }<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; if c1 == nil &amp;&amp; c2 == nil {<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; break<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 />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; fmt.Println(&quot;over&quot;)<br />
	}</font></p>
<p><font face="Courier New">$go run testnilchannel.go<br />
	5<br />
	7<br />
	over</font></p>
<p>可以看出：通过将已经关闭的channel置为nil，下次select将会阻塞在该channel上，使得select继续下面的分支evaluation。</p>
<p><b>七、Timers</b></p>
<p>1、超时机制Timeout</p>
<p>带超时机制的select是常规的tip，下面是示例代码，实现30s的超时select：</p>
<p><font face="Courier New">func worker(start chan bool) {<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; timeout := time.After(30 * time.Second)<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; for {<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; select {<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; // &#8230; do some stuff<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; case &lt;- timeout:<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; return<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>2、心跳HeartBeart</p>
<p>与timeout实现类似，下面是一个简单的心跳select实现：</p>
<p><font face="Courier New">func worker(start chan bool) {<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; heartbeat := time.Tick(30 * time.Second)<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; for {<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; select {<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; // &#8230; do some stuff<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; case &lt;- heartbeat:<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; //&#8230; do heartbeat stuff<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 style='text-align:left'>&copy; 2014, <a href='https://tonybai.com'>bigwhite</a>. 版权所有. </p>
]]></content:encoded>
			<wfw:commentRss>https://tonybai.com/2014/09/29/a-channel-compendium-for-golang/feed/</wfw:commentRss>
		<slash:comments>3</slash:comments>
		</item>
		<item>
		<title>再谈那些代码中的“中国式”命名</title>
		<link>https://tonybai.com/2013/11/22/those-chinese-style-naming-in-code-again/</link>
		<comments>https://tonybai.com/2013/11/22/those-chinese-style-naming-in-code-again/#comments</comments>
		<pubDate>Fri, 22 Nov 2013 07:56:31 +0000</pubDate>
		<dc:creator>bigwhite</dc:creator>
				<category><![CDATA[思考控]]></category>
		<category><![CDATA[Blog]]></category>
		<category><![CDATA[Blogger]]></category>
		<category><![CDATA[Chinglish]]></category>
		<category><![CDATA[geilivable]]></category>
		<category><![CDATA[Google]]></category>
		<category><![CDATA[Naming]]></category>
		<category><![CDATA[Programmer]]></category>
		<category><![CDATA[taikonaut]]></category>
		<category><![CDATA[tuhao]]></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">http://tonybai.com/?p=1448</guid>
		<description><![CDATA[近期博客访问量提高了不少，分析了下原因，发现是有几篇近期写的文章被某个好心网友提交到dbanotes的Startup News上了。与此同时，一些反馈也随之而来。从反馈来看，《那些代码中的&#8220;中国式&#8221;命名》一文似乎受到了更多的关注，或许是文章标题比较容易引起好奇的 缘故吧。但文章的本意仅是想阐述一些事实罢了，并没有&#8220;哗众取宠&#8221;的意思。网友的观点也促使我重新对&#8220;中国式&#8221;命名做了反思。 * &#8220;中国式&#8221;命名的普遍性 我曾天真地希望该问题只是我们项目中的个例，但现实是&#8220;沮丧&#8221;的。看到评论中几个网友都反馈&#8220;中枪&#8221;，说明该命名方式似乎是普遍存在于中华大地程 序员们的代码库中的。 中国式命名归跟结底是文化差异性和表达方式的问题，就和Chinglish一样。由于中式词汇、语法结构已经成为了我们的潜意识一部分，存在与大 脑的核心层，每当 我们要命名或表达一个事物时，大多数人首先在大脑中展现的是这个事物的中文拼写方式、中文语法的结构，其次才可能是英文的（对于第一外语是英语的人），如 果想不出正确的英文名并且懒得去求 谷哥，那么该事物在程序代码中就很可能以&#8220;中国式&#8221;的命名而存在着。 * 不是所有Chinglish都是English 在再次谈&#8220;中国式&#8221;命名之前，我们先要搞清楚：&#8220;不是所有Chinglish都是English&#8221;。 Chinglish刚出现的时候，标准英语的支持者认为Chinglish是垃圾，是错误的表达，无法被接受，对其进行抨击。但万事万物都有一个 接受的过程。今天来看，越来越多的选词达意准确的Chinglish词汇以及表达方式正在被国人接受甚至被以英语为母语的人所接受而成为 English，比如近年来的热词：geilivable（给力），再比如很早之前就接受的&#34;long time no see&#34;等。 作为人类的优秀语言，无论是英文还是中文，都具有很强的开放性和包容性。随着时代的变迁，新生事物的出现，词汇与表达方式都是在语言间相互渗透， 相互补充的。比如随着近些年中国航天事业的迅猛发展，尤其是神舟飞船的多次成功发射，标准英语中接纳了&#8220;中国宇航员&#8221;这个词 汇：taikonaut；再比如很可能于明年被收录到牛津英语词典中的&#8221;Tuhao(土豪)&#8221;、 &#8220;Dama(大妈)&#8221;和&#8220;Hukou(户口)&#8221;等。而近些年来，一些外词的音译中文词汇也被加入汉语词典了，比如博客 （blog)、粉丝(fans)等。 但不是所有Chinglish都可以被接受而成为English的。Chinglish是良莠不齐的，那些完全错误的、让人啼笑皆非的词汇和表达 方式现在不会被接受，以后也是不会被接受的。比如下面这两个典型的错误： &#160;&#160;&#160; 杯子 &#8211; Cup son &#160;&#160;&#160; 开水房 &#8211; Open Water House * 用Chinglish != &#34;中国式&#34;命名 既然&#8220;中国式&#8221;命名是普遍存在的，那是否是合理的呢？在上一篇文章中，我个人将其归类为bad smell一类，现在的观点依旧如此。 有人不禁要问：既然有些中国式英语(Chinglish)都能被老外所接受，那&#8220;中国式&#8221;命名为何不可呢？ 我的答案如下：在代码中使用已经被老外接受了的Chinglish词汇，实际上与使用地道英文词汇本质上是相同的，算不上&#8220;中国式&#8221;命名；这里的 &#8220;中国式&#8221;命名仅针对我在上一篇文章中提到的那些命名方式，当然包括那些并未被广泛接受的Chinglish词汇和使用方法。 * 对&#34;中国式&#34;命名的态度 网友观点：&#8220;认真你就痛苦了&#8221;。 我倒不是这么想的。既然我们认为命名在编码过程是重要的、困难的，我们就更是要认真对待，在这方面我们有些时候真得较较真儿。我想这也是专业性的 一种体现。 * 到底该如何做？ [...]]]></description>
			<content:encoded><![CDATA[<p>近期博客访问量提高了不少，分析了下原因，发现是有几篇近期写的文章被某个好心网友提交到<a href="http://dbanotes.net/">dbanotes</a>的<a href="http://news.dbanotes.net/">Startup News</a>上了。与此同时，一些反馈也随之而来。从反馈来看，《<a href="http://tonybai.com/2013/11/06/those-chinese-style-naming-in-code/">那些代码中的&ldquo;中国式&rdquo;命名</a>》一文似乎受到了更多的关注，或许是文章标题比较容易引起好奇的 缘故吧。但文章的本意仅是想阐述一些事实罢了，并没有&ldquo;哗众取宠&rdquo;的意思。网友的观点也促使我重新对&ldquo;中国式&rdquo;命名做了反思。</p>
<p><b>* &ldquo;中国式&rdquo;命名的普遍性</b></p>
<p>我曾天真地希望该问题只是我们项目中的个例，但现实是&ldquo;沮丧&rdquo;的。看到评论中几个网友都反馈&ldquo;中枪&rdquo;，说明该命名方式似乎是普遍存在于中华大地程 序员们的代码库中的。</p>
<p>中国式命名归跟结底是文化差异性和表达方式的问题，就和<a href="http://en.wikipedia.org/wiki/Chinglish‎">Chinglish</a>一样。由于中式词汇、语法结构已经成为了我们的潜意识一部分，存在与大 脑的核心层，每当 我们要命名或表达一个事物时，大多数人首先在大脑中展现的是这个事物的中文拼写方式、中文语法的结构，其次才可能是英文的（对于第一外语是英语的人），如 果想不出正确的英文名并且懒得去求 谷哥，那么该事物在程序代码中就很可能以&ldquo;中国式&rdquo;的命名而存在着。</p>
<p><b>* 不是所有Chinglish都是English</b></p>
<p>在再次谈&ldquo;中国式&rdquo;命名之前，我们先要搞清楚：&ldquo;不是所有Chinglish都是English&rdquo;。</p>
<p>Chinglish刚出现的时候，标准英语的支持者认为Chinglish是垃圾，是错误的表达，无法被接受，对其进行抨击。但万事万物都有一个 接受的过程。今天来看，越来越多的选词达意准确的Chinglish词汇以及表达方式正在被国人接受甚至被以英语为母语的人所接受而成为 English，比如近年来的热词：geilivable（给力），再比如很早之前就接受的&quot;long time no see&quot;等。</p>
<p>作为人类的优秀语言，无论是英文还是中文，都具有很强的开放性和包容性。随着时代的变迁，新生事物的出现，词汇与表达方式都是在语言间相互渗透， 相互补充的。比如随着近些年中国航天事业的迅猛发展，尤其是神舟飞船的多次成功发射，标准英语中接纳了&ldquo;中国宇航员&rdquo;这个词 汇：taikonaut；再比如很可能于明年被收录到牛津英语词典中的&rdquo;<font color="#333333">Tuhao(土豪)&rdquo;、 &ldquo;Dama(大妈)&rdquo;和&ldquo;Hukou(户口)&rdquo;等。</font>而近些年来，一些外词的音译中文词汇也被加入汉语词典了，比如博客 （blog)、粉丝(fans)等。</p>
<p>但不是所有Chinglish都可以被接受而成为English的。Chinglish是良莠不齐的，那些完全错误的、让人啼笑皆非的词汇和表达 方式现在不会被接受，以后也是不会被接受的。比如下面这两个典型的错误：</p>
<p><font face="Courier New">&nbsp;&nbsp;&nbsp; 杯子 &#8211; Cup son<br />
	&nbsp;&nbsp;&nbsp; 开水房 &#8211; Open Water House</font></p>
<p><b>* 用Chinglish != &quot;中国式&quot;命名</b></p>
<p>既然&ldquo;中国式&rdquo;命名是普遍存在的，那是否是合理的呢？在上一篇文章中，我个人将其归类为bad smell一类，现在的观点依旧如此。</p>
<p>有人不禁要问：既然有些中国式英语(Chinglish)都能被老外所接受，那&ldquo;中国式&rdquo;命名为何不可呢？</p>
<p>我的答案如下：在代码中使用已经被老外接受了的Chinglish词汇，实际上与使用地道英文词汇本质上是相同的，算不上&ldquo;中国式&rdquo;命名；这里的 &ldquo;中国式&rdquo;命名仅针对我在上一篇文章中提到的那些命名方式，当然包括那些并未被广泛接受的Chinglish词汇和使用方法。</p>
<p><b>* 对&quot;中国式&quot;命名的态度</b></p>
<p>网友观点：&ldquo;认真你就痛苦了&rdquo;。<br />
	我倒不是这么想的。既然我们认为命名在编码过程是重要的、困难的，我们就更是要认真对待，在这方面我们有些时候真得较较真儿。我想这也是专业性的 一种体现。</p>
<p><b>* 到底该如何做？</b></p>
<p>一句话：尽可能用English（编程界主流文化在欧美，主流语言是英语，这才是根本原因），包括那些广泛接受的Chinglish。纯自造的 &ldquo;词汇&rdquo;，比如网友评论中提到的left_kuohao这种中英结合词还是不写为好。</p>
<p>如果有一天中文编程语言成为编程界的主流，那中国程序员也就不用在命名上纠结了。</p>
<p style='text-align:left'>&copy; 2013, <a href='https://tonybai.com'>bigwhite</a>. 版权所有. </p>
]]></content:encoded>
			<wfw:commentRss>https://tonybai.com/2013/11/22/those-chinese-style-naming-in-code-again/feed/</wfw:commentRss>
		<slash:comments>3</slash:comments>
		</item>
		<item>
		<title>简析指针与多维数组</title>
		<link>https://tonybai.com/2013/03/28/pointer-and-multi-dimension-array-in-c/</link>
		<comments>https://tonybai.com/2013/03/28/pointer-and-multi-dimension-array-in-c/#comments</comments>
		<pubDate>Thu, 28 Mar 2013 13:28:49 +0000</pubDate>
		<dc:creator>bigwhite</dc:creator>
				<category><![CDATA[技术志]]></category>
		<category><![CDATA[Array]]></category>
		<category><![CDATA[Blog]]></category>
		<category><![CDATA[Blogger]]></category>
		<category><![CDATA[C]]></category>
		<category><![CDATA[C专家编程]]></category>
		<category><![CDATA[Pointer]]></category>
		<category><![CDATA[Programmer]]></category>
		<category><![CDATA[内存]]></category>
		<category><![CDATA[博客]]></category>
		<category><![CDATA[多级指针]]></category>
		<category><![CDATA[多维数组]]></category>
		<category><![CDATA[学习]]></category>
		<category><![CDATA[工作]]></category>
		<category><![CDATA[思考]]></category>
		<category><![CDATA[指针]]></category>
		<category><![CDATA[数组]]></category>
		<category><![CDATA[程序员]]></category>
		<category><![CDATA[编程]]></category>

		<guid isPermaLink="false">http://tonybai.com/?p=1236</guid>
		<description><![CDATA[上一篇文章中对多级指针做了简要分析，其实只有当指针与多维数组以及函数联合在一起使用时，麻烦才算真正到来。 零、数组与数组名 C语言中的数组的一般声明形式如下： T arr_name[n]; /* T为类型，n为数组元素个数 */ 从内存布局角度来说，数组T arr_name[n]就是内存中连续的内存单元，每个内存单元的长度为sizeof(T)，数组的起始内存单元地址为arr_name所在的内存地址， 同时也是数组第一个元素arr_name[0]的内存地址。 C语言数组的数组名(arr_name)有这样的特点：arr_name = &#38;arr_name = *arr_name = 数组起始地址。见下面例子： char a[5]; printf(&#34;a = %p\n&#34;, a); printf(&#34;&#38;a = %p\n&#34;, &#38;a); printf(&#34;*a = %p\n&#34;, *a); 输出结果： a = 0xbfb146c0 &#38;a = 0xbfb146c0 *a = 0xbfb146c0 C语言数组与指针有着紧密的联系。数组名本身的值就是数组的起始地址，有了地址，就有了指针存在的理由了。 1) 数组名可以被当作指针来用 &#160;&#160;&#160; char a[5] = {1, 2, 3, 4, 5}; &#160;&#160;&#160; [...]]]></description>
			<content:encoded><![CDATA[<p>上一篇文章中对<a href="http://tonybai.com/2013/03/23/multi-dimension-pointer-in-c/">多级指针</a>做了简要分析，其实只有当指针与多维数组以及函数联合在一起使用时，麻烦才算真正到来。</p>
<p><b>零、数组</b><b>与数组名</b></p>
<p><a href="http://en.wikipedia.org/wiki/C_(programming_language)">C语言</a>中的数组的一般声明形式如下：</p>
<p><span style="font-family:courier new,courier,monospace;">T arr_name[n]; /* T为类型，n为数组元素个数 */</span></p>
<p>从<b>内存布局</b>角度来说，数组T arr_name[n]就是内存中连续的内存单元，每个内存单元的长度为sizeof(T)，数组的起始内存单元地址为arr_name所在的内存地址， 同时也是数组第一个元素arr_name[0]的内存地址。</p>
<p>C语言数组的<b>数组名</b>(arr_name)有这样的特点：arr_name = &amp;arr_name = *arr_name = 数组起始地址。见下面例子：</p>
<p><span style="font-family:courier new,courier,monospace;">char a[5];</span></p>
<p>printf(&quot;a = %p\n&quot;, a);<br />
	printf(&quot;&amp;a = %p\n&quot;, &amp;a);<br />
	printf(&quot;*a = %p\n&quot;, *a);</p>
<p>输出结果：</p>
<p><span style="font-family:courier new,courier,monospace;">a = 0xbfb146c0<br />
	&amp;a = 0xbfb146c0<br />
	*a = 0xbfb146c0</span></p>
<p>C语言<b>数组与指针</b>有着紧密的联系。数组名本身的值就是数组的起始地址，有了地址，就有了指针存在的理由了。</p>
<p>1) 数组名可以被当作指针来用</p>
<p>&nbsp;&nbsp;&nbsp; <span style="font-family:courier new,courier,monospace;">char a[5] = {1, 2, 3, 4, 5};<br />
	&nbsp;&nbsp;&nbsp; printf(&quot;%d, %d, %d\n&quot;, *a, *(a+1), *(a+2)); // 输出1, 2, 3</span><br />
	&nbsp;&nbsp;&nbsp;<br />
	&nbsp;&nbsp;&nbsp; 这种用法下，数组名相当于指向数组首地址的char*指针变量。</p>
<p>2) 数组名可以作为地址被赋值给兼容类型的指针变量<br />
	&nbsp;&nbsp;&nbsp;<br />
	&nbsp;&nbsp;&nbsp; <span style="font-family:courier new,courier,monospace;">char a[5] = {1, 2, 3, 4, 5};<br />
	&nbsp;&nbsp;&nbsp; char *p = a;<br />
	&nbsp;&nbsp;&nbsp; printf(&quot;%d, %d, %d\n&quot;, *p, *(p+1), *(p+2)); //输出1, 2, 3</span></p>
<p>3) 数组名不可以被当作指针变量来赋值</p>
<p>&nbsp;&nbsp;&nbsp; <span style="font-family:courier new,courier,monospace;">char a[5] = {1, 2, 3, 4, 5};<br />
	&nbsp;&nbsp;&nbsp; char b[5] = {6, 7, 8, 9, 0};</span></p>
<p>&nbsp;&nbsp;&nbsp; a = b; //编译器提示错误：将&lsquo;char *&rsquo;赋值给&lsquo;char[5]&rsquo;时类型不兼容</p>
<p>&nbsp;&nbsp;&nbsp; 数组名与指针变量不同：指针变量有单独的存储空间，其存储空间内存储的是指向的内存单元的地址，但数组名只是个&quot;代号&quot;而已，其没有单独的存储空间，其所 在内存地址中存储的是数组第一个元素的元素值，而不是一个地址。或者说数组名代表的是一个值类型，char a[5]中的a可理解为是一个char[5]的值类型变量。将一个数组指针变量值赋值给一个值变量显然是不合逻辑的，也是非法的。</p>
<p>4) 考虑到效率，数组无法被按值传递给函数<br />
	&nbsp;&nbsp;&nbsp;<br />
	&nbsp;&nbsp;&nbsp; 虽然数组名可以理解为一个值类型变量，但将数组名传递给函数时，传递的不是数组的全部，而只是数组的首地址，这显然是有效率方面考虑的。如果是传递数组的 全部，那碰到大数组时，这个mem copy的效率显然是不可接受的。但通过这个首地址，函数内部也是可以访问和修改数组中的所有元素的。<br />
	&nbsp;&nbsp;&nbsp;<br />
	5) 函数形参中的数组变量将被转化为兼容类型指针变量对待</p>
<p>正如4)中所言，数组是以传址方式传入函数的。对于以数组变量作为形参的函数来说，在函数内部引用该参数时，会自动将该参数视为数组类型兼容的指 针变量，比如：<br />
	&nbsp;&nbsp;&nbsp; <span style="font-family:courier new,courier,monospace;">char a[5] = {1, 2, 3, 4, 5};</span></p>
<p>&nbsp;&nbsp;&nbsp; void foo(char a[5]) {<br />
	&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; printf(&quot;sizeof(a) = %d\n&quot;, sizeof(a));<br />
	&nbsp;&nbsp;&nbsp; }</p>
<p>&nbsp;&nbsp;&nbsp; 这是一个经典的C语言&ldquo;陷阱&rdquo;。foo形参中变量a已经转化为一个char*类型指针了。对该指针变量进行sizeof操作，所得的 size仅是一个指针的长度(在32bit编译下是4)，而不是a数组的长度(4 * 5)。</p>
<p><b>一、多维数组的理解</b></p>
<p>C语言中管数组的数组(的数组的&#8230;)称为多维数组，虽然高于二维的多维数组并不经常使用和遇见。</p>
<p><span style="font-family:courier new,courier,monospace;">T multi_arr_name[i][j][k];</span></p>
<p>多维数组也是数组，根据数组的理解，多维数组也是内存中连续分配的内存单元，只是这些物理分配的内存单元被从逻辑上看成是&ldquo;行&rdquo;、&ldquo;列&rdquo;以及各种 维度罢了。《<a href="http://book.douban.com/subject/2377310/">C专家编程</a>》中有一种理解方法：将数组看成是一种向量，也就是某种对象的一维数组；当其元素为其他数组时，这个向量也就是我们所说的 多维数组。</p>
<p>我们来结合例子理解一下多维数组，从低维到高维度逐步理解：</p>
<p>1) 一维数组</p>
<p>char a[2];<br />
	这是一个向量，拥有两个元素，向量中的元素类型为char。可以理解为：</p>
<p><span style="font-family:courier new,courier,monospace;">char a[2]; <=> (char) a[2];</span></p>
<p>2) 二维数组</p>
<p>char a[2][3];<br />
	这是一个向量，拥有两个元素，向量中的元素类型为char[3]。可以理解为：</p>
<p><span style="font-family:courier new,courier,monospace;">char a[2][3]; <=> (char[3]) a[2];</span></p>
<p>3) 三维数组</p>
<p>char a[2][3][5];<br />
	这是一个向量，拥有两个元素，向量中的元素类型为char[3][5]。可以理解为：</p>
<p><span style="font-family:courier new,courier,monospace;">char a[2][3][5]; <=> (char[3][5]) a[2];</span></p>
<p>4) N维数组</p>
<p>char a[i][j][k]&#8230;[z];<br />
	这是一个向量，拥有i个元素，向量中的元素类型为char[j][k]&#8230;[z]。可以理解为：</p>
<p><span style="font-family:courier new,courier,monospace;">char a[i][j][k]&#8230;[z]; <=> (char [j][k]&#8230;[z]) a[i];</span></p>
<p><b>二、与数组类型兼容</b><b>的指针类型</b></p>
<p>假设有下面这样一个数组：</p>
<p><span style="font-family:courier new,courier,monospace;">char a[2][3];</span></p>
<p>我要声明一个可以指向该数组的指针变量，这个声明该如何书写呢？是 char *p[3]还是char (*p)[3]？按照上面对多维数组的理解:</p>
<p><span style="font-family:courier new,courier,monospace;">char a[2][3]; <=> char[3] a[2];</span></p>
<p>这样我们只需构造出一个指向char[3]类型的指针即可，显然这样的指针声明是(char[3]) *p。哦，不对，这样的声明C编译器是不认的，乾坤大挪移！把(char[3])从中间劈开 => char *p[3]，这样对么？这个是指向数组a的指针么？怎么越看越像是一个指针数组阿，char *p[3]<=> (char*) p[3]。哇，真的弄错了，改！ 对了，刚才忘记了(char[3]) *p中还有一对括号呢，给*p穿上，=> char (*p)[3]。这回没错了，就是它了。</p>
<p><span style="font-family:courier new,courier,monospace;">char a[2][3];<br />
	char (*p)[3];</span></p>
<p>p = a; /* 没有什么比这个还正确的了 */</p>
<p>再来一个三维数组的例子，这次简单直白点。</p>
<p><span style="font-family:courier new,courier,monospace;">char a[2][3][5];</span></p>
<p>变形！=&gt; (char[3][5]) a[2];<br />
	指针有了 =&gt; (char[3][5]) *p => char (*p)[3][5];</p>
<p>有了上面的例子分析，对于更高维度数组，你还不会声明其兼容的指针类型吗？</p>
<p>理解了多维数组兼容的指针变量的类型声明，那么将多维数组与函数结合在一起使用时，你就会得心应手了，在函数内部你看到的、能用到的就是多维数组 对应的兼容指针类型变量。</p>
<p><b>三、多维数组中的&ldquo;隐式数组名&rdquo;</b></p>
<p>在很多C语言书中，我们会经常看到这样的描述：对于多维数组char a[m][n][h]，其中的某个元素a[i][j][k] &lt;=&gt; *(*(*(a + i) + j) + k)。这种等价形式是如何形成的呢？</p>
<p>第零小节的描述告诉我们：<b>数组名</b>是具有指针属性的，除了标准的下标引用方式外，还可以以指针的方式做指针运算以及访问元素，这就是 *(*(*(a + i) + j) + k)是合法的原因。</p>
<p>接下来我们来对*(*(*(a + i) + j) + k)做一次分解分析。鉴于一般形式不易理解和输出结果，我们用一个具体的例子来说明。</p>
<p>&nbsp;&nbsp;&nbsp; <span style="font-family:courier new,courier,monospace;">char a[2][3][5] = {<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; {<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; {1, 2, 3, 4, 5},<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; {6, 7, 8, 9, 10},<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; {11, 12, 13, 14, 15},<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; },</span></p>
<p>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; {<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; {21, 22, 23, 24, 25},<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; {26, 27, 28, 29, 30},<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; {31, 32, 33, 34, 35},<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; }<br />
	&nbsp;&nbsp;&nbsp; };</p>
<p>&nbsp;&nbsp;&nbsp; char (*p)[3][5] = a;<br />
	&nbsp;&nbsp;&nbsp; printf(&quot;a[1][2][3] = %d\n”, a[1][2][3]);<br />
	&nbsp;&nbsp;&nbsp; printf(&quot;a addr = %p\n&quot;, a);<br />
	&nbsp;&nbsp;&nbsp; printf(&quot;a + 1 = %p\n&quot;, a + 1);<br />
	&nbsp;&nbsp;&nbsp; printf(&quot;*(a + 1) = %p\n&quot;, *(a + 1));<br />
	&nbsp;&nbsp;&nbsp; printf(&quot;*(a + 1) + 2 = %p\n&quot;, *(a + 1) + 2);<br />
	&nbsp;&nbsp;&nbsp; printf(&quot;*(*(a + 1) + 2) = %p\n&quot;, *(*(a + 1) + 2));<br />
	&nbsp;&nbsp;&nbsp; printf(&quot;*(*(a + 1) + 2) + 3 = %p\n&quot;, *(*(a + 1) + 2) + 3);<br />
	&nbsp;&nbsp;&nbsp; printf(&quot;*(*(*(a + 1) + 2) + 3) = %d\n&quot;, *(*(*(a + 1) + 2) + 3));</p>
<p>编译这个程序，执行输出：</p>
<p><span style="font-family:courier new,courier,monospace;">a[1][2][3] = 34<br />
	a addr = 0xbfa0893e<br />
	a + 1 = 0xbfa0894d<br />
	*(a + 1) = 0xbfa0894d<br />
	*(a + 1) + 2 = 0xbfa08957<br />
	*(*(a + 1) + 2) = 0xbfa08957<br />
	*(*(a + 1) + 2) + 3 = 0xbfa0895a<br />
	*(*(*(a + 1) + 2) + 3) = 34</span></p>
<p>我们以*(*(*(a + 1) + 2) + 3)为例，再根据上面的输出结果，逐步拆解分析。</p>
<p>1) a + 1</p>
<p>a的等价指针类型是char (*p)[3][5]; 因此a + 1这个指针运算的结果相当于在数组a的起始地址开始向后移动sizeof(char [3][5])个字节。从输出结果来看，a + 1 = 0xbfa0894d = 0xbfa0893e + 15 = a addr +15也印证了这点。</p>
<p>2) *(a + 1)</p>
<p>通常指针的解引用操作会得到指针所指内存地址所在存储单元中存储的值。但上面的输出结果让我们产生疑问：</p>
<p>*(a + 1) = 0xbfa0894d == a + 1</p>
<p>在若干年前我的文章《<a href="http://tonybai.com/2006/11/29/understand-multiple-dimension-array-in-c/">挖掘一下C语言中的多维数组</a>》中曾经探讨过这个问题，当时针对这个问题并未给出答案。这次对此问题我又有了新的认识。还记得我们在开篇中对数组名做的操作以及输出结果么：</p>
<p><span style="font-family:courier new,courier,monospace;">char a[5];</span></p>
<p>a = 0xbfb146c0<br />
	&amp;a = 0xbfb146c0<br />
	*a = 0xbfb146c0</p>
<p>也是a == *a。而这里同样是*(a + 1) == a + 1。通过这个对比我们得到一个大胆的推论：a + 1也可以看作是一个&ldquo;数组名&rdquo;，这是一个<b>隐式数组名</b>。只有这个解释看起来是合理的。</p>
<p>3) *(a + 1) + 2</p>
<p>a + 1这个隐式数组名对应的指针类型是char (*p)[5]，因此 *(a+1) +2相当于从a + 1地址的开始再向后移动10(2 x 5)个字节，也就是0xbfa08957，输出结果也印证了这点。</p>
<p>4) *(*(a + 1) + 2)</p>
<p>我们又遇到了一个<b>隐式数组名</b>。*(*(a + 1) + 2) = 0xbfa08957 == *(a + 1) + 2。</p>
<p>5) *(*(a + 1) + 2) + 3</p>
<p>*(a + 1) + 2这个隐式数组名对应的指针类型是char *p，因此*(*(a + 1) + 2) + 3相当于从*(a + 1) + 2开始再向后移动3个字节，也就是0xbfa0895a，注意这个地址所在单元上存储的是一个char值。</p>
<p>6) *(*(*(a + 1) + 2) + 3)</p>
<p>如果将*(*(a + 1) + 2) + 3赋值给char *p，那么*(*(*(a + 1) + 2) + 3)就相当于*p，这个再简单不过了，34就是这个单元存储的char值。</p>
<p style='text-align:left'>&copy; 2013, <a href='https://tonybai.com'>bigwhite</a>. 版权所有. </p>
]]></content:encoded>
			<wfw:commentRss>https://tonybai.com/2013/03/28/pointer-and-multi-dimension-array-in-c/feed/</wfw:commentRss>
		<slash:comments>4</slash:comments>
		</item>
		<item>
		<title>简析多级指针解引用</title>
		<link>https://tonybai.com/2013/03/23/multi-dimension-pointer-in-c/</link>
		<comments>https://tonybai.com/2013/03/23/multi-dimension-pointer-in-c/#comments</comments>
		<pubDate>Sat, 23 Mar 2013 02:38:18 +0000</pubDate>
		<dc:creator>bigwhite</dc:creator>
				<category><![CDATA[技术志]]></category>
		<category><![CDATA[Blog]]></category>
		<category><![CDATA[Blogger]]></category>
		<category><![CDATA[C]]></category>
		<category><![CDATA[dereference]]></category>
		<category><![CDATA[Pointer]]></category>
		<category><![CDATA[Programmer]]></category>
		<category><![CDATA[博客]]></category>
		<category><![CDATA[学习]]></category>
		<category><![CDATA[思考]]></category>
		<category><![CDATA[指针]]></category>
		<category><![CDATA[程序员]]></category>
		<category><![CDATA[编程]]></category>
		<category><![CDATA[解引用]]></category>
		<category><![CDATA[语法]]></category>

		<guid isPermaLink="false">http://tonybai.com/?p=1227</guid>
		<description><![CDATA[指针是C语言中公认的最为强大的语法要素，但同时也是最难理解的语法要素，它曾给程序员带来了无数麻烦和痛苦，以致于在C语言之后诞生的很多新兴 语言中我们再也难觅指针的身影了。 下面是一个最简单的C语言指针的例子： int a = 5; int *p = &#38;a; 其中p就是一个指针变量。如果C语言中仅仅存在这类指针，那显然指针不会形成&#8220;大患&#8221;。经常地我们会在代码中看到下面的情形： int **q = &#38;p; int ***z = &#38;q; 随着符号&#39;*&#39;个数的增加，C代码的理解复杂度似乎也曾指数级别增长似的。像q、z这样的指向指针的指针(pointer to pointer to &#8230;)变量，中文俗称&#8220;多级指针&#8221;。不过在一些正式的英文C语言教程中，我没能找到其正式的英文说法。在老外的这些书 中，它们多被称为pointer to pointer (to pointer to &#8230;.)。多级指针的确是很难理解的，特别当与函数、数组等联合在一起使用时。今天在写代码时恰好撞见了多级指针，于是就打算在这里说说对多级指针以及 其解引用的一些粗浅理解。 指针究竟是啥？ 和普通变量想比，指针变量到底有何不同，究竟何为指针(变量)？我们来看一个例子： int a = 5; int *p = &#38;a; printf(&#34;a addr = [%p]\n&#34;, &#38;a); printf(&#34;a content = [%d]\n&#34;, a); printf(&#34;p addr [...]]]></description>
			<content:encoded><![CDATA[<p>指针是<a href="http://en.wikipedia.org/wiki/C_(programming_language)">C语言</a>中公认的最为强大的语法要素，但同时也是最难理解的语法要素，它曾给程序员带来了无数麻烦和痛苦，以致于在C语言之后诞生的很多新兴 语言中我们再也难觅指针的身影了。</p>
<p>下面是一个最简单的C语言指针的例子：<br />
	<span style="font-family:courier new,courier,monospace;">int a = 5;<br />
	int *p = &amp;a;</span></p>
<p>其中p就是一个指针变量。如果C语言中仅仅存在这类指针，那显然指针不会形成&ldquo;大患&rdquo;。经常地我们会在代码中看到下面的情形：</p>
<p><span style="font-family:courier new,courier,monospace;">int **q = &amp;p;<br />
	int ***z = &amp;q;</span></p>
<p>随着符号&#39;*&#39;个数的增加，C代码的理解复杂度似乎也曾指数级别增长似的。像q、z这样的指向指针的指针(pointer to pointer to &#8230;)变量，中文俗称&ldquo;<b>多级指针</b>&rdquo;。不过在一些正式的英文C语言教程中，我没能找到其正式的英文说法。在老外的这些书 中，它们多被称为pointer to pointer (to pointer to &#8230;.)。多级指针的确是很难理解的，特别当与函数、数组等联合在一起使用时。今天在写代码时恰好撞见了多级指针，于是就打算在这里说说对多级指针以及 其解引用的一些粗浅理解。</p>
<p><b>指针究竟是啥？</b></p>
<p>和普通变量想比，指针变量到底有何不同，究竟何为指针(变量)？我们来看一个例子：</p>
<p><span style="font-family:courier new,courier,monospace;">int a = 5;<br />
	int *p = &amp;a;</span></p>
<p>printf(&quot;a addr = [%p]\n&quot;, &amp;a);<br />
	printf(&quot;a content = [%d]\n&quot;, a);<br />
	printf(&quot;p addr = [%p]\n&quot;, &amp;p);<br />
	printf(&quot;p content = [%p]\n&quot;, p);<br />
	printf(&quot;*p = [%d]\n&quot;, *p);</p>
<p>*p = 6;<br />
	printf(&quot;after modify, *p = [%d]\n&quot;, *p);</p>
<p>编译这个小程序并执行，输出结果如下：</p>
<p><span style="font-family:courier new,courier,monospace;">a addr = [0xbfb609b8]<br />
	a content = [5]<br />
	p addr = [0xbfb609bc]<br />
	p content = [0xbfb609b8]<br />
	*p = [5]<br />
	after modify, *p = [6]</span></p>
<p>通过两个变量的addr，我们可以看到a、p两个变量都是在栈上分配的变量。不同的是普通整型变量a对应的内存单元(a content)中存储的值为整型值5，是一个数值；而变量p对应的内存单元(p content)中存储的值为0xbfb609b8，是变量a的地址，用栈变量简图可以表示如下：</p>
<p><span style="font-family:courier new,courier,monospace;">| &#8230;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; |<br />
	|<u>0xbfb609b8</u>| &lt;- &amp;p [0xbfb609bc]<br />
	|5&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; | &lt;- &amp;a [<u>0xbfb609b8</u>]<br />
	| &#8230;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; |</span></p>
<p>可以看出指针变量的<b>第一个特点</b>是它是一种以存储其他变量地址为目的的变量。一个T类型的指针变量(一级指针)就是一个存储了某T类 型值变量的地址的内存单元。</p>
<p>例子中最后那个输出是对指针的解引用(dereference)操作，指针的解引用操作的结果是得到指针所指的地址上的变量的值。在这个例子中指 针所指到内存地址为0xbfb609b8，也就是a变量的位置，因此*p的结果为变量a的值，即5。因此我们得到指针变量的<b>第二个特点</b>： 通过对指针的解引用，我们可以获得其指向的内存单元所表示的值。</p>
<p>在例子中，我们看到了这行代码 *p = 6，并发现执行这行代码后，a变量的值变为了6。这就是指针的<b>第三个特点</b>：当解引用作左值时，它可以修改其所指内存地址上变量的值。a被修改后的栈变量分布简图：</p>
<p><span style="font-family:courier new,courier,monospace;">| &#8230;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; |<br />
	|0xbfb609b8| &lt;- &amp;p [0xbfb609bc]<br />
	|6 &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; | &lt;- &amp;a [0xbfb609b8]<br />
	| &#8230;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; |</span></p>
<p><b>二级指针</b></p>
<p>我们再来分析一下下面的示例程序的输出结果。</p>
<p><span style="font-family:courier new,courier,monospace;">int a = 5;<br />
	int b = 13;<br />
	int *p = &amp;a;<br />
	printf(&quot;*p = %d\n&quot;, *p);&nbsp;<br />
	int **q = &amp;p;<br />
	(*q) = &amp;b;<br />
	printf(&quot;*p = %d\n&quot;, *p);</span></p>
<p>根据前面的分析，第一次*p输出时p指向a的地址，对p解引用的结果就是a所在内存单元的值，即5。接下来的代码分析起来就需要谨慎一些了。我们先来看看 int **q = &amp;p这行代码。根据对一级指针的分析，我们可以将int **q理解成(int*) *q，这样q指向的地址就是一个int*型的变量的内存地址，该地址上的值本身也是一个地址值。在这个例子中，(int*) *q = &amp;p; 也就是说q中存储的值就是变量p的地址。通过*q我们可以得到p中存储的地址值(&amp;a)；而若*q作为左值，显然就是修改p中存储的地址值喽，因 此(*q) = &amp;b则相当于p = &amp;b，则第二个*p的输出结果为变量b所在内存单元的值，即13。</p>
<p>在修改*q前，栈上内存布局：</p>
<p><span style="font-family:courier new,courier,monospace;">| &#8230;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; |<br />
	|<u>0xbf830ec8</u>| &lt;- &amp;q [0xbf830ecc]<br />
	|0xbf830ec0| &lt;- &amp;p [<u>0xbf830ec8</u>]<br />
	|11 &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; | &lt;- &amp;b [0xbf830ec4]<br />
	|5 &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; | &lt;- &amp;a [0xbf830ec0]<br />
	| &#8230;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; |</span></p>
<p>在修改*q的值后，栈上内存布局：</p>
<p><span style="font-family:courier new,courier,monospace;">| &#8230;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; |<br />
	|0xbf830ec8| &lt;- &amp;q [0xbf830ecc]<br />
	|<u>0xbf830ec4</u>| &lt;- &amp;p [0xbf830ec8] /* 通过*q修改 */<br />
	|11&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; | &lt;- &amp;b [<u>0xbf830ec4</u>]<br />
	|5&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; | &lt;- &amp;a [0xbf830ec0]<br />
	| &#8230;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; |</span></p>
<p>再来分析一下**q的值又是啥呢？有了前面的铺垫：*q &lt;=&gt; p，那**q &lt;=&gt; *(*q) &lt;=&gt; *p，其值自然就明了了，就是b的值。</p>
<p><b>多级指针</b></p>
<p>有了一级指针和二级指针的分析打基础，当我们遇到更多*的时候，只是遵循这个方法耐心分析就是了，比如：</p>
<p><span style="font-family:courier new,courier,monospace;">int a = 5;<br />
	int *p = &amp;a;<br />
	int **q = &amp;p;<br />
	int ***z = &amp;q;</span></p>
<p>我们可以对比着前面一、二级指针的理解方法来理解这三个指针p、q和z：<br />
	&nbsp;&nbsp;&nbsp; &#8211; 一级指针p自身存储的是整型值变量a的地址，对一级指针解引用(*p)得到的是值变量a的值；*p作左值，修改的是变量a的值；<br />
	&nbsp;&nbsp;&nbsp; &#8211; 二级指针q自身存储的是一级整型指针变量p的地址，对二级指针解引用(*q)得到的是一级指针p自身存储的值(a的地址:&amp;a)；*p作左值时，修改的一级指针p的指向；<br />
	&nbsp;&nbsp;&nbsp; &#8211; 三级指针z自身存储的是二级整型指针变量q的地址，对三级指针解引用(*z)得到的是二级指针q自身存储的值，也就是p的地址(&amp;p)；对*z再 解引用(**z)，相当于得到p自身存储的值，也就是a的地址&amp;a；对**z再解引用，即***z，相当于得到a自身存储的变量值，即5。用一个 等价式可以更形象的表达：***z &lt;=&gt; **(*z) &lt;=&gt; **q &lt;=&gt; *(*q) &lt;=&gt; *p &lt;=&gt; 5。<br />
	&nbsp;&nbsp;&nbsp; &#8211; 更高级别的指针可依次类推。不过如果再对***z解引用，即****z，那则相当于对整型数5（非地址）进行解引用，会出现编译错误： 一元 &lsquo;*&rsquo;参数类型无效(有&lsquo;int&rsquo;)。</p>
<p style='text-align:left'>&copy; 2013, <a href='https://tonybai.com'>bigwhite</a>. 版权所有. </p>
]]></content:encoded>
			<wfw:commentRss>https://tonybai.com/2013/03/23/multi-dimension-pointer-in-c/feed/</wfw:commentRss>
		<slash:comments>3</slash:comments>
		</item>
	</channel>
</rss>
