Uber Go 语言编码规范中文版. The Uber Go Style Guide .
APACHE-2.0 License
Uber Go adopter golang Gopher zapjaeger 2018 Uber Go GitHub Gopher
(style) `` gofmt
Uber Go Go
Prashant Varanasi Simon Newton Go
Uber Go Go
Go releases.
golint``go vet
goimports
golint
go vet
Go https://go.dev/wiki/IDEsAndTextEditorPlugins
()
type F interface {
f()
}
type S1 struct{}
func (s S1) f() {}
type S2 struct{}
func (s *S2) f() {}
// f1.f()
// f2.f() f2
var f1 F = S1{}
var f2 F = &S2{}
interface.gotestmyinterfacemystructmystruct
type myinterface interface{
print()
}
func test(value *myinterface){
//someting to do ...
}
type mystruct struct {
i int
}
//
func (this *mystruct) print(){
fmt.Println(this.i)
this.i=1
}
func main(){
m := &mystruct{0}
test(m)//
test(*m)//
}
3
// Handler http.Handler
type Handler struct {
// ...
}
func (h *Handler) ServeHTTP(
w http.ResponseWriter,
r *http.Request,
) {
...
}
type Handler struct {
// ...
}
//
// Handler http.Handler
var _ http.Handler = (*Handler)(nil)
func (h *Handler) ServeHTTP(
w http.ResponseWriter,
r *http.Request,
) {
// ...
}
*Handler
http.Handler
var _ http.Handler = (*Handler)(nil)
*Handler
nil
type LogHandler struct {
h http.Handler
log *zap.Logger
}
var _ http.Handler = LogHandler{}
func (h LogHandler) ServeHTTP(
w http.ResponseWriter,
r *http.Request,
) {
// ...
}
type S struct {
data string
}
func (s S) Read() string {
return s.data
}
func (s *S) Write(str string) {
s.data = str
}
sVals := map[int]S{1: {"A"}}
// Read
sVals[1].Read()
//
// sVals[1].Write("test")
sPtrs := map[int]*S{1: {"A"}}
// Read Write
sPtrs[1].Read()
sPtrs[1].Write("test")
type F interface {
f()
}
type S1 struct{}
func (s S1) f() {}
type S2 struct{}
func (s *S2) f() {}
s1Val := S1{}
s1Ptr := &S1{}
s2Val := S2{}
s2Ptr := &S2{}
var i F
i = s1Val
i = s1Ptr
i = s2Ptr
// s2Val S2 f
// i = s2Val
Effective Go pointers vs. values
()
i = s2Val
sync.Mutex
sync.RWMutex
mutex
mu := new(sync.Mutex)
mu.Lock()
var mu sync.Mutex
mu.Lock()
mutex mutex
type SMap struct {
sync.Mutex
data map[string]string
}
func NewSMap() *SMap {
return &SMap{
data: make(map[string]string),
}
}
func (m *SMap) Get(k string) string {
m.Lock()
defer m.Unlock()
return m.data[k]
}
type SMap struct {
mu sync.Mutex
data map[string]string
}
func NewSMap() *SMap {
return &SMap{
data: make(map[string]string),
}
}
func (m *SMap) Get(k string) string {
m.mu.Lock()
defer m.mu.Unlock()
return m.data[k]
}
Mutex
Lock
Unlock
SMap
API
mutex SMap
slices maps
map slice
func (d *Driver) SetTrips(trips []Trip) {
d.trips = trips
}
trips := ...
d1.SetTrips(trips)
// d1.trips
trips[0] = ...
func (d *Driver) SetTrips(trips []Trip) {
d.trips = make([]Trip, len(trips))
copy(d.trips, trips)
}
trips := ...
d1.SetTrips(trips)
// trips[0] d1.trips
trips[0] = ...
map slice
type Stats struct {
mu sync.Mutex
counters map[string]int
}
// Snapshot
func (s *Stats) Snapshot() map[string]int {
s.mu.Lock()
defer s.mu.Unlock()
return s.counters
}
// snapshot
// snapshot
// stats.counters
snapshot := stats.Snapshot()
type Stats struct {
mu sync.Mutex
counters map[string]int
}
func (s *Stats) Snapshot() map[string]int {
s.mu.Lock()
defer s.mu.Unlock()
result := make(map[string]int, len(s.counters))
for k, v := range s.counters {
result[k] = v
}
return result
}
// snapshot
snapshot := stats.Snapshot()
defer
p.Lock()
if p.count < 10 {
p.Unlock()
return p.count
}
p.count++
newCount := p.count
p.Unlock()
return newCount
// return unlock
p.Lock()
defer p.Unlock()
if p.count < 10 {
return p.count
}
p.count++
return p.count
//
Defer defer defer
channel size 1 channel size channel ()
//
c := make(chan int, 64)
// 1
c := make(chan int, 1) //
// channel 0
c := make(chan int)
Go iota const 0
type Operation int
const (
Add Operation = iota
Subtract
Multiply
)
// Add=0, Subtract=1, Multiply=2
type Operation int
const (
Add Operation = iota + 1
Subtract
Multiply
)
// Add=1, Subtract=2, Multiply=3
type LogOutput int
const (
LogToStdout LogOutput = iota
LogToFile
LogToRemote
)
// LogToStdout=0, LogToFile=1, LogToRemote=2
1 24
time.Time
time.Time
time.Time
func isActive(now, start, stop int) bool {
return start <= now && now < stop
}
func isActive(now, start, stop time.Time) bool {
return (start.Before(now) || start.Equal(now)) && now.Before(stop)
}
time.Duration
func poll(delay int) {
for {
// ...
time.Sleep(time.Duration(delay) * time.Millisecond)
}
}
poll(10) //
func poll(delay time.Duration) {
for {
// ...
time.Sleep(delay)
}
}
poll(10*time.Second)
24 () Time.AddDate
24 Time.Add
newDay := t.AddDate(0 /* years */, 0 /* months */, 1 /* days */)
maybeNewDay := t.Add(24 * time.Hour)
time.Time
time.Duration
time.Duration
time.Time
:
flag
time.ParseDuration
time.Duration
encoding/json
UnmarshalJSON
method time.Time
RFC 3339
database/sql
DATETIME
TIMESTAMP
time.Time
gopkg.in/yaml.v2
time.Time
RFC 3339 time.ParseDuration
time.Duration
time.Duration
int
float64
encoding/json
time.Duration
// {"interval": 2}
type Config struct {
Interval int `json:"interval"`
}
// {"intervalMillis": 2000}
type Config struct {
IntervalMillis int `json:"intervalMillis"`
}
time.Time
string
RFC 3339 Time.UnmarshalText
time.RFC3339
Time.Format
time.Parse
"time"
8728
No | static | errors.New |
No | dynamic | fmt.Errorf |
Yes | static | top-level var with errors.New
|
Yes | dynamic | custom error type |
errors.New
errors.Is
// package foo
func Open() error {
return errors.New("could not open")
}
// package bar
if err := foo.Open(); err != nil {
// Can't handle the error.
panic("unknown error")
}
// package foo
var ErrCouldNotOpen = errors.New("could not open")
func Open() error {
return ErrCouldNotOpen
}
// package bar
if err := foo.Open(); err != nil {
if errors.Is(err, foo.ErrCouldNotOpen) {
// handle the error
} else {
panic("unknown error")
}
}
fmt.Errorf
error
// package foo
func Open(file string) error {
return fmt.Errorf("file %q not found", file)
}
// package bar
if err := foo.Open("testfile.txt"); err != nil {
// Can't handle the error.
panic("unknown error")
}
// package foo
type NotFoundError struct {
File string
}
func (e *NotFoundError) Error() string {
return fmt.Sprintf("file %q not found", e.File)
}
func Open(file string) error {
return &NotFoundError{File: file}
}
// package bar
if err := foo.Open("testfile.txt"); err != nil {
var notFound *NotFoundError
if errors.As(err, ¬Found) {
// handle the error
} else {
panic("unknown error")
}
}
API
,
fmt.Errorf
%w
fmt.Errorf
%v
foo
fmt.Errorf
%w
%v
%w
var
%v
%w
"failed to"
s, err := store.New()
if err != nil {
return fmt.Errorf(
"failed to create new store: %w", err)
}
s, err := store.New()
if err != nil {
return fmt.Errorf(
"new store: %w", err)
}
failed to x: failed to y: failed to create new store: the error
x: y: new store: the error
err
"Failed"
[]
[]: https://dave.cheney.net/2016/04/27/dont-just-check-errors-handle-them-gracefully
Err
err
_
var (
// errors.Is
ErrBrokenLink = errors.New("link is broken")
ErrCouldNotOpen = errors.New("could not open")
// API
errNotFound = errors.New("not found")
)
Error
// errors.As
type NotFoundError struct {
File string
}
func (e *NotFoundError) Error() string {
return fmt.Sprintf("file %q not found", e.File)
}
// API errors.As
type resolveError struct {
Path string
}
func (e *resolveError) Error() string {
return fmt.Sprintf("resolve %q", e.Path)
}
errors.Is``errors.As
its
Bad:
u, err := getUser(id)
if err != nil {
// BAD: See description
log.Printf("Could not get user %q: %v", id, err)
return err
}
Good:
%w``errors.Is``errors.As
u, err := getUser(id)
if err != nil {
return fmt.Errorf("get user %q: %w", id, err)
}
Good:
if err := emitMetrics(); err != nil {
// Failure to write metrics should not
// break the application.
log.Printf("Could not emit metrics: %v", err)
}
Good:
tz, err := getUserTimeZone(id)
if err != nil {
if errors.Is(err, ErrUserNotFound) {
// User doesn't exist. Use UTC.
tz = time.UTC
} else {
return fmt.Errorf("get user %q: %w", id, err)
}
}
[] panic ok
[]: https://golang.org/ref/spec#Type_assertions
t := i.(string)
t, ok := i.(string)
if !ok {
//
}
panicpanic []
[]: https://en.wikipedia.org/wiki/Cascading_failure
func run(args []string) {
if len(args) == 0 {
panic("an argument is required")
}
// ...
}
func main() {
run(os.Args[1:])
}
func run(args []string) error {
if len(args) == 0 {
return errors.New("an argument is required")
}
// ...
return nil
}
func main() {
if err := run(os.Args[1:]); err != nil {
fmt.Fprintln(os.Stderr, err)
os.Exit(1)
}
}
panic/recover nil panic panic
var _statusTemplate = template.Must(template.New("name").Parse("_statusHTML"))
t.Fatal``t.FailNow
panic
// func TestFoo(t *testing.T)
f, err := os.CreateTemp("", "test")
if err != nil {
panic("failed to set up test")
}
// func TestFoo(t *testing.T)
f, err := os.CreateTemp("", "test")
if err != nil {
t.Fatal("failed to set up test")
}
sync/atomic (int32
, int64
go.uber.org/atomic atomic.Bool
type foo struct {
running int32 // atomic
}
func (f* foo) start() {
if atomic.SwapInt32(&f.running, 1) == 1 {
// already running
return
}
// start the Foo
}
func (f *foo) isRunning() bool {
return f.running == 1 // race!
}
type foo struct {
running atomic.Bool
}
func (f *foo) start() {
if f.running.Swap(true) {
// already running
return
}
// start the Foo
}
func (f *foo) isRunning() bool {
return f.running.Load()
}
// sign.go
var _timeNow = time.Now
func sign(msg string) string {
now := _timeNow()
return signWithTime(msg, now)
}
// sign.go
type signer struct {
now func() time.Time
}
func newSigner() *signer {
return &signer{
now: time.Now,
}
}
func (s *signer) Sign(msg string) string {
now := s.now()
return signWithTime(msg, now)
}
// sign_test.go
func TestSign(t *testing.T) {
oldTimeNow := _timeNow
_timeNow = func() time.Time {
return someFixedTime
}
defer func() { _timeNow = oldTimeNow }()
assert.Equal(t, want, sign(give))
}
// sign_test.go
func TestSigner(t *testing.T) {
s := newSigner()
s.now = func() time.Time {
return someFixedTime
}
assert.Equal(t, want, s.Sign(give))
}
AbstractList
AbstractList
type AbstractList struct {}
//
func (l *AbstractList) Add(e Entity) {
// ...
}
//
func (l *AbstractList) Remove(e Entity) {
// ...
}
// ConcreteList
type ConcreteList struct {
*AbstractList
}
// ConcreteList
type ConcreteList struct {
list *AbstractList
}
//
func (l *ConcreteList) Add(e Entity) {
l.list.Add(e)
}
//
func (l *ConcreteList) Remove(e Entity) {
l.list.Remove(e)
}
public public
interface
// AbstractList
type AbstractList interface {
Add(Entity)
Remove(Entity)
}
// ConcreteList
type ConcreteList struct {
AbstractList
}
// ConcreteList
type ConcreteList struct {
list AbstractList
}
//
func (l *ConcreteList) Add(e Entity) {
l.list.Add(e)
}
//
func (l *ConcreteList) Remove(e Entity) {
l.list.Remove(e)
}
Go [] Go []
[]: https://golang.org/ref/spec []: https://golang.org/ref/spec#Predeclared_identifiers
var error string
// `error`
// or
func handleErrorMessage(error string) {
// `error`
}
var errorMessage string
// `error`
// or
func handleErrorMessage(msg string) {
// `error`
}
type Foo struct {
// `error``string`
error error
string string
}
func (f Foo) Error() error {
// `error` `f.error`
return f.error
}
func (f Foo) String() string {
// `string` and `f.string`
return f.string
}
type Foo struct {
// `error` and `string`
err error
str string
}
func (f Foo) Error() error {
return f.err
}
func (f Foo) String() string {
return f.str
}
go vet
init()
init()``init()
init()``init()
init()
I/O
main()
main()
init magic
type Foo struct {
// ...
}
var _defaultFoo Foo
func init() {
_defaultFoo = Foo{
// ...
}
}
var _defaultFoo = Foo{
// ...
}
// or
var _defaultFoo = defaultFoo()
func defaultFoo() Foo {
return Foo{
// ...
}
}
type Config struct {
// ...
}
var _config Config
func init() {
// Bad:
cwd, _ := os.Getwd()
// Bad: I/O
raw, _ := os.ReadFile(
path.Join(cwd, "config", "config.yaml"),
)
yaml.Unmarshal(raw, &_config)
}
type Config struct {
// ...
}
func loadConfig() Config {
cwd, err := os.Getwd()
// handle err
raw, err := os.ReadFile(
path.Join(cwd, "config", "config.yaml"),
)
// handle err
var config Config
yaml.Unmarshal(raw, &config)
return config
}
init()
database/sql
make()
for n := 0; n < b.N; n++ {
data := make([]int, 0)
for k := 0; k < size; k++{
data = append(data, k)
}
}
for n := 0; n < b.N; n++ {
data := make([]int, 0, size)
for k := 0; k < size; k++{
data = append(data, k)
}
}
BenchmarkBad-4 100000000 2.48s
BenchmarkGood-4 100000000 0.21s
Go os.Exit
log.Fatal*
(panic
panic)
main()
os.Exit
log.Fatal*
func main() {
body := readFile(path)
fmt.Println(body)
}
func readFile(path string) string {
f, err := os.Open(path)
if err != nil {
log.Fatal(err)
}
b, err := os.ReadAll(f)
if err != nil {
log.Fatal(err)
}
return string(b)
}
func main() {
body, err := readFile(path)
if err != nil {
log.Fatal(err)
}
fmt.Println(body)
}
func readFile(path string) (string, error) {
f, err := os.Open(path)
if err != nil {
return "", err
}
b, err := os.ReadAll(f)
if err != nil {
return "", err
}
return string(b), nil
}
go test
defer
main
**** os.Exit``log.Fatal
main()
package main
func main() {
args := os.Args[1:]
if len(args) != 1 {
log.Fatal("missing file")
}
name := args[0]
f, err := os.Open(name)
if err != nil {
log.Fatal(err)
}
defer f.Close()
// log.Fatal
// f.Close
b, err := os.ReadAll(f)
if err != nil {
log.Fatal(err)
}
// ...
}
package main
func main() {
if err := run(); err != nil {
log.Fatal(err)
}
}
func run() error {
args := os.Args[1:]
if len(args) != 1 {
return errors.New("missing file")
}
name := args[0]
f, err := os.Open(name)
if err != nil {
return err
}
defer f.Close()
b, err := os.ReadAll(f)
if err != nil {
return err
}
// ...
}
log.Fatal``os.Exit``os.Exit
func main() {
if err := run(); err != nil {
fmt.Fprintln(os.Stderr, err)
os.Exit(1)
}
}
run()``run()
func main() {
os.Exit(run(args))
}
func run() (exitCode int) {
// ...
}
run()
run()
run(os.Args[1:])
)main()``run
main
package main
main()
JSONYAML
type Stock struct {
Price int
Name string
}
bytes, err := json.Marshal(Stock{
Price: 137,
Name: "UBER",
})
type Stock struct {
Price int `json:"price"`
Name string `json:"name"`
// Safe to rename Name to Symbol.
}
bytes, err := json.Marshal(Stock{
Price: 137,
Name: "UBER",
})
Goroutines CPU Goroutines Goroutines
goroutine go.uber.org/goleak goroutine goroutine
goroutine:
goroutine
For example:
go func() {
for {
flush()
time.Sleep(delay)
}
}()
var (
stop = make(chan struct{}) // goroutine
done = make(chan struct{}) // goroutine
)
go func() {
defer close(done)
ticker := time.NewTicker(delay)
defer ticker.Stop()
for {
select {
case <-tick.C:
flush()
case <-stop:
return
}
}
}()
// ...
close(stop) // goroutine
<-done // and wait for it to exit
goroutine
goroutine close(stop)
,
<-done
.
goroutine goroutine
sync.WaitGroup
.
goroutine
var wg sync.WaitGroup
for i := 0; i < N; i++ {
wg.Add(1)
go func() {
defer wg.Done()
// ...
}()
}
// To wait for all to finish:
wg.Wait()
chan struct{}
goroutine
goroutine
done := make(chan struct{})
go func() {
defer close(done)
// ...
}()
// To wait for the goroutine to finish:
<-done
init()
goroutinesinit()
goroutines
init()
goroutine
goroutine
Close``Stop``Shutdown
goroutine
func init() {
go doWork()
}
func doWork() {
for {
// ...
}
}
type Worker struct{ /* ... */ }
func NewWorker(...) *Worker {
w := &Worker{
stop: make(chan struct{}),
done: make(chan struct{}),
// ...
}
go w.doWork()
}
func (w *Worker) doWork() {
defer close(w.done)
for {
// ...
case <-w.stop:
return
}
}
// Shutdown worker
//
func (w *Worker) Shutdown() {
close(w.stop)
<-w.done
}
goroutine goroutine
goroutineWaitGroup
goroutines
strconv``fmt
for i := 0; i < b.N; i++ {
s := fmt.Sprint(rand.Int())
}
for i := 0; i < b.N; i++ {
s := strconv.Itoa(rand.Int())
}
BenchmarkFmtSprint-4 143 ns/op 2 allocs/op
BenchmarkStrconv-4 64.2 ns/op 1 allocs/op
slice
for i := 0; i < b.N; i++ {
w.Write([]byte("Hello world"))
}
data := []byte("Hello world")
for i := 0; i < b.N; i++ {
w.Write(data)
}
BenchmarkBad-4 50000000 22.2 ns/op
BenchmarkGood-4 500000000 3.25 ns/op
make()
make(map[T1]T2, hint)
make()
map map map
slices map hashmap bucket map map
m := make(map[string]os.FileInfo)
files, _ := os.ReadDir("./files")
for _, f := range files {
m[f.Name()] = f
}
files, _ := os.ReadDir("./files")
m := make(map[string]os.FileInfo, len(files))
for _, f := range files {
m[f.Name()] = f
}
m
m
make()
make([]T, length, capacity)
maps slice capacity make()
slice
append()
slice append
for n := 0; n < b.N; n++ {
data := make([]int, 0)
for k := 0; k < size; k++{
data = append(data, k)
}
}
for n := 0; n < b.N; n++ {
data := make([]int, 0, size)
for k := 0; k < size; k++{
data = append(data, k)
}
}
BenchmarkBad-4 100000000 2.48s
BenchmarkGood-4 100000000 0.21s
99 characters (99 ).
****.
bug
bug
package
Go
import "a"
import "b"
import (
"a"
"b"
)
const a = 1
const b = 2
var a = 1
var b = 2
type Area float64
type Volume float64
const (
a = 1
b = 2
)
var (
a = 1
b = 2
)
type (
Area float64
Volume float64
)
type Operation int
const (
Add Operation = iota + 1
Subtract
Multiply
EnvVar = "MY_ENV"
)
type Operation int
const (
Add Operation = iota + 1
Subtract
Multiply
)
const EnvVar = "MY_ENV"
func f() string {
red := color.New(0xff0000)
green := color.New(0x00ff00)
blue := color.New(0x0000ff)
...
}
func f() string {
var (
red = color.New(0xff0000)
green = color.New(0x00ff00)
blue = color.New(0x0000ff)
)
...
}
func (c *client) request() {
caller := c.name
format := "json"
timeout := 5*time.Second
var err error
// ...
}
func (c *client) request() {
var (
caller = c.name
format = "json"
timeout = 5*time.Second
err error
)
// ...
}
goimports
import (
"fmt"
"os"
"go.uber.org/atomic"
"golang.org/x/sync/errgroup"
)
import (
"fmt"
"os"
"go.uber.org/atomic"
"golang.org/x/sync/errgroup"
)
net/url``net/urls
Go MixedCaps TestMyFunction_WhatIsBeingTested
.
import (
"net/http"
client "example.com/client-go"
trace "example.com/trace/v2"
)
import (
"fmt"
"os"
nettrace "golang.net/x/trace"
)
import (
"fmt"
"os"
"runtime/trace"
nettrace "golang.net/x/trace"
)
struct
, const
, var
newXYZ()
/NewXYZ()
func (s *something) Cost() {
return calcCost(s.weights)
}
type something struct{ ... }
func calcCost(n []int) int {...}
func (s *something) Stop() {...}
func newSomething() *something {
return &something{}
}
type something struct{ ... }
func newSomething() *something {
return &something{}
}
func (s *something) Cost() {
return calcCost(s.weights)
}
func (s *something) Stop() {...}
func calcCost(n []int) int {...}
/
for _, v := range data {
if v.F1 == 1 {
v = process(v)
if err := v.Call(); err == nil {
v.Send()
} else {
return err
}
} else {
log.Printf("Invalid v: %v", v)
}
}
for _, v := range data {
if v.F1 != 1 {
log.Printf("Invalid v: %v", v)
continue
}
v = process(v)
if err := v.Call(); err != nil {
return err
}
v.Send()
}
if if
var a int
if b {
a = 100
} else {
a = 10
}
a := 10
if b {
a = 100
}
var
var _s string = F()
func F() string { return "A" }
var _s = F()
// F _s
//
func F() string { return "A" }
type myError struct{}
func (myError) Error() string { return "error" }
func F() myError { return myError{} }
var _e error = F()
// F myError error
vars``consts
_
// foo.go
const (
defaultPort = 8080
defaultUser = "user"
)
// bar.go
func Bar() {
defaultPort := 9090
...
fmt.Println("Default port", defaultPort)
// We will not see a compile error if the first line of
// Bar() is deleted.
}
// foo.go
const (
_defaultPort = 8080
_defaultUser = "user"
)
mutex
type Client struct {
version int
http.Client
}
type Client struct {
http.Client
version int
}
[]
Mutex [ Mutex ]
[]: # [ Mutex ]: #-mutex-
****:
"/"
some``no
-
type A struct {
// Bad: A.Lock() and A.Unlock()
// A
sync.Mutex
}
type countingWriteCloser struct {
// Good: Write()
// Write()
io.WriteCloser
count int
}
func (w *countingWriteCloser) Write(bs []byte) (int, error) {
w.count += len(bs)
return w.WriteCloser.Write(bs)
}
type Book struct {
// Bad:
io.ReadWriter
// other fields
}
// later
var b Book
b.Read(...) // panic: nil pointer
b.String() // panic: nil pointer
b.Write(...) // panic: nil pointer
type Book struct {
// Good:
bytes.Buffer
// other fields
}
// later
var b Book
b.Read(...) // ok
b.String() // ok
b.Write(...) // ok
type Client struct {
sync.Mutex
sync.WaitGroup
bytes.Buffer
url.URL
}
type Client struct {
mtx sync.Mutex
wg sync.WaitGroup
buf bytes.Buffer
url url.URL
}
(:=
)
var s = "foo"
s := "foo"
var
[]
[]: https://go.dev/wiki/CodeReviewComments#declaring-empty-slices
func f(list []int) {
filtered := []int{}
for _, v := range list {
if v > 10 {
filtered = append(filtered, v)
}
}
}
func f(list []int) {
var filtered []int
for _, v := range list {
if v > 10 {
filtered = append(filtered, v)
}
}
}
nil
0 slice
nil
if x == "" {
return []int{}
}
if x == "" {
return nil
}
len(s) == 0
nil
func isEmpty(s []string) bool {
return s == nil
}
func isEmpty(s []string) bool {
return len(s) == 0
}
var``make()
nums := []int{}
// or, nums := make([]int)
if add1 {
nums = append(nums, 1)
}
if add2 {
nums = append(nums, 2)
}
var nums []int
if add1 {
nums = append(nums, 1)
}
if add2 {
nums = append(nums, 2)
}
nil 0 nil
err := os.WriteFile(name, data, 0644)
if err != nil {
return err
}
if err := os.WriteFile(name, data, 0644); err != nil {
return err
}
if
if data, err := os.ReadFile(name); err == nil {
err = cfg.Decode(data)
if err != nil {
return err
}
fmt.Println(cfg)
return nil
} else {
return err
}
data, err := os.ReadFile(name)
if err != nil {
return err
}
if err := cfg.Decode(data); err != nil {
return err
}
fmt.Println(cfg)
return nil
`` C (/* ... */
)
// func printInfo(name string, isLocal, done bool)
printInfo("foo", true, true)
// func printInfo(name string, isLocal, done bool)
printInfo("foo", true /* isLocal */, true /* done */)
bool
true/false
type Region int
const (
UnknownRegion Region = iota
Local
)
type Status int
const (
StatusReady Status= iota + 1
StatusDone
// Maybe we will have a StatusInProgress in the future.
)
func printInfo(name string, region Region, status Status)
wantError := "unknown name:\"test\""
wantError := `unknown error:"test"`
k := User{"John", "Doe", true}
k := User{
FirstName: "John",
LastName: "Doe",
Admin: true,
}
3 may
tests := []struct{
op Operation
want string
}{
{Add, "add"},
{Subtract, "subtract"},
}
user := User{
FirstName: "John",
LastName: "Doe",
MiddleName: "",
Admin: false,
}
user := User{
FirstName: "John",
LastName: "Doe",
}
tests := []struct{
give string
want int
}{
{give: "0", want: 0},
// ...
}
var
var
user := User{}
var user User
&T{}``new(T)
sval := T{Name: "foo"}
// inconsistent
sptr := new(T)
sptr.Name = "bar"
sval := T{Name: "foo"}
sptr := &T{Name: "bar"}
map make(..)
map
map make
var (
// m1 ;
// m2 panic
m1 = map[T1]T2{}
m2 map[T1]T2
)
var (
// m1 ;
// m2 panic
m1 = make(map[T1]T2)
m2 map[T1]T2
)
map Map
map map literals(map )
m := make(map[T1]T2, 3)
m[k1] = v1
m[k2] = v2
m[k3] = v3
m := map[T1]T2{
k1: v1,
k2: v2,
k3: v3,
}
map make
( map )
Printf
-style const
go vet
msg := "unexpected values %v, %v\n"
fmt.Printf(msg, 1, 2)
const msg = "unexpected values %v, %v\n"
fmt.Printf(msg, 1, 2)
Printf
-style go vet
Printf
-style go vet
Printf
f Wrapf``Wrap``go vet
Printf f
go vet -printfuncs=wrapf,statusf
subtests table case
// func TestSplitHostPort(t *testing.T)
host, port, err := net.SplitHostPort("192.0.2.0:8000")
require.NoError(t, err)
assert.Equal(t, "192.0.2.0", host)
assert.Equal(t, "8000", port)
host, port, err = net.SplitHostPort("192.0.2.0:http")
require.NoError(t, err)
assert.Equal(t, "192.0.2.0", host)
assert.Equal(t, "http", port)
host, port, err = net.SplitHostPort(":8000")
require.NoError(t, err)
assert.Equal(t, "", host)
assert.Equal(t, "8000", port)
host, port, err = net.SplitHostPort("1:8")
require.NoError(t, err)
assert.Equal(t, "1", host)
assert.Equal(t, "8", port)
// func TestSplitHostPort(t *testing.T)
tests := []struct{
give string
wantHost string
wantPort string
}{
{
give: "192.0.2.0:8000",
wantHost: "192.0.2.0",
wantPort: "8000",
},
{
give: "192.0.2.0:http",
wantHost: "192.0.2.0",
wantPort: "http",
},
{
give: ":8000",
wantHost: "",
wantPort: "8000",
},
{
give: "1:8",
wantHost: "1",
wantPort: "8",
},
}
for _, tt := range tests {
t.Run(tt.give, func(t *testing.T) {
host, port, err := net.SplitHostPort(tt.give)
require.NoError(t, err)
assert.Equal(t, tt.wantHost, host)
assert.Equal(t, tt.wantPort, port)
})
}
test table test case
tests
tt``give``want
tests := []struct{
give string
wantHost string
wantPort string
}{
// ...
}
for _, tt := range tests {
// ...
}
goroutine
tests := []struct{
give string
// ...
}{
// ...
}
for _, tt := range tests {
tt := tt // for t.Parallel
t.Run(tt.give, func(t *testing.T) {
t.Parallel()
// ...
})
}
t.Parallel()``tt
tt
Option
API
// package db
func Open(
addr string,
cache bool,
logger *zap.Logger
) (*Connection, error) {
// ...
}
// package db
type Option interface {
// ...
}
func WithCache(c bool) Option {
// ...
}
func WithLogger(log *zap.Logger) Option {
// ...
}
// Open creates a connection.
func Open(
addr string,
opts ...Option,
) (*Connection, error) {
// ...
}
db.Open(addr, db.DefaultCache, zap.NewNop())
db.Open(addr, db.DefaultCache, log)
db.Open(addr, false /* cache */, zap.NewNop())
db.Open(addr, false /* cache */, log)
db.Open(addr)
db.Open(addr, db.WithLogger(log))
db.Open(addr, db.WithCache(false))
db.Open(
addr,
db.WithCache(false),
db.WithLogger(log),
)
Option
options
type options struct {
cache bool
logger *zap.Logger
}
type Option interface {
apply(*options)
}
type cacheOption bool
func (c cacheOption) apply(opts *options) {
opts.cache = bool(c)
}
func WithCache(c bool) Option {
return cacheOption(c)
}
type loggerOption struct {
Log *zap.Logger
}
func (l loggerOption) apply(opts *options) {
opts.logger = l.Log
}
func WithLogger(log *zap.Logger) Option {
return loggerOption{Log: log}
}
// Open creates a connection.
func Open(
addr string,
opts ...Option,
) (*Connection, error) {
options := options{
cache: defaultCache,
logger: zap.NewNop(),
}
for _, o := range opts {
o.apply(&options)
}
// ...
}
fmt.Stringer
"blessed" linter lint
linters
golangci-lint go-to lint repo .golangci.yml linter
golangci-lint various-linters linters set linters