<?xml version="1.0" encoding="utf-8" standalone="yes"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
  <channel>
    <title>第 17 章 模块与依赖 on Go 语言原本</title>
    <link>/zh-cn/part5toolchain/ch17modules/</link>
    <description>Recent content in 第 17 章 模块与依赖 on Go 语言原本</description>
    <generator>Hugo</generator>
    <language>zh-cn</language>
    <atom:link href="/zh-cn/part5toolchain/ch17modules/index.xml" rel="self" type="application/rss+xml" />
    <item>
      <title>17.1 依赖管理的难点</title>
      <link>/zh-cn/part5toolchain/ch17modules/challenges/</link>
      <pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate>
      <guid>/zh-cn/part5toolchain/ch17modules/challenges/</guid>
      <description>&lt;h1 id=&#34;171-依赖管理的难点&#34;&gt;17.1 依赖管理的难点&lt;/h1&gt;&#xA;&lt;p&gt;现代软件几乎不再从零写起。一个寻常的服务端程序，&lt;code&gt;import&lt;/code&gt; 列表往下走一层是日志库、HTTP&#xA;路由、序列化器，再往下是它们各自依赖的加密、压缩、网络原语,真正属于你自己的代码，可能不到&#xA;整张依赖图的百分之一。这带来了复用的红利，也带来了一个本不属于你、却必须由你回答的问题：&#xA;&lt;strong&gt;这张图上每个包，到底该用哪个版本？&lt;/strong&gt; 这一节不急着给 Go 的答案，先把问题本身的难讲清楚。&#xA;难点有两条主线：一是&lt;strong&gt;版本如何选&lt;/strong&gt;（钻石依赖与约束求解），二是&lt;strong&gt;选定之后如何不漂移&lt;/strong&gt;&#xA;（可重现构建）。理解了这两条，才能看懂 &lt;a href=&#34;.././minimum&#34;&gt;17.3&lt;/a&gt; 那套「最小版本选择」为什么值得&#xA;专门设计，以及 Go 为走到那一步付出过什么代价。&lt;/p&gt;&#xA;&lt;h2 id=&#34;1711-钻石依赖一个约束求解问题&#34;&gt;17.1.1 钻石依赖：一个约束求解问题&lt;/h2&gt;&#xA;&lt;p&gt;依赖管理的核心难题，集中体现在一种叫&lt;strong&gt;钻石依赖&lt;/strong&gt;（diamond dependency）的拓扑上。你的程序同时&#xA;依赖 A 和 B，而 A 和 B 又都依赖同一个 C,依赖图从顶点出发分成两支，又在 C 处重新汇合，画出来&#xA;正是一颗钻石。问题出在两支对 C 提出了&lt;strong&gt;不同的版本要求&lt;/strong&gt;：&lt;/p&gt;&#xA;&#xA;&lt;script src=&#34;/mermaid.min.js&#34;&gt;&lt;/script&gt;&#xA;&lt;script src=&#34;/mermaid-init.js&#34;&gt;&lt;/script&gt;&#xA;&#xA;&#xA;&lt;pre class=&#34;mermaid&#34;&gt;flowchart TD&#xA;    APP[&amp;#34;你的程序 main&amp;#34;] --&amp;gt; A[&amp;#34;A v1.0&amp;#34;]&#xA;    APP --&amp;gt; B[&amp;#34;B v1.0&amp;#34;]&#xA;    A --&amp;gt;|&amp;#34;require C v1.2&amp;#34;| C12[&amp;#34;C v1.2&amp;#34;]&#xA;    B --&amp;gt;|&amp;#34;require C v1.5&amp;#34;| C15[&amp;#34;C v1.5&amp;#34;]&#xA;    C12 -.-&amp;gt;|&amp;#34;二者只能合一&amp;#34;| PICK{&amp;#34;链进哪个 C?&amp;#34;}&#xA;    C15 -.-&amp;gt; PICK&lt;/pre&gt;&#xA;&lt;p&gt;为什么不能让 A 用 C v1.2、B 用 C v1.5，各取所需？多数语言做不到。C 的两个版本通常导出同一个&#xA;包路径、同一组类型名，把两份链进同一个程序，链接器会撞上重复符号；即便链接器允许，C v1.2 的&#xA;&lt;code&gt;Token&lt;/code&gt; 和 C v1.5 的 &lt;code&gt;Token&lt;/code&gt; 在类型系统里也是两个互不兼容的类型,一旦 A 把自己拿到的 &lt;code&gt;Token&lt;/code&gt;&#xA;传给 B，就会在边界上炸开。这条裂缝用一段示意代码看得更清楚：&lt;/p&gt;</description>
    </item>
    <item>
      <title>17.2 语义化版本管理</title>
      <link>/zh-cn/part5toolchain/ch17modules/semantics/</link>
      <pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate>
      <guid>/zh-cn/part5toolchain/ch17modules/semantics/</guid>
      <description>&lt;h1 id=&#34;172-语义化版本管理&#34;&gt;17.2 语义化版本管理&lt;/h1&gt;&#xA;&lt;p&gt;要管理版本，先得让版本号&lt;strong&gt;有意义&lt;/strong&gt;。&lt;a href=&#34;.././challenges&#34;&gt;17.1&lt;/a&gt; 把依赖管理的难处归到两点：钻石依赖&#xA;里的版本冲突，与跨机器、跨时间的可重现构建。这两点能不能解，取决于一个更底层的前提，&lt;strong&gt;一个版本&#xA;号到底承诺了什么&lt;/strong&gt;。如果 &lt;code&gt;v1.5&lt;/code&gt; 与 &lt;code&gt;v1.4&lt;/code&gt; 之间的关系完全无从预料，那么任何选择算法都无从下手，&#xA;只能退回到「把所有版本两两试一遍」的求解。Go 模块的做法，是先把版本号变成一份&lt;strong&gt;可被机器信任的&#xA;契约&lt;/strong&gt;，再在这份契约之上提出一条独特而强硬的规则,&lt;strong&gt;语义化导入版本&lt;/strong&gt;。这一节讲清这两者，它们是&#xA;&lt;a href=&#34;.././minimum&#34;&gt;17.3&lt;/a&gt; 那套版本选择算法能够成立、且能简单到「一遍图遍历」的前提。&lt;/p&gt;&#xA;&lt;h2 id=&#34;1721-语义化版本把兼容性写进数字&#34;&gt;17.2.1 语义化版本：把兼容性写进数字&lt;/h2&gt;&#xA;&lt;p&gt;语义化版本（Semantic Versioning，semver）由 Tom Preston-Werner 在 2013 年定形为 2.0.0&#xA;规范，Go 模块直接采用了它，并在版本号前统一冠以 &lt;code&gt;v&lt;/code&gt;。版本号形如 &lt;strong&gt;&lt;code&gt;vMAJOR.MINOR.PATCH&lt;/code&gt;&lt;/strong&gt;&#xA;（如 &lt;code&gt;v1.4.2&lt;/code&gt;），三段都是非负整数，且每一段的递增都&lt;strong&gt;有规范约定的含义&lt;/strong&gt;：&lt;/p&gt;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;&lt;strong&gt;PATCH&lt;/strong&gt;（补丁）递增：「只引入向后兼容的 bug 修复」。&lt;code&gt;v1.4.1 → v1.4.2&lt;/code&gt;，行为不变，只是更正了&#xA;错误。&lt;/li&gt;&#xA;&lt;li&gt;&lt;strong&gt;MINOR&lt;/strong&gt;（次版本）递增：「以向后兼容的方式向公开 API 增加了功能」。&lt;code&gt;v1.4 → v1.5&lt;/code&gt;，老代码&#xA;原封不动仍能编译、仍能运行，只是多了可用的东西。&lt;/li&gt;&#xA;&lt;li&gt;&lt;strong&gt;MAJOR&lt;/strong&gt;（主版本）递增：「引入了向后不兼容的 API 变更」。&lt;code&gt;v1.x → v2.0&lt;/code&gt;，老代码可能编译不过，&#xA;或行为悄然改变。&lt;/li&gt;&#xA;&lt;/ul&gt;&#xA;&lt;p&gt;把这套约定落到一个具体改动上，递增哪一段是被改动的性质唯一决定的，并无自由裁量：&lt;/p&gt;&#xA;&lt;div class=&#34;highlight&#34;&gt;&lt;div class=&#34;chroma&#34;&gt;&#xA;&lt;table class=&#34;lntable&#34;&gt;&lt;tr&gt;&lt;td class=&#34;lntd&#34;&gt;&#xA;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code&gt;&lt;span class=&#34;lnt&#34;&gt;1&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;2&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;3&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;4&#xA;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&#xA;&lt;td class=&#34;lntd&#34;&gt;&#xA;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-text&#34; data-lang=&#34;text&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;现有版本 v1.4.2，下一次发布该打什么版本号？&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;  修了一个 panic，不动任何签名         → v1.4.3   （PATCH：兼容的修复）&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;  新增一个导出函数 Encode，旧 API 不变  → v1.5.0   （MINOR：兼容的新功能）&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;  删掉 Decode、或改了它的参数类型        → v2.0.0   （MAJOR：破坏兼容）&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;&#xA;&lt;/div&gt;&#xA;&lt;/div&gt;&lt;p&gt;注意每升一段，更低的段都归零：&lt;code&gt;v1.4.2&lt;/code&gt; 的次版本一升就是 &lt;code&gt;v1.5.0&lt;/code&gt; 而非 &lt;code&gt;v1.5.2&lt;/code&gt;，主版本一升就是&#xA;&lt;code&gt;v2.0.0&lt;/code&gt;。这条归零规则保证了「同一主版本下，版本号越大、含的兼容功能越全」，&lt;a href=&#34;.././minimum&#34;&gt;17.3&lt;/a&gt;&#xA;的「取最大」才有明确语义。&lt;/p&gt;</description>
    </item>
    <item>
      <title>17.3 最小版本选择算法</title>
      <link>/zh-cn/part5toolchain/ch17modules/minimum/</link>
      <pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate>
      <guid>/zh-cn/part5toolchain/ch17modules/minimum/</guid>
      <description>&lt;h1 id=&#34;173-最小版本选择算法&#34;&gt;17.3 最小版本选择算法&lt;/h1&gt;&#xA;&lt;p&gt;确定了语义化版本（&lt;a href=&#34;.././semantics&#34;&gt;17.2&lt;/a&gt;）后，还剩最后一问：当依赖图里各模块要求同一个依赖的&#xA;不同版本时，到底&lt;strong&gt;选哪个版本&lt;/strong&gt;？Go 的答案是一个反直觉、却异常简洁的算法,&lt;strong&gt;最小版本选择&lt;/strong&gt;&#xA;（Minimal Version Selection, MVS）。它是 Go 模块最具特色、也最值得玩味的设计：别家包管理器把&#xA;这件事做成了一个需要约束求解器的难题，Go 却把它压成了一遍图遍历。本节先把规则讲清，再看它为何&#xA;能这么简单，最后把它放回整个领域的谱系里，看清它究竟换走了什么、换来了什么。&lt;/p&gt;&#xA;&lt;h2 id=&#34;1731-选满足要求的最低版本&#34;&gt;17.3.1 选「满足要求的最低版本」&lt;/h2&gt;&#xA;&lt;p&gt;MVS 的规则简单到令人意外：对每个依赖，选&lt;strong&gt;所有要求中最高的那个最低要求&lt;/strong&gt;,换句话说，选择&#xA;&lt;strong&gt;能满足所有模块要求的最低版本&lt;/strong&gt;。先用本书一贯的小例子建立直觉。你的程序直接要求 C ≥ v1.2;&#xA;依赖 A 要求 C ≥ v1.3;依赖 B 要求 C ≥ v1.1。三个要求里最高的下限是 v1.3,MVS 就选 &lt;strong&gt;C v1.3&lt;/strong&gt;。&#xA;注意这里的关键：哪怕此刻 C 已经发布了 v1.9，MVS &lt;strong&gt;也不选 v1.9&lt;/strong&gt;,它只选「满足所有要求的最低&#xA;版本」，即 v1.3。&lt;/p&gt;&#xA;&lt;p&gt;把这条规则放进一张真实的依赖图，就是 Go 官方文档采用的例子。主模块要求 A ≥ v1.2、B ≥ v1.2;&#xA;A v1.2 要求 C ≥ v1.3;B v1.2 要求 C ≥ v1.4;而 C v1.3 与 C v1.4 都要求 D ≥ v1.2。MVS 从主模块&#xA;出发，沿 &lt;code&gt;require&lt;/code&gt; 边走遍整张图，为每个模块记下「见过的最高要求」：&lt;/p&gt;</description>
    </item>
    <item>
      <title>17.4 vgo 与 dep 之争</title>
      <link>/zh-cn/part5toolchain/ch17modules/fight/</link>
      <pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate>
      <guid>/zh-cn/part5toolchain/ch17modules/fight/</guid>
      <description>&lt;h1 id=&#34;174-vgo-与-dep-之争&#34;&gt;17.4 vgo 与 dep 之争&lt;/h1&gt;&#xA;&lt;p&gt;今天的 Go 模块（Go Modules）并非一开始就有。它从一场颇具戏剧性、也颇有争议的社区事件里&#xA;诞生：&lt;strong&gt;vgo 与 dep 之争&lt;/strong&gt;。这段历史不只是八卦，它折射出开源项目治理、技术决策与社区情绪之间&#xA;的真实张力，是理解 Go 模块「为何是今天这样」的最后一块拼图，也是这本书一路谈下来的设计哲学&#xA;在工具链层面的一次集中演出。&lt;/p&gt;&#xA;&lt;p&gt;前面两节已经把 Go 模块的技术内核摆出来了：语义化版本管理（&lt;a href=&#34;.././semantics&#34;&gt;17.2&lt;/a&gt;）把主版本&#xA;写进导入路径，最小版本选择（&lt;a href=&#34;.././minimum&#34;&gt;17.3&lt;/a&gt;）用一条确定性算法挑出每个依赖的版本，二者&#xA;合起来，让 Go 既不需要 lock 文件、也不需要约束求解器。这一节回答的是另一个问题：为什么是&#xA;&lt;strong&gt;这套&lt;/strong&gt;方案，而不是当时社区已经投入两年、几乎被默认为「未来」的那一套。要讲清楚这个，得把&#xA;时间拨回到 2016 年。&lt;/p&gt;&#xA;&lt;h2 id=&#34;1741-dep社区的官方实验&#34;&gt;17.4.1 dep：社区的「官方实验」&lt;/h2&gt;&#xA;&lt;p&gt;GOPATH 时代的依赖混乱（&lt;a href=&#34;.././challenges&#34;&gt;17.1&lt;/a&gt;）催生过一大批第三方工具：&lt;code&gt;godep&lt;/code&gt;、&lt;code&gt;glide&lt;/code&gt;、&#xA;&lt;code&gt;govendor&lt;/code&gt;，各自为政，互不兼容。2016 年，Go 团队决定收口，成立了一个依赖管理工作组，由&#xA;Sam Boyer 等社区成员牵头，做出一个被定位为&lt;strong&gt;官方实验&lt;/strong&gt;（official experiment）的工具：&#xA;&lt;strong&gt;&lt;code&gt;dep&lt;/code&gt;&lt;/strong&gt;。它不是某个人的玩具，而是 Go 团队背书、社区深度参与、被广泛理解为「将来会并入&#xA;&lt;code&gt;go&lt;/code&gt; 命令」的候选方案。许多公司和个人据此调整了工程实践，把项目迁到 dep 上。&lt;/p&gt;&#xA;&lt;p&gt;技术上，dep 走的是当时&lt;strong&gt;主流&lt;/strong&gt;的路子，与 Rust 的 Cargo、Ruby 的 Bundler、JavaScript 的 npm&#xA;一脉相承：一个清单文件声明你想要的版本范围，一个 &lt;strong&gt;lock 文件&lt;/strong&gt;钉死本次解析出的精确版本，中间&#xA;夹着一个&lt;strong&gt;约束求解器&lt;/strong&gt;。求解器要做的事，本质是布尔可满足性（SAT）：把「A 需要 B 的 &lt;code&gt;&amp;gt;=1.2&lt;/code&gt;」&#xA;「C 需要 B 的 &lt;code&gt;&amp;lt;2.0&lt;/code&gt;」这类约束翻译成逻辑公式，搜一组同时满足所有人的版本赋值出来。&lt;/p&gt;</description>
    </item>
  </channel>
</rss>
