go-packages

Compare original and translation side by side

🇺🇸

Original

English
🇨🇳

Translation

Chinese

Go 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
util
,
helper
,
common
, or similar—they make code harder to read and cause import conflicts.
go
// 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
util
can be used as part of a name (e.g.,
stringutil
) but should not be the entire package name.
建议:这是最佳实践推荐。
包名应能描述该包提供的功能。避免使用
util
helper
common
这类通用名称——它们会降低代码可读性,还可能导致导入冲突。
go
// 推荐:使用有意义的包名
db := spannertest.NewDatabaseFromFile(...)
_, err := f.Seek(0, io.SeekStart)

// 不推荐:模糊的包名会掩盖含义
db := test.NewDatabaseFromFile(...)
_, err := f.Seek(0, common.SeekStart)
util
这类通用名称可以作为包名的一部分(例如
stringutil
),但不能作为完整的包名。

Package 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.Buffer
    ,
    ring.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.Buffer
    ring.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
pb
suffix).
May rename: uninformative names (e.g.,
v1
), collision with local variable.
go
// 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
后缀)。
可以重命名的场景:无意义的名称(例如
v1
)、与本地变量名称冲突。
go
// 推荐:协议缓冲区包添加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 _

Normative: This is required per Go Wiki CodeReviewComments and Google's Go style guide.
Packages that are imported only for their side effects (using
import _ "pkg"
) should only be imported in the main package of a program, or in tests that require them.
go
// Good: Blank import in main package
package main

import (
    _ "time/tzdata"
    _ "image/jpeg"
)
规范:这是Go Wiki CodeReviewComments和Google Go风格指南中的强制要求。
仅为了副作用而导入的包(使用
import _ "pkg"
),应仅在程序的main包或需要它们的测试中导入。
go
// 推荐:在main包中使用空白导入
package main

import (
    _ "time/tzdata"
    _ "image/jpeg"
)

Dot Imports (
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
Quux
is a top-level identifier in the current package or in an imported package.
Exception: The
import .
form can be useful in tests that, due to circular dependencies, cannot be made part of the package being tested:
go
package foo_test

import (
	"bar/testutil" // also imports "foo"
	. "foo"
)
In this case, the test file cannot be in package
foo
because it uses
bar/testutil
, which imports
foo
. So the
import .
form lets the file pretend to be part of package
foo
even though it is not.
Except for this one case, do not use
import .
in your programs.
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"
)
在这种情况下,测试文件不能属于
foo
包,因为它使用了
bar/testutil
(后者导入了
foo
)。所以
import .
形式让该文件可以假装是
foo
包的一部分,尽管实际上不是。
除了这种情况,不要在程序中使用
import .
go
// 不推荐:点导入隐藏了标识符来源
import . "foo"
var myThing = Bar() // Bar来自哪里?

// 推荐:显式限定
import "foo"
var myThing = foo.Bar()

Avoid init()

避免使用init()

Source: Uber Go Style Guide
Avoid
init()
where possible. When
init()
is unavoidable, code should:
  1. Be completely deterministic, regardless of program environment
  2. Avoid depending on ordering or side-effects of other
    init()
    functions
  3. Avoid global/environment state (env vars, working directory, args)
  4. 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.,
    database/sql
    dialects, encoding registries)
  • Deterministic precomputation

来源:Uber Go风格指南
尽可能避免使用
init()
。如果不得不使用
init()
,代码应满足:
  1. 完全确定,不受程序环境影响
  2. 避免依赖其他
    init()
    函数的执行顺序或副作用
  3. 避免全局/环境状态(环境变量、工作目录、命令行参数)
  4. 避免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
os.Exit
or
log.Fatal*
only in
main()
. All other functions should return errors to signal failure.
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:
    defer
    statements are skipped
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.Exit
log.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
os.Exit
or
log.Fatal
at most once in
main()
. Extract business logic into a separate function that returns errors.
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
run()
pattern:
  • Short
    main()
    function with single exit point
  • All business logic is testable
  • defer
    statements always execute

main()
中最多调用一次
os.Exit
log.Fatal
。将业务逻辑提取到单独的函数中,由该函数返回错误。
go
// 推荐:采用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

快速参考

TopicRuleType
Import organizationstd first, groups separated by blank linesNormative
Import groupingstd → other (→ proto → side-effect)Combined
Import renamingOnly when necessary; prefer renaming local/project importNormative
Blank importsOnly in main packages or testsNormative
Dot importsOnly for circular test dependenciesNormative
Util packagesAvoid; use descriptive namesAdvisory
Package sizeBalance cohesion vs. distinct conceptsAdvisory
init()Avoid; must be deterministic if usedAdvisory
Exit in mainOnly exit from main(); return errorsAdvisory

主题规则类型
导入组织标准库优先,组之间用空行分隔规范
导入分组标准库 → 其他包(→ 协议缓冲区 → 副作用包)合并规范
导入重命名仅在必要时使用;优先重命名本地/项目导入规范
空白导入仅在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