go-functions

Compare original and translation side by side

🇺🇸

Original

English
🇨🇳

Translation

Chinese

Go Function Design

Go函数设计



Function Grouping and Ordering

函数分组与排序

Organize functions in a file by these rules:
  1. Functions sorted in rough call order
  2. Functions grouped by receiver
  3. Exported functions appear first, after
    struct
    /
    const
    /
    var
    definitions
  4. NewXxx
    /
    newXxx
    constructors appear right after the type definition
  5. Plain utility functions appear toward the end of the file
Bad:
go
func (s *something) Cost() int {
    return calcCost(s.weights)
}

type something struct{ ... }

func calcCost(n []int) int { ... }

func (s *something) Stop() { ... }

func newSomething() *something {
    return &something{}
}
Good:
go
type something struct{ ... }

func newSomething() *something {
    return &something{}
}

func (s *something) Cost() int {
    return calcCost(s.weights)
}

func (s *something) Stop() { ... }

func calcCost(n []int) int { ... }

按以下规则在文件中组织函数:
  1. 函数按大致调用顺序排序
  2. 函数按接收者分组
  3. 导出函数出现在
    struct
    /
    const
    /
    var
    定义之后
  4. NewXxx
    /
    newXxx
    构造函数紧跟在类型定义之后
  5. 普通工具函数放在文件末尾
Bad:
go
func (s *something) Cost() int {
    return calcCost(s.weights)
}

type something struct{ ... }

func calcCost(n []int) int { ... }

func (s *something) Stop() { ... }

func newSomething() *something {
    return &something{}
}
Good:
go
type something struct{ ... }

func newSomething() *something {
    return &something{}
}

func (s *something) Cost() int {
    return calcCost(s.weights)
}

func (s *something) Stop() { ... }

func calcCost(n []int) int { ... }

Function Signature Formatting

函数签名格式化

Keep the signature on a single line when possible. When it must wrap, put all arguments on their own lines with a trailing comma:
Bad:
go
func (r *SomeType) SomeLongFunctionName(foo1, foo2, foo3 string,
    foo4, foo5, foo6 int) {
    foo7 := bar(foo1)
}
Good:
go
func (r *SomeType) SomeLongFunctionName(
    foo1, foo2, foo3 string,
    foo4, foo5, foo6 int,
) {
    foo7 := bar(foo1)
}
Shorten call sites by factoring out local variables instead of splitting arbitrarily:
go
// Good: factor out locals
local := helper(some, parameters, here)
result := foo.Call(list, of, parameters, local)

尽可能将签名放在一行。当必须换行时,将所有参数单独放在一行并添加 trailing comma:
Bad:
go
func (r *SomeType) SomeLongFunctionName(foo1, foo2, foo3 string,
    foo4, foo5, foo6 int) {
    foo7 := bar(foo1)
}
Good:
go
func (r *SomeType) SomeLongFunctionName(
    foo1, foo2, foo3 string,
    foo4, foo5, foo6 int,
) {
    foo7 := bar(foo1)
}
通过提取局部变量而非随意拆分来简化调用代码:
go
// Good: factor out locals
local := helper(some, parameters, here)
result := foo.Call(list, of, parameters, local)

Avoid Naked Parameters

避免裸参数

Naked parameters in function calls hurt readability. Add C-style comments for ambiguous arguments:
go
// Bad
printInfo("foo", true, true)

// Good
printInfo("foo", true /* isLocal */, true /* done */)
Better yet, replace naked
bool
parameters with custom types:
go
type Region int

const (
    UnknownRegion Region = iota
    Local
)

func printInfo(name string, region Region, status Status)

函数调用中的裸参数会损害可读性。为模糊的参数添加C风格注释:
go
// Bad
printInfo("foo", true, true)

// Good
printInfo("foo", true /* isLocal */, true /* done */)
更好的方式是,用自定义类型替换裸
bool
参数:
go
type Region int

const (
    UnknownRegion Region = iota
    Local
)

func printInfo(name string, region Region, status Status)

Pointers to Interfaces

指向接口的指针

You almost never need a pointer to an interface. Pass interfaces as values — the underlying data can still be a pointer.
go
// Bad: pointer to interface is almost always wrong
func process(r *io.Reader) { ... }

// Good: pass the interface value directly
func process(r io.Reader) { ... }
If you need interface methods to modify the underlying data, the concrete type stored in the interface must be a pointer — not the interface itself.

几乎永远不需要指向接口的指针。直接传递接口值——底层数据仍然可以是指针。
go
// Bad: pointer to interface is almost always wrong
func process(r *io.Reader) { ... }

// Good: pass the interface value directly
func process(r io.Reader) { ... }
如果需要接口方法修改底层数据,接口中存储的具体类型必须是指针——而非接口本身。

Use
%q
for Strings

对字符串使用
%q

The
%q
verb prints strings inside double quotes, making empty strings and control characters visible:
go
// Good: %q makes boundaries and special chars visible
fmt.Printf("value %q looks like English text", someText)

// Bad: manually adding quotes
fmt.Printf("value \"%s\" looks like English text", someText)
Prefer
%q
in output intended for humans where the value could be empty or contain control characters.

%q
动词会将字符串用双引号包裹输出,让空字符串和控制字符可见:
go
// Good: %q makes boundaries and special chars visible
fmt.Printf("value %q looks like English text", someText)

// Bad: manually adding quotes
fmt.Printf("value \"%s\" looks like English text", someText)
当输出面向人类且值可能为空或包含控制字符时,优先使用
%q

Format Strings Outside Printf

在Printf外部定义格式化字符串

When declaring format strings outside a
Printf
-style call, use
const
. This enables
go vet
to perform static analysis:
go
// Bad: variable format string — go vet can't check it
msg := "unexpected values %v, %v\n"
fmt.Printf(msg, 1, 2)

// Good: const format string — go vet can validate
const msg = "unexpected values %v, %v\n"
fmt.Printf(msg, 1, 2)

Printf
类调用外部声明格式化字符串时,使用
const
。这样
go vet
可以执行静态分析:
go
// Bad: variable format string — go vet can't check it
msg := "unexpected values %v, %v\n"
fmt.Printf(msg, 1, 2)

// Good: const format string — go vet can validate
const msg = "unexpected values %v, %v\n"
fmt.Printf(msg, 1, 2)

Naming Printf-style Functions

Printf风格函数的命名

Functions that accept a format string should end in
f
. This lets
go vet
check format strings automatically:
go
// Good: go vet checks Wrapf format strings by default
func Wrapf(err error, format string, args ...any) error

// Bad: go vet won't check Wrap's format string
func Wrap(err error, format string, args ...any) error
If using a non-standard name:
bash
go vet -printfuncs=wrapf,statusf
Read
go-functions/references/FUNCTIONAL-OPTIONS.md
when designing a constructor with 3+ optional parameters.

接受格式化字符串的函数名称应以
f
结尾。这样
go vet
可以自动检查格式化字符串:
go
// Good: go vet checks Wrapf format strings by default
func Wrapf(err error, format string, args ...any) error

// Bad: go vet won't check Wrap's format string
func Wrap(err error, format string, args ...any) error
如果使用非标准名称:
bash
go vet -printfuncs=wrapf,statusf
当设计带有3个及以上可选参数的构造函数时,请阅读
go-functions/references/FUNCTIONAL-OPTIONS.md

Quick Reference

快速参考

TopicRule
File orderingType -> constructor -> exported -> unexported -> utils
Signature wrappingAll args on own lines with trailing comma
Naked parametersAdd
/* name */
comments or use custom types
Pointers to interfacesAlmost never needed; pass interfaces by value
%q
Use for human-readable string output
Format string storageDeclare as
const
outside Printf calls
Printf function namesEnd with
f
for
go vet
support

主题规则
文件排序类型 -> 构造函数 -> 导出函数 -> 未导出函数 -> 工具函数
签名换行所有参数单独占一行并添加 trailing comma
裸参数添加
/* 名称 */
注释或使用自定义类型
指向接口的指针几乎不需要;按值传递接口
%q
用于人类可读的字符串输出
格式化字符串存储在Printf调用外部声明为
const
Printf函数命名
f
结尾以支持
go vet

See Also

另请参阅

  • go-error-handling: Error return patterns and wrapping
  • go-style-core: Line length and formatting principles
  • go-declarations: Variable declaration and initialization patterns
  • go-naming: Function and method naming conventions
  • go-interfaces: Interface design and type assertions
  • go-error-handling:错误返回模式与包装
  • go-style-core:行长度与格式化原则
  • go-declarations:变量声明与初始化模式
  • go-naming:函数与方法命名约定
  • go-interfaces:接口设计与类型断言