Go for Reviewers · Lesson 03

Decode the Signature

Read any Go function header at a glance. It's the densest line in the file — and the one that tells you the most.

Why this, now: You said the concepts are clear but the syntax slows the read. The fix isn't "learn all of Go's syntax" — it's to make the function header instant, because it packs the most unfamiliar tokens (*, [], receivers, multi-return) into one line, and it's the first thing you read in every PR hunk.

1 · One real line, fully decoded

From services/asset/service.go — a real signature you'll review.

func (s *Service) GetAssetByImplementation(ctx context.Context, chain types.Chain, address *types.Address, currency types.Currency) (*models.Asset, error) {
receiver — which type owns this method parameters — name type, name first returns — value + error, always last

Read left-to-right, five beats:

  1. func — it's a function.
  2. (s *Service) — the receiver. This is a method on *Service. s is the Go equivalent of Python's self — but you name it yourself, and it sits before the function name.
  3. GetAssetByImplementation — the name. Capital Gexported (public). lowercase ⇒ package-private.
  4. (ctx context.Context, …) — parameters. Each is name type — name first, type after (the reverse of C/Java). ctx context.Context being first is a hard convention. [rule]
  5. (*models.Asset, error) — the return. Two values: a pointer to an Asset, and an error. The error-last shape from Lesson 1.
The reading order to internalize: receiver → name → params → returns. Once this is automatic, every method header parses in well under a second, and you can skim a 40-method file by its signatures alone.

2 · The token table — Go vs your Python instinct

These are the symbols that don't exist (or mean something else) in Python. Memorize this table; it's the bulk of the friction.

Go tokenReads asPython instinct to override
name typevariable/param name of typetype comes afterNo annotations / name: type
*T"pointer to T" — an address, may be nilEverything's a reference; no *
&x"address of x" — take a pointer to itNo address-of operator
[]T"slice of T" — a growable listlist[T]
map[K]V"map from K to V"dict[K, V]
x := vdeclare and assign, type inferred (new variable)x = v for everything
x = vassign to an already-declared xsame = as above — Go splits them
(A, B) returnmultiple return values (a tuple-ish, but not a tuple type)return a, b packs a real tuple
chan T / <-ch"channel of T" / "receive from ch" (concurrency — later lesson)No direct analog (asyncio queues, loosely)
`tag:"..."`struct tag — metadata read by libs via reflectionDecorators / pydantic Field(...)
The one that bites most: := vs =. := declares a new variable; = assigns to an existing one. A PR that writes x := inside an inner scope when it meant to update the outer x creates a shadowed variable — a fresh local that silently masks the outer one. This is a real, common Go bug to flag. (We'll hunt shadowing directly in a later lesson.)

3 · * in two roles — don't conflate them

The asterisk does two different jobs depending on where it sits. Reviewers must read them apart instantly:

// in a TYPE position → "pointer to"
func f(a *Asset)        // a is *Asset
var p *Asset           // p holds an address
(s *Service)             // pointer receiver
// in an EXPRESSION → "value at" (deref)
x := *p                 // read the Asset p points to
*p = newAsset           // write through the pointer
// and & is the inverse:
p = &asset              // take address of asset

Rule of thumb when scanning: * next to a type name = "pointer to"; * next to a variable in an expression = "the value it points at". & is always "give me the address." The deep why (when Go copies, why pointer receivers exist) is Lesson 4 — here you just need to read them.

4 · Other declaration shapes you'll see

// struct with tags (config, seen all over the repo)
type Config struct {
    UpdatePeriod time.Duration `validate:"required"`  // field  type  `tag`
    BatchSize    int           `validate:"required"`
}

// constructor returning the (value, cleanup, error) triple — a repo idiom
func NewService(config *Config, logger *zap.Logger, client Client) (*Service, func(), error)
//   params: name type, name type, name type      returns: *Service, a func(), error

// short var decl with inferred type + comma-ok (Lesson 1)
dApp, ok := dApps[id]   // dApp:*DApp, ok:bool — both NEW vars via :=

Note func() as a type in the return list — Go functions are values, so "a function taking nothing, returning nothing" is a first-class type. That's the cleanup closure you saw in Lessons 1–2.

5 · Drill — decode at a glance

Read each header. Instant feedback. Reps, not grades.

Q1 Parse the header
func (c *Client) ListDApps(ctx context.Context, limit, offset int64) ([]*dapp_storage.DApp, error)
What does this return?
Q2 := or =
asset, err := s.getAsset(ctx, chain, addr, cur, false)
What is := doing here?
Q3 Which is the receiver?
func (s *Service) GetAssetByID(ctx context.Context, chain types.Chain, id string, currency types.Currency) (*models.Asset, error)
Pick the receiver — the "self".
Q4 * role check
p := &asset
*p = updated
What do these two lines do?
I'm your teacher — ask me anything.
Want a one-page printable cheat sheet of these tokens for your second monitor while reviewing? Say so — I'll add it to reference/. Or paste any line from a real PR that won't parse for you and I'll decode it token-by-token.

Terms introduced

Added to your GLOSSARY.md: