Tech Handbook Null Yard

Go - Reading Code

Go is a small, convention-heavy language with a strong built-in toolchain. A practical way to read a project is to start with go.mod and main(), then follow data through structs, interfaces, functions and explicit errors.

This handbook covers Go 1.27.x, including generic methods introduced in Go 1.27.

Related topics: APIs and System Integrations, SQL and PostgreSQL for Developers, Software Testing and Docker.

Table of Contents

1. How to think about Go

Go is deliberately small, explicit and convention-heavy. Read code in terms of packages, structs, interfaces, functions and data flow rather than searching for framework magic.

2. Installation and tools

Debian

sudo apt install golang
go version

FreeBSD

pkg install go
go version

Editor

Any editor works. Neovim, Vim and VS Code with gopls are common choices.

3. First program and file anatomy

package main

import "fmt"

func main() {
    fmt.Println("hello")
}

package main

Marks an executable package.

import "fmt"

Imports the standard fmt package.

func main()

Entry point of an executable program.

4. Syntax - the most important rules

Code blocks

Curly braces delimit blocks.

Semicolons

Normally omitted; the lexer inserts them automatically.

Comments

Use // for line comments and /* ... */ for block comments.

Names

Exported identifiers begin with an uppercase letter; unexported identifiers begin with lowercase.

5. Variables, constants and zero values

Full declaration

var count int = 10

Short declaration :=

count := 10

Valid inside functions.

Multiple values

name, age := "Ada", 30

Constants

const MaxRetries = 3

Zero values

Numbers become 0, booleans false, strings empty, pointers/maps/slices/functions/interfaces nil where applicable.

6. Basic data types

Integers

Common types: int, int8/16/32/64 and unsigned variants.

Floating-point numbers

Use float32 or float64; float64 is the common default.

Boolean

var ok bool

Character aliases

byte is alias for uint8; rune is alias for int32.

Named types

type UserID int64

Named types improve domain clarity and are distinct from their underlying type.

7. String, byte and rune

Strings are immutable byte sequences, usually UTF-8 text.

UTF-8

Range over a string to iterate Unicode code points:

for i, r := range s {
    fmt.Println(i, r)
}

8. Arrays, slices and maps

Array

var a [3]int

Array length is part of the type.

Slice

items := []string{"a", "b"}
items = append(items, "c")

Important

Slices are descriptors over backing arrays; copying a slice does not necessarily copy underlying data.

Map

m := map[string]int{"a": 1}
v, ok := m["a"]

9. Struct - the basic data building block

type User struct {
    ID   int64
    Name string
}

Nesting

A struct can contain another struct as a named field.

Embedding

type Admin struct {
    User
    Level int
}

Embedding promotes fields/methods but is composition, not inheritance.

10. Pointers

p := &user
fmt.Println(p.Name)

Why pointers?

Use pointers to share/mutate a value, avoid large copies, or represent optional identity/reference semantics.

nil

A pointer can be nil. Check before dereferencing when nil is possible.

11. Control statements

if

if err != nil {
    return err
}

else

Go keeps braces explicit and discourages deeply nested logic through early returns.

switch

switch status {
case "ready":
    ...
default:
    ...
}

for

Go has one loop keyword: for. It covers classic loops, while loops and range loops.

12. Functions

func Add(a, b int) int { return a + b }

Multiple return values

func Lookup(id int) (User, error)

Named return values

Allowed, but use sparingly when they genuinely improve clarity.

Function as a value

f := strings.ToUpper

Function as an argument

Callbacks and higher-order helpers accept function values.

Function as a result

Functions can return closures.

Variadic

func Sum(values ...int) int

13. Methods and receivers

Value receiver

func (u User) Label() string { return u.Name }

Pointer receiver

func (u *User) Rename(name string) { u.Name = name }

Use when mutating receiver or avoiding copies.

14. Interfaces

Interfaces are satisfied implicitly.

Example

type Reader interface {
    Read([]byte) (int, error)
}

Empty interface

any is alias for interface{} and can hold any value.

Type assertion

s, ok := v.(string)

Type switch

switch x := v.(type) {
case string:
    _ = x
}

15. Generics

Generics parameterize functions/types over sets of types.

Constraints

func Max[T cmp.Ordered](a, b T) T

Generic type

type Box[T any] struct { Value T }

Generic methods

Methods may use type parameters already declared on the receiver type. Go does not allow methods to introduce their own independent type parameters.

