Giter Club home page Giter Club logo

computable-go-style's Introduction

Use Go Like So

COGS - COmputable Golang Style

Introduction

The goal of this document is to create consistently styled Go code as followed by Computable.

These are general guidelines, for further in-depth resources please see the following:

  1. Effective Go
  2. Common Go Mistakes

Tooling (optional)

Vscode users may get annoying linting issues] via the default linter. An improvement over the default linter is using GolangCI-Lint

Can be installed with the following command: go get -u github.com/go-delve/delve/cmd/dlv

Please follow the repo instructions to setup correctly.

Project Structure

For further detailed resources see the following:

  1. How to structure your Go apps by Kat Zien (video)
  2. Best practices for Industrial Programming by Peter Bourgon (video)
  3. Golang project layout

/cmd

  • Main application for this project
  • Don't put a lot of code in the application directory. If the code can be imported and used in other projects, it should live in /pkg.
  • It is common to have small main() functions. e.g.
func main() {
  if err := run(); err != nil {
    fmt.Printf(os.Stderr, "%v", err)
    os.Exit(1)
  }
}

/pkg

  • Code that's okay to be used by external applications.
  • Specifically we will be grouping packages by context as related by Domain Driven Design.

/internal

  • Private applications and library code.
  • Code you don't want others importing.
  • This layout pattern is enfored by the Go compiler.
  • You can have more than one internal directory at any level in the tree.

/vendor

  • Application dependencies

/configs

  • Configuration files

Domain Driven Design

The goal of DDD is to create a ubiquitous language allowing easy communication between cross-functional groups. It creates clarity by standardizing the use of words with pre-agreed meanings.

It also informs us how to structure our projects. This is known as "grouping by context." Our project structure will follow Domain Driven Design as best described by Kat Zien here.

Every repository with be accompanied with a D3.md featuring Ubiquitous Language that should be enforced by the package structure and actual code itself.

Prefer explicit idiomatic file names as opposed to default DDD naming. e.g.

Bad

/pkg/adding
--- service.go

Good

/pkg/adding
--- beer_adder.go

Style

Group Similar Declarations

BadGood
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
)

Do not group unrelated declarations.

BadGood
type Operation int

const (
  Add Operation = iota + 1
  Subtract
  Multiply
  ENV_VAR = "MY_ENV"
)
type Operation int

const (
  Add Operation = iota + 1
  Subtract
  Multiply
)

const ENV_VAR = "MY_ENV"

Import Group Ordering

There should be two import groups:

  • Standard library
  • Everything else
BadGood
import (
  "fmt"
  "os"
  "go.uber.org/atomic"
  "golang.org/x/sync/errgroup"
)
import (
  "fmt"
  "os"

  "go.uber.org/atomic"
  "golang.org/x/sync/errgroup"
)

Reduce Nesting

Code should reduce nesting where possible. This can be done by handling special cases/errors early.

BadGood
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()
}

And avoiding unnecessary else's.

BadGood
if ok {
  err := something()
  if err != nil {
    return errors.Wrap(err, "something")
  }
  // do things
} else {
  return errors.New("not ok")
}
if !ok {
  return errors.New("not ok")
}

err := something()
if err != nil {
  return errors.Wrap(err, "something")
}
// do things

Don't Panic

If errors occur, the function must return an error and allow the caller to decide how to handle.

BadGood
func foo(bar string) {
  if len(bar) == 0 {
    panic("bar must not be empty")
  }
  // ...
}

func main() {
  if len(os.Args) != 2 {
    fmt.Println("USAGE: foo <bar>")
    os.Exit(1)
  }
  foo(os.Args[1])
}
func foo(bar string) error {
  if len(bar) == 0 {
    return errors.New("bar must not be empty")
  }
  // ...
  return nil
}

func main() {
  if len(os.Args) != 2 {
    fmt.Println("USAGE: foo <bar>")
    os.Exit(1)
  }
  if err := foo(os.Args[1]); err != nil {
    panic(err)
  }
}

Errors strings

Error strings should not be capitalized (unless beginning with proper nouns or acronyms) or end with punctuation. Errors are usually usually printed following other context.

E.g. use fmt.Errorf("something bad") not fmt.Errorf("Something bad"), so that log.Printf("Reading %s: %v", filename, err) formats without a spurious capital letter mid-message.

Use field names to initialize Structs

Explicit should be favored over implicit.

BadGood
k := User{"John", "Doe", true}
k := User{
    FirstName: "John",
    LastName: "Doe",
    Admin: true,
}

Reduce scope of variables.

