A small but full-featured application showcasing go beyond the basics.
Go to file
2024-04-21 22:07:01 -04:00
cmd/furthur basic statement of intent 2024-04-21 22:07:01 -04:00
.gitignore basic statement of intent 2024-04-21 22:07:01 -04:00
go.mod basic statement of intent 2024-04-21 22:07:01 -04:00
readme.md basic statement of intent 2024-04-21 22:07:01 -04:00

Going Further

This project aims to showcase "intermediate" go programming -- once you've learned the language itself, what are the idioms that make for a proficient go programmer?

This is an implementation of a golinks service using only the standard library.

Structure

The go toolchain is very unopinionated about how a project is laid out, except for a few specific folder names and rules:

  • packages with internal/ in the path can only be imported by other packages that they share a "root" with. In go, any package that can be loaded by the compiler can be pulled into another project, even packages that weren't designed to be "libraries" in a traditional sense. The internal convention was introduced as a way to allow the authors of public (or even closed-source) code to truly keep a package internal to its parent module.
  • folders named testdata are ignored by the toolchain, and are used to store exactly that -- files and data that tests are to use as either input or validation.

Outside of that, there are a few conventions that have become relatively common:

  • For modules that generate a command-line binary -- that is, those that have a main package -- it's common to place that entrypoint file in a subfolder of cmd named the same as the expected binary's name. In this repo, that's /cmd/furthur. Running go build ./cmd/furthur in this module results in a binary named furthur.
  • Packages should be named after what they provide, and packages that collect together a lot of related functionality aren't a negative thing. Package names like util or etc are signs that your code is poorly laid-out. If you find yourself needing to put a bunch of shared functionality into one utility class, it might be a sign that all of the packages that use that utility might actually be more coherent as one larger package.
    • See the experience of the stdlib itself with io/ioutil, which ended up being entirely deprecated and moved into io and os.
  • One pattern that used to be recommended before the advent of internal was to explicitly place packages that were "intended" to be public in a folder called pkg, but the community was never uniform in that approach, and it is now generally discouraged.

HTTP Services

Generics

Go has generics now, and they're useful... but mostly for authors of libraries, not for application developers. They have allowed for the standard library to finally ship a bunch of generic functions in the slices package that developers used to have to write themselves.

//go:embed

https://pkg.go.dev/embed

Testing and Benchmarking

Concurrency

It turns out channels are hard to reason about, but that's because concurrency is hard to reason about.

Always remember that read/write access to a shared map must be gated with a mutex.

Go Generate

Build tags

Logging

slog package

init functions and globals

Don't use them! They're hard to reason about and until recent versions of go the order they ran in was undefined, leading to subtle bugs.

Common tools

Other common idioms and stumbling blocks

./... is a common shorthand to pass to parts of the go toolchain that means "this directory and all directories underneath it". You'll often come across it when running tests (go test ./...) or linters (staticcheck ./...).

When working with closed-source code that is not cached in the public GOPROXY, you need to set GOPRIVATE to match the name of the module or else you'll get cryptic errors when go tries to build. This can be set globally for the entire toolchain using go env: go env -w GOPRIVATE="https://git.yetaga.in/*". See https://pkg.go.dev/cmd/go#hdr-Configuration_for_downloading_non_public_code for more details.

  • Rob Pike's Go Proverbs. Of particular value:
    • The bigger the interface, the weaker the abstraction.
    • A little copying is better than a little dependency.
    • Clear is better than clever.
    • Don't just check errors, handle them gracefully.
  • Mat Ryer's How I write HTTP services in Go after 13 years