GithubHelp home page GithubHelp logo

go-styleguide's Introduction

Go Styleguide

This serves as a supplement to Effective Go, based on years of experience and inspiration/ideas from conference talks.

Table of contents

Add context to errors

Don't:

file, err := os.Open("foo.txt")
if err != nil {
	return err
}

Using the approach above can lead to unclear error messages because of missing context.

Do:

import "github.com/pkg/errors"

// ...

file, err := os.Open("foo.txt")
if err != nil {
	return errors.Wrap(err, "open foo.txt failed")
}

Wrapping the error with a custom message provides context as it get's propagated up the stack. github.com/pkg/errors

Dependency management

Use dep

Use dep, since it's production ready and will soon become part of the toolchain. – Sam Boyer at GopherCon 2017

Use Semantic Versioning

Since dep can handle versions, tag your packages using Semantic Versioning.

Avoid gopkg.in

gopkg.in tags one version and is not meant to work with dep. Prefer direct import and specify version in Gopkg.toml.

Structured logging

Don't:

log.Printf("Listening on :%d", port)
http.ListenAndServe(fmt.Sprintf(":%d", port), nil)
// 2017/07/29 13:05:50 Listening on :80

Do:

import "github.com/uber-go/zap"

// ...

logger, _ := zap.NewProduction()
defer logger.Sync()
logger.Info("Server started",
	zap.Int("port", port),
	zap.String("env", env),
)
http.ListenAndServe(fmt.Sprintf(":%d", port), nil)
// {"level":"info","ts":1501326297.511464,"caller":"Desktop/structured.go:17","msg":"Server started","port":80,"env":"production"}

This is a harmless example, but using structured logging makes debugging and log parsing easier.

Avoid global variables

Don't:

var db *sql.DB

func main() {
	db = // ...
	http.HandleFunc("/drop", DropHandler)
	// ...
}

func DropHandler(w http.ResponseWriter, r *http.Request) {
	db.Exec("DROP DATABASE prod")
}

