go-packages
Compare original and translation side by side
🇺🇸
Original
English🇨🇳
Translation
ChineseGo Packages and Imports
Go包与导入
This skill covers package organization and import management following Google's
and Uber's Go style guides.
本技能涵盖遵循Google和Uber Go风格指南的包组织与导入管理规范。
Package Organization
包组织
Avoid Util Packages
避免使用Util类包
Advisory: This is a best practice recommendation.
Package names should describe what the package provides. Avoid generic names
like , , , or similar—they make code harder to read and
cause import conflicts.
utilhelpercommongo
// Good: Meaningful package names
db := spannertest.NewDatabaseFromFile(...)
_, err := f.Seek(0, io.SeekStart)
// Bad: Vague package names obscure meaning
db := test.NewDatabaseFromFile(...)
_, err := f.Seek(0, common.SeekStart)Generic names like can be used as part of a name (e.g., )
but should not be the entire package name.
utilstringutil建议:这是最佳实践推荐。
包名应能描述该包提供的功能。避免使用、、这类通用名称——它们会降低代码可读性,还可能导致导入冲突。
utilhelpercommongo
// 推荐:使用有意义的包名
db := spannertest.NewDatabaseFromFile(...)
_, err := f.Seek(0, io.SeekStart)
// 不推荐:模糊的包名会掩盖含义
db := test.NewDatabaseFromFile(...)
_, err := f.Seek(0, common.SeekStart)像这类通用名称可以作为包名的一部分(例如),但不能作为完整的包名。
utilstringutilPackage Size
包的大小
Advisory: This is best practice guidance.
When to combine packages:
- If client code likely needs two types to interact, keep them together
- If types have tightly coupled implementations
- If users would need to import both packages to use either meaningfully
When to split packages:
- When something is conceptually distinct
- The short package name + exported type creates a meaningful identifier:
,
bytes.Bufferring.New
File organization: No "one type, one file" convention in Go. Files should be
focused enough to know which file contains something and small enough to find
things easily.
建议:这是最佳实践指导。
何时合并包:
- 如果客户端代码可能需要两个类型交互,应将它们放在一起
- 如果类型的实现高度耦合
- 如果用户需要同时导入两个包才能有效使用其中任意一个
何时拆分包:
- 当某个模块在概念上独立时
- 简短的包名 + 导出类型能构成有意义的标识符:例如、
bytes.Bufferring.New
文件组织: Go中没有“一个类型对应一个文件”的约定。文件应足够聚焦,让开发者能快速定位内容,同时也要足够简洁,方便查找。
Imports
导入
Import Organization
导入组织
Normative: This is required per Go Wiki CodeReviewComments.
Imports are organized in groups, with blank lines between them. The standard
library packages are always in the first group.
go
package main
import (
"fmt"
"hash/adler32"
"os"
"github.com/foo/bar"
"rsc.io/goversion/version"
)Use goimports to manage
this automatically.
规范:这是Go Wiki CodeReviewComments中的强制要求。
导入需按组划分,组之间用空行分隔。标准库包始终放在第一组。
go
package main
import (
"fmt"
"hash/adler32"
"os"
"github.com/foo/bar"
"rsc.io/goversion/version"
)使用goimports工具可自动管理导入分组。
Import Grouping (Extended)
导入分组(扩展规范)
Combined: Google + Uber guidance
Minimal grouping (Uber): stdlib, then everything else.
Extended grouping (Google): stdlib → other → protocol buffers → side-effects.
go
// Good: Standard library separate from external packages
import (
"fmt"
"os"
"go.uber.org/atomic"
"golang.org/x/sync/errgroup"
)go
// Good: Full grouping with protos and side-effects
import (
"fmt"
"os"
"github.com/dsnet/compress/flate"
"golang.org/x/text/encoding"
foopb "myproj/foo/proto/proto"
_ "myproj/rpc/protocols/dial"
)合并规范:Google与Uber的联合指导
极简分组(Uber):标准库 → 其他所有包。
扩展分组(Google):标准库 → 其他包 → 协议缓冲区 → 副作用包。
go
// 推荐:标准库与外部包分开
import (
"fmt"
"os"
"go.uber.org/atomic"
"golang.org/x/sync/errgroup"
)go
// 推荐:包含协议缓冲区和副作用包的完整分组
import (
"fmt"
"os"
"github.com/dsnet/compress/flate"
"golang.org/x/text/encoding"
foopb "myproj/foo/proto/proto"
_ "myproj/rpc/protocols/dial"
)Import Renaming
导入重命名
Normative: This is required per Go Wiki CodeReviewComments and Google's Go style guide.
Avoid renaming imports except to avoid a name collision; good package names
should not require renaming. In the event of collision, prefer to rename the
most local or project-specific import.
Must rename: collision with other imports, generated protocol buffer packages
(remove underscores, add suffix).
pbMay rename: uninformative names (e.g., ), collision with local variable.
v1go
// Good: Proto packages renamed with pb suffix
import (
foosvcpb "path/to/package/foo_service_go_proto"
)
// Good: urlpkg when url variable is needed
import (
urlpkg "net/url"
)
func parseEndpoint(url string) (*urlpkg.URL, error) {
return urlpkg.Parse(url)
}规范:这是Go Wiki CodeReviewComments和Google Go风格指南中的强制要求。
除非是为了避免名称冲突,否则不要重命名导入;好的包名不需要重命名。如果发生冲突,优先重命名本地或项目特定的导入。
必须重命名的场景:与其他导入名称冲突、生成的协议缓冲区包(移除下划线,添加后缀)。
pb可以重命名的场景:无意义的名称(例如)、与本地变量名称冲突。
v1go
// 推荐:协议缓冲区包添加pb后缀重命名
import (
foosvcpb "path/to/package/foo_service_go_proto"
)
// 推荐:当需要使用url变量时,将net/url重命名为urlpkg
import (
urlpkg "net/url"
)
func parseEndpoint(url string) (*urlpkg.URL, error) {
return urlpkg.Parse(url)
}Blank Imports (import _
)
import _空白导入(import _
)
import _Normative: This is required per Go Wiki CodeReviewComments and Google's Go style guide.
Packages that are imported only for their side effects (using )
should only be imported in the main package of a program, or in tests that
require them.
import _ "pkg"go
// Good: Blank import in main package
package main
import (
_ "time/tzdata"
_ "image/jpeg"
)规范:这是Go Wiki CodeReviewComments和Google Go风格指南中的强制要求。
仅为了副作用而导入的包(使用),应仅在程序的main包或需要它们的测试中导入。
import _ "pkg"go
// 推荐:在main包中使用空白导入
package main
import (
_ "time/tzdata"
_ "image/jpeg"
)Dot Imports (import .
)
import .点导入(import .
)
import .Normative: This is required per Go Wiki CodeReviewComments and Google's Go style guide.
Do not use dot imports. They make programs much harder to read because it is
unclear whether a name like is a top-level identifier in the current
package or in an imported package.
QuuxException: The form can be useful in tests that, due to circular
dependencies, cannot be made part of the package being tested:
import .go
package foo_test
import (
"bar/testutil" // also imports "foo"
. "foo"
)In this case, the test file cannot be in package because it uses
, which imports . So the form lets the file
pretend to be part of package even though it is not.
foobar/testutilfooimport .fooExcept for this one case, do not use in your programs.
import .go
// Bad: Dot import hides origin
import . "foo"
var myThing = Bar() // Where does Bar come from?
// Good: Explicit qualification
import "foo"
var myThing = foo.Bar()规范:这是Go Wiki CodeReviewComments和Google Go风格指南中的强制要求。
不要使用点导入。它会让程序的可读性大幅降低,因为像这样的名称无法明确是当前包的顶级标识符还是来自导入包。
Quux例外情况:当测试由于循环依赖无法成为被测包的一部分时,形式会很有用:
import .go
package foo_test
import (
"bar/testutil" // 同时导入了"foo"
. "foo"
)在这种情况下,测试文件不能属于包,因为它使用了(后者导入了)。所以形式让该文件可以假装是包的一部分,尽管实际上不是。
foobar/testutilfooimport .foo除了这种情况,不要在程序中使用。
import .go
// 不推荐:点导入隐藏了标识符来源
import . "foo"
var myThing = Bar() // Bar来自哪里?
// 推荐:显式限定
import "foo"
var myThing = foo.Bar()Avoid init()
避免使用init()
Source: Uber Go Style Guide
Avoid where possible. When is unavoidable, code should:
init()init()- Be completely deterministic, regardless of program environment
- Avoid depending on ordering or side-effects of other functions
init() - Avoid global/environment state (env vars, working directory, args)
- Avoid I/O (filesystem, network, system calls)
go
// Bad: init() with I/O and environment dependencies
var _config Config
func init() {
cwd, _ := os.Getwd()
raw, _ := os.ReadFile(path.Join(cwd, "config.yaml"))
yaml.Unmarshal(raw, &_config)
}go
// Good: Explicit function for loading config
func loadConfig() (Config, error) {
cwd, err := os.Getwd()
if err != nil {
return Config{}, err
}
raw, err := os.ReadFile(path.Join(cwd, "config.yaml"))
if err != nil {
return Config{}, err
}
var config Config
if err := yaml.Unmarshal(raw, &config); err != nil {
return Config{}, err
}
return config, nil
}Acceptable uses of init():
- Complex expressions that cannot be single assignments
- Pluggable hooks (e.g., dialects, encoding registries)
database/sql - Deterministic precomputation
来源:Uber Go风格指南
尽可能避免使用。如果不得不使用,代码应满足:
init()init()- 完全确定,不受程序环境影响
- 避免依赖其他函数的执行顺序或副作用
init() - 避免全局/环境状态(环境变量、工作目录、命令行参数)
- 避免I/O操作(文件系统、网络、系统调用)
go
// 不推荐:init()包含I/O和环境依赖
var _config Config
func init() {
cwd, _ := os.Getwd()
raw, _ := os.ReadFile(path.Join(cwd, "config.yaml"))
yaml.Unmarshal(raw, &_config)
}go
// 推荐:使用显式函数加载配置
func loadConfig() (Config, error) {
cwd, err := os.Getwd()
if err != nil {
return Config{}, err
}
raw, err := os.ReadFile(path.Join(cwd, "config.yaml"))
if err != nil {
return Config{}, err
}
var config Config
if err := yaml.Unmarshal(raw, &config); err != nil {
return Config{}, err
}
return config, nil
}可接受的init()使用场景:
- 无法用单一赋值语句实现的复杂表达式
- 可插拔钩子(例如方言、编码注册器)
database/sql - 确定性的预计算
Exit in Main
仅在Main中退出
Source: Uber Go Style Guide
Call or only in . All other functions should
return errors to signal failure.
os.Exitlog.Fatal*main()Why this matters:
- Non-obvious control flow: Any function can exit the program
- Difficult to test: Functions that exit also exit the test
- Skipped cleanup: statements are skipped
defer
go
// Bad: log.Fatal in helper function
func readFile(path string) string {
f, err := os.Open(path)
if err != nil {
log.Fatal(err) // Exits program, skips defers
}
b, err := io.ReadAll(f)
if err != nil {
log.Fatal(err)
}
return string(b)
}go
// Good: Return errors, let main() decide to exit
func main() {
body, err := readFile(path)
if err != nil {
log.Fatal(err)
}
fmt.Println(body)
}
func readFile(path string) (string, error) {
f, err := os.Open(path)
if err != nil {
return "", err
}
b, err := io.ReadAll(f)
if err != nil {
return "", err
}
return string(b), nil
}来源:Uber Go风格指南
仅在中调用或。所有其他函数应返回错误以表示失败。
main()os.Exitlog.Fatal*为什么这很重要:
- 非直观的控制流:任何函数都可能终止程序
- 难以测试:会终止程序的函数也会终止测试
- 跳过清理操作:语句不会执行
defer
go
// 不推荐:在辅助函数中使用log.Fatal
func readFile(path string) string {
f, err := os.Open(path)
if err != nil {
log.Fatal(err) // 终止程序,跳过defer语句
}
b, err := io.ReadAll(f)
if err != nil {
log.Fatal(err)
}
return string(b)
}go
// 推荐:返回错误,由main()决定是否退出
func main() {
body, err := readFile(path)
if err != nil {
log.Fatal(err)
}
fmt.Println(body)
}
func readFile(path string) (string, error) {
f, err := os.Open(path)
if err != nil {
return "", err
}
b, err := io.ReadAll(f)
if err != nil {
return "", err
}
return string(b), nil
}Exit Once
单次退出
Prefer to call or at most once in . Extract
business logic into a separate function that returns errors.
os.Exitlog.Fatalmain()go
// Good: Single exit point with run() pattern
func main() {
if err := run(); err != nil {
log.Fatal(err)
}
}
func run() error {
args := os.Args[1:]
if len(args) != 1 {
return errors.New("missing file")
}
f, err := os.Open(args[0])
if err != nil {
return err
}
defer f.Close() // Will always run
b, err := io.ReadAll(f)
if err != nil {
return err
}
// Process b...
return nil
}Benefits of the pattern:
run()- Short function with single exit point
main() - All business logic is testable
- statements always execute
defer
在中最多调用一次或。将业务逻辑提取到单独的函数中,由该函数返回错误。
main()os.Exitlog.Fatalgo
// 推荐:采用run()模式实现单一退出点
func main() {
if err := run(); err != nil {
log.Fatal(err)
}
}
func run() error {
args := os.Args[1:]
if len(args) != 1 {
return errors.New("缺少文件参数")
}
f, err := os.Open(args[0])
if err != nil {
return err
}
defer f.Close() // 一定会执行
b, err := io.ReadAll(f)
if err != nil {
return err
}
// 处理b...
return nil
}run()模式的优势:
- main()函数简短,只有一个退出点
- 所有业务逻辑都可测试
- defer语句始终会执行
Quick Reference
快速参考
| Topic | Rule | Type |
|---|---|---|
| Import organization | std first, groups separated by blank lines | Normative |
| Import grouping | std → other (→ proto → side-effect) | Combined |
| Import renaming | Only when necessary; prefer renaming local/project import | Normative |
| Blank imports | Only in main packages or tests | Normative |
| Dot imports | Only for circular test dependencies | Normative |
| Util packages | Avoid; use descriptive names | Advisory |
| Package size | Balance cohesion vs. distinct concepts | Advisory |
| init() | Avoid; must be deterministic if used | Advisory |
| Exit in main | Only exit from main(); return errors | Advisory |
| 主题 | 规则 | 类型 |
|---|---|---|
| 导入组织 | 标准库优先,组之间用空行分隔 | 规范 |
| 导入分组 | 标准库 → 其他包(→ 协议缓冲区 → 副作用包) | 合并规范 |
| 导入重命名 | 仅在必要时使用;优先重命名本地/项目导入 | 规范 |
| 空白导入 | 仅在main包或测试中使用 | 规范 |
| 点导入 | 仅用于解决测试中的循环依赖 | 规范 |
| Util包 | 避免使用;使用描述性名称 | 建议 |
| 包大小 | 在内聚性与概念独立性之间取得平衡 | 建议 |
| init() | 避免使用;若必须使用则需完全确定 | 建议 |
| 仅在Main中退出 | 仅从main()退出;其他函数返回错误 | 建议 |
See Also
另请参阅
- For core style principles:
go-style-core - For naming conventions:
go-naming - For error handling patterns:
go-error-handling - For defensive coding:
go-defensive - For linting tools:
go-linting
- 核心风格原则:
go-style-core - 命名规范:
go-naming - 错误处理模式:
go-error-handling - 防御式编程:
go-defensive - linting工具:
go-linting