JavaRush /Courses /Go SELF /doc.go and package documentation

doc.go and package documentation

Go SELF
Level 36, Lesson 1
Available

1. Package comment and synopsis

If you are just starting out, it may seem like: “Well, I named the package todo, it has tasks… what else is there to explain?”. The problem is that a package is not just a folder with files. A package is a unit of reuse: people import it, read it on pkg.go.dev, and treat its interface as a promise. Go tools can build documentation from comments directly from source code, and this is not “showing off”, but a genuinely working communication channel with your future reader.

Imagine your package is a coffee shop. The code is the kitchen. And the package documentation is the sign at the entrance: “We brew coffee here, we don’t repair bicycles.” You can, of course, guess by the smell… but it is still better to write it down.

Package comment: the first paragraph a person sees

When someone opens package documentation (locally via tools, in an IDE, or on a documentation site), the first thing they see is the package description. This description comes from the so-called package comment—a comment that appears immediately before the line package name.

The mechanics matter here: if the comment is placed “nearby” but not directly above package, tools may treat it as a regular comment “into the void”. So the rule is simple and a bit pedantic (like most useful rules): the package comment goes directly before package.

A minimal example looks like this:


// Package todo provides primitives for working with tasks.
package todo

Notice the style: the first sentence starts with the word Package and the package name. This is a GoDoc convention. It does not have to “compile instantly” in your brain, but it helps tools and people scan documentation quickly.

Why the first sentence matters most

A package comment has a small but powerful effect: the first sentence is usually used as the synopsis—a short package description in lists, search results, and documentation navigation.

That means a sentence like:

"Package todo contains useful stuff."

…is like a sign that says “Food”. Formally true, practically useless.

It is much better when the first sentence answers “what is this” and “why”:

// Package todo implements a minimal task model and validation helpers.
package todo

Here the reader immediately understands the boundary of responsibility: “model + validation”, not “everything for the app”. And, nicely enough, you also start designing the API more carefully, because it becomes embarrassing to write “useful stuff”. (A rare case where embarrassment improves quality.)

3. doc.go: why it exists and what to write in it

Why doc.go exists if a package comment can be written in any file

Technically, you can write the package comment in any .go file in your package, as long as it is placed directly before package. Then the logical question is: why have a separate doc.go?

The reason is practical: as a project grows, files accumulate types, functions, imports, and the package comment starts to “sink” among the code. doc.go is a convention: we move package documentation into a dedicated file with little to no logic, but with a clear “passport” for the package.

Another benefit: doc.go is usually stable across refactors. You can rename files, move types around, but the “package page” stays in one place.

A classic doc.go looks like this:

// Package todo implements a minimal task model and validation helpers.
//
// It is designed to be used by CLI and HTTP adapters in this project,
// keeping business rules independent from I/O.
package todo

There are no imports, no code, no init(). Just text. That is exactly how it should be in most cases.

What to write in package documentation: purpose, boundaries, and signposts

Package documentation should not turn into a novel. But a single line is often too little. A good package comment usually answers three questions.

The first question is why the package exists. This is your main point: what problem the package solves, why it is separated out, what role it plays.

The second question is boundaries of responsibility. What the package does and what it explicitly does not do. This is surprisingly important: it saves hours of future time because it reduces false expectations.

The third question is API signposts. Not a list of every function (the documentation itself is for that), but a small “map”: which types/functions are the main entry points.

In text, it can look like this (with a bit of formatting):

// Package todo implements a minimal task model and validation helpers.
//
// The package focuses on these primitives:
//     - Task: a domain entity for a single task
//     - Status: task lifecycle state
//     - ValidateTitle: input validation helper
//
// The package does not handle persistence or CLI/HTTP I/O.
package todo

One important formatting detail: lines with indentation (a space or a tab at the beginning) are usually rendered by GoDoc as a “pre-formatted” block. This is convenient for small “API maps” and short protocols.

doc.go is still a Go file

It is important to remember one thing: doc.go is not a “Markdown documentation file”, but an ordinary .go file that participates in the package build. So if you start adding code, imports, and side effects there, they will actually affect the program.

Sometimes beginners (or tired adults) accidentally turn doc.go into a dumping ground:

  • they add constants “since the file exists anyway”;
  • they add init() “well where else?”;
  • they put examples there “so they are close by”.

Technically, the compiler does not mind. But from a maintenance standpoint, this is almost always a bad idea: documentation should be visible and predictable, not mixed with logic. If you really want it “to live next to it”, create a separate normal file for code and keep doc.go as text.

4. Example in a learning project and the boundaries of documentation

How this looks in our learning app

So the examples do not float in mid-air, let’s continue evolving our learning app: a small task manager. Earlier we had a main that did everything itself, and then we started splitting the code into packages (because otherwise everything turns into one file with 1200 lines and one big sadness).

Let’s imagine the current project structure:

taskman/
  go.mod
  cmd/
    taskman/
      main.go
  internal/
    todo/
      task.go
      validate.go
      doc.go

We keep domain logic in internal/todo, and cmd/taskman is the application entry point (CLI). Our goal now is to make it so that a person opening the todo package immediately understands: “what this is, why it exists, what the key entities are”.

Example: internal/todo/doc.go

// Package todo implements core domain types for a simple task manager.
//
// The package provides a small, stable API around tasks and their validation.
// It intentionally does not deal with printing, user input, or file storage.
package todo

Notice that we do not promise extra things. The package does not “run the application”, does not “build the CLI”, does not “read files”. It is about the domain model.

How package documentation differs from type documentation

Sometimes you want to list every field of Task in the package comment, describe every function, and explain every variable. That is an understandable desire, but it makes the documentation heavier, not more useful.

Package documentation answers the question “what kind of box is this?”. Type documentation answers the question “what is inside the box and how do I use it?”.

For example, we have Task:

package todo

// Task represents a user task in the system.
type Task struct {
	ID     int
	Title  string
	Done   bool
}

And the package comment does not have to repeat that field-by-field. Its job is to explain why Task exists in this package at all and what the package boundaries are.

It is like a museum sign: at the entrance it says “Impressionists Hall”, and next to each painting there is a label. Nobody expects the entrance sign to be a catalog with every painting and frame.

5. Package conventions and how this becomes a “page”

Document conventions that are not visible from signatures

A function signature shows types, but it often does not show conventions. And conventions are what people argue about later in code review.

Imagine we have a title validation function:

package todo

import "errors"

var ErrEmptyTitle = errors.New("empty title")

// ValidateTitle validates a task title.
func ValidateTitle(title string) error {
	if title == "" {
		return ErrEmptyTitle
	}
	return nil
}

The function comment (from the previous lecture) locks down the details: when an error is returned and which one. But at the package level, it is useful to state the overall philosophy once, for example: “validation returns errors instead of panicking; the package does not print messages to the user”. Such things are better written in doc.go, because these are properties of the entire package, not just one function.

How the package comment becomes a “package page”

To help you form a mental picture, it is useful to imagine a simple pipeline. The documentation tool reads the sources, finds the package comment and doc comments, and builds a “showcase” for the package that you can view locally or on documentation sites.

flowchart LR
    A[package .go files] --> B[GoDoc comments]
    B --> C[Documentation tool]
    C --> D[Package page: description, exported elements]

You do not need to remember specific commands right now. The important idea is this: comments in Go are not only for the programmer “here and now”, but also for tools.

doc.go style: tone and volume

There is a temptation to write package documentation either too dry (“Package todo provides functions.”) or too lyrical (“This package is the soul of our application…”). It is better to stay in the middle.

A good tone is straightforward technical Russian/English (usually English in code), without pathos, but with clear wording. A couple of sentences about purpose, a couple about boundaries, and, if needed, a small API map. That’s it.

And yes, a little self-irony is fine, but carefully: documentation is still a contract. The joke “don’t use this in prod, it will break anyway” might be funny, but as a contract it is terrible. Better to joke in lectures, and in doc.go be adult and responsible (or at least pretend).

6. Common mistakes when working with doc.go and package documentation

Error No. 1: the package comment is not where it should be.
If the comment is written at the top of the file, but between it and package there is a blank line, an import, or some code, tools may not attach it to the package. As a result, the package documentation will be empty or will look like the package is “without a passport”. The fix is simple: the comment must be directly above package.

Error No. 2: the first sentence is too generic and does not help explain the purpose.
Phrases like “Package utils …” or “Package helpers …” tell the reader almost nothing. It is better to spend 20 seconds and write what exactly those “utils” are and what they are for. You will be surprised how much faster architectural decisions get made after that.

Error No. 3: turning doc.go into a file with logic.
Technically Go allows it, but in practice it quickly leads to mess: documentation gets mixed with implementation, imports appear “for a single constant”, and then someone adds init() and the magic begins. If the file is called doc.go, keep it textual.

Error No. 4: package documentation duplicates the documentation of every type.
When the package comment turns into an encyclopedia, people stop reading it. Package documentation should be an entry point and a map of the terrain, not a detailed description of every stone on the road.

Error No. 5: the package promises more than the code actually guarantees.
If doc.go says “the package provides stable output ordering” or “validation always returns ErrX”, but in reality it does not, the documentation becomes a trap. Document only what you truly consider a contract and are willing to maintain.

1
Task
Go SELF, level 36, lesson 1
Locked
Library passport
Library passport
1
Task
Go SELF, level 36, lesson 1
Locked
Two paragraphs
Two paragraphs
1
Task
Go SELF, level 36, lesson 1
Locked
Utilities map
Utilities map
1
Task
Go SELF, level 36, lesson 1
Locked
Parsing contract
Parsing contract
Comments
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION