DEV Community

Rocky Warren
Rocky Warren

Posted on • Originally published at rocky.dev on

Go Learning Resources

I've only dabbled with Go in my spare time, but compiled this list of resources I found helpful.

Getting Started

  • Take A Tour of Go
  • brew update && brew install golang
  • Install the extension for VS Code
    • Add these to your User Settings (⌘ + ,):
"go.testOnSave": true,
"go.coverOnSave": true,
"go.addTags": {
  "tags": "json",
  "options": "json=omitempty",
  "promptForTags": false,
  "transform": "camelcase"
}
Enter fullscreen mode Exit fullscreen mode

First impressions

Pros

  • ⚑ Feedback cycle is fast, feels like a dynamic language
  • πŸ”¨ Tooling is excellent, here's what I mean
  • πŸ€“ Encourages you to look at source code since it's all pulled down, which is an excellent way to learn new tricks
  • πŸ“– Automatic, simple, offline documentation with godoc. It can even have testable examples! More info, Godoc
  • πŸ“ In Scala, seemingly simple bits of code can be written many different ways. In code review, comments relate to formatting or refactoring into something easier to read. In Go, there's one way to code most things and one way to format, gofmt. Reviewers can concentrate on other things

Cons

  • ❌ No generics (here's why), though it's under consideration
  • 😫 Mocking can get verbose
  • πŸ’£ signal SIGSEGV: segmentation violation brings back memories, painful memories
  • 😭 gofmt uses tabs

Dependency Injection

Create structs that define their dependencies as interfaces so they can be mocked in tests,

type HTTPClient interface {
  Get(string) (*http.Response, error)
}

type Handler struct {
  Client HTTPClient
}

func (h *Handler) Handle(url string) (string, error) {
  res, err := h.Client.Get(url)
  ...
}
Enter fullscreen mode Exit fullscreen mode

In main(),

func main() {
  // http.Client from standard library implicitly satisfies HTTPClient interface
  handler := Handler{Client: &http.Client{}}
  ...
}
Enter fullscreen mode Exit fullscreen mode

And in your test,

type httpMock struct{}

// Satisfy HTTPClient interface, modify to return whatever you need
func (m *httpMock) Get(url string) (resp *http.Response, err error) {
  return &http.Response{}, nil
}

func TestGetError(t *testing.T) {
  handler := Handler{Client: &httpMock{}}
  ...
}
Enter fullscreen mode Exit fullscreen mode

Testing

Table-driven tests are common and made simple with existing Go constructs.

func TestIsValidRtn(t *testing.T) {
  cases := map[string]struct {
    input string
    expected bool
  }{
    "zero value": {"", false},
    "< 9 digits": {"00000000", false},
    "valid": {"010000003", true},
  }

  for k, c := range cases {
    actual, _ := IsValidRtn(c.input)
    if actual != c.expected {
      t.Errorf("IsValidRtn(%q) == %v, expected %v. %v", c.input, actual, c.expected, k)
    }
  }
}
Enter fullscreen mode Exit fullscreen mode

Even if you have one test case, consider writing it like this to easily add more later. More info, Table Driven Tests, Advanced Testing with Go, Testify.

Errors

Always handle errors, do not ignore them,

// 😎
result, err := couldReturnError()
if err != nil {
  ...
}

// πŸ™
result, _ := couldReturnError()
Enter fullscreen mode Exit fullscreen mode

If you're worried about code riddled with error checks, read this. Prefer errors over panics, more info

Channels

Identify separate pieces of work and compose their interactions with channels. Separating processes makes programs simple to follow and leads to better design. More info, Pipelines, Concurrency Is Not Parallelism

Up your game

Top comments (0)