Go的秘密生活:包和结构

发布: (2025年12月28日 GMT+8 13:32)
11 min read
原文: Dev.to

Source: Dev.to

请提供您希望翻译的正文内容,我会按照要求保留源链接、格式和技术术语,仅翻译文本部分。

Chapter 11: The Architect’s Blueprint

The Friday afternoon sun slanted low through the archive windows, illuminating dust motes dancing in the air. Ethan found Eleanor standing atop a rolling ladder, carefully rearranging a high shelf of leather‑bound ledgers.

Ethan: “你在重新整理吗?”
He set a paper bag on the desk.

Eleanor: “我在纠正。” She slid a heavy volume into place with a satisfied sigh. “前任档案员是按 ‘装订颜色’ 而不是 ‘获取年份’ 来归档的。这样看起来书架很漂亮,但检索非常困难。要找任何东西都得到处找。”

She climbed down, dusting off her hands.

Ethan: “你带了什么?”
“开心果羊角面包。还有一杯cortado。”

Eleanor’s expression softened into a genuine smile.

Eleanor: “你记得cortado。谢谢你,Ethan。” She gestured to the chair opposite her. “请坐。这个下午正适合来杯热饮。”

Ethan opened his laptop.

Ethan: “我已经实现了你建议的库存系统。运行得很顺畅。原子操作快,互斥锁安全。我已经准备好部署了。”

Eleanor peered at the screen, leaning in with interest.

Eleanor: “我们来看看。不只是逻辑本身,还要看看你为它搭建的‘家’。”

She pointed at the file explorer on the left.

/my-project
├── main.go
├── user.go
├── product.go
├── db.go
├── server.go
├── utils.go
└── types.go

Eleanor: “这让我想起我刚开始整理这个档案时的情景。把所有东西放在同一个地方起初感觉很方便,对吧?所有东西都在眼前。”
Ethan: “是的,正是这样。省去了在文件夹里四处搜寻的麻烦。”
Eleanor: “但想象一下,如果这个图书馆没有分区——只有一大堆书堆在地板中央。随着藏书增多,想找到需要的东西就会变得沉重。在 Go 中,文件系统 本身 就是架构。我们必须讨论包(Package)。”

The Encapsulation Boundary

Eleanor: “在很多语言里,文件夹只是用来组织的。而在 Go 中,文件夹 就是 一个 Package。它是一个边界——可以把它想象成一个安全的阅览室。”

She opened a new terminal.

Eleanor: “同一目录下的每个文件必须在顶部声明相同的 package 名称。它们共处一室,分享一切。但对外部世界呢?它们只会展示想展示的东西。”

Ethan: “你是说公开和私有吗?”

Eleanor: “在 Go 中我们称之为 Exported(导出)和 Unexported(未导出)。美妙之处在于,没有 publicprivate 之类的关键字。只有 大小写规则。”

She typed a quick example:

package users

// Exported (Public) - Starts with Capital Letter
type User struct {
    Name string // Exported field
    id   int    // Unexported field (private)
}

// Exported function
func New(name string) *User {
    return &User{
        Name: name,
        id:   generateID(), // We can call unexported functions inside the package
    }
}

// Unexported function (Private) - Starts with lowercase
func generateID() int {
    return 42 // Simplified
}

Eleanor: “如果以大写字母开头,其他包就能看到它。如果以小写字母开头,则只能在当前包内部可见。简单而优雅。你不需要额外的配置文件来了解 API——只要看大小写就行。”

Ethan nodded.

Ethan: “明白了。那我应该把东西划分到不同的包里。我会创建一个 models 包和一个 controllers 包。”

Eleanor raised a hand, not to stop him, but to guide him.

Eleanor: “小心。这是其他框架的习惯。在 Go 中我们尽量不要按 种类 来分组——把所有模型放在一起或所有控制器放在一起。我们按 职责 来分组。”

She sketched a diagram on her notepad.

BAD ARCHITECTURE (Group by Kind):
/models
  - user.go
  - product.go
/controllers
  - user.go
  - product.go

GOOD ARCHITECTURE (Group by Domain):
/users
  - user.go
  - handler.go
  - repositor

> **Source:** ...  

y.go
/products
  - product.go
  - handler.go

Eleanor: “看到了区别吗?如果按业务域来组织,所有关于 Users 的代码都在 package users 中。它是自包含的。它讲述的是你的软件 做了什么,而不仅仅是它是如何构建的。”

内部堡垒