16. Errors - error

Errors are normal return values.

Creating an error

errors.New("not found")
fmt.Errorf("load user: %w", err)

Wrapping errors

Use %w so callers can inspect the original error with errors.Is/errors.As.

Sentinel errors

var ErrNotFound = errors.New("not found")

Why is if err != nil everywhere?

Because Go makes failure paths explicit instead of hiding them in exceptions.

17. defer, panic, recover

defer

Schedules a call when the surrounding function returns. Common for Close/Unlock cleanup.

panic

Abort normal flow for truly exceptional programmer/runtime conditions, not ordinary validation errors.

recover

Can intercept panic inside a deferred function. Usually used at process/request boundaries, not as routine control flow.

18. Packages, modules and imports

File

A .go file belongs to exactly one package.

Package

A directory normally contains source files for one package.

Module

A module is a versioned collection of packages defined by go.mod.

Example

example.com/myapp
├── go.mod
├── cmd/server
└── internal/app

19. Name visibility

Uppercase identifiers are exported from a package; lowercase identifiers are package-private.

20. Typical project structure

cmd/

Executable entry points, often one directory per binary.

internal/

Packages importable only within the parent module tree.

pkg/

Optional convention for reusable public packages; not required by Go.

21. go.mod, go.sum and dependencies

Adding a library

go get MODULE@VERSION

Tidying dependencies

go mod tidy

go.sum

Contains checksums used to verify module content.

Module list

go list -m all

Why does a dependency exist?

go mod why MODULE

Dependency graph

go mod graph

22. Running and compiling

Run project

go run .

Specific file

go run main.go

Build

go build

Binary name

go build -o myapp .

Build a specific program

go build ./cmd/server

All packages

go build ./...

Install a program

go install example.com/tool@latest

23. Cross-compilation

GOOS=linux GOARCH=amd64 go build .

Important: CGO

Pure Go cross-compiles easily. CGO introduces native compiler/library requirements.

24. Formatting and code analysis

gofmt

gofmt -w .

Canonical formatting is part of Go culture.

go vet

go vet ./...

staticcheck

Third-party static analyzer that catches many bugs and suspicious patterns.

Practical set

gofmt -w .
go vet ./...
go test ./...

25. Tests

One test

func TestAdd(t *testing.T) {
    if got := Add(2,3); got != 5 { t.Fatalf("got %d", got) }
}

Table-driven tests

Use slices of test cases and loop with t.Run.

HTTP test

Use net/http/httptest to test handlers without binding a real port.

26. Benchmarks, fuzzing and race detector

Benchmark

func BenchmarkParse(b *testing.B) {
    for i := 0; i < b.N; i++ { Parse(data) }
}

Coverage

go test -cover ./...

Race detector

go test -race ./...

Fuzzing

Use FuzzXxx(*testing.F) tests to explore unexpected input automatically.

27. Debugging

Delve is the standard Go debugger.

Simplest debugging

Start with tests/logging and go test -run TestName -v; use Delve when state/control flow needs interactive inspection.

28. Files, directories and operating system

Read entire file

data, err := os.ReadFile(path)

Write

err := os.WriteFile(path, data, 0644)

Open file

f, err := os.Open(path)
defer f.Close()

Create directory

err := os.MkdirAll(path, 0755)

Paths

Use path/filepath for OS filesystem paths.

Environment variables

value := os.Getenv("APP_ENV")

29. JSON

Encoding

data, err := json.Marshal(v)

Decoding

err := json.Unmarshal(data, &v)

HTTP

Use json.NewEncoder(w).Encode and json.NewDecoder(r.Body).Decode for streams.

omitempty

Omits zero-valued fields during encoding when the tag requests it.

Ignore field

Secret string `json:"-"`

30. Time and dates

Use time.Time, time.Duration, time.NewTicker, time.After and explicit locations/time zones.

31. Logging

Standard library offers log and structured log/slog. Include useful context, not secrets.

32. context.Context

Context carries cancellation, deadlines and request-scoped values across API boundaries.

HTTP request

Incoming requests expose r.Context().

Timeout

ctx, cancel := context.WithTimeout(parent, 2*time.Second)
defer cancel()

Important rules

Pass context explicitly, usually as the first parameter. Do not store it in structs casually. Always call cancel when you create a cancellable child context.

33. Concurrency: goroutines and channels

Goroutine

go work()

Anonymous function