Reduce variable scope where possible. Do not reduce scope if it conflicts with reducing nesting.

BadGood
err := ioutil.WriteFile(name, data, 0644)
if err != nil {
 return err
}
if err := ioutil.WriteFile(name, data, 0644); err != nil {
 return err
}

Use Raw String Literals to Avoid Escaping

Go supports raw string literals, which can span multiple lines and include quotes. Use these to avoid hand-escaped strings which are much harder to read.

BadGood
wantError := "unknown name:\"test\""
wantError := `unknown error:"test"`

Avoid using new

sval := T{Name: "foo"}

// inconsistent
sptr := new(T)
sptr.Name = "bar"
sval := T{Name: "foo"}

sptr := &T{Name: "bar"}

Declaring empty slices

BadGood
t := []string{}
var t []string

Prefer minimal interfaces

Prefer using only as narrow an interface as needed.

BadGood
type File interface {
  io.Closer
  io.Reader
  io.ReaderAt
  io.Seeker
}

func ReadIn(f File) {
  b := []byte{}
  n, err := f.Read(b)
}
func ReadIn(r Reader) {
  b := []byte{}
  n, err := r.Read(b)
}

We needn't create an entire File to use ReadIn() when we're only interested in using the Reader.

Defer Close After Confirmed Open

BadGood
func getConfig() (*config, error) {
  var cfg config
  configFile, openErr := os.Open(SMART_CONTRACT_CONFIG_PATH)

  defer configFile.Close()

  if openErr != nil {
    return &cfg, openErr
  }
  // ...
}
func getConfig() (*config, error) {
  var cfg config
  configFile, openErr := os.Open(SMART_CONTRACT_CONFIG_PATH)

  if openErr != nil {
    return &cfg, openErr
  }
  defer configFile.Close()
  // ...
}

Minimal Method Signatures

Method signatures should be as minimal as possible.

When a single letter isn't available, use the shortest sensible two letter variable name.

BadGood
func AddListingHandlers(router Routable, list list.Listor, client projectorhttp.HttpClient, loggingChannel chan log.Message) Routable {
  // ...
}
func AddListingHandlers(r Routable, l list.Listor, c projectorhttp.HttpClient, lc chan log.Message) Routable {
  // ...
}

Constants should be SCREAMING_SNAKE_CASE

Our constants are in SCREAMING_SNAKE_CASE, in lieu of the commonly recommended camelCase. This was done for readability. They should never be imported.

BadGood
const userId = "userId"
const slug = "slug"
const xCorrelationId = "X-Correlation-ID"
const USER_ID = "userId"
const SLUG = "slug"
const X_CORRELATION_ID = "X-Correlation-ID"

Hexagonal Architecture

  • Our architecture is separated into Left, Center, Right.

  • Left

    • Application code
    • HTTP and REST for our API
    • Is free to import center
    • Must implement interfaces dictated by center
    • Drives the center
      • By instantiating aggregates/entities
      • By calling methods on center domain object, -able's
  • Center

    • Where we define aggregates/entities
    • Where we define center domain objects, -able's
    • Where we define our infrastructure interfaces, -or's
      • -or's are interfaces that define the center's ports
      • Right creates an adapter by fulfilling interfaces defined by these ports
      • Right and center meet where ports and adapters connect
    • Is importable by all
    • Drives right
    • Imports 3rd party libraries when context encapsulates 3rd party library
    • Is sectioned into Bounded Contexts
      • Contexts don't import each other
    • Does not import left or right
      • Prevents circular imports
      • Allows repository mocking
  • Right

    • Is free to import center
    • Can be
      • Infastructure
      • a Service (e.g. Stripe)
      • a Repository (e.g. Postgres, Dynamo)
    • Fulfills the -or interface as dictated by the center

Example

func registration(able register.Registerable, or register.Registeror) httprouter.Handle {
	return func(w http.ResponseWriter, r *http.Request, _ httprouter.Params) {

		loggor := getChannelLoggor(r)
		correlationId(r)

		if err := decodeBody(r, able); err != nil {
			logging.LogError(loggor, err)
			respondHTTPErr(w, http.StatusBadRequest)
			return
		}
    
    # left drives center by calling method on the center domain object -able
		if err := able.Register(or, loggor); err != nil {
			logging.LogError(loggor, err)
			respondHTTPErr(w, http.StatusInternalServerError)
			return
		}

		respond(w, http.StatusOK, &able)
	}
}

computable-go-style's People

Contributors

melvinmallari avatar

Watchers

 avatar  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.