Skip to content

Commit cc72fb0

Browse files
authored
go: Export the latest version of the internal guide. (#947)
The updates revise the import ordering and grouping guidance, adds information to errors and wrapping and more clearly suggests to think versus reflexively wrapping, makes clear that `%w` verb placement does not encourage reflexively wrapping, more clearly differentiates the `must` prefix, revises package naming, and applies other small instances of copy editing.
1 parent 1387bff commit cc72fb0

File tree

2 files changed

+178
-188
lines changed

2 files changed

+178
-188
lines changed

go/best-practices.md

Lines changed: 122 additions & 126 deletions
Original file line numberDiff line numberDiff line change
@@ -728,31 +728,7 @@ changed.
728728

729729
### Import ordering
730730

731-
Imports are typically grouped into the following two (or more) blocks, in order:
732-
733-
1. Standard library imports (e.g., `"fmt"`)
734-
1. imports (e.g., "/path/to/somelib")
735-
1. (optional) Protobuf imports (e.g., `fpb "path/to/foo_go_proto"`)
736-
1. (optional) Side-effect imports (e.g., `_ "path/to/package"`)
737-
738-
If a file does not have a group for one of the optional categories above, the
739-
relevant imports are included in the project import group.
740-
741-
Any import grouping that is clear and easy to understand is generally fine. For
742-
example, a team may choose to group gRPC imports separately from protobuf
743-
imports.
744-
745-
> **Note:** For code maintaining only the two mandatory groups (one group for
746-
> the standard library and one for all other imports), the `goimports` tool
747-
> produces output consistent with this guidance.
748-
>
749-
> However, `goimports` has no knowledge of groups beyond the mandatory ones; the
750-
> optional groups are prone to invalidation by the tool. When optional groups
751-
> are used, attention on the part of both authors and reviewers is required to
752-
> ensure that groupings remain compliant.
753-
>
754-
> Either approach is fine, but do not leave the imports section in an
755-
> inconsistent, partially grouped state.
731+
See the [Go Style Decisions: Import grouping](decisions.md#import-grouping).
756732

757733
<a id="error-handling"></a>
758734

@@ -905,140 +881,160 @@ to know if using status codes is the right choice.
905881

906882
### Adding information to errors
907883

908-
Any function returning an error should strive to make the error value useful.
909-
Often, the function is in the middle of a callchain and is merely propagating an
910-
error from some other function that it called (maybe even from another package).
911-
Here there is an opportunity to annotate the error with extra information, but
912-
the programmer should ensure there's sufficient information in the error without
913-
adding duplicate or irrelevant detail. If you're unsure, try triggering the
914-
error condition during development: that's a good way to assess what the
915-
observers of the error (either humans or code) will end up with.
916-
917-
Convention and good documentation help. For example, the standard package `os`
918-
advertises that its errors contain path information when it is available. This
919-
is a useful style, because callers getting back an error don't need to annotate
920-
it with information that they had already provided the failing function.
884+
When adding information to errors, avoid redundant information that the
885+
underlying error already provides. The `os` package, for instance, already
886+
includes path information in its errors.
921887

922888
```go
923889
// Good:
924890
if err := os.Open("settings.txt"); err != nil {
925-
return err
891+
return fmt.Errorf("launch codes unavailable: %v", err)
926892
}
927893

928894
// Output:
929895
//
930-
// open settings.txt: no such file or directory
896+
// launch codes unavailable: open settings.txt: no such file or directory
931897
```
932898

933-
If there is something interesting to say about the *meaning* of the error, of
934-
course it can be added. Just consider which level of the callchain is best
935-
positioned to understand this meaning.
899+
Here, "launch codes unavailable" adds specific meaning to the `os.Open` error
900+
that's relevant to the current function's context, without duplicating the
901+
underlying file path information.
936902

937903
```go
938-
// Good:
904+
// Bad:
939905
if err := os.Open("settings.txt"); err != nil {
940-
// We convey the significance of this error to us. Note that the current
941-
// function might perform more than one file operation that can fail, so
942-
// these annotations can also serve to disambiguate to the caller what went
943-
// wrong.
944-
return fmt.Errorf("launch codes unavailable: %v", err)
906+
return fmt.Errorf("could not open settings.txt: %v", err)
945907
}
946908

947909
// Output:
948910
//
949-
// launch codes unavailable: open settings.txt: no such file or directory
911+
// could not open settings.txt: open settings.txt: no such file or directory
950912
```
951913

952-
Contrast with the redundant information here:
914+
Don't add an annotation if its sole purpose is to indicate a failure without
915+
adding new information. The presence of an error sufficiently conveys the
916+
failure to the caller.
953917

954918
```go
955919
// Bad:
956-
if err := os.Open("settings.txt"); err != nil {
957-
return fmt.Errorf("could not open settings.txt: %w", err)
958-
}
959-
960-
// Output:
961-
//
962-
// could not open settings.txt: open settings.txt: no such file or directory
920+
return fmt.Errorf("failed: %v", err) // just return err instead
963921
```
964922

965-
When adding information to a propagated error, you can either wrap the error or
966-
present a fresh error. Wrapping the error with the `%w` verb in `fmt.Errorf`
967-
allows callers to access data from the original error. This can be very useful
968-
at times, but in other cases these details are misleading or uninteresting to
969-
the caller. See the
970-
[blog post on error wrapping](https://blog.golang.org/go1.13-errors) for more
971-
information. Wrapping errors also expands the API surface of your package in a
972-
non-obvious way, and this can cause breakages if you change the implementation
973-
details of your package.
923+
The
924+
[choice between `%v` and `%w` when wrapping errors](https://go.dev/blog/go1.13-errors#whether-to-wrap)
925+
with `fmt.Errorf` is a nuanced decision that significantly impacts how errors
926+
are propagated handled, inspected, and documented within your application. The
927+
core principle is to make error values useful to their observers, whether those
928+
observers are humans or code.
974929

975-
It is best to avoid using `%w` unless you also document (and have tests that
976-
validate) the underlying errors that you expose. If you do not expect your
977-
caller to call `errors.Unwrap`, `errors.Is` and so on, don't bother with `%w`.
930+
1. **`%v` for simple annotation or new error**
978931

979-
The same concept applies to [structured errors](#error-structure) like
980-
[`*status.Status`][status] (see [canonical codes]). For example, if your server
981-
sends malformed requests to a backend and receives an `InvalidArgument` code,
982-
this code should *not* be propagated to the client, assuming that the client has
983-
done nothing wrong. Instead, return an `Internal` canonical code to the client.
932+
The `%v` verb is your general-purpose tool for string formatting of any Go
933+
value, including errors. When used with `fmt.Errorf`, it embeds the string
934+
representation of an error (what its `Error()` method returns) into a new
935+
error value, dropping any structured information from the original error.
936+
Examples to use `%v`:
984937

985-
However, annotating errors helps automated logging systems preserve the status
986-
payload of an error. For example, annotating the error is appropriate in an
987-
internal function:
938+
* Adding interesting, non-redundant context: as in the example above.
988939

989-
```go
990-
// Good:
991-
func (s *Server) internalFunction(ctx context.Context) error {
992-
// ...
993-
if err != nil {
994-
return fmt.Errorf("couldn't find remote file: %w", err)
995-
}
996-
}
997-
```
940+
* Logging or displaying errors: When the primary goal is to present a
941+
human-readable error message in logs or to a user, and you don't intend
942+
for the caller to programmatically `errors.Is` or `errors.As` the error
943+
(Note: `errors.Unwrap` is generally not recommended here as it doesn't
944+
handle multi-errors).
998945

999-
Code directly at system boundaries (typically RPC, IPC, storage, and similar)
1000-
should report errors using the canonical error space. It is the responsibility
1001-
of code here to handle domain-specific errors and represent them canonically.
1002-
For example:
946+
* Creating fresh, independent errors: Sometimes it is necessary to
947+
transform an error into a new error message, thereby hiding the
948+
specifics of the original error. This practice is particularly
949+
beneficial at system boundaries, including but not limited to RPC, IPC,
950+
and storage, where we translate domain-specific errors into a canonical
951+
error space.
1003952

1004-
```go
1005-
// Bad:
1006-
func (*FortuneTeller) SuggestFortune(context.Context, *pb.SuggestionRequest) (*pb.SuggestionResponse, error) {
1007-
// ...
1008-
if err != nil {
1009-
return nil, fmt.Errorf("couldn't find remote file: %w", err)
1010-
}
1011-
}
1012-
```
953+
```go
954+
// Good:
955+
func (*FortuneTeller) SuggestFortune(context.Context, *pb.SuggestionRequest) (*pb.SuggestionResponse, error) {
956+
// ...
957+
if err != nil {
958+
return nil, fmt.Errorf("couldn't find fortune database: %v", err)
959+
}
960+
}
961+
```
1013962

1014-
```go
1015-
// Good:
1016-
import (
1017-
"google.golang.org/grpc/codes"
1018-
"google.golang.org/grpc/status"
1019-
)
1020-
func (*FortuneTeller) SuggestFortune(context.Context, *pb.SuggestionRequest) (*pb.SuggestionResponse, error) {
1021-
// ...
1022-
if err != nil {
1023-
// Or use fmt.Errorf with the %w verb if deliberately wrapping an
1024-
// error which the caller is meant to unwrap.
1025-
return nil, status.Errorf(codes.Internal, "couldn't find fortune database", status.ErrInternal)
1026-
}
1027-
}
1028-
```
963+
We could also explicitly annotate RPC code `Internal` to the example
964+
above.
965+
966+
```go
967+
// Good:
968+
import (
969+
"google.golang.org/grpc/codes"
970+
"google.golang.org/grpc/status"
971+
)
972+
973+
func (*FortuneTeller) SuggestFortune(context.Context, *pb.SuggestionRequest) (*pb.SuggestionResponse, error) {
974+
// ...
975+
if err != nil {
976+
// Or use fmt.Errorf with the %w verb if deliberately wrapping an
977+
// error which the caller is meant to unwrap.
978+
return nil, status.Errorf(codes.Internal, "couldn't find fortune database", status.ErrInternal)
979+
}
980+
}
981+
```
982+
983+
1. **`%w` (wrap) for programmatic inspection and error chaining**
984+
985+
The `%w` verb is specifically designed for error wrapping. It creates a new
986+
error that provides an `Unwrap()` method, allowing callers to
987+
programmatically inspect the error chain using `errors.Is` and `errors.As`.
988+
Examples to use `%w`:
989+
990+
* Adding context while preserving the original error for programmatic
991+
inspection: This is the primary use case within helpers of your
992+
application. You want to enrich an error with additional context (e.g.,
993+
what operation was being performed when it failed) but still allow the
994+
caller to check if the underlying error is a specific sentinel error or
995+
type.
996+
997+
```go
998+
// Good:
999+
func (s *Server) internalFunction(ctx context.Context) error {
1000+
// ...
1001+
if err != nil {
1002+
return fmt.Errorf("couldn't find remote file: %w", err)
1003+
}
1004+
}
1005+
```
1006+
1007+
This allows a higher-level function to do `errors.Is(err,
1008+
fs.ErrNotExist)` if the underlying error was `fs.ErrNotExist`, even
1009+
though it's wrapped.
1010+
1011+
At points where your system interacts with external systems like RPC,
1012+
IPC, or storage, it's often better to translate domain-specific errors
1013+
into a standardized error space (e.g., gRPC status codes) rather than
1014+
simply wrapping the raw underlying error with `%w`. The client typically
1015+
doesn't care about the exact internal file system error; they care about
1016+
the canonical result (e.g., `Internal`, `NotFound`, `PermissionDenied`).
1017+
1018+
* When you explicitly document and test the underlying errors you expose:
1019+
If your package's API guarantees that certain underlying errors can be
1020+
unwrapped and checked by callers (e.g., "this function might return
1021+
`ErrInvalidConfig` wrapped within a more general error"), then `%w` is
1022+
appropriate. This forms part of your package's contract.
10291023
10301024
See also:
10311025
10321026
* [Error Documentation Conventions](#documentation-conventions-errors)
1027+
* [Blog post on error wrapping](https://blog.golang.org/go1.13-errors)
10331028
10341029
<a id="error-percent-w"></a>
10351030
10361031
### Placement of %w in errors
10371032
1038-
Prefer to place `%w` at the end of an error string.
1033+
Prefer to place `%w` at the end of an error string *if* you are to use
1034+
[error wrapping](https://go.dev/blog/go1.13-errors) with the `%w` formatting
1035+
verb.
10391036
1040-
Errors can be wrapped with
1041-
[the `%w` verb](https://blog.golang.org/go1.13-errors), or by placing them in a
1037+
Errors can be wrapped with the `%w` verb, or by placing them in a
10421038
[structured error](https://google.github.io/styleguide/go/index.html#gotip) that
10431039
implements `Unwrap() error` (ex:
10441040
[`fs.PathError`](https://pkg.go.dev/io/fs#PathError)).
@@ -1881,7 +1877,7 @@ It's acceptable to use value types for local variables of composites (such as
18811877
structs and arrays) even if they contain such uncopyable fields. However, if the
18821878
composite is returned by the function, or if all accesses to it end up needing
18831879
to take an address anyway, prefer declaring the variable as a pointer type at
1884-
the outset. Similarly, protobufs should be declared as pointer types.
1880+
the outset. Similarly, protobuf messages should be declared as pointer types.
18851881
18861882
```go
18871883
// Good:
@@ -2768,7 +2764,7 @@ func badSetup(t *testing.T) {
27682764
}
27692765
}
27702766

2771-
func mustGoodSetup(t *testing.T) {
2767+
func goodSetup(t *testing.T) {
27722768
t.Helper()
27732769
if err := paint("lilac"); err != nil {
27742770
t.Fatalf("Could not paint the house under test: %v", err)
@@ -2781,7 +2777,7 @@ func TestBad(t *testing.T) {
27812777
}
27822778

27832779
func TestGood(t *testing.T) {
2784-
mustGoodSetup(t) // line 32
2780+
goodSetup(t) // line 32
27852781
// ...
27862782
}
27872783
```
@@ -3141,11 +3137,11 @@ func TestRegression682831(t *testing.T) {
31413137
```
31423138
31433139
The reason that common teardown is tricky is there is no uniform place to
3144-
register cleanup routines. If the setup function (in this case `loadDataset`)
3145-
relies on a context, `sync.Once` may be problematic. This is because the second
3146-
of two racing calls to the setup function would need to wait for the first call
3147-
to finish before returning. This period of waiting cannot be easily made to
3148-
respect the context's cancellation.
3140+
register cleanup routines. If the setup function (in this case
3141+
`mustLoadDataset`) relies on a context, `sync.Once` may be problematic. This is
3142+
because the second of two racing calls to the setup function would need to wait
3143+
for the first call to finish before returning. This period of waiting cannot be
3144+
easily made to respect the context's cancellation.
31493145
31503146
<a id="string-concat"></a>
31513147

0 commit comments

Comments
 (0)