Comments, Docstrings, and Documentation
Good documentation is crucial for maintainable code. Go has excellent built-in documentation tools and conventions. This guide covers Go’s comment conventions, godoc, and best practices for documenting your code.
Comment Basics
Single-Line Comments
package main
import "fmt"
func main() {
// This is a single-line comment
fmt.Println("Hello, World!")
x := 42 // Inline comment
fmt.Println(x)
}
Multi-Line Comments
package main
import "fmt"
/*
This is a multi-line comment.
It can span multiple lines.
Useful for longer explanations.
*/
func main() {
fmt.Println("Hello, World!")
}
Documentation Comments
Package Documentation
// Package users provides user management functionality.
//
// This package handles user creation, retrieval, and management.
// It includes authentication and authorization features.
package users
import "errors"
var ErrNotFound = errors.New("user not found")
Function Documentation
package users
// NewUser creates a new user with the given name and email.
//
// It returns a pointer to the newly created User.
// If name or email is empty, it returns nil.
func NewUser(name, email string) *User {
if name == "" || email == "" {
return nil
}
return &User{Name: name, Email: email}
}
// Save persists the user to the database.
//
// It returns an error if the user cannot be saved.
// Common errors include database connection failures.
func (u *User) Save() error {
// Implementation
return nil
}
Type Documentation
package users
// User represents a user in the system.
//
// It contains basic user information including ID, name, and email.
// The ID is automatically assigned when the user is created.
type User struct {
ID int // Unique identifier
Name string // User's full name
Email string // User's email address
IsActive bool // Whether the user is active
}
// Status represents the status of a user.
type Status int
const (
StatusActive Status = iota // User is active
StatusInactive // User is inactive
StatusSuspended // User is suspended
)
Constant and Variable Documentation
package config
// DefaultTimeout is the default timeout for operations in seconds.
const DefaultTimeout = 30
// MaxRetries is the maximum number of retries for failed operations.
const MaxRetries = 3
// ErrNotFound is returned when a resource is not found.
var ErrNotFound = errors.New("not found")
// ErrUnauthorized is returned when access is denied.
var ErrUnauthorized = errors.New("unauthorized")
Godoc Format
Godoc Conventions
package math
// Add returns the sum of a and b.
func Add(a, b int) int {
return a + b
}
// Subtract returns the difference of a and b.
func Subtract(a, b int) int {
return a - b
}
// Multiply returns the product of a and b.
func Multiply(a, b int) int {
return a * b
}
// Divide returns the quotient of a and b.
// It returns an error if b is zero.
func Divide(a, b int) (int, error) {
if b == 0 {
return 0, errors.New("division by zero")
}
return a / b, nil
}
Godoc with Examples
package math
// Add returns the sum of a and b.
//
// Example:
// result := Add(2, 3)
// fmt.Println(result) // Output: 5
func Add(a, b int) int {
return a + b
}
Godoc with Code Blocks
package strings
// Split splits a string by a separator.
//
// Example:
// parts := Split("a,b,c", ",")
// // parts is []string{"a", "b", "c"}
//
// If the separator is not found, it returns a slice with the original string.
func Split(s, sep string) []string {
// Implementation
return nil
}
Documenting Interfaces
package io
// Reader is the interface that wraps the basic Read method.
//
// Read reads up to len(p) bytes into p. It returns the number of bytes
// read (0 <= n <= len(p)) and any error encountered. Even if Read
// returns n < len(p), it may use all of p as scratch space during the call.
// If some data is available but not len(p) bytes, Read conventionally
// returns what is available instead of waiting for more.
type Reader interface {
Read(p []byte) (n int, err error)
}
// Writer is the interface that wraps the basic Write method.
//
// Write writes len(p) bytes from p to the underlying data stream.
// It returns the number of bytes written from p (0 <= n <= len(p))
// and any error encountered that caused the write to stop early.
type Writer interface {
Write(p []byte) (n int, err error)
}
Documenting Methods
package users
// User represents a user in the system.
type User struct {
ID int
Name string
Email string
}
// GetName returns the user's name.
func (u *User) GetName() string {
return u.Name
}
// SetName sets the user's name.
func (u *User) SetName(name string) {
u.Name = name
}
// IsValid checks if the user has valid data.
//
// A user is valid if it has a non-empty name and email.
func (u *User) IsValid() bool {
return u.Name != "" && u.Email != ""
}
// Save persists the user to the database.
//
// It returns an error if the user cannot be saved.
// Possible errors include:
// - ErrInvalidUser: if the user data is invalid
// - ErrDatabaseError: if there's a database error
func (u *User) Save() error {
if !u.IsValid() {
return ErrInvalidUser
}
// Save to database
return nil
}
Using Godoc
Viewing Documentation
# View documentation for a package
go doc fmt
# View documentation for a function
go doc fmt.Println
# View documentation for a type
go doc http.Server
# View documentation for a method
go doc http.Server.ListenAndServe
# View all documentation
go doc -all fmt
# View in browser
go doc -http=:6060
# Then visit http://localhost:6060
Generating HTML Documentation
# Generate HTML documentation
godoc -html fmt > fmt.html
# Generate for entire project
godoc -html ./... > docs.html
Best Practices for Comments
โ Good Comments
package calculator
// Add returns the sum of two integers.
func Add(a, b int) int {
return a + b
}
// User represents a user in the system.
type User struct {
ID int // Unique identifier
Name string // User's full name
Email string // User's email address
}
// IsValid checks if the user has valid data.
// A user is valid if it has a non-empty name and email.
func (u *User) IsValid() bool {
return u.Name != "" && u.Email != ""
}
// TODO: Add email validation
// FIXME: Handle edge case for empty strings
// NOTE: This function is performance-critical
// DEPRECATED: Use NewUser instead
โ Bad Comments
// โ Bad: Obvious comments
x := 5 // Set x to 5
// โ Bad: Unclear comments
// Do the thing
func Process() {
// Implementation
}
// โ Bad: Outdated comments
// This function returns the user
// Actually, it returns the product now
func GetUser() *Product {
// Implementation
}
// โ Bad: No documentation for exported items
func PublicFunction() {
// Implementation
}
Documentation Examples
Complete Package Documentation
// Package users provides user management functionality.
//
// This package handles user creation, retrieval, updating, and deletion.
// It includes authentication and authorization features.
//
// Basic usage:
// user := users.NewUser("Alice", "[email protected]")
// if err := user.Save(); err != nil {
// log.Fatal(err)
// }
//
// For more information, see the documentation for specific types and functions.
package users
import "errors"
var (
// ErrNotFound is returned when a user is not found.
ErrNotFound = errors.New("user not found")
// ErrInvalidUser is returned when user data is invalid.
ErrInvalidUser = errors.New("invalid user")
)
// User represents a user in the system.
type User struct {
ID int
Name string
Email string
}
// NewUser creates a new user with the given name and email.
func NewUser(name, email string) *User {
return &User{Name: name, Email: email}
}
// Save persists the user to the database.
func (u *User) Save() error {
if u.Name == "" || u.Email == "" {
return ErrInvalidUser
}
// Save to database
return nil
}
// GetByID retrieves a user by ID.
func GetByID(id int) (*User, error) {
// Retrieve from database
return nil, ErrNotFound
}
Complete Type Documentation
package http
// Server defines parameters for running an HTTP server.
//
// The zero value for Server is a valid configuration.
type Server struct {
// Addr optionally specifies the TCP address for the server to listen on,
// in the form "host:port". If empty, ":http" is used.
Addr string
// Handler to invoke, http.DefaultServeMux if nil.
Handler Handler
// ReadTimeout is the maximum duration before timing out
// a read from the client's connection.
ReadTimeout time.Duration
// WriteTimeout is the maximum duration before timing out
// a write to the client's connection.
WriteTimeout time.Duration
}
// ListenAndServe listens on the TCP network address srv.Addr and then
// calls Serve to handle requests on incoming connections.
//
// If srv.Addr is blank, ":http" is used.
//
// ListenAndServe always returns a non-nil error. After Shutdown or Close,
// the returned error is ErrServerClosed.
func (srv *Server) ListenAndServe() error {
// Implementation
return nil
}
Special Comments
Build Tags
// +build linux darwin
// This code only compiles on Linux and macOS
package main
// Or use the newer syntax
//go:build linux || darwin
package main
Deprecated Functions
// Deprecated: Use NewUser instead.
func CreateUser(name string) *User {
return NewUser(name, "")
}
TODO and FIXME
// TODO: Add email validation
func ValidateEmail(email string) bool {
return true
}
// FIXME: This doesn't handle edge cases
func Process(data string) error {
return nil
}
Documentation Tools
Using go doc
# View package documentation
go doc mypackage
# View function documentation
go doc mypackage.MyFunction
# View type documentation
go doc mypackage.MyType
# View method documentation
go doc mypackage.MyType.MyMethod
# View all documentation
go doc -all mypackage
# View in browser
go doc -http=:6060
Using godoc
# Start godoc server
godoc -http=:6060
# Generate HTML
godoc -html mypackage > mypackage.html
# Generate for all packages
godoc -html ./... > docs.html
Best Practices
โ Good Practices
- Document all exported items - Functions, types, constants, variables
- Start with the name - “Function does X”
- Be concise - Clear and brief
- Use examples - Show how to use
- Keep comments updated - Sync with code
- Use proper grammar - Professional documentation
- Explain why, not what - The code shows what
- Use godoc format - Follow conventions
โ Anti-Patterns
// โ Bad: No documentation
func Process() {
// Implementation
}
// โ Bad: Obvious comments
x := 5 // Set x to 5
// โ Bad: Outdated comments
// This returns the user
// (Actually returns the product now)
func GetUser() *Product {
// Implementation
}
// โ Bad: Unclear comments
// Do stuff
func DoStuff() {
// Implementation
}
Summary
Good documentation is essential:
- Document all exported items
- Use godoc format
- Start comments with the name
- Provide examples
- Keep comments updated
- Use proper grammar
- Follow Go conventions
Well-documented code is easier to maintain and use.
Comments