31 Notes on Writing Good Comments in Go
When it comes to commenting in Go, I've noticed it mainly revolves around three aspects: the "What," the "How," and the "Why."
Before proceeding, just to complain, Substack does not support syntax highlighting.
We’re going to dive into doc comments in Go, rather than regular comments, mainly because doc comments are pivotal in creating documentation.
When it comes to commenting in Go, I’ve noticed it mainly revolves around three aspects: the “What,” the “How,” and the “Why.”
“What” comments describe what the code does and when it’s typically used, assuming it’s not immediately obvious.
“How” comments explain how the code achieves its goal, detailing the mechanics behind it.
“Why” comments provide the reasoning behind our approach, explaining why we chose this method over others.
So, what’s the standard for commenting on exported types, methods, or fields?
In Go, comments for exported items are mostly about doc comments. These comments tend to cover the “What” and the “Why,” while the code itself clarifies the “How.”
“Why?”
Let me give you an example that doesn’t quite cut it:
// UpdatePrice connects to the database,
// constructs an update query, and applies it to the relevant record.
func UpdatePrice(bookID int, newPrice float64) {
// (implementation details)
}
This comment, while explaining the ‘how’, the process of updating a price doesn’t really touch on the ‘what’ or the ‘why’.
It’s important to remember that teammate using this function will likely be more interested in its purpose and appropriate use cases, rather than the nitty-gritty of how it updates the price.
Let’s consider a comment that does a better job:
// UpdatePrice modifies the price of a specific book in our inventory.
// It ensures a uniform procedure for all price modifications,
// incorporating necessary validation checks and enabling the integration of
// additional actions such as hooks or alerts as required.
func UpdatePrice(bookID int, newPrice float64) {
// (implementation details)
}
This version succinctly communicates the function’s purpose and also elaborates on why this particular function is the go-to choice.
“So… when should we use ‘how’ type comments?”
We usually keep ‘how’ comments within the function body, detailing the inner workings, but not in the doc comments (there’re exceptions)
I’ll share some insights on writing impactful doc-comments below. These aren’t strict rules, but rather guidelines based on my personal experience in coding.
Package
1/ “What” and “Why”: Every package should have a doc comment that succinctly explains its core purpose and rationale, just like we’ve discussed.
// - BAD:
// This package has functions for analytics.
package analytics
// - PREFERRED:
// Package analytics provides tools for collecting and analyzing user interaction data.
// It simplifies data aggregation and
// offers insights into user behavior.
package analytics
2/ Naming convention: Begin your package doc comments with “Package <name>“.
// - BAD:
// This package deals with network communication and data transmission.
package transport
// - BETTER:
// Package transport abstracts network communication,
// offering a simplified interface for data transmission.
package transport
3/ Summary sentence: The first sentence of your package doc-comment should be a complete sentence, start with a capital letter and end with a period. This sentence will be used as the summary for the package, so it’s crucial for it to be clear and concise.
// - BAD:
// handles user authentication and authorization
package authentication
// - BETTER:
// Package authentication provides mechanisms for
// secure user authentication and authorization.
package authentication
4/ Single file commentary: If your package spans multiple files, ensure to write the package doc comment in only one file. This prevents unintended concatenation of comments across files.
Type
5/ Explain the type’s purpose: Your type doc comments should clearly define the purpose of each type, explaining what each instance represents or accomplishes. Keep it succinct, especially for simpler APIs.
// - BAD:
// This is a user structure.
type User struct {}
// - BETTER:
// User represents an individual user of the system,
// holding essential profile information.
type User struct {}
6/ Concurrency information: Indicate if the type is safe for concurrent use by multiple goroutines. If there are exceptions, such as certain methods not being concurrency-safe, mention them.
// Cache offers a thread-safe way to store and retrieve data efficiently.
// Methods can be called concurrently from multiple goroutines.
type Cache struct {}
7/ Document zero value meaning: Explain what the zero value of the type signifies, especially if it’s not immediately obvious, providing clarity on initialization practices.
// - NOT BAD:
// Timestamp represents a point in time.
type Timestamp int64
// - BETTER:
// Timestamp represents a point in time. The zero value represents
// the Unix epoch (January 1, 1970, 00:00:00 UTC).
type Timestamp int64
8/ Document exported fields: For structs with exported fields, describe the purpose and intended use of each field, ensuring clarity and proper usage.
// Config holds the configuration settings for the application.
// Each field is required and must be set before starting.
type Config struct {
Port int // Port defines the port the server listens on.
Mode string // Mode defines the runtime mode, e.g., "development" or "production".
}
9/ Begin with complete sentences: Initiate type comments with full sentences, mentioning the type explicitly, to enhance clarity and searchability.
// Logger provides a unified interface for logging messages
// with various severity levels.
type Logger struct {}
10/ Provide usage instructions: If there are specific steps or guidelines for using the type or its fields, include these details in the comments to guide the user effectively.
// Connector manages connections to the database.
// Ensure to call Close() after use to free resources.
type Connector struct {}
11/ Focus only on what users need to know: Provide information that users need to know to understand and use the type properly, without getting into the complex details of how it’s built.
// Session stores user-specific data during a single interaction session.
// Use NewSession to initialize with default values.
type Session struct {}
12/ Be consistent: Make sure your style and the amount of detail you give are similar across your documentation.
Functions
13/ Describe function actions: Make sure the comment explains clearly what the function does or what it returns. If the function has side effects, like changing the state of the system or updating a database, mention this.
// ProcessPayment processes the payment for the given orderID and returns a receipt.
// It also updates the order status in the database as a side effect.
func ProcessPayment(orderID string) (receipt string) {}
14/ Mention parameters and results: Feel free to refer to named parameters and results directly in your comments. Just be cautious with names that might be mistaken for common words to avoid confusion.
15/ Clarify multiple results: If your function returns more than one result, it helps to name them in the comment. This adds clarity, even if these names aren’t explicitly used in the function’s code
// - BAD:
// Calculate tax based on income and rate.
func CalculateTax(income float64, rate float64) (owed float64, credit float64) {}
// - BETTER
// CalculateTax computes the tax amount for a given income and tax rate.
// The function returns the tax owed and any applicable tax credit.
func CalculateTax(income float64, taxRate float64) (taxOwed float64, taxCredit float64) {}
16/ Use full sentences: Stick to complete sentences to maintain clarity and a professional tone.
17/ Be clear with boolean functions: When documenting functions that return a boolean, phrases like “reports/ checks whether” are your friend.
It’s cleaner and more to the point than adding “or not”.
// Check if session is active (or not).
func IsActiveSession(sessionID string) bool {}
// - BETTER:
// IsActiveSession checks whether the session
// with the given sessionID is currently active.
func IsActiveSession(sessionID string) bool {}
18/ Keep it simple with results: If there’s no need to name the results in your doc comments, it’s usually better not to name them in your code either.
// - NOT BAD:
// Authenticate verifies the user credentials and
// returns true if they are valid.
func Authenticate(username, password string) (isValid bool) {}
// - BETTER:
// Authenticate verifies the user credentials
// and returns true if they are valid.
func Authenticate(username, password string) bool {}
19/ Be consistent with receiver names: It’s not directly about comments, but it’s crucial for readability and consistency.
Ensure you use the same name for the receiver in all methods of a type. This is also enforced by some linting tools (e.g. “stylecheck”)
// - BAD:
func (m *UserManager) Add(user User) error {}
func (mgr *UserManager) Remove(userID string) error {}
// - GOOD:
func (manager *UserManager) Add(user User) error {}
func (manager *UserManager) Remove(userID string) error {}
20/ Mention concurrency safety: If a function or method is designed to be safe for concurrent use, and this isn’t usually assumed, make sure to state this in your comments.
// Increment safely increments the Counter by one.
// This method is safe for concurrent use.
func (counter *Counter) Increment() {}
21/ Document special cases: If your function has particular behaviors or exceptions, like returning an error under specific conditions, clearly document these.
It helps users understand how to handle different scenarios:
// Divide divides two numbers and returns the result.
// Returns an error if the divisor is zero, not infinity.
func Divide(dividend float64, divisor float64) (float64, error) {}
22/ Avoid internal details: Your comments shouldn’t delve into how the function works internally, unless knowing the function’s time or space complexity is critical for the user.
// - BAD:
// FindShortestPath calculates the shortest path by exploring all possible paths using Dijkstra's algorithm and
// keeps track of the shortest one found so far.
func FindShortestPath(graph Graph, startNode, endNode Node) (Path, error) {}
// FindShortestPath calculates the shortest path between two nodes in a graph using Dijkstra's algorithm.
// Note: This function has a time complexity of O(V^2),
// where V is the number of vertices.
func FindShortestPath(graph Graph, startNode, endNode Node) (Path, error) {}
23/ Focus on the caller’s perspective: Ensure your comments offer specific information necessary for the caller, rather than just a broad description of the function.
// - BAD: Too general and doesn't provide specific information
// necessary for the caller.
// Authenticate checks credentials.
func (s *AuthenticationService) Authenticate(username, password string) bool {}
// - BETTER: Provides specific information about what the function does
// and what it returns
// Authenticate checks the provided username and password against
// the stored credentials.
// Returns true if the credentials are valid, false otherwise.
func (s *AuthenticationService) Authenticate(username, password string) bool {}
Constants
24/ Grouped constants, one intro: When you have a bunch of constants together, you can use one comment at the start to introduce the whole group.
If needed, add brief comments at the end of each line for any specifics.
// HTTPStatusCodes defines the HTTP status codes used in the application.
const (
StatusOK = 200 // Standard response for successful HTTP requests.
StatusNotFound = 404 // The requested resource could not be found.
StatusError = 500 // A generic error message, given when an unexpected condition was encountered.
)
25/ No need to over-explain the obvious: If you’re dealing with a group of constants that are pretty straightforward, like color codes, you might not even need a doc comment, they speak for themselves.
const (
Red = "#FF0000"
Green = "#00FF00"
Blue = "#0000FF"
)
26/ Solo constants need clear comments: When you’ve got constants that aren’t part of a group, make sure each one has a complete doc comment, start with a full sentence that lays out what the constant is for.
// - BAD:
// Constants for file size and timeout
const MaxFileSize = 1024 * 1024 * 10
const TimeoutDuration = 30
// - BETTER:
// MaxFileSize defines the maximum size (in bytes) for an uploaded file.
const MaxFileSize = 1024 * 1024 * 10
// TimeoutDuration defines the default timeout (in seconds) for requests.
const TimeoutDuration = 30
27/ Type-specific constants, focus on the type: If you’ve got constants that are part of a specific type, the main doc comment usually goes on the type itself.
Then you could explain the constants as they relate to that type in the declaration block.
// AccountType represents the type of a user account in the system.
type AccountType int
const (
Admin AccountType = iota // Administrator account with full permissions.
User // Regular user account with limited permissions.
)
Variables
28/ Introduce variable groups: Just like with constants, when you have several variables together, use a single doc comment to describe the group’s overall purpose.
Add brief individual comments for each variable to clarify their specific roles (if needed).
// serverConfig holds configuration variables
// related to the server setup.
var (
serverIP = "192.168.1.1" // The IP address where the server is hosted.
serverPort = 8080 // The port on which the server listens.
)
29/ Describe single variables fully: Every individual variable should come with a comprehensive doc comment. It should lay out the variable’s role and how it’s used in your code.
// - BAD
// User count
var activeUsers int
// - BETTER
// activeUsers holds the count of currently active users in the system.
var activeUsers int
30/ Use examples for clarity: Including examples in your comments can really help demonstrate how to use the variables or constants, especially when their use might not be immediately obvious.
// retryInterval specifies the time (in seconds) between consecutive retries.
// For example, if retryInterval is set to 5,
// the system will retry failed operations every 5 seconds.
var retryInterval int
31/ Provide usage context: Don’t just state what the variable is, also explain how it fits into the broader context of your package or application.
Give your users a clearer picture of its importance and function.
// maxConnections defines the maximum number of simultaneous connections the server can handle.
// This variable ensures that system resources are not
// overwhelmed by too many concurrent requests.
var maxConnections int