go func() { defer wg.Done(); work() }()

Channel

ch := make(chan Result)

Buffered channel

ch := make(chan Result, 10)

Close

The sender that owns completion usually closes a channel. Receivers should not close channels they do not own.

select

Wait on multiple channel operations/cancellation cases.

Channel timeout

select {
case v := <-ch:
    _ = v
case <-time.After(time.Second):
}

34. Mutex, WaitGroup and atomics

Mutex

Protect shared mutable state with sync.Mutex.

RWMutex

Useful when many readers and few writers justify the added complexity.

WaitGroup

Wait for a set of goroutines to finish.

Atomics

Use sync/atomic for simple lock-free counters/state where appropriate.

35. HTTP in the standard library

Handler

Anything implementing ServeHTTP(http.ResponseWriter,*http.Request).

HandlerFunc

Function adapter for handlers.

Modern ServeMux

Recent Go versions support richer method/path patterns in http.ServeMux.

Status

w.WriteHeader(http.StatusCreated)

Set headers before writing status/body.

JSON response

Set Content-Type: application/json and encode with json.Encoder.

Server with configuration

Use http.Server with explicit timeouts and graceful shutdown rather than bare http.ListenAndServe for production.

36. HTML templates and static files

Template

Use html/template for escaped server-side HTML rendering.

Static files

Use http.FileServer or embedded filesystem handlers.

37. Embed - package files into the binary

//go:embed templates/*
var files embed.FS

38. Backend: routers and frameworks

1. Standard net/http

Excellent default for small/medium services.

2. Chi

Lightweight router that stays close to net/http.

3. Gin

Feature-rich HTTP framework with its own conventions.

4. Echo

Another mature web framework with routing/middleware helpers.

5. Fiber

Fast framework inspired by Express, built on fasthttp rather than net/http.

Mental model

Learn net/http first; frameworks mostly add routing, middleware ergonomics and conventions.

39. Databases

Native pgx

pgx is a strong PostgreSQL driver/toolkit.

Pool

Use pgxpool.Pool for concurrent application access.

database/sql

Standard abstraction used by many drivers/tools.

Transaction

Begin, defer rollback, perform operations, then commit only after all succeed.

40. ORM, sqlc and migrations

GORM

Full ORM; productive but adds abstraction and behavior you need to understand.

sqlc

Generates type-safe Go code from SQL; good when you want explicit SQL.

Migrations

Use versioned schema migrations with tools such as golang-migrate, Goose or framework-specific tooling.

41. Redis and cache

Use Redis for caching, queues or ephemeral coordination when the architecture needs it. Define expiration and failure behavior explicitly.

42. Application configuration

.env

Useful locally, but do not commit real secrets.

Viper

Popular configuration library supporting files, env vars and multiple formats. Standard library plus explicit config structs are often enough for small apps.

43. CLI and tools

Cobra

Popular framework for larger command-line applications with subcommands and flags.

44. Games in Go

Go is suitable for 2D games and tooling. Ecosystem is smaller than Unity/Godot but pleasant for code-first projects.

45. Ebitengine - practical 2D game start

Ebitengine provides a cross-platform game loop and graphics/audio/input APIs.

Update

Update game state at fixed ticks.

Draw

Render the current state.

Layout

Define logical screen dimensions.

Run

go run .

What belongs in Game?

World state, player state, assets/references, timers and systems needed by update/draw.

46. raylib-go and other game libraries

raylib-go

Go bindings for raylib; simple and effective for 2D/3D experiments.

SDL

Low-level multimedia bindings; powerful but more manual.

Pixel

Older Go 2D library; useful to recognize in existing projects.

47. CGO - when Go uses C

CGO enables calls into C libraries but complicates builds, portability and cross-compilation. Prefer pure Go unless native integration is needed.

48. Common Go idioms

if err != nil

Explicit error handling.

Ignoring a value

_, err := io.Copy(dst, src)

Compile-time interface check

var _ io.Reader = (*MyReader)(nil)

Constructor-like function

NewClient(...) *Client is a convention, not a language feature.

Functional options

NewServer(WithPort(8080), WithLogger(log)) pattern for configurable constructors.

Must...

Convention for helpers that panic on failure when failure is programmer/startup error.

New...

Convention for constructors.

With...

Convention for options/modifiers.

49. Things that look strange but are normal

:=

Short variable declaration.

&User{}

Pointer to a newly constructed User value.

*User

Pointer-to-User type.

