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"
}
- Work through How to Write Go Code
- Fall in love with the tooling, watch Go Tooling in Action
- Take a codewalk written by the creators, First-Class Functions in Go
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)
...
}
In main()
,
func main() {
// http.Client from standard library implicitly satisfies HTTPClient interface
handler := Handler{Client: &http.Client{}}
...
}
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{}}
...
}
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)
}
}
}
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()
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
- The Go Blog has great articles in general, a few in particular follow
- Arrays and Slices in Go
- Strings, bytes, runes and characters in Go
- Go Best Practices
- Functional Options for Friendly APIs
- The Go Programming Language Specification
- Effective Go
Top comments (0)