mirror of
https://github.com/google/styleguide.git
synced 2024-03-22 13:11:43 +08:00
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:
parent
19f31499d7
commit
c10555a867
|
@ -597,7 +597,11 @@ to have them in the same package.
|
||||||
Code within a package can access unexported identifiers in the package. If you
|
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
|
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
|
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
|
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,
|
likely make that package too large. When something is conceptually distinct,
|
||||||
|
@ -776,7 +780,7 @@ var (
|
||||||
ErrMarsupial = errors.New("marsupials are not supported")
|
ErrMarsupial = errors.New("marsupials are not supported")
|
||||||
)
|
)
|
||||||
|
|
||||||
func pet(animal Animal) error {
|
func process(animal Animal) error {
|
||||||
switch {
|
switch {
|
||||||
case seen[animal]:
|
case seen[animal]:
|
||||||
return ErrDuplicate
|
return ErrDuplicate
|
||||||
|
@ -849,6 +853,8 @@ to know if using status codes is the right choice.
|
||||||
|
|
||||||
[`os.PathError`]: https://pkg.go.dev/os#PathError
|
[`os.PathError`]: https://pkg.go.dev/os#PathError
|
||||||
[`errors.Is`]: https://pkg.go.dev/errors#Is
|
[`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
|
[status]: https://pkg.go.dev/google.golang.org/grpc/status
|
||||||
[canonical codes]: https://pkg.go.dev/google.golang.org/grpc/codes
|
[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>
|
<a id="error-percent-w"></a>
|
||||||
|
|
||||||
### Placement of %w in errors
|
### Placement of %w in errors
|
||||||
|
@ -1255,7 +1265,7 @@ information to the reader:
|
||||||
// string.
|
// string.
|
||||||
//
|
//
|
||||||
// format is the format, and data is the interpolation data.
|
// 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
|
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
|
// the format specification, the function will inline warnings about formatting
|
||||||
// errors into the output string as described by the Format errors section
|
// errors into the output string as described by the Format errors section
|
||||||
// above.
|
// 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.
|
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
|
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:
|
cancelled:
|
||||||
|
|
||||||
```go
|
```go
|
||||||
|
@ -1330,8 +1340,7 @@ documented:
|
||||||
func (Worker) Run(ctx context.Context) error
|
func (Worker) Run(ctx context.Context) error
|
||||||
```
|
```
|
||||||
|
|
||||||
* If the function has other mechanisms that may interrupt it or affect
|
* The function has other mechanisms that may interrupt it or affect lifetime:
|
||||||
lifetime:
|
|
||||||
|
|
||||||
```go
|
```go
|
||||||
// Good:
|
// Good:
|
||||||
|
@ -1347,7 +1356,7 @@ documented:
|
||||||
func (Worker) Stop()
|
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:
|
attached values:
|
||||||
|
|
||||||
```go
|
```go
|
||||||
|
@ -1394,9 +1403,9 @@ Similarly, the extra remark about concurrency can safely be removed here:
|
||||||
func (*Buffer) Grow(n int)
|
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
|
```go
|
||||||
// Good:
|
// 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
|
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.
|
this is implemented may not be obvious to all readers.
|
||||||
|
|
||||||
* synchronization is provided by API
|
* Synchronization is provided by the API:
|
||||||
|
|
||||||
```go
|
```go
|
||||||
// Good:
|
// Good:
|
||||||
|
@ -1427,7 +1436,7 @@ Documentation is strongly encouraged if:
|
||||||
**Note:** If the API is a type and the API provides synchronization in
|
**Note:** If the API is a type and the API provides synchronization in
|
||||||
entirety, conventionally only the type definition documents the semantics.
|
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:
|
consumer has particular concurrency requirements:
|
||||||
|
|
||||||
```go
|
```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)
|
func (c *Client) Get(url string) (resp *Response, err error)
|
||||||
```
|
```
|
||||||
|
|
||||||
|
See also:
|
||||||
|
|
||||||
|
* [GoTip #110: Don’t Mix Exit With Defer]
|
||||||
|
|
||||||
|
[GoTip #110: Don’t 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>
|
<a id="documentation-preview"></a>
|
||||||
|
|
||||||
### Preview
|
### 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:
|
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 {
|
if cfg.Simulation == Modem {
|
||||||
conn, err := modempool.Allocate()
|
conn, err := modempool.Allocate()
|
||||||
if err != nil {
|
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) })
|
t.Cleanup(func() { modempool.Return(conn) })
|
||||||
}
|
}
|
||||||
|
@ -2437,7 +2524,7 @@ func TestAcceptance(t *testing.T) {
|
||||||
player := deepblue.New()
|
player := deepblue.New()
|
||||||
err := chesstest.ExerciseGame(t, chesstest.SimpleGame, player)
|
err := chesstest.ExerciseGame(t, chesstest.SimpleGame, player)
|
||||||
if err != nil {
|
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) {
|
func badSetup(t *testing.T) {
|
||||||
// This should call t.Helper, but doesn't.
|
// This should call t.Helper, but doesn't.
|
||||||
if err := paint("taupe"); err != nil {
|
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) {
|
func mustGoodSetup(t *testing.T) {
|
||||||
t.Helper()
|
t.Helper()
|
||||||
if err := paint("lilac"); err != nil {
|
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
|
```text
|
||||||
=== RUN TestBad
|
=== 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)
|
--- FAIL: TestBad (0.00s)
|
||||||
=== RUN TestGood
|
=== 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: TestGood (0.00s)
|
||||||
FAIL
|
FAIL
|
||||||
```
|
```
|
||||||
|
@ -2616,7 +2703,7 @@ FAIL
|
||||||
The error with `paint_test.go:15` refers to the line of the setup function that
|
The error with `paint_test.go:15` refers to the line of the setup function that
|
||||||
failed in `badSetup`:
|
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
|
Whereas `paint_test.go:32` refers to the line of the test that failed in
|
||||||
`TestGood`:
|
`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
|
helper [registers a fatal test failure](#test-helper-error-handling), it can and
|
||||||
should do so from the test's goroutine.
|
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>
|
<a id="t-field-labels"></a>
|
||||||
|
|
||||||
### Use field labels for struct literals
|
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
|
||||||
In table-driven tests, prefer to specify the key for each test case specified.
|
vertical space (e.g. more than 20-30 lines), when there are adjacent fields with
|
||||||
This is helpful when the test cases cover a large amount of vertical space (e.g.
|
the same type, and also when you wish to omit fields which have the zero value.
|
||||||
more than 20-30 lines), when there are adjacent fields with the same type, and
|
For example:
|
||||||
also when you wish to omit fields which have the zero value. For example:
|
|
||||||
|
|
||||||
```go
|
```go
|
||||||
// Good:
|
// Good:
|
||||||
tests := []struct {
|
func TestStrJoin(t *testing.T) {
|
||||||
foo *pb.Foo
|
tests := []struct {
|
||||||
bar *pb.Bar
|
slice []string
|
||||||
want string
|
separator string
|
||||||
}{
|
skipEmpty bool
|
||||||
{
|
want string
|
||||||
foo: pb.Foo_builder{
|
}{
|
||||||
Name: "foo",
|
{
|
||||||
// ...
|
slice: []string{"a", "b", ""},
|
||||||
}.Build(),
|
separator: ",",
|
||||||
bar: pb.Bar_builder{
|
want: "a,b,",
|
||||||
Name: "bar",
|
},
|
||||||
// ...
|
{
|
||||||
}.Build(),
|
slice: []string{"a", "b", ""},
|
||||||
want: "result",
|
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")
|
data, err := os.ReadFile("path/to/your/project/testdata/dataset")
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("could not load dataset: %v", err)
|
t.Fatalf("Could not load dataset: %v", err)
|
||||||
}
|
}
|
||||||
return data
|
return data
|
||||||
}
|
}
|
||||||
|
@ -2756,7 +2851,7 @@ func TestParseData(t *testing.T) {
|
||||||
data := mustLoadDataset(t)
|
data := mustLoadDataset(t)
|
||||||
parsed, err := ParseData(data)
|
parsed, err := ParseData(data)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("unexpected error parsing data: %v", err)
|
t.Fatalf("Unexpected error parsing data: %v", err)
|
||||||
}
|
}
|
||||||
want := &DataTable{ /* ... */ }
|
want := &DataTable{ /* ... */ }
|
||||||
if got := parsed; !cmp.Equal(got, want) {
|
if got := parsed; !cmp.Equal(got, want) {
|
||||||
|
@ -2768,7 +2863,7 @@ func TestListContents(t *testing.T) {
|
||||||
data := mustLoadDataset(t)
|
data := mustLoadDataset(t)
|
||||||
contents, err := ListContents(data)
|
contents, err := ListContents(data)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("unexpected error listing contents: %v", err)
|
t.Fatalf("Unexpected error listing contents: %v", err)
|
||||||
}
|
}
|
||||||
want := []string{ /* ... */ }
|
want := []string{ /* ... */ }
|
||||||
if got := contents; !cmp.Equal(got, want) {
|
if got := contents; !cmp.Equal(got, want) {
|
||||||
|
@ -2916,7 +3011,7 @@ func mustLoadDataset(t *testing.T) []byte {
|
||||||
dataset.err = err
|
dataset.err = err
|
||||||
})
|
})
|
||||||
if err := dataset.err; err != nil {
|
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
|
return dataset.data
|
||||||
}
|
}
|
||||||
|
@ -2972,8 +3067,8 @@ guidance outlines when each method is preferred.
|
||||||
|
|
||||||
### Prefer "+" for simple cases
|
### Prefer "+" for simple cases
|
||||||
|
|
||||||
Prefer using "+" when concatenating few strings. This method is the
|
Prefer using "+" when concatenating few strings. This method is syntactically
|
||||||
syntactically the simplest and requires no import.
|
the simplest and requires no import.
|
||||||
|
|
||||||
```go
|
```go
|
||||||
// Good:
|
// Good:
|
||||||
|
@ -3024,7 +3119,7 @@ for i, d := range digitsOfPi {
|
||||||
str := b.String()
|
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).
|
[GoTip #29: Building Strings Efficiently](https://google.github.io/styleguide/go/index.html#gotip).
|
||||||
|
|
||||||
<a id="string-constants"></a>
|
<a id="string-constants"></a>
|
||||||
|
|
185
go/decisions.md
185
go/decisions.md
|
@ -897,9 +897,14 @@ import (
|
||||||
)
|
)
|
||||||
```
|
```
|
||||||
|
|
||||||
It is acceptable to split the project packages into multiple groups, for example
|
It is acceptable to split the project packages into multiple groups if you want
|
||||||
if you want a separate group for renamed, imported-only-for-side-effects or
|
a separate group, as long as the groups have some meaning. Common reasons to do
|
||||||
another special group of imports.
|
this:
|
||||||
|
|
||||||
|
* renamed imports
|
||||||
|
* packages imported for their side-effects
|
||||||
|
|
||||||
|
Example:
|
||||||
|
|
||||||
```go
|
```go
|
||||||
// Good:
|
// Good:
|
||||||
|
@ -1273,14 +1278,19 @@ maintainable.
|
||||||
|
|
||||||
#### Field names
|
#### Field names
|
||||||
|
|
||||||
Struct literals should usually specify **field names** for types defined outside
|
Struct literals must specify **field names** for types defined outside the
|
||||||
the current package.
|
current package.
|
||||||
|
|
||||||
* Include field names for types from other packages.
|
* Include field names for types from other packages.
|
||||||
|
|
||||||
```go
|
```go
|
||||||
// Good:
|
// 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
|
The position of fields in a struct and the full set of fields (both of which
|
||||||
|
@ -1290,19 +1300,9 @@ the current package.
|
||||||
|
|
||||||
```go
|
```go
|
||||||
// Bad:
|
// Bad:
|
||||||
// https://pkg.go.dev/encoding/csv#Reader
|
|
||||||
r := csv.Reader{',', '#', 4, false, false, false, false}
|
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.
|
* For package-local types, field names are optional.
|
||||||
|
|
||||||
```go
|
```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
|
See [best practices](best-practices#funcargs) for a few options for shortening
|
||||||
the call sites of functions that would otherwise have many arguments.
|
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.
|
Lines can often be shortened by factoring out local variables.
|
||||||
|
|
||||||
```go
|
```go
|
||||||
|
@ -1770,9 +1743,9 @@ bad := foo.Call(long, list, of, parameters,
|
||||||
with, arbitrary, line, breaks)
|
with, arbitrary, line, breaks)
|
||||||
```
|
```
|
||||||
|
|
||||||
Do not add comments to specific function parameters. Instead, use an
|
Avoid adding inline comments to specific function arguments where possible.
|
||||||
[option struct](best-practices#option-structure) or add more detail to the
|
Instead, use an [option struct](best-practices#option-structure) or add more
|
||||||
function documentation.
|
detail to the function documentation.
|
||||||
|
|
||||||
```go
|
```go
|
||||||
// Good:
|
// 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
|
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
|
call is too long), it is always permissible to add line breaks if it aids in
|
||||||
understanding the call.
|
understanding the call.
|
||||||
|
@ -2110,7 +2068,7 @@ exclusively at
|
||||||
func MustParse(version string) *Version {
|
func MustParse(version string) *Version {
|
||||||
v, err := Parse(version)
|
v, err := Parse(version)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatalf("MustParse(%q) = _, %v", version, err)
|
panic(fmt.Sprintf("MustParse(%q) = _, %v", version, err))
|
||||||
}
|
}
|
||||||
return v
|
return v
|
||||||
}
|
}
|
||||||
|
@ -2120,8 +2078,6 @@ func MustParse(version string) *Version {
|
||||||
var DefaultVersion = MustParse("1.2.3")
|
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
|
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,
|
(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
|
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()
|
t.Helper()
|
||||||
any, err := anypb.New(m)
|
any, err := anypb.New(m)
|
||||||
if err != nil {
|
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
|
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.
|
When you spawn goroutines, make it clear when or whether they exit.
|
||||||
|
|
||||||
Goroutines can leak by blocking on channel sends or receives. The garbage
|
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
|
collector will not terminate a goroutine blocked on a channel even if no other
|
||||||
are unreachable.
|
goroutine has a reference to the channel.
|
||||||
|
|
||||||
Even when goroutines do not leak, leaving them in-flight when they are no longer
|
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
|
needed can cause other subtle and hard-to-diagnose problems. Sending on a
|
||||||
|
@ -2764,11 +2720,11 @@ See also:
|
||||||
|
|
||||||
### Logging
|
### Logging
|
||||||
|
|
||||||
Go programs in the Google codebase use a variant of the
|
Go programs in the Google codebase use a variant of the standard [`log`]
|
||||||
[standard `log` package]. It has a similar but more powerful interface and
|
package. It has a similar but more powerful interface and interoperates well
|
||||||
interoperates well with internal Google systems. An open source version of this
|
with internal Google systems. An open source version of this library is
|
||||||
library is available as [package `glog`], and open source Google projects may
|
available as [package `glog`], and open source Google projects may use that, but
|
||||||
use that, but this guide refers to it as `log` throughout.
|
this guide refers to it as `log` throughout.
|
||||||
|
|
||||||
**Note:** For abnormal program exits, this library uses `log.Fatal` to abort
|
**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`
|
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
|
* When and how to use the log package to
|
||||||
[stop the program](best-practices#checks-and-panics)
|
[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
|
[package `glog`]: https://pkg.go.dev/github.com/golang/glog
|
||||||
[`log.Exit`]: https://pkg.go.dev/github.com/golang/glog#Exit
|
[`log.Exit`]: https://pkg.go.dev/github.com/golang/glog#Exit
|
||||||
[`log.Fatal`]: https://pkg.go.dev/github.com/golang/glog#Fatal
|
[`log.Fatal`]: https://pkg.go.dev/github.com/golang/glog#Fatal
|
||||||
|
@ -2978,15 +2935,15 @@ right:
|
||||||
// Bad:
|
// Bad:
|
||||||
package assert
|
package assert
|
||||||
|
|
||||||
func IsNotNil(t *testing.T, name string, val interface{}) {
|
func IsNotNil(t *testing.T, name string, val any) {
|
||||||
if val == nil {
|
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) {
|
func StringEq(t *testing.T, name, got, want string) {
|
||||||
if got != want {
|
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) {
|
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."}
|
post := BlogPost{Body: "I am Gunnery Sergeant Hartman, your senior drill instructor."}
|
||||||
|
|
||||||
if got, want := postLength(post), 60; got != want {
|
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.
|
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
|
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
|
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`
|
> **Note:** If a test uses [`cmpopts.EquateErrors`] but all of its `wantErr`
|
||||||
> values are either `nil` or `cmpopts.AnyError`, then using `cmp` is
|
> 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
|
> ```go
|
||||||
> // Good:
|
> // Good:
|
||||||
> gotErr := f(test.input) != nil
|
> err := f(test.input)
|
||||||
|
> gotErr := err != nil
|
||||||
> if gotErr != test.wantErr {
|
> 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
|
[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
|
[`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>
|
<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", ...)
|
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
|
[Go test runner]: https://golang.org/cmd/go/#hdr-Testing_flags
|
||||||
[identify the inputs]: #identify-the-input
|
[identify the inputs]: #identify-the-input
|
||||||
[special meaning for test filters]: https://blog.golang.org/subtests#:~:text=Perhaps%20a%20bit,match%20any%20tests
|
[special meaning for test filters]: https://blog.golang.org/subtests#:~:text=Perhaps%20a%20bit,match%20any%20tests
|
||||||
|
@ -3491,39 +3454,37 @@ similar testing logic.
|
||||||
[tests of `fmt.Sprintf`]: https://cs.opensource.google/go/go/+/master:src/fmt/fmt_test.go
|
[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
|
[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
|
Here is the minimal structure of a table-driven test. If needed, you may use
|
||||||
`strings` library. If needed, you may use different names, move the test slice
|
different names or add extra facilities such as subtests or setup and cleanup
|
||||||
into the test function, or add extra facilities such as subtests or setup and
|
functions. Always keep [useful test failures](#useful-test-failures) in mind.
|
||||||
cleanup functions. Always keep [useful test failures](#useful-test-failures) in
|
|
||||||
mind.
|
|
||||||
|
|
||||||
```go
|
```go
|
||||||
// Good:
|
// Good:
|
||||||
var compareTests = []struct {
|
|
||||||
a, b string
|
|
||||||
i int
|
|
||||||
}{
|
|
||||||
{"", "", 0},
|
|
||||||
{"a", "", 1},
|
|
||||||
{"", "a", -1},
|
|
||||||
{"abc", "abc", 0},
|
|
||||||
{"ab", "abc", -1},
|
|
||||||
{"abc", "ab", 1},
|
|
||||||
{"x", "ab", 1},
|
|
||||||
{"ab", "x", -1},
|
|
||||||
{"x", "a", 1},
|
|
||||||
{"b", "x", -1},
|
|
||||||
// test runtime·memeq's chunked implementation
|
|
||||||
{"abcdefgh", "abcdefgh", 0},
|
|
||||||
{"abcdefghi", "abcdefghi", 0},
|
|
||||||
{"abcdefghi", "abcdefghj", -1},
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestCompare(t *testing.T) {
|
func TestCompare(t *testing.T) {
|
||||||
for _, tt := range compareTests {
|
compareTests := []struct {
|
||||||
cmp := Compare(tt.a, tt.b)
|
a, b string
|
||||||
if cmp != tt.i {
|
want int
|
||||||
t.Errorf(`Compare(%q, %q) = %v`, tt.a, tt.b, cmp)
|
}{
|
||||||
|
{"", "", 0},
|
||||||
|
{"a", "", 1},
|
||||||
|
{"", "a", -1},
|
||||||
|
{"abc", "abc", 0},
|
||||||
|
{"ab", "abc", -1},
|
||||||
|
{"abc", "ab", 1},
|
||||||
|
{"x", "ab", 1},
|
||||||
|
{"ab", "x", -1},
|
||||||
|
{"x", "a", 1},
|
||||||
|
{"b", "x", -1},
|
||||||
|
// test runtime·memeq's chunked implementation
|
||||||
|
{"abcdefgh", "abcdefgh", 0},
|
||||||
|
{"abcdefghi", "abcdefghi", 0},
|
||||||
|
{"abcdefghi", "abcdefghj", -1},
|
||||||
|
}
|
||||||
|
|
||||||
|
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:
|
case prod:
|
||||||
codex = setupCodex(t)
|
codex = setupCodex(t)
|
||||||
default:
|
default:
|
||||||
t.Fatalf("unknown codex type: %v", codex)
|
t.Fatalf("Unknown codex type: %v", codex)
|
||||||
}
|
}
|
||||||
output, err := Decode(test.input, codex)
|
output, err := Decode(test.input, codex)
|
||||||
if got, want := output, test.output; got != want {
|
if got, want := output, test.output; got != want {
|
||||||
|
@ -3673,7 +3634,7 @@ tests := []struct {
|
||||||
}
|
}
|
||||||
for i, d := range tests {
|
for i, d := range tests {
|
||||||
if strings.ToUpper(d.input) != d.want {
|
if strings.ToUpper(d.input) != d.want {
|
||||||
t.Errorf("failed on case #%d", i)
|
t.Errorf("Failed on case #%d", i)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
|
@ -419,8 +419,8 @@ initial capitalization.
|
||||||
|
|
||||||
### Line length
|
### Line length
|
||||||
|
|
||||||
There is no fixed line length for Go source code. If a line feels too long, it
|
There is no fixed line length for Go source code. If a line feels too long,
|
||||||
should be refactored instead of broken. If it is already as short as it is
|
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.
|
practical for it to be, the line should be allowed to remain long.
|
||||||
|
|
||||||
Do not split a line:
|
Do not split a line:
|
||||||
|
|
Loading…
Reference in New Issue
Block a user