[]User

Slice of User values.

[]*User

Slice of pointers to User.

map[string]User

Map string keys to User values.

map[string][]User

Map string keys to slices of users.

chan Result

Bidirectional channel of Result.

<-chan Result

Receive-only channel.

chan<- Result

Send-only channel.

func() error

Function type returning error.

func(context.Context, string) (*User, error)

Function type taking context/string and returning user pointer plus error.

...string

Variadic string parameter.

struct{}

Zero-size empty struct, commonly used as a signal/set value.

map[string]any

Dynamic JSON-like object. Use typed structs when schema is known.

50. How to read an unfamiliar Go project

Step 1 - find go.mod

It reveals module path, Go version and top-level dependencies.

Step 2 - find package main

Locate executables, often under cmd/.

Step 3 - see what main() constructs

Follow dependency wiring: config, DB, services, handlers, server.

Step 4 - recognize layers

Handler

HTTP/RPC boundary; parses requests and writes responses.

Service

Business logic/application use cases.

Repository

Persistence/data access.

Step 5 - read structs

Struct fields show the application's main data and dependencies.

Step 6 - read interfaces

Interfaces show boundaries and what implementations are expected to do.

Step 7 - find HTTP routes

Search for Handle, HandleFunc, router.Get/Post or framework-specific registration.

Step 8 - find goroutines

Search for go and understand lifecycle/cancellation.

Step 9 - find I/O

Database, HTTP, filesystem, queues and external APIs are major failure boundaries.

Step 10 - run tests

go test ./...

51. Mini backend - complete shape

cmd/server/main.go
internal/http/handlers.go
internal/service/users.go
internal/repository/postgres.go
internal/model/user.go

Main wires dependencies; handlers translate HTTP; services hold business rules; repositories persist data.

52. Mini game - structure example

cmd/game/main.go
internal/game/game.go
internal/game/player.go
internal/assets/

Keep update/render state together and isolate reusable systems when complexity grows.

53. Command cheat sheet

Project

go mod init example.com/app
go run .
go build ./...

Dependencies

go get MODULE
go mod tidy
go mod why MODULE

Quality

gofmt -w .
go vet ./...
go test -race ./...

Information

go version
go env
go list ./...

54. Syntax cheat sheet

Variable

x := 1

Constant

const N = 10

Slice

xs := []int{1,2,3}

Map

m := map[string]int{}

Struct

type User struct { Name string }

Pointer

p := &u

Function

func Add(a,b int) int

Function with error

func Load() (Data, error)

Method

func (u User) Name() string

Interface

type Store interface { Save(User) error }

if

if ok { ... }

switch

switch x { case 1: ... }

for

for i := 0; i < 10; i++ { }

range

for i, v := range xs { _ = i; _ = v }

Error handling

if err != nil { return err }

Goroutine

go work()

Channel

ch := make(chan T)

Context

ctx, cancel := context.WithCancel(parent)

JSON tag

Name string `json:"name"`

Grouped import

import (
    "context"
    "fmt"
 )

55. What to know now vs later

You must recognize well

Packages/modules, structs, methods, interfaces, errors, slices/maps, pointers, net/http basics and tests.

You should understand generally

Contexts, goroutines/channels, DB layers, generics and common project structure.

You can initially treat as 'I know it exists'

Advanced reflection, unsafe, compiler internals, cgo edge cases and complex lock-free algorithms.

For web backends

Focus on net/http/chi, context, JSON, PostgreSQL, configuration, logging, testing and graceful shutdown.

For games

Focus on update loops, input, state, asset management, rendering and your chosen game library.

56. Further documentation

Official Go

https://go.dev/doc/ https://pkg.go.dev/

Backend

Read net/http, context, database/sql and your chosen router/database driver's documentation.

Games

Ebitengine: https://ebitengine.org/ Raylib: https://www.raylib.com/

Mental summary

module
→ packages
→ structs/interfaces/functions
→ explicit errors
→ goroutines/context for concurrency
→ tests + gofmt + go vet

One practical final tip

When reading Go, start from go.mod and main(), then follow concrete values being constructed. Go code is usually easier to understand by tracing data and interfaces than by reading every file in directory order.

Official references

  • Go documentation: https://go.dev/doc/
  • Go 1.27 release notes: https://go.dev/doc/go1.27
  • Go language specification: https://go.dev/ref/spec
  • Standard library and packages: https://pkg.go.dev/