Eleanor: “现在你终会遇到需要在自己的包之间共享的代码——辅助函数或核心逻辑——但你不想让外部世界导入它们。比如给用户和商品专用的加密助手。”

Ethan: “我可以建一个 shared 包吗?”

Eleanor: “可以。但那样的话,任何在互联网上导入你的库的人都能使用你的 shared 包。它会成为你的公开承诺。你再也无法在不破坏他们的情况下修改它。”

她轻点屏幕。

Eleanor: “Go 为我们提供了一个绝佳的工具:internal。”

她在屏幕上重新排列了 Ethan 的项目结构:

/my-project
├── go.mod
├── main.go
├── internal
│   └── platform
│       └── crypto.go   // Only visible to my-project!
├── users
│   ├── service.go      // Can import internal/platform
│   └── user.go
└── products
    └── product.go

Eleanor: “Go 编译器专门对这点进行强制检查。任何位于名为 internal 的目录中的包,只能被根目录下的代码导入。它让你能够编写那些必要但凌乱的细节,而外部项目永远触及不到。”

Ethan: “所以 internal 只对我们有效?”

Eleanor: “只对我们自己有效。它让你在以后重构时毫无顾虑。它是一张安全网。”

“Utils” 的疑问

Ethan 注视着屏幕上的新结构,心中渐生秩序感。随后他指着自己的文件列表。

Ethan: “那……utils.go 怎么办?里面有字符串格式化、一些数学函数、随机数生成器。”

Eleanor 微笑,露出同情的神情。

Eleanor: “啊,utils …(对话继续)*

Source:

关于包的对话

“我们最终都会创建一个包。这是一种自然的冲动,想要给零碎的东西找个地方。但把它想象成一个杂物抽屉。一旦你开始往里放东西,就停不下来,而且再也找不到它们了。”

“那就删掉它?”

“把它分发,”她温柔地纠正道。“给它们一个合适的归宿。如果一个函数格式化用户名,就放在 users 包里。如果它计算税金,就放在 billing 包里。包名应该描述它提供的功能,而不仅仅是‘装东西’的地方。”

她咬了一口羊角面包,点头表示赞同。

“这真好吃,Ethan。”

她把面包屑从笔记本上刷掉。

“结构不仅仅是对文件进行排序。它是设计系统以防止依赖循环。如果 package A 需要 package B,而 package B 又需要 package A,Go 会拒绝编译。平铺的目录会隐藏这些循环,而结构化的项目会暴露它们,让你有机会修复。”

Ethan 开始创建 users 目录。

“再也没有杂物抽屉了。”

“万物各得其所,事物各安其位,”Eleanor 热情地赞同道。“并且把导出的标识符大写。你在写一个库,Ethan,而不是日记。让全世界都能读取它。”

第 11 章关键概念

  • Go 中代码组织的基本单元。
  • 每个目录都是一个包。
  • 同一目录下的所有文件必须声明相同的 package 名称。

导出 vs. 未导出

符号可见性示例
大写(例如 User导出(公共)对其他包可见
小写(例如 user未导出(私有)仅在同一包内部可见

领域驱动结构

  • 更倾向于按领域(例如 usersordersbilling)组织包,而不是按层次(例如 modelscontrollersutils)。
  • 将相关逻辑放在一起。

internal 目录

  • Go 工具链识别的特殊目录。
  • internal 中的代码只能被其父目录及其子目录导入。
  • 防止实现细节被外部用户使用。

包命名

  • 简短、简洁、全小写(例如 nethttpjson)。
  • 避免使用 utilscommonhelpers 等通用名称。
  • 包名应描述该包提供的功能

循环依赖

  • Go 不允许导入循环(A 导入 B,B 再导入 A)。
  • 良好的包结构通过单向依赖来防止这种情况。

go.mod

  • 定义模块并管理依赖。
  • 位于项目根目录。

下一章:错误处理——Eleanor 将解释为什么 Go 没有异常,Ethan 学会将错误视为值来处理。


Aaron Rose 是一名软件工程师兼技术作家,供稿于 tech-reader.blog,也是《Think Like a Genius》的作者。https://amazon.com/author/aaron.rose

Back to Blog

相关文章

阅读更多 »

Go的秘密生活:错误处理

第12章:碎玻璃的声音 星期一的早晨,厚重的灰色雾气笼罩着整座城市。档案馆内部,寂静至极,随后被打破……

PSX:项目结构检查器

PSX – Project Structure eXtractor 是一个命令行工具,能够验证你的项目布局并自动修复。可以把它看作是整个仓库的 linter……