go: Export the latest version of internal guide.

The updates chiefly include copy editing, deduplication of material, and
additional considerations around documentation standards for APIs.
This commit is contained in:
Matt T. Proud 2024-01-30 17:19:28 +01:00
parent 19f31499d7
commit c10555a867
3 changed files with 220 additions and 164 deletions

View File

@ -597,7 +597,11 @@ to have them in the same package.
Code within a package can access unexported identifiers in the package. If you
have a few related types whose *implementation* is tightly coupled, placing them
in the same package lets you achieve this coupling without polluting the public
API with these details.
API with these details. A good test for this coupling is to imagine a
hypothetical user of two packages, where the packages cover closely related
topics: if the user must import both packages in order to use either in any
meaningful way, combining them together is usually the right thing to do. The
standard library generally demonstrates this kind of scoping and layering well.
All of that being said, putting your entire project in a single package would
likely make that package too large. When something is conceptually distinct,
@ -776,7 +780,7 @@ var (
ErrMarsupial = errors.New("marsupials are not supported")
)
func pet(animal Animal) error {
func process(animal Animal) error {
switch {
case seen[animal]:
return ErrDuplicate
@ -849,6 +853,8 @@ to know if using status codes is the right choice.
[`os.PathError`]: https://pkg.go.dev/os#PathError
[`errors.Is`]: https://pkg.go.dev/errors#Is
[`errors.As`]: https://pkg.go.dev/errors#As
[`package cmp`]: https://pkg.go.dev/github.com/google/go-cmp/cmp
[status]: https://pkg.go.dev/google.golang.org/grpc/status
[canonical codes]: https://pkg.go.dev/google.golang.org/grpc/codes
@ -978,6 +984,10 @@ func (*FortuneTeller) SuggestFortune(context.Context, *pb.SuggestionRequest) (*p
}
```
See also:
* [Error Documentation Conventions](#documentation-conventions-errors)
<a id="error-percent-w"></a>
### Placement of %w in errors
@ -1255,7 +1265,7 @@ information to the reader:
// string.
//
// format is the format, and data is the interpolation data.
func Sprintf(format string, data ...interface{}) string
func Sprintf(format string, data ...any) string
```
However, this snippet demonstrates a code scenario similar to the previous where
@ -1272,7 +1282,7 @@ reader:
// the format specification, the function will inline warnings about formatting
// errors into the output string as described by the Format errors section
// above.
func Sprintf(format string, data ...interface{}) string
func Sprintf(format string, data ...any) string
```
Consider your likely audience in choosing what to document and at what depth.
@ -1317,9 +1327,9 @@ func (Worker) Run(ctx context.Context) error
```
Where context behavior is different or non-obvious, it should be expressly
documented:
documented if any of the following are true.
* If the function returns an error other than `ctx.Err()` when the context is
* The function returns an error other than `ctx.Err()` when the context is
cancelled:
```go
@ -1330,8 +1340,7 @@ documented:
func (Worker) Run(ctx context.Context) error
```
* If the function has other mechanisms that may interrupt it or affect
lifetime:
* The function has other mechanisms that may interrupt it or affect lifetime:
```go
// Good:
@ -1347,7 +1356,7 @@ documented:
func (Worker) Stop()
```
* If the function has special expectations about context lifetime, lineage, or
* The function has special expectations about context lifetime, lineage, or
attached values:
```go
@ -1394,9 +1403,9 @@ Similarly, the extra remark about concurrency can safely be removed here:
func (*Buffer) Grow(n int)
```
Documentation is strongly encouraged if:
Documentation is strongly encouraged if any of the following are true.
* it is unclear whether the operation is read-only or a mutating
* It is unclear whether the operation is read-only or mutating:
```go
// Good:
@ -1411,7 +1420,7 @@ Documentation is strongly encouraged if:
Why? A cache hit when looking up the key mutate a LRU cache internally. How
this is implemented may not be obvious to all readers.
* synchronization is provided by API
* Synchronization is provided by the API:
```go
// Good:
@ -1427,7 +1436,7 @@ Documentation is strongly encouraged if:
**Note:** If the API is a type and the API provides synchronization in
entirety, conventionally only the type definition documents the semantics.
* the API consumes user-implemented types of interfaces, and the interface's
* The API consumes user-implemented types of interfaces, and the interface's
consumer has particular concurrency requirements:
```go
@ -1489,6 +1498,84 @@ If it is potentially unclear how to clean up the resources, explain how:
func (c *Client) Get(url string) (resp *Response, err error)
```
See also:
* [GoTip #110: Dont Mix Exit With Defer]
[GoTip #110: Dont Mix Exit With Defer]: https://google.github.io/styleguide/go/index.html#gotip
<a id="documentation-conventions-errors"></a>
#### Errors
Document significant error sentinel values or error types that your functions
return to callers so that callers can anticipate what types of conditions they
can handle in their code.
```go
// Good:
package os
// Read reads up to len(b) bytes from the File and stores them in b. It returns
// the number of bytes read and any error encountered.
//
// At end of file, Read returns 0, io.EOF.
func (*File) Read(b []byte) (n int, err error) {
```
When a function returns a specific error type, correctly note whether the error
is a pointer receiver or not:
```go
// Good:
package os
type PathError struct {
Op string
Path string
Err error
}
// Chdir changes the current working directory to the named directory.
//
// If there is an error, it will be of type *PathError.
func Chdir(dir string) error {
```
Documenting whether the values returned are pointer receivers enables callers to
correctly compare the errors using [`errors.Is`], [`errors.As`], and
[`package cmp`]. This is because a non-pointer value is not equivalent to a
pointer value.
**Note:** In the `Chdir` example, the return type is written as `error` rather
than `*PathError` due to
[how nil interface values work](https://go.dev/doc/faq#nil_error).
Document overall error conventions in the
[package's documentation](decisions#package-comments) when the behavior is
applicable to most errors found in the package:
```go
// Good:
// Package os provides a platform-independent interface to operating system
// functionality.
//
// Often, more information is available within the error. For example, if a
// call that takes a file name fails, such as Open or Stat, the error will
// include the failing file name when printed and will be of type *PathError,
// which may be unpacked for more information.
package os
```
Thoughtful application of these approaches can add
[extra information to errors](#error-extra-info) without much effort and help
callers avoid adding redundant annotations.
See also:
* [Go Tip #106: Error Naming Conventions](https://google.github.io/styleguide/go/index.html#gotip)
* [Go Tip #89: When to Use Canonical Status Codes as Errors](https://google.github.io/styleguide/go/index.html#gotip)
<a id="documentation-preview"></a>
### Preview
@ -1944,7 +2031,7 @@ func foo(ctx context.Context) {
}
```
**Note**: [Contexts are never included in option structs](decisions#contexts).
**Note:** [Contexts are never included in option structs](decisions#contexts).
This option is often preferred when some of the following apply:
@ -2411,7 +2498,7 @@ func ExerciseGame(t *testing.T, cfg *Config, p chess.Player) error {
if cfg.Simulation == Modem {
conn, err := modempool.Allocate()
if err != nil {
t.Fatalf("no modem for the opponent could be provisioned: %v", err)
t.Fatalf("No modem for the opponent could be provisioned: %v", err)
}
t.Cleanup(func() { modempool.Return(conn) })
}
@ -2437,7 +2524,7 @@ func TestAcceptance(t *testing.T) {
player := deepblue.New()
err := chesstest.ExerciseGame(t, chesstest.SimpleGame, player)
if err != nil {
t.Errorf("deepblue player failed acceptance test: %v", err)
t.Errorf("Deep Blue player failed acceptance test: %v", err)
}
}
```
@ -2578,14 +2665,14 @@ func paint(color string) error {
func badSetup(t *testing.T) {
// This should call t.Helper, but doesn't.
if err := paint("taupe"); err != nil {
t.Fatalf("could not paint the house under test: %v", err) // line 15
t.Fatalf("Could not paint the house under test: %v", err) // line 15
}
}
func mustGoodSetup(t *testing.T) {
t.Helper()
if err := paint("lilac"); err != nil {
t.Fatalf("could not paint the house under test: %v", err)
t.Fatalf("Could not paint the house under test: %v", err)
}
}
@ -2605,10 +2692,10 @@ differs:
```text
=== RUN TestBad
paint_test.go:15: could not paint the house under test: no "taupe" paint today
paint_test.go:15: Could not paint the house under test: no "taupe" paint today
--- FAIL: TestBad (0.00s)
=== RUN TestGood
paint_test.go:32: could not paint the house under test: no "lilac" paint today
paint_test.go:32: Could not paint the house under test: no "lilac" paint today
--- FAIL: TestGood (0.00s)
FAIL
```
@ -2616,7 +2703,7 @@ FAIL
The error with `paint_test.go:15` refers to the line of the setup function that
failed in `badSetup`:
`t.Fatalf("could not paint the house under test: %v", err)`
`t.Fatalf("Could not paint the house under test: %v", err)`
Whereas `paint_test.go:32` refers to the line of the test that failed in
`TestGood`:
@ -2695,33 +2782,41 @@ and those should not depend on the system under test. Therefore, if a test
helper [registers a fatal test failure](#test-helper-error-handling), it can and
should do so from the test's goroutine.
<a id="t-field-names"></a>
### Use field names in struct literals
<a id="t-field-labels"></a>
### Use field labels for struct literals
In table-driven tests, prefer to specify the key for each test case specified.
This is helpful when the test cases cover a large amount of vertical space (e.g.
more than 20-30 lines), when there are adjacent fields with the same type, and
also when you wish to omit fields which have the zero value. For example:
In table-driven tests, prefer to specify field names when initializing test case
struct literals. This is helpful when the test cases cover a large amount of
vertical space (e.g. more than 20-30 lines), when there are adjacent fields with
the same type, and also when you wish to omit fields which have the zero value.
For example:
```go
// Good:
func TestStrJoin(t *testing.T) {
tests := []struct {
foo *pb.Foo
bar *pb.Bar
slice []string
separator string
skipEmpty bool
want string
}{
{
foo: pb.Foo_builder{
Name: "foo",
// ...
}.Build(),
bar: pb.Bar_builder{
Name: "bar",
// ...
}.Build(),
want: "result",
slice: []string{"a", "b", ""},
separator: ",",
want: "a,b,",
},
{
slice: []string{"a", "b", ""},
separator: ",",
skipEmpty: true,
want: "a,b",
},
// ...
}
// ...
}
```
@ -2742,7 +2837,7 @@ func mustLoadDataset(t *testing.T) []byte {
data, err := os.ReadFile("path/to/your/project/testdata/dataset")
if err != nil {
t.Fatalf("could not load dataset: %v", err)
t.Fatalf("Could not load dataset: %v", err)
}
return data
}
@ -2756,7 +2851,7 @@ func TestParseData(t *testing.T) {
data := mustLoadDataset(t)
parsed, err := ParseData(data)
if err != nil {
t.Fatalf("unexpected error parsing data: %v", err)
t.Fatalf("Unexpected error parsing data: %v", err)
}
want := &DataTable{ /* ... */ }
if got := parsed; !cmp.Equal(got, want) {
@ -2768,7 +2863,7 @@ func TestListContents(t *testing.T) {
data := mustLoadDataset(t)
contents, err := ListContents(data)
if err != nil {
t.Fatalf("unexpected error listing contents: %v", err)
t.Fatalf("Unexpected error listing contents: %v", err)
}
want := []string{ /* ... */ }
if got := contents; !cmp.Equal(got, want) {
@ -2916,7 +3011,7 @@ func mustLoadDataset(t *testing.T) []byte {
dataset.err = err
})
if err := dataset.err; err != nil {
t.Fatalf("could not load dataset: %v", err)
t.Fatalf("Could not load dataset: %v", err)
}
return dataset.data
}
@ -2972,8 +3067,8 @@ guidance outlines when each method is preferred.
### Prefer "+" for simple cases
Prefer using "+" when concatenating few strings. This method is the
syntactically the simplest and requires no import.
Prefer using "+" when concatenating few strings. This method is syntactically
the simplest and requires no import.
```go
// Good:
@ -3024,7 +3119,7 @@ for i, d := range digitsOfPi {
str := b.String()
```
**NOTE**: For more discussion, see
**Note:** For more discussion, see
[GoTip #29: Building Strings Efficiently](https://google.github.io/styleguide/go/index.html#gotip).
<a id="string-constants"></a>

View File

@ -897,9 +897,14 @@ import (
)
```
It is acceptable to split the project packages into multiple groups, for example
if you want a separate group for renamed, imported-only-for-side-effects or
another special group of imports.
It is acceptable to split the project packages into multiple groups if you want
a separate group, as long as the groups have some meaning. Common reasons to do
this:
* renamed imports
* packages imported for their side-effects
Example:
```go
// Good:
@ -1273,14 +1278,19 @@ maintainable.
#### Field names
Struct literals should usually specify **field names** for types defined outside
the current package.
Struct literals must specify **field names** for types defined outside the
current package.
* Include field names for types from other packages.
```go
// Good:
good := otherpkg.Type{A: 42}
// https://pkg.go.dev/encoding/csv#Reader
r := csv.Reader{
Comma: ',',
Comment: '#',
FieldsPerRecord: 4,
}
```
The position of fields in a struct and the full set of fields (both of which
@ -1290,19 +1300,9 @@ the current package.
```go
// Bad:
// https://pkg.go.dev/encoding/csv#Reader
r := csv.Reader{',', '#', 4, false, false, false, false}
```
Field names may be omitted within small, simple structs whose composition
and order are documented as being stable.
```go
// Good:
okay := image.Point{42, 54}
also := image.Point{X: 42, Y: 54}
```
* For package-local types, field names are optional.
```go
@ -1721,33 +1721,6 @@ func (r *SomeType) SomeLongFunctionName(foo1, foo2, foo3 string,
See [best practices](best-practices#funcargs) for a few options for shortening
the call sites of functions that would otherwise have many arguments.
```go
// Good:
good := foo.Call(long, CallOptions{
Names: list,
Of: of,
The: parameters,
Func: all,
Args: on,
Now: separate,
Visible: lines,
})
```
```go
// Bad:
bad := foo.Call(
long,
list,
of,
parameters,
all,
on,
separate,
lines,
)
```
Lines can often be shortened by factoring out local variables.
```go
@ -1770,9 +1743,9 @@ bad := foo.Call(long, list, of, parameters,
with, arbitrary, line, breaks)
```
Do not add comments to specific function parameters. Instead, use an
[option struct](best-practices#option-structure) or add more detail to the
function documentation.
Avoid adding inline comments to specific function arguments where possible.
Instead, use an [option struct](best-practices#option-structure) or add more
detail to the function documentation.
```go
// Good:
@ -1787,21 +1760,6 @@ bad := server.New(
)
```
If call-sites are uncomfortably long, consider refactoring:
```go
// Good:
// Sometimes variadic arguments can be factored out
replacements := []string{
"from", "to", // related values can be formatted adjacent to one another
"source", "dest",
"original", "new",
}
// Use the replacement struct as inputs to NewReplacer.
replacer := strings.NewReplacer(replacements...)
```
If the API cannot be changed or if the local call is unusual (whether or not the
call is too long), it is always permissible to add line breaks if it aids in
understanding the call.
@ -2110,7 +2068,7 @@ exclusively at
func MustParse(version string) *Version {
v, err := Parse(version)
if err != nil {
log.Fatalf("MustParse(%q) = _, %v", version, err)
panic(fmt.Sprintf("MustParse(%q) = _, %v", version, err))
}
return v
}
@ -2120,8 +2078,6 @@ func MustParse(version string) *Version {
var DefaultVersion = MustParse("1.2.3")
```
**Note:** `log.Fatalf` is not the standard library log. See [#logging].
The same convention may be used in test helpers that only stop the current test
(using `t.Fatal`). Such helpers are often convenient in creating test values,
for example in struct fields of [table driven tests](#table-driven-tests), as
@ -2133,7 +2089,7 @@ func mustMarshalAny(t *testing.T, m proto.Message) *anypb.Any {
t.Helper()
any, err := anypb.New(m)
if err != nil {
t.Fatalf("MustMarshalAny(t, m) = %v; want %v", err, nil)
t.Fatalf("mustMarshalAny(t, m) = %v; want %v", err, nil)
}
return any
}
@ -2189,8 +2145,8 @@ func Version(o *servicepb.Object) (*version.Version, error) {
When you spawn goroutines, make it clear when or whether they exit.
Goroutines can leak by blocking on channel sends or receives. The garbage
collector will not terminate a goroutine even if the channels it is blocked on
are unreachable.
collector will not terminate a goroutine blocked on a channel even if no other
goroutine has a reference to the channel.
Even when goroutines do not leak, leaving them in-flight when they are no longer
needed can cause other subtle and hard-to-diagnose problems. Sending on a
@ -2764,11 +2720,11 @@ See also:
### Logging
Go programs in the Google codebase use a variant of the
[standard `log` package]. It has a similar but more powerful interface and
interoperates well with internal Google systems. An open source version of this
library is available as [package `glog`], and open source Google projects may
use that, but this guide refers to it as `log` throughout.
Go programs in the Google codebase use a variant of the standard [`log`]
package. It has a similar but more powerful interface and interoperates well
with internal Google systems. An open source version of this library is
available as [package `glog`], and open source Google projects may use that, but
this guide refers to it as `log` throughout.
**Note:** For abnormal program exits, this library uses `log.Fatal` to abort
with a stacktrace, and `log.Exit` to stop without one. There is no `log.Panic`
@ -2785,7 +2741,8 @@ See also:
* When and how to use the log package to
[stop the program](best-practices#checks-and-panics)
[standard `log` package]: https://pkg.go.dev/log
[`log`]: https://pkg.go.dev/log
[`log/slog`]: https://pkg.go.dev/log/slog
[package `glog`]: https://pkg.go.dev/github.com/golang/glog
[`log.Exit`]: https://pkg.go.dev/github.com/golang/glog#Exit
[`log.Fatal`]: https://pkg.go.dev/github.com/golang/glog#Fatal
@ -2978,15 +2935,15 @@ right:
// Bad:
package assert
func IsNotNil(t *testing.T, name string, val interface{}) {
func IsNotNil(t *testing.T, name string, val any) {
if val == nil {
t.Fatalf("data %s = nil, want not nil", name)
t.Fatalf("Data %s = nil, want not nil", name)
}
}
func StringEq(t *testing.T, name, got, want string) {
if got != want {
t.Fatalf("data %s = %q, want %q", name, got, want)
t.Fatalf("Data %s = %q, want %q", name, got, want)
}
}
```
@ -3013,7 +2970,7 @@ want := BlogPost{
}
if !cmp.Equal(got, want) {
t.Errorf("blog post = %v, want = %v", got, want)
t.Errorf("Blog post = %v, want = %v", got, want)
}
```
@ -3029,7 +2986,7 @@ func TestBlogPost_VeritableRant(t *testing.T) {
post := BlogPost{Body: "I am Gunnery Sergeant Hartman, your senior drill instructor."}
if got, want := postLength(post), 60; got != want {
t.Errorf("length of post = %v, want %v", got, want)
t.Errorf("Length of post = %v, want %v", got, want)
}
}
```
@ -3361,7 +3318,8 @@ than relying on parsing the error message.
Within unit tests, it is common to only care whether an error occurred or not.
If so, then it is sufficient to only test whether the error was non-nil when you
expected an error. If you would like to test that the error semantically matches
some other error, then consider using `cmp` with [`cmpopts.EquateErrors`].
some other error, then consider using [`errors.Is`] or `cmp` with
[`cmpopts.EquateErrors`].
> **Note:** If a test uses [`cmpopts.EquateErrors`] but all of its `wantErr`
> values are either `nil` or `cmpopts.AnyError`, then using `cmp` is
@ -3370,9 +3328,10 @@ some other error, then consider using `cmp` with [`cmpopts.EquateErrors`].
>
> ```go
> // Good:
> gotErr := f(test.input) != nil
> err := f(test.input)
> gotErr := err != nil
> if gotErr != test.wantErr {
> t.Errorf("f(%q) returned err = %v, want error presence = %v", test.input, gotErr, test.wantErr)
> t.Errorf("f(%q) = %v, want error presence = %v", test.input, err, test.wantErr)
> }
> ```
@ -3381,6 +3340,7 @@ See also
[tott-350]: https://testing.googleblog.com/2015/01/testing-on-toilet-change-detector-tests.html
[`cmpopts.EquateErrors`]: https://pkg.go.dev/github.com/google/go-cmp/cmp/cmpopts#EquateErrors
[`errors.Is`]: https://pkg.go.dev/errors#Is
<a id="test-structure"></a>
@ -3471,6 +3431,9 @@ t.Run("check that there is no mention of scratched records or hovercrafts", ...)
t.Run("AM/PM confusion", ...)
```
See also
[Go Tip #117: Subtest Names](https://google.github.io/styleguide/go/index.html#gotip).
[Go test runner]: https://golang.org/cmd/go/#hdr-Testing_flags
[identify the inputs]: #identify-the-input
[special meaning for test filters]: https://blog.golang.org/subtests#:~:text=Perhaps%20a%20bit,match%20any%20tests
@ -3491,17 +3454,16 @@ similar testing logic.
[tests of `fmt.Sprintf`]: https://cs.opensource.google/go/go/+/master:src/fmt/fmt_test.go
[tests for `net.Dial`]: https://cs.opensource.google/go/go/+/master:src/net/dial_test.go;l=318;drc=5b606a9d2b7649532fe25794fa6b99bd24e7697c
Here is the minimal structure of a table-driven test, copied from the standard
`strings` library. If needed, you may use different names, move the test slice
into the test function, or add extra facilities such as subtests or setup and
cleanup functions. Always keep [useful test failures](#useful-test-failures) in
mind.
Here is the minimal structure of a table-driven test. If needed, you may use
different names or add extra facilities such as subtests or setup and cleanup
functions. Always keep [useful test failures](#useful-test-failures) in mind.
```go
// Good:
var compareTests = []struct {
func TestCompare(t *testing.T) {
compareTests := []struct {
a, b string
i int
want int
}{
{"", "", 0},
{"a", "", 1},
@ -3519,11 +3481,10 @@ var compareTests = []struct {
{"abcdefghi", "abcdefghj", -1},
}
func TestCompare(t *testing.T) {
for _, tt := range compareTests {
cmp := Compare(tt.a, tt.b)
if cmp != tt.i {
t.Errorf(`Compare(%q, %q) = %v`, tt.a, tt.b, cmp)
for _, test := range compareTests {
got := Compare(test.a, test.b)
if got != test.want {
t.Errorf("Compare(%q, %q) = %v, want %v", test.a, test.b, got, test.want)
}
}
}
@ -3639,7 +3600,7 @@ func TestDecode(t *testing.T) {
case prod:
codex = setupCodex(t)
default:
t.Fatalf("unknown codex type: %v", codex)
t.Fatalf("Unknown codex type: %v", codex)
}
output, err := Decode(test.input, codex)
if got, want := output, test.output; got != want {
@ -3673,7 +3634,7 @@ tests := []struct {
}
for i, d := range tests {
if strings.ToUpper(d.input) != d.want {
t.Errorf("failed on case #%d", i)
t.Errorf("Failed on case #%d", i)
}
}
```

View File

@ -419,8 +419,8 @@ initial capitalization.
### Line length
There is no fixed line length for Go source code. If a line feels too long, it
should be refactored instead of broken. If it is already as short as it is
There is no fixed line length for Go source code. If a line feels too long,
prefer refactoring instead of splitting it. If it is already as short as it is
practical for it to be, the line should be allowed to remain long.
Do not split a line: