Skip to content
Theory · Final project · lesson 6 of 7

The final project

Picture assembling a bookshelf from a kit. There are no new parts in the box: you have held the boards, screws and brackets before. The task is different—to join them in the right order so the shelf stands straight.

Programs work the same way. A type in a real program usually rests on several ideas at once. For example, it:

  • keeps data in a map;
  • changes it through methods;
  • returns an error when an operation is impossible.

The final exercise works the same way: it introduces no new constructs; you combine familiar ones.

The warehouse

The exercise is a warehouse that counts the units of each item. The type Inventory keeps the quantities in a map, and the constructor NewInventory creates an empty warehouse. The methods share the work like this:

  • Add puts items in;
  • Take takes them out;
  • Count reports a quantity;
  • Names returns the list of items.

Parts from earlier themes

The table shows which parts from earlier themes each piece of the exercise needs:

Part of the exerciseWhat it needs
the field and the constructora struct, map, make, the function NewInventory
Add and Takemethods with pointer receivers, delete
errors from Takereturning an error, sentinel errors from the stub
Namesrange over a map, append, slices.Sort or sort.Strings

Errors from Take

If an item cannot be taken, Take returns an error and leaves the warehouse unchanged. Both errors are already declared in the stub, and the tests recognize them with errors.Is.

ErrUnknownItem

Take returns ErrUnknownItem when the item name is not in the warehouse.

ErrNotEnough

Take returns ErrNotEnough when the item is there, but there are fewer units than qty.

The order in Names

The Names method sorts the names itself, because range over a map visits the keys in an unpredictable order.

How to proceed

Write the solution step by step:

  1. Start with the field and the constructor: without the map, the other methods have nothing to work with. It is the base of the shelf—without it there is nothing to fix the other boards to.
  2. Then write one method at a time.
  3. Run the tests after each method.

Two tests

There are two tests. Each test creates its own warehouse, so a bug caught in one does not affect the other.

TestInventory

TestInventory checks Add, Count, Names and a successful Take.

TestTake

TestTake checks the errors of Take and an item taken out in full.

A failure message

This is where the shelf picture stops working. A crooked shelf is visible at once, while a bug in code shows up only in the tests. A check that passes prints nothing. A failed check prints a line with the test file's name and line number—this is how the stub answers the first check:

TEXT
inventory_test.go:14: inv.Count("apple") = -1, want 8

Let's read this line:

  • left of = is the call from the test;
  • right of it is your code's result;
  • after want comes the expected value.

Here -1 comes from the stub: the Count method is not written yet. The outcome of each test is the line --- PASS or --- FAIL with its name.

Within a test, the result of a call depends on the calls before it. So fix the first failed check: the ones after it often fail because of it.

Common mistake

The constructor returns the struct, but the map inside it is never created:

GO
package main

type Inventory struct {
    items map[string]int
}

func NewInventory() *Inventory {
    return &Inventory{} // ✗
}

func main() {
    inv := NewInventory()
    inv.items["apple"] += 5
}
TEXT
panic: assignment to entry in nil map

The field items gets the zero value nil, and writing to a nil map causes a panic. Create the map in the constructor: return &Inventory{items: make(map[string]int)}.

Key points

  • The final exercise introduces no new constructs: it combines a struct, a map, methods and errors.
  • The constructor creates the map with make; writing to a nil map causes a panic.
  • Write one method at a time and run the tests after each.
  • Fix the first failed check: the line = -1, want 8 shows what your code returned and what was expected.

Subscribe to take the course

The course is free. Sign in with Telegram and subscribe to the channel to open every lesson.

Sign in another way