go: Export the latest version of the internal guide. (#965)

The update revises guidance around line length, import renaming, context
usage, sentinel placement, and interface usage.
This commit is contained in:
Matt T. Proud authored and GitHub committed 2026-03-18 15:56:05 +01:00
1 parent c885dc260b
commit c098353acb
3 files changed
+444 -100

No files matched your search

+393
View File
@@ -1096,6 +1096,75 @@ fmt.Println(err3) // err3-1 err2-1 err1 err2-2 err3-2
Therefore, in order for error text to mirror error chain structure, prefer
placing the `%w` verb at the end with the form `[...]: %w`.
<a id="error-percent-w-sentinel-placement"></a>
#### Sentinel error placement
An exception to this rule is when wrapping sentinel errors. A sentinel error is
an error that serves as a primary categorization of a failure. This helps
observers quickly understand the nature of a failure (such as "not found" or
"invalid argument") without having to parse the entire error message.
Identifying that error type as early as possible in the error string is
beneficial.
Examples of sentinel errors include os errors (e.g., [`os.ErrInvalid`]) and
package-level errors.
In these cases, placing the `%w` verb at the beginning of the error string can
improve readability by immediately identifying the category of the error.
```go
// Good:
package parser
var ErrParse = fmt.Errorf("parse error")
// This is another package error that could be returned.
var ErrParseInvalidHeader = fmt.Errorf("%w: invalid header", ErrParse)
func parseHeader() error {
err := checkHeader()
return fmt.Errorf("%w: invalid character in header: %v", ErrParseInvalidHeader, err)
}
err := fmt.Errorf("%w: couldn't find fortune database: %v", ErrInternal, err)
```
Placing the status at the beginning ensures that the most relevant categorical
information is most prominent.
```go
// Bad:
package parser
var ErrParse = fmt.Errorf("parse error")
// This is another package error that could be returned.
var ErrParseInvalidHeader = fmt.Errorf("%w: invalid header", ErrParse)
func parseHeader() error {
err := checkHeader()
return fmt.Errorf("invalid character in header: %v: %w", err, ErrParseInvalidHeader)
}
var ErrInternal = status.Error(codes.Internal, "internal")
err2 := fmt.Errorf("couldn't find fortune database: %v: %w", err, ErrInternal)
```
When you place it at the end, it makes it harder to identify the error category
when reading the error text, as it's buried in the specific error details.
[`os.ErrInvalid`]: https://pkg.go.dev/os#ErrInvalid
See also:
* [Go Tip #48: Error Sentinel Values]
* [Go Tip #106: Error Naming Conventions]
[commentary]: decisions#commentary
[Go Tip #48: Error Sentinel Values]: https://google.github.io/styleguide/go/index.html#gotip
[Go Tip #106: Error Naming Conventions]: https://google.github.io/styleguide/go/index.html#gotip
<a id="error-logging"></a>
### Logging errors
@@ -3597,3 +3666,327 @@ See also:
* [Go Tip #36: Enclosing Package-Level State](https://google.github.io/styleguide/go/index.html#gotip)
* [Go Tip #80: Dependency Injection Principles](https://google.github.io/styleguide/go/index.html#gotip)
<a id="interfaces"></a>
## Interfaces
Interfaces in Go are powerful but can be overused or misunderstood. Because Go
interfaces are satisfied implicitly, they are a structural tool rather than a
declarative one. The following guidance provides the best practices for how to
design and return interfaces in Go without over-engineering your codebase.
Refer to [Decisions' section on interfaces](decisions#interfaces) for a summary.
<a id="avoid-unnecessary-interfaces"></a>
### Avoid unnecessary interfaces
The most common mistake is creating an interface before a
[real need](guide#simplicity) exists.
1. **Don’t confuse the concept with the keyword:** Just because you are
designing a "service" or a "repository" or similar pattern doesn't mean you
need a named interface type (e.g., `type Service interface`). Focus on the
behavior and its concrete implementation first.
2. **Reuse existing interfaces:** If an interface already exists, especially in
generated code, like a RPC client or server, use it ([testing RPC]). Do not
wrap a generated RPC code in a new, manual interface just for the sake of
abstraction or testing. [Use real transports](#use-real-transports) instead.
3. **Don't define back doors only for tests:** Do not export a [test double]
implementation of an interface from an API that consumes it. Instead, prefer
to design the API so that it can be tested using the [public API] of the
real implementation.
Every exported type increases the cognitive load for the reader. When you
export a test double alongside the real implementation, you force the reader
to understand three entities (the interface, the real implementation, and
the test double) instead of one.
Export an interface for a test double when you have a
[material need](guide#least-mechanism) to support substitution.
When it does make sense to create an interface:
1. **Multiple implementations:** When there are two or more concrete types that
must be handled by the same logic (e.g., something that operates with both
[json.Encoder](https://pkg.go.dev/encoding/json#Encoder) and
[gob.GobEncoder](https://pkg.go.dev/encoding/gob#GobEncoder)), the API
consumer could define an interface.
2. **Decoupling packages:** To break circular dependencies between two packages
(see an [example](#avoiding-circular-dependencies)), an API producer could
define an interface.
**Caution:** Carefully observe guidance on [Package Size](#package-size).
Introducing interfaces to break dependency cycles is often a signal of
improperly structured packages.
3. **Hiding complexity:** When a concrete type has a massive API surface, but a
specific function only needs one or two methods, an API consumer may define
an interface.
<a id="interface-ownership-and-visibility"></a>
### Interface ownership and visibility
1. **Do not export interface types unnecessarily:** If an interface is only
used internally within a package to satisfy a specific logic flow, keep the
interface unexported. Exporting an interface commits you to maintaining that
API for external callers.
2. **The consumer defines the interface:** In Go, interfaces generally belong
in the package that uses them, not the package that implements them. The
consumer should define only the methods they actually use
[GoTip #78: Minimal Viable Interfaces], adhering to the idea that
[the bigger the interface, the weaker the abstraction](https://go-proverbs.github.io/).
There are common scenarios where it often makes sense for the producer (the
package providing the logic) to export the interface:
* **The interface is the product:** When a package’s primary purpose is to
provide a common protocol that many different implementations must
follow, the producer defines the interface. For example,
[io.Writer](https://pkg.go.dev/io#Writer),
[hash.Hash](https://pkg.go.dev/hash#Hash). The concept of "protocol"
includes aspects like [documentation](#documentation) about critical
behaviors (e.g., expected use case, edge cases, concurrency) that need
to be centrally and canonically explicated. Another prominent example of
this is generated interfaces from protobuf. It doesn't abstract a
specific behavior, it defines a boundary. Its purpose is to ensure that
your server implementation exactly matches the schema defined in the
`.proto` file. Here, the interface serves as a rigid legal contract
between the service and its clients.
For large systems, if the interface lives inside a huge implementation
package, every client is forced to import the entire world just to
reference the interface. You may define the interface in a standalone,
implementation-free package, avoiding unnecessary symbols and potential
circular dependencies. This is also the same philosophy used by
generated code from protobuf.
* **Prevent interface bloat:** In large codebases, maintenance becomes
difficult if numerous packages utilize the same `AuthService` while each
defining an identical `type Authorizer interface`. While Go often favors
[a little copying over a little dependency](https://go-proverbs.github.io/),
keep in mind that maintaining perfectly mirrored interfaces (see point
above) across many packages can create an unnecessary burden.
* **Resolve circular dependency:** see
[an example](#avoiding-circular-dependencies) below.
<a id="designing-effective-interfaces"></a>
### Designing effective interfaces
1. **Keep interfaces small:** The larger the interface,
[the harder it is to implement and to write code that takes advantage of it](https://go-proverbs.github.io/).
Small interfaces are easier to compose into larger ones if needed.
2. **Documentation:** Treat every interface as the "user manual" for your
abstraction. The depth of your documentation should be proportional to the
interface's cognitive load, not just the count of its methods. Whether an
interface has ten methods or a single `Write` of
[io.Writer](https://pkg.go.dev/io#Writer), if a programmer is expected to
interact with that type, the API must be documented thoroughly.
* **Single-method interfaces:** documentation on the type itself is
usually sufficient (e.g., io.Writer). Explain its contract, edge cases,
and expected errors.
* **Multi-method interfaces:** each individual method requires its own
documentation.
* **Unexported interfaces:** consider documenting them anyway. They are
often the glue that holds complex internal logic together, and because
they are invisible to external users, they can easily become mystery
code for future maintainers (including your future self).
3. **Accept interfaces, return concrete types:** Returning a concrete type
allows the caller to use the full functionality of the value without being
locked into a specific interface abstraction
[GoTip #49: Accept Interfaces, Return Concrete Types].
There are several common scenarios where returning an interface is the idiomatic
choice:
1. **Encapsulation:** While interfaces cannot strictly hide exported methods
(as they remain accessible via type assertions), returning an interface is a
powerful tool for limiting the default API surface and guiding the caller's
behavior.. The most common example is the `error` interface; you
[almost never return a concrete error type](decisions#errors) like
`*MyCustomError`.
Consider a `ThrottledReader` that implements `io.Reader` but also has a
`Refill` method for internal bucket management. Returning the concrete
`*ThrottledReader` invites the caller to manage the bucket manually, which
could lead to race conditions or broken rate-limiting logic. By returning an
interface, you tell the caller that your only job is to consume this reader.
If you try to cast this back to a `ThrottledReader` to `Refill` the internal
bucket, you are breaking the contract.
```go
// Good:
type ThrottledReader struct {
source io.Reader
limit int // bytes per second
balance int // current allowance of bytes
lastRefill time.Time
}
// Read implements the io.Reader interface with rate-limiting logic.
func (t *ThrottledReader) Read(p []byte) (int, error) { ... }
// Refill manually adds tokens to the bucket.
// INTERNAL USE ONLY: Calling this from outside breaks the rate limit logic.
func (t *ThrottledReader) Refill(amount int) {
t.balance = min(t.balance + amount, t.limit)
}
// New returns the io.Reader with rate-limiting.
func New(r io.Reader, bytesPerSec int) io.Reader {
return &ThrottledReader{
source: r,
limit: bytesPerSec,
balance: bytesPerSec, // start with a full bucket
lastRefill: time.Now(),
}
}
```
This raises a natural question: if `Refill` is dangerous, why export it at
all? In complex systems, you often need internal orchestration. For example,
an `AggregateReader` manages multiple `ThrottledReader` values to ensure
total bandwidth across all streams stays under a global limit. This
coordinator needs to call Refill to distribute tokens, but the non-power
user processing the data should never see that capability.
**Caution:** Before returning an interface to hide implementation, ask:
"Would a user calling these extra methods actually break the system's
integrity or meaningfully limit maintainability?" If the extra details allow
the user to bypass safety checks, or if exposing the concrete type makes it
impossible to change the underlying provider later without a breaking
change, you may return an interface. Do not rotely encapsulate without
reason.
2. **Certain patterns:** If a function is designed to return one of several
different concrete types based on decisions made at runtime, it must return
an interface. This is commonly true with command, chaining, factory, and
[strategy](https://en.wikipedia.org/wiki/Strategy_pattern) patterns.
Consider this code that selects which encoder to use based the requested
format:
```go
// Good:
func NewWriter(format string) io.Writer {
switch format {
case "json":
return &jsonWriter{}
case "xml":
return &xmlWriter{}
default:
return &textWriter{}
}
}
```
The following example of a chaining API demonstrates how returning an
interface enables polymorphic behavior. By allowing callers to use either
`client.Do(req)` or `client.WithAuth("token").Do(req)`, you can swap
implementations without breaking the calling code.
```go
// Good:
type Client interface {
WithAuth(token string) Client
Do(req *Request) error
}
```
These patterns are guidelines, not rules. Avoid forcing an interface if a
single, robust concrete type can handle the abstraction internally. For
example, the standard [database/sql](https://pkg.go.dev/database/sql#DB)
library exports a single concrete `DB` type instead of forcing an interface
to handle types like `MySQLDB` and `OracleDB`.
3. <span id="avoiding-circular-dependencies">**Avoiding circular
dependencies:**</span> If returning a concrete type would require importing
a package that already imports your current package, you must return an
interface to break the circular dependency.
For example:
```go
// Bad:
package app
import "myproject/plugin"
type Config struct {
APIKey string
}
func Start() {
p := plugin.New()
}
```
```go
// Bad:
package plugin
import "myproject/app" // ERROR: Import cycle!
func New() *app.Config {
return &app.Config{APIKey: "secret"}
}
```
In this case, `plugin`'s `New` cannot return `*app.Config` because it would
create a circular import. To break this, we use the fact that interfaces are
satisfied implicitly. We move the "contract" to a neutral place or have the
producer return an interface that the consumer already understands.
If `plugin`'s `New` returns an interface instead of the concrete
`*app.Config` struct, it no longer needs to import package `app`.
```go
package plugin
type Configurer interface {
APIKey() string
}
type localConfig struct {
key string
}
func (c localConfig) APIKey() string { return c.key }
// New returns the interface Configurer instead of the concrete app.Config
func New() Configurer {
return &localConfig{key: "secret"}
}
```
```go
package app
import "myproject/plugin"
func Start() {
conf := plugin.New() // 'conf' is now a Configurer interface
fmt.Println(conf.APIKey())
}
```
**Caution:** Carefully observe guidance on [Package Size](#package-size).
Introducing interfaces to break dependency cycles is often a signal of
improperly structured packages. Consolidated packages are often preferred
over too many too small packages that fail to stand on their own.
[GoTip #78: Minimal Viable Interfaces]: https://google.github.io/styleguide/go/index.html#gotip
[GoTip #49: Accept Interfaces, Return Concrete Types]: https://google.github.io/styleguide/go/index.html#gotip
[testing RPC]: https://codelabs.developers.google.com/grpc/getting-started-grpc-go#3
[test double]: https://abseil.io/resources/swe-book/html/ch13.html
[public API]: https://abseil.io/resources/swe-book/html/ch12.html#test_via_public_apis
+50 -99
View File
@@ -144,6 +144,7 @@ See also: [Go blog post about package names](https://go.dev/blog/package-names).
* Short (usually one or two letters in length)
* Abbreviations for the type itself
* Applied consistently to every receiver for that type
* Not an underscore; omit the name if it is unused
Long Name | Better Name
--------------------------- | -------------------------
@@ -529,22 +530,16 @@ decoration should generally be avoided.
### Comment line length
Ensure that commentary is readable from source even on narrow screens.
There is no fixed [line length] for comments in Go.
When a comment gets too long, it is recommended to wrap it into multiple
single-line comments. When possible, aim for comments that will read well on an
80-column wide terminal, however this is not a hard cut-off; there is no fixed
line length limit for comments in Go. The standard library, for example, often
chooses to break a comment based on punctuation, which sometimes leaves the
individual lines closer to the 60-70 character mark.
[line length]: guide#line-length
There is plenty of existing code in which comments exceed 80 characters in
length. This guidance should not be used as a justification to change such code
as part of a readability review (see [consistency](guide#consistency)), though
teams are encouraged to opportunistically update comments to follow this
guideline as a part of other refactors. The primary goal of this guideline is to
ensure that all Go readability mentors make the same recommendation when and if
recommendations are made.
Long comment lines should be wrapped to ensure that source is readable in tools
which do not perform automatic wrapping of comment lines. If you are uncertain
where to wrap, 80 or 100 columns are common choices. However, this is not a hard
cut-off; there are situations where breaking a long literal text is harmful.
There is no requirement for the specific column width at which wrapping occurs.
Aim to be [consistent](guide#consistency) within a file.
See this [post from The Go Blog on documentation] for more on commentary.
@@ -564,17 +559,12 @@ See this [post from The Go Blog on documentation] for more on commentary.
// if it helps rather than hinders.
```
Avoid comments that will wrap repeatedly on small screens, which is a poor
reader experience.
Avoid comments that fit large amounts of text onto a single line, which is a
poor reader experience.
```text
# Bad:
// This is a comment paragraph. The length of individual lines doesn't matter in
Godoc;
// but the choice of wrapping causes jagged lines on narrow screens or in code
review,
// which can be annoying, especially when in a comment block that will wrap
repeatedly.
// This is a comment paragraph. While some code editors and viewers will wrap the paragraph for the reader, others will display a very long line that will overflow most windows and require users to scroll horizontally. In addition, even on a screen capable of displaying the entire line, it is easier to read a narrower paragraph than very wide one.
//
// Don't worry too much about the long URL:
// https://supercalifragilisticexpialidocious.example.com:8080/Animalia/Chordata/Mammalia/Rodentia/Geomyoidea/Geomyidae/
@@ -2227,88 +2217,47 @@ See also:
<a id="TOC-Interfaces"></a>
Go interfaces generally belong in the package that *consumes* values of the
interface type, not a package that *implements* the interface type. The
implementing package should return concrete (usually pointer or struct) types.
That way, new methods can be added to implementations without requiring
extensive refactoring. See [GoTip #49: Accept Interfaces, Return Concrete Types]
for more details.
Avoid creating interfaces until a [real need](guide#simplicity) exists. Focus on
the required behavior rather than just abstract named patterns like "service" or
"repository" and the like.
Do not export a [test double][double types] implementation of an interface from
an API that consumes it. Instead, design the API so that it can be tested using
the [public API] of the [real implementation]. See
[GoTip #42: Authoring a Stub for Testing] for more details. Even when it is not
feasible to use the real implementation, it may not be necessary to introduce an
interface fully covering all methods in the real type; the consumer can create
an interface containing only the methods it needs, as demonstrated in
[GoTip #78: Minimal Viable Interfaces].
* Do not wrap RPC clients in new manual interfaces just for the sake of
abstraction or testing.
[Use real transports](best-practices#use-real-transports) instead
([testing RPC]).
To test packages that use Stubby RPC clients, use a real client connection. If a
real server cannot be run in the test, Google's internal practice is to obtain a
real client connection to a local [test double] using the internal rpctest
package (coming soon!).
* Do not define back doors or export [test double] implementations of an
interface solely for testing. Prefer testing via the [public API] of the
real implementation instead.
Do not define interfaces before they are used (see
[TotT: Code Health: Eliminate YAGNI Smells][tott-438] ). Without a realistic
example of usage, it is too difficult to see whether an interface is even
necessary, let alone what methods it should contain.
Design interfaces to be small for easier implementation and composition
([GoTip #78: Minimal Viable Interfaces]). Document interfaces appropriately
including their contract, edge cases, and expected errors. Keep interface types
unexported if they are only used internally within a package.
Do not use interface-typed parameters if the users of the package do not need to
pass different types for them.
The consumer of the interface should define it (not the package implementing the
interface), ensuring it includes only the methods they actually use. The
producer package may export the interface if the interface is the product (a
common protocol) to prevent interface redefinition bloat.
Do not export interfaces that the users of the package do not need.
There is an adage: Functions should take interfaces as arguments but return
concrete types ([GoTip #49: Accept Interfaces, Return Concrete Types]).
Returning concrete types allows the caller to have access to every public method
and field of that specific implementation, not just the subset of methods
defined in a pre-chosen interface. The caller can still pass that concrete
result into any other function that expects an interface. Sometimes returning an
interface is acceptable for encapsulation (e.g., `error` interface), and certain
constructs like command, chaining, factory, and
[strategy](https://en.wikipedia.org/wiki/Strategy_pattern) patterns.
**TODO:** Write a more in-depth doc on interfaces and link to it here.
Deeper discussion on interfaces exists in the
[Best Practices' section on interfaces](best-practices#interfaces).
[GoTip #42: Authoring a Stub for Testing]: https://google.github.io/styleguide/go/index.html#gotip
[GoTip #49: Accept Interfaces, Return Concrete Types]: https://google.github.io/styleguide/go/index.html#gotip
[GoTip #78: Minimal Viable Interfaces]: https://google.github.io/styleguide/go/index.html#gotip
[real implementation]: best-practices#use-real-transports
[GoTip #49: Accept Interfaces, Return Concrete Types]: https://google.github.io/styleguide/go/index.html#gotip
[testing RPC]: https://codelabs.developers.google.com/grpc/getting-started-grpc-go#3
[test double]: https://abseil.io/resources/swe-book/html/ch13.html
[public API]: https://abseil.io/resources/swe-book/html/ch12.html#test_via_public_apis
[double types]: https://abseil.io/resources/swe-book/html/ch13.html#techniques_for_using_test_doubles
[test double]: https://abseil.io/resources/swe-book/html/ch13.html#basic_concepts
[tott-438]: https://testing.googleblog.com/2017/08/code-health-eliminate-yagni-smells.html
```go
// Good:
package consumer // consumer.go
type Thinger interface { Thing() bool }
func Foo(t Thinger) string { ... }
```
```go
// Good:
package consumer // consumer_test.go
type fakeThinger struct{ ... }
func (t fakeThinger) Thing() bool { ... }
...
if Foo(fakeThinger{...}) == "x" { ... }
```
```go
// Bad:
package producer
type Thinger interface { Thing() bool }
type defaultThinger struct{ ... }
func (t defaultThinger) Thing() bool { ... }
func NewThinger() Thinger { return defaultThinger{ ... } }
```
```go
// Good:
package producer
type Thinger struct{ ... }
func (t Thinger) Thing() bool { ... }
func NewThinger() Thinger { return Thinger{ ... } }
```
<a id="generics"></a>
@@ -2770,13 +2719,15 @@ Exceptions are:
the generated server type, which implements `grpc.ServerStream`. See
[gRPC Generated Code documentation](https://grpc.io/docs/languages/go/generated-code/).
* In entrypoint functions (see below for examples of such functions), use
[`context.Background()`] or, for tests,
[`tb.Context()`](https://pkg.go.dev/testing#TB.Context).
* In test functions (e.g. `TestXXX`, `BenchmarkXXX`, `FuzzXXX`), where the
context comes from
[`(testing.TB).Context()`](https://pkg.go.dev/testing#TB.Context).
* In other entrypoint functions (see below for examples of such functions),
use [`context.Background()`].
* In binary targets: `main`
* In general purpose code and libraries: `init`
* In tests: `TestXXX`, `BenchmarkXXX`, `FuzzXXX`
> **Note**: It is very rare for code in the middle of a callchain to require
> creating a base context of its own using [`context.Background()`]. Always
+1 -1
View File
@@ -257,7 +257,7 @@ if err := doSomething(); err == nil { // if NO error
}
```
[Table-driven testing]: https://github.com/golang/go/wiki/TableDrivenTests
[Table-driven testing]: https://go.dev/wiki/TableDrivenTests
[error handling]: https://go.dev/blog/errors-are-values
["boosting"]: best-practices#signal-boost