Go for Reviewers · Lesson 01

Errors Are Values

The one mental switch that unlocks reading every Go PR. No exceptions — literally.

Why this, first: Your job is to read & review Go PRs. The shape if err != nil { return ... } appears in nearly every function you'll review. It's also the #1 thing Go reviewers flag. Master this and you can parse the control flow of any Go diff.

1 · The instinct to unlearn

In Python, failure travels up the stack on its own. You call a function and trust that if something breaks, an exception will unwind until someone catches it. The happy path reads top-to-bottom; errors are an out-of-band channel.

Go deletes that channel. There is no throw, no raise, no exceptions for ordinary failure. A function that can fail simply returns the error as its last value, and the caller must look at it. This is the error-as-value model.

Python · errors fly
def get_dapp(id):
    # raises KeyError if missing
    return dapps[id]

# caller
try:
    d = get_dapp(id)
except KeyError:
    d = None
Go · errors are returned
func GetDApp(id string) (*DApp, error) {
    d, ok := dapps[id]
    if !ok {
        return nil, ErrNotFound
    }
    return d, nil
}

// caller — must inspect err
d, err := GetDApp(id)
if err != nil { /* handle */ }

An error is just an interface with one method (you'll meet interfaces properly in a later lesson):

type error interface {
    Error() string
}

So a function returning error is returning a value you can store, compare, wrap, or ignore — it does not hijack control flow. [Go blog: Error handling and Go]

2 · The shape you'll see 10,000 times

Because errors are values, the caller checks immediately and returns early. This is the early-return / indent-error-flow idiom. The official rule:

Effective Go: when an if body ends in break, continue, goto, or return, the unnecessary else is omitted — the happy path stays at the leftmost indentation. [source]
Non-idiomatic · nested
d, err := GetDApp(id)
if err == nil {
    use(d)        // happy path
    return d, nil   // drifts right
} else {
    return nil, err
}
Idiomatic · early return
d, err := GetDApp(id)
if err != nil {
    return nil, err   // bail first
}
use(d)            // happy path, flat
return d, nil

Reading trick: every if err != nil block is a place the function can exit. Scan a Go function by jumping between these blocks — they are the failure map. Whatever's left, un-indented, is the success story.

3 · Real code from your repo

From portfolio-service-go/services/dapp/service.go — a service you'll review.

func (s *Service) GetCachedByID(ctx context.Context, id string) (*models.DApp, error) {
    dApps, _, err := s.cache.dApps.Get(ctx)
    if err != nil {
        return nil, err           // ① bail on infra error
    }
    if dApp, ok := dApps[id]; ok {  // ② comma-ok map read
        return dApp, nil          // ③ happy path
    }
    return nil, ErrNotFound       // ④ sentinel: "not found" is not a crash
}

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

Read it as a reviewer — four exit points, three concepts:

Where the Python analogy breaks: a missing map key in Python is an exception; in Go it's a non-event — you get the zero value silently. If a PR does v := m[k] and then uses v without the ok check, that may be a bug hiding behind a zero value, not a crash. That's a real review comment to leave.
Reviewer's eye — what to flag:

4 · Drill — review like it's a PR

Pick the answer. Feedback is instant. No score kept — this is for the rep, not the grade.

Q1 Spot the failure path
In GetCachedByID above, how many ways can the function return to its caller?
Q2 Leave the review comment
A teammate's PR contains this. What do you comment?
val, _ := cache.Get(ctx, key)
return process(val)
Q3 Idiom check
Which error string is idiomatic Go?
I'm your teacher — ask me anything.
Stuck on why err comes last, or what errors.Is / %w wrapping does, or how the errorx namespaces in services/charge/errors.go relate to plain sentinels? Ask in chat. Next lesson goes deeper into the error chain (errors.Is, errors.As, wrapping) using your repo's own patterns.

Terms introduced

Now in your GLOSSARY.md: