Skip to content

kliph-gladly/learn-go-with-tests-notes

Repository files navigation

Learn Go With Tests

Following along with https://quii.gitbook.io/learn-go-with-tests/

Other resources

Simplicity in Go

Hello, World

  • Had to turn on yasnippet minor mode to have golang completions work
  • I have my editor configured to display tabs as two spaces to make golang more accessible for me
  • I have opted to edit files directly rather than tangling so that I can get the benefit of LSP integration in golang
    • [ ] There may be a good way to make it work inside Org-mode but I haven’t investigated
    • Pretty cool to be able to see compiler errors in the editor without needing to run the tests 😎
  • I have had to run M-x projectile-invalidate-cache occasionally to get Projectile to pick up the .dir-locals config for jumping between tests and impls.

Go modules

  • [ ] Can I get go test to report using dots?

To see a module’s godocs:

godoc -http :8000

Can I watch and rerun tests?

  • Not through go test directly :(
  • I can do some shenanigans with fswatch (an OS X tool, similar tools exist for other operating systems)
  • Install with brew install fswatch
fswatch -o ./*.go | xargs -n1 -I{} go test

See https://emcrisostomo.github.io/fswatch/usage.html

Hello, world… again

This is interesting:

t.Helper() is needed to tell the test suite that this method is a helper. By doing this when it fails the line number reported will be in our function call rather than inside our test helper. This will help other developers track down problems easier.

This is a feature of Playwright that I prefer as well.

Doesn’t look like Go has syntactic sugar for default values for function parameters :( https://stackoverflow.com/questions/19612449/default-value-in-gos-method#comment77755758_23650312

One last refactor?

Finally breaking code out of this toy function that’s already too large and concerned with too much. The book illustrates that there are implicit returns in golang

func greetingPrefix(language string) (prefix string) {
  switch language {
  case french:
    prefix = frenchHelloPrefix
  case spanish:
    prefix = spanishHelloPrefix
  default:
    prefix = englishHelloPrefix
  }
  return
}

which will return the prefix because it’s specified as a named return value to which I say, “Ewww!” This is a language feature that I would discourage using because it makes it harder to reason about the code inplace. You need to be aware of the call signature in order to interpret the return statement rather than simply interpreting the return statement by itself.

See https://github.com/golang/go/wiki/CodeReviewComments#named-result-parameters for more details.

Integers

When you have more than one argument of the same type (in our case two integers) rather than having (x int, y int) you can shorten it to (x, y int).

I found this unclear. I thought it was saying x is any type rather than int.

Implementing Repeat

I’d rather use reduce to construct a string like this. The standard strings library https://cs.opensource.google/go/go/+/refs/tags/go1.19.5:src/strings/strings.go;l=528 uses a chunked approach to writing into a Builder. Doesn’t look like Go has functional constructs like reduce. Maybe there’s a more idiomatically functional way to do this rather than imperatively iterating through a loop and concatenating strings.

Ah make seems sort of like what I want. make and range feel a little bit like initializing a collection and then mapping over a collection to transform it into another collection.

Arrays And Slices

Refactoring to use a slice instead of an array of defined size (tuple?)

  • [ ] If a function consumes a slice, why can’t an array of defined size be passed to it?

Coverage

go test -cover

Feels like I’d reach for reduce

In order to avoid the kind of error-prone boundary checking, I almost always find it more advantageous to express this sort of computation as a reduce operation over the inputs.

Structs, methods and interfaces

Go doesn’t support function dispatch based on the argument type, something like multimethods in Clojure.

According to the book:

We have two choices:

  • You can have functions with the same name declared in different packages. So we could create our Area(Circle) in a new package, but that feels overkill here.
  • We can define methods on our newly defined types instead.

The first choice seems like wasteful overhead. Let’s see what’s up with methods. Oh you just bind them on the struct. That’s not that complicated, but it forces tying abstract implementations to concrete types :/. It seems like this would not allow for the flexibility that you can achieve with Clojure protocols. Interfaces get some of the way there, just like they do in Clojure, but cannot be extended by consumers without opening up the initial implementation.

The book’s author “would like to reiterate how great the compiler is here”. Go’s compiler messages have been largely helpful. It’s not as fancy as Elm, but it seems pretty good. It’s as good or better than Typescript’s compiler, but not as good as Haskell and other fancy compilers.

The syntax for declaring methods binds the method to a receiver.

Public and private fields on structs

The public/private distinction applies to packages. So a capitalized field is publicly available in an external package, a lower-case field is not. I had previously thought that the public/private distinct applied to each entity itself.

An Aside: Understanding go.sum and go.mod file in Go (Golang)

Following along at https://golangbyexample.com/go-mod-sum-module/

  • go.mod specifies the root of a Go module.
    • It defines the module import path
    • The version of go with which the module was created
    • Dependency requirements for the module (including specific versions)
  • go.sum lists the checksums of direct and indirect dependencies

Also please note that both go.mod as well as go.sum file should be checked into the Version Control System (VCS) such as git

Note: when following the example, I needed to add ./learn to the root go.work to make the imported dependency resolve properly. Before I did this, I repeatedly got an error saying that no required module provides the package, even after following the go get instructions to install the package.

  • [ ] It’s not clear to me why I would need to install a package’s indirect dependencies. If they’re not necessary to run the code that I’m imported, why would I need them. If they are necessary, then why aren’t they specified among the package’s direct dependencies?

Pointers & errors

Following along at https://quii.gitbook.io/learn-go-with-tests/go-fundamentals/pointers-and-errors#write-the-test-first

Variables copy on calling in golang (maybe this isn’t the right way to describe it, thinking of the copy on write sense). In order to update state in a struct, you need to pass a pointer to the struct in memory so that updates mutate the original struct rather than a copy. To achieve this, we use the *Foo receiver type func (f *Foo). Struct pointers are automatically dereferenced which leads to some convenience at the cost of some confusion (explicit vs. implicit dereference).

Stringer is how golang refers to the string representation of an struct.

https://dave.cheney.net/2016/04/27/dont-just-check-errors-handle-them-gracefully

Maps

Following along at https://quii.gitbook.io/learn-go-with-tests/go-fundamentals/maps

The map keyword defines a mapping of map[keyType]valueType which is definitely a syntax that a parser may be able to parse.

Maps in golang return a second value so you can tell whether a value was present in the dictionary. The book says “This property allows us to differentiate between a word that doesn’t exist and a word that just doesn’t have a definition.” It’s not clear to me when a word doesn’t exist and how this helps differentiate. Maybe they’re referring to a “word that doesn’t have a definition” as an entry in the map that has a value of empty string.

Remember what we just learned about pointers and all that. Forget it. Maps are different. The whole map doesn’t copy when passed as an argument, just the pointers. Under the hood their values are just pointers to underlying structures.

Confusing? Sure. More detail than I asked for when I went to look up my value in an aggregate? Yep. Simple enough to learn and develop heuristics around. I guess.

A gotcha with maps is that they can be a nil value. A nil map behaves like an empty map when reading, but attempts to write to a nil map will cause a runtime panic. You can read more about maps here.

Therefore, you should never initialize an empty map variable:

// don't do
var m map[string]string


// instead do
var m = map[string]string{}

// or

var m = make(map[string]string)

Both approaches create an empty hash map and point m at it. Which ensures that you will never get a runtime panic.

Leaving off at https://quii.gitbook.io/learn-go-with-tests/go-fundamentals/maps#refactor-2

Custom JSON Marshalling

https://calvinfeng.gitbook.io/gonotebook/idioms/custom-json-marshaling from https://sagan.slack.com/archives/C02SUTFE3/p1684425228308799

Linting golang

Following along at https://freshman.tech/linting-golang/

Running on this project

golangci-lint run

In order to make this work, I needed to define a module in the root of my project. I think this is related to golangci/golangci-lint#2654 .

go mod init

go mod tidy

echo "package main" > main.go

go work use .

However it doesn’t appear to recursively check the submodules. I needed to pass a specific directory each time I ran the linter.

Enabling in Emacs

I needed to install the https://github.com/weijiangan/flycheck-golangci-lint package locally as I couldn’t find it on the normal package repos I use. I had to use https://www.flycheck.org/en/latest/user/syntax-checkers.html#configuring-checker-chains to set up the golangci-lint as the next checker following lsp.

WTF is cuddling?

Cuddling refers to not leaving extra space between statements in code. See https://github.com/bombsimon/wsl/blob/master/doc/rules.md .

Mutating an external variable from within an anonymous function

Writing a custom linter

https://arslan.io/2019/06/13/using-go-analysis-to-write-a-custom-linter/

About

No description, website, or topics provided.

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published

Languages