Global variables make testing and readability hard and every method has access to them (even those, that don't need it).

Do:

func main() {
	db ;= // ...
	http.HandleFunc("/drop", DropHandler(db))
	// ...
}

func DropHandler(db *sql.DB) http.HandleFunc {
	return func (w http.ResponseWriter, r *http.Request) {
		db.Exec("DROP DATABASE prod")
	}
}

Use higher-order functions instead of global variables to inject dependencies accordingly.

Testing

Use testify

Don't:

func TestAdd(t *testing.T) {
	actual := 2 + 2
	expected := 4
	if (actual != expected) {
		t.Errorf("Expected %d, but got %d", expected, actual)
	}
}

Do:

import "github.com/stretchr/testify/assert"

func TestAdd(t *testing.T) {
	actual := 2 + 2
	expected := 4
	assert.Equal(t, expected, actual)
}

Using assert makes your tests more readable and provides consistent error output.

Use table driven tests

Don't:

func TestAdd(t *testing.T) {
	assert.Equal(t, 1+1, 2)
	assert.Equal(t, 1+-1, 0)
	assert.Equal(t, 1, 0, 1)
	assert.Equal(t, 0, 0, 0)
}

The above approach looks simpler, but it's much harder to find a failing case, especially when having hundreds of cases.

Do:

func TestAdd(t *testing.T) {
	cases := []struct {
		A, B, Expected int
	}{
		{1, 1, 2},
		{1, -1, 0},
		{1, 0, 1},
		{0, 0, 0},
	}

	for _, tc := range cases {
		t.Run(fmt.Sprintf("%d + %d", tc.A, tc.B), func(t *testing.T) {
			assert.Equal(t, t.Expected, tc.A+tc.B)
		})
	}
}

Using table driven tests in combination with subtests gives you direct insight about which case is failing and which cases are tested. – Mitchell Hashimoto at GopherCon 2017

Avoid mocks

Don't:

func TestRun(t *testing.T) {
	mockConn := new(MockConn)
	run(mockConn)
}

Do:

func TestRun(t *testing.T) {
	ln, err := net.Listen("tcp", "127.0.0.1:0")
	t.AssertNil(t, err)

	var server net.Conn
	go func() {
		defer ln.Close()
		server, err := ln.Accept()
		t.AssertNil(t, err)
	}()

	client, err := net.Dial("tcp", ln.Addr().String())
	t.AssertNil(err)

	run(client)
}

Only use mocks if not otherwise possible, favor real implementations. – Mitchell Hashimoto at GopherCon 2017

Avoid DeepEqual

Don't:

type myType struct {
	id         int
	name       string
	irrelevant []byte
}

func TestSomething(t *testing.T) {
	actual := &myType{/* ... */}
	expected := &myType{/* ... */}
	assert.True(t, reflect.DeepEqual(expected, actual))
}

Do:

type myType struct {
	id         int
	name       string
	irrelevant []byte
}

func (m *myType) testString() string {
	return fmt.Sprintf("%d.%s", m.id, m.name)
}

func TestSomething(t *testing.T) {
	actual := &myType{/* ... */}
	expected := &myType{/* ... */}
	assert.Equal(t, actual.testString(), expected.testString())
}

Using testString() for comparing structs helps on complex structs with many fields that are not relevant for the equality check. – Mitchell Hashimoto at GopherCon 2017

Avoid testing unexported funcs

Only test unexported funcs if you can't access a path via exported funcs. Since they are unexported, they are prone to change.

Use gometalinter

Use gometalinter to lint your projects before committing.

Use gofmt

Only commit gofmt'd files, use -s to simplify code.

Avoid side-effects

Don't:

func init() {
	someStruct.Load()
}

Side effects are only okay in special cases (e.g. parsing flags in a cmd). If you find no other way, rethink and refactor.

Favour pure funcs

In computer programming, a function may be considered a pure function if both of the following statements about the function hold:

  1. The function always evaluates the same result value given the same argument value(s). The function result value cannot depend on any hidden information or state that may change while program execution proceeds or between different executions of the program, nor can it depend on any external input from I/O devices (usually—see below).
  2. Evaluation of the result does not cause any semantically observable side effect or output, such as mutation of mutable objects or output to I/O devices (usually—see below).

Wikipedia

Don't:

func MarshalAndWrite(some *Thing) error {
	b, err := json.Marshal(some)
	if err != nil {
		return err
	}

	return ioutil.WriteFile("some.thing", b, 0644)
}

Do:

// Marshal is a pure func (even though useless)
func Marshal(some *Thing) ([]bytes, error) {
	return json.Marshal(some)
}

// ...

This is obviously not possible at all times, but trying to make every possible func pure makes code more understandable and improves debugging.

Don't over-interface

Don't:

type Server interface {
	Serve() error
	Some() int
	Fields() float64
	That() string
	Are([]byte) error
	Not() []string
	Necessary() error
}

func debug(srv Server) {
	fmt.Println(srv.String())
}

func run(srv Server) {
	srv.Serve()
}

Do:

type Server interface {
	Serve() error
}

func debug(v fmt.Stringer) {
	fmt.Println(v.String())
}

func run(srv Server) {
	srv.Serve()
}

Favour small interfaces and only expect the interfaces you need in your funcs.

Don't under-package

Deleting or merging packages is fare more easier than splitting big ones up.

Handle signals

Don't:

func main() {
	for {
		time.Sleep(1 * time.Second)
		ioutil.WriteFile("foo", []byte("bar"), 0644)
	}
}

Do:

func main() {
	logger := // ...
	sc := make(chan os.Signal)
	done := make(chan bool)

	go func() {
		for {
			select {
			case s := <-sc:
				logger.Info("Received signal, stopping application",
					zap.String("signal", s.String()))
				break
			default:
				time.Sleep(1 * time.Second)
				ioutil.WriteFile("foo", []byte("bar"), 0644)
			}
		}
		done <- true
	}()

	signal.Notify(sc, os.Interrupt, os.Kill)
	<-done // Wait for go-routine
}

Handling signals allows us to gracefully stop our server, close open files and connections and therefore prevent file corruption among other things.

Divide imports

Don't:

import (
	"encoding/json"
	"go.uber.org/zap"
	"fmt"
	"github.com/this-project/pkg/some-lib"
	"os"
)

Do:

import (
	"encoding/json"
	"fmt"
	"os"

	"go.uber.org/zap"

	"github.com/this-project/pkg/some-lib"
)

Dividing std, external and internal imports improves readability.

Avoid unadorned return

Don't:

func run() (n int, err error) {
	// ...
	return
}

Do:

func run() (n int, err error) {
	// ...
	return n, err
}

Named returns are good for documentation, unadorned returns are bad for readability and error-prone.

Use import comment

Don't:

import sub

Do:

import sub // import "github.com/my-package/pkg/sth/else/sub"

Adding the import comment adds context to the package and make importing easy.

Avoid empty interface

Don't:

func run(foo interface{}) {
	// ...
}

Empty interfaces make code more complex and unclear, avoid them where you can.

Main first

Don't:

package main // import "github.com/me/my-project"

func someHelper() int {
	// ...
}

func someOtherHelper() string {
	// ...
}

func Handler(w http.ResponseWriter, r *http.Reqeust) {
	// ...
}

func main() {
	// ...
}

Do:

package main // import "github.com/me/my-project"

func main() {
	// ...
}

func Handler(w http.ResponseWriter, r *http.Reqeust) {
	// ...
}

func someHelper() int {
	// ...
}

func someOtherHelper() string {
	// ...
}

Putting main() first makes reading the file a lot more easier. Only the init() function should be above it.

Use Makefile if necessary

If you always want or need specific flags or need to generate protobuffers, create a Makefile.

Use internal packages

If you're creating a cmd, consider moving libraries to internal/ to prevent import of unstable, changing packages.

Avoid helper/util

Use clear names and try to avoid creating a helper.go, utils.go or even package.

Use go-bindata

To enable single-binary deployments, use github.com/jteeuwen/go-bindata for adding templates and other static assets to your binary.

Use decorator pattern

struct Config {
	port    int
	timeout time.Duration
}

type ServerOpt func(*Config)

func WithPort(port int) ServerOpt {
	return func(cfg *Config) {
		cfg.port = port
	}
}

func WithTimeout(timeout time.Duration) ServerOpt {
	return func(cfg *Config) {
		cfg.timeout = timeout
	}
}

func startServer(opts ...ServerOpt) {
	cfg := new(Config)
	for _, fn := range opts {
		fn(cfg)
	}

	// ...
}

func main() {
	// ...
	startServer(
		WithPort(8080),
		WithTimeout(1 * time.Second),
	)
}

go-styleguide's People

Contributors

bahlo avatar seiflotfy avatar

Watchers

 avatar  avatar  avatar

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.