Introduction
Writing clear and purposeful comments in Go is essential for maintaining clean, understandable code. In Go, comments serve two main purposes: ordinary comments, which explain the code to developers, and doc comments, which serve as documentation for users, outlining the functionality and usage of packages or functions. This article covers the best practices for formatting and using comments in Go, emphasizing the importance of clarity, maintainability, and readability. Whether you’re collaborating with others or working on your own code, mastering these commenting techniques will help ensure that your Go programs remain easy to understand and maintain.
What is Commenting in Go programming?
Commenting in Go programming involves adding explanatory notes to code, which helps programmers understand the purpose and logic behind the code. There are two main types: ordinary comments, which are for developers to explain the ‘why’ behind code, and doc comments, which provide official documentation for users of the code. This practice makes code more maintainable, easier to understand, and helps prevent future mistakes or confusion.
Ordinary Comments
In Go, adding a comment is super simple: you start with two forward slashes ( // ), followed by a space (though the space isn’t required, it’s just the Go way of doing things). You can place your comment either right above or to the right of the code it’s explaining. If you put it above, just make sure it’s lined up with the code for clarity. Let’s look at a basic example from a Go program with a comment:
package main
import “fmt”
func main() {
// Say hi via the console
fmt.Println(“Hello, World!”)
}
Here’s a handy trick: if you ever place a comment that doesn’t align with the code, Go has your back. The gofmt tool will automatically fix it. This tool, which comes with your Go setup, ensures your code—and comments—are formatted the same way everywhere, no matter where you’re working. So, no more arguing about whether to use tabs or spaces for indentation. Go takes care of that! If you’re a Go programmer (or a “Gopher,” as we like to call ourselves), it’s really important to format your code as you go. That way, you avoid dealing with messy code later on. Plus, it’s super important to run gofmt before you commit anything to version control. Sure, you can manually run
$ gofmt -w hello.go
Since the comment in the example is short, you could also place it directly to the right of the code, like this:
fmt.Println("Hello, World!") // Say hi via the console
In Go, most comments go on their own line, especially when they’re long or need more explanation. But for short comments, like the one above, it’s totally fine to put them inline. Longer comments, on the other hand, often span multiple lines and give you more detail. Go also lets you use C-style block comments, which are wrapped with /* and */ . But you’ll mostly use those in special cases, not for your everyday comments. Instead, multi-line comments in Go start each line with // rather than using block comment tags.
Here’s an example where multiple comments are used in a Go program, all nicely indented for readability. I’ve highlighted one multi-line comment here:
package main
import “fmt”
const favColor string = “blue” // Could have chosen any color
func main() {
var guess string // Create an input loop
for {
// Ask the user to guess my favorite color
fmt.Println(“Guess my favorite color:”)
// Try to read a line of input from the user
// Print out an error and exit if there is one
if _, err := fmt.Scanln(&guess); err != nil {
fmt.Printf(“%s\n”, err)
return
}
// Did they guess the correct color?
if favColor == guess {
// They guessed it!
fmt.Printf(“%q is my favorite color!\n”, favColor)
return
}
// Wrong! Have them guess again.
fmt.Printf(“Sorry, %q is not my favorite color. Guess again.\n”, guess)
}
Now, while these comments might seem helpful, many of them are just adding clutter. In a small and simple program like this, you don’t need so many comments. Actually, most of these just explain things that are already obvious from the code itself. For example, any Go developer can easily figure out the basics, like reading user input or running a loop, without needing a comment saying so. As a general rule, you don’t need to comment on every line, especially if it’s just stating that you’re looping or multiplying two numbers.
But here’s the thing—one of these comments is actually super helpful. The comment that says:
// Could have chosen any color
This is the kind of comment that really adds value. It explains why the color “blue” was chosen for the variable favColor. It’s like saying, “Hey, I picked blue just randomly. You can change it to any other color, and everything will still work fine.” This is the kind of context that code can’t provide on its own. It keeps the code flexible and reminds others (or even you, in the future) that it’s easy to swap things out without breaking the program.
So, next time you write some code, think about this: sometimes it’s not just what the code does that’s important, but also why you made certain choices. And that’s where thoughtful comments really shine.
Remember to run
$ gofmt -w hello.go
Good Comments Explain Why
Imagine you’re diving into a piece of code—it’s your first time working on a project that someone else has been tinkering with for a while. As you explore, you come across a comment in the code, not just explaining what’s happening or how it’s happening, but why the decision was made in the first place. That “why” is the golden nugget of information that you, the future developer, need to understand the purpose behind the code’s design. These types of comments are the most valuable because they give you insight into the thought process of the original developer.
For example, let’s say you stumble upon a line of code like this in Go:
const favColor string = “blue” // Could have chosen any color
Now, at first glance, the comment might seem trivial. It says, “Could have chosen any color.” But there’s more to it than meets the eye. This simple statement is actually a helpful clue for future developers like you. It means that the color “blue” was chosen without any particular reason. In other words, the choice was arbitrary! This opens up possibilities for you because it’s a reminder that the color can easily be changed to any other color without breaking the functionality of the program. It’s an invitation to future modifications without fear of messing things up. Imagine the relief of knowing that a seemingly small change won’t cause a big disruption.
But here’s the twist—most of the time, there’s more than just an arbitrary “why” behind the code. Sometimes, the reasoning is tied to specific constraints or conditions that require more thoughtful commentary. Take a look at this example from the Go standard library’s net/http package:
// refererForURL returns a referer without any authentication info or
// an empty string if lastReq scheme is https and newReq scheme is http.
func refererForURL(lastReq, newReq *url.URL) string {
// https://tools.ietf.org/html/rfc7231#section-5.5.2
// “Clients SHOULD NOT include a Referer header field in a
// (non-secure) HTTP request if the referring page was
// transferred with a secure protocol.”
if lastReq.Scheme == “https” && newReq.Scheme == “http” {
return “”
}
referer := lastReq.String()
if lastReq.User != nil {
// This is not very efficient, but is the best we can
// do without:
// – introducing a new method on URL
// – creating a race condition
// – copying the URL struct manually, which would cause
// maintenance problems down the line
auth := lastReq.User.String() + “@”
referer = strings.Replace(referer, auth, “”, 1)
}
return referer
}
In this code snippet, the first comment explains why the function returns an empty string if the last request was made over HTTPS and the new request is made over HTTP. It’s in accordance with a specific rule in the RFC (Request for Comments) specification, which says that clients shouldn’t include a Referer header field in an HTTP request if the referring page was loaded securely (i.e., via HTTPS). It’s not just a random decision; it’s a design choice made to follow a security protocol.
The second comment digs even deeper into the reasoning behind the code. It admits that the solution is not the most efficient one, but explains why it was chosen. The comment acknowledges that this approach has some trade-offs, such as not introducing a new method on the URL or risking the creation of a race condition. It also touches on the complications that could arise if the URL struct were to be copied manually. This level of commentary is priceless because it provides transparency into the design decisions, even if the solution is imperfect. It’s a warning sign for future developers, guiding them to consider the consequences before making changes.
These types of comments aren’t just for the sake of being thorough—they’re essential for maintaining high-quality code. They help prevent future mistakes by providing clarity on why certain decisions were made. Without these comments, future developers might accidentally introduce bugs or break something that was intentionally designed a certain way. But with these thoughtful comments, developers are invited to understand the reasoning behind the code and to proceed with caution when making modifications. It’s like getting a guidebook to avoid stepping on landmines.
Now, don’t forget the comment above the function declaration. While not as detailed as the inline comments, it still serves an important purpose. This function-level comment helps users understand what the function is supposed to do and how it behaves. It’s not as in-depth as the comments within the function, but it still gives users a high-level overview of the function’s purpose. So, even when you’re not getting into the nitty-gritty details, these top-level comments ensure that you’re heading in the right direction.
In the end, comments that explain why decisions were made are invaluable. They provide the context that’s often missing from just reading the code itself. So, the next time you’re writing Go code (or any code), take a moment to explain the “why” behind your decisions—it’ll be a lifesaver for those who come after you, and it’ll help them (and you) make sense of the code when it’s time to revisit it.
Doc Comments
In the world of Go programming, doc comments are like the map to a treasure chest—they guide you and others to the right place with clear directions. These comments appear directly above the top-level (non-indented) declarations like package , func , const , var , and type . They serve as the official documentation for a package and all of its exported names. Now, here’s an interesting Go quirk: “exported” in Go is basically the same thing as “public” in other languages. If you want a component to be accessible to other packages, all you need to do is capitalize it. Simple, right?
Unlike your regular comments, which often explain how the code works, doc comments go the extra mile. They explain what the code does and why it does it the way it does. These comments are aimed not at the people maintaining the code but at users—developers who might not want to dive into the code itself but simply want clear, usable documentation. They want to know how the code functions without needing to study every line of it.
Now, where do users typically access these doc comments? Well, there are three primary places:
- By running
$ go doc
- On pkg.go.dev, the official hub for public Go package documentation, where packages are neatly listed and easy to navigate.
- On a privately hosted web server using the godoc tool, which lets teams create their own private documentation portal for Go packages.
When developing Go packages, you should always write a doc comment for every exported name. And don’t forget the unexported names that might be important for your users—sometimes they need a bit of explanation too.
Let’s look at a simple example from a Go client library:
// Client manages communication with Caasify V2 API.
type Client struct {
…
}
This comment might seem trivial at first, but it’s very important because it appears alongside all other doc comments, forming a complete set of documentation for every usable component of the package.
Now, let’s level up to a more detailed example:
// Do sends an API request and returns the API response. The API response is JSON decoded and stored in the value
// pointed to by v, or returned as an error if an API error has occurred. If v implements the io.Writer interface,
// the raw response will be written to v, without attempting to decode it.
func (c *Client) Do(ctx context.Context, req *http.Request, v interface{}) (*Response, error) {
…
}
This doc comment is a bit more complex—it explains the purpose of the function and goes into detail about the format of the parameters and the expected output. Doc comments for functions should always specify how the parameters should be formatted, especially when it’s not immediately obvious, and what kind of data the function will return. These comments can also provide a summary of how the function works, offering clarity on its purpose.
For example, here’s a comment that gives important context within a function:
// Ensure the response body is fully read and closed
// before we reconnect, so that we reuse the same TCPConnection.
// Close the previous response’s body. But read at least some of
// the body so if it’s small the underlying TCP connection will be
// re-used. No need to check for errors: if it fails, the Transport
// won’t reuse it anyway.
At first glance, you might wonder, “Why isn’t there any error checking here?” This comment explains that no error checking is necessary because if the body fails to close, the system won’t reuse the TCP connection anyway. It’s a small but significant insight into why the code works the way it does.
These types of comments aren’t just useful—they’re essential. They help future developers understand the reasoning behind design choices, preventing them from introducing unintended bugs when they modify the code.
Now, let’s talk about the most important doc comments: the package-level comments. These are the top-tier comments that explain the entire package, its purpose, and how to use it. Each package should have just one of these, sitting above the package name declaration. These comments are essential for giving users a quick, clear understanding of what the package does and how to interact with it. They often include code examples or command usage, making it easier for users to understand how to get started with the package.
For example, here’s the start of a package comment for the gofmt tool:
/*
Gofmt formats Go programs. It uses tabs for indentation and blanks for alignment.
Alignment assumes that an editor is using a fixed-width font.
Without an explicit path, it processes the standard input. Given a file, it operates on that file; given a directory, it operates on all .go files in that directory, recursively. (Files starting with a period are ignored.)
By default, gofmt prints the reformatted sources to standard output. Usage:
gofmt [flags] [path …]
The flags are:
-d Do not print reformatted sources to standard output. If a file’s formatting is different than gofmt’s, print diffs to standard output.
…
*/
Package-level comments are crucial because they give users a high-level overview of what the package is all about. It’s like a quick briefing to get them up to speed so they don’t have to read through every line of code to understand its purpose.
Doc Comment Formatting
While there’s no strict rule for how to format doc comments, Go does suggest a simpler version of Markdown to make your comments more readable. This format allows for well-structured comments with paragraphs, lists, and even example code. When formatted properly, these comments can be neatly rendered into well-organized web pages for users to browse.
Here’s an example of doc comments used in a “Hello World” Go program:
// This is a doc comment for greeting.go.
// – prompt user for name.
// – wait for name
// – print name.
// This is the second paragraph of this doc comment.
// `gofmt` (and `go doc`) will insert a blank line before it.
package main
import (
“fmt”
“strings”
)
func main() {
// This is not a doc comment. Gofmt will NOT format it.
// – prompt user for name
// – wait for name
// – print name
// This is not a “second paragraph” because this is not a doc comment.
// It’s just more lines to this non-doc comment.
fmt.Println(“Please enter your name.”)
var name string
fmt.Scanln(&name)
name = strings.TrimSpace(name)
fmt.Printf(“Hi, %s! I’m Go!”, name)
}
You can see that the comment above package main is a proper doc comment, following the formatting rules. However, the comments inside the main() function aren’t considered doc comments, which is why gofmt doesn’t format them accordingly.
When you run gofmt or go doc on this code, the doc comment at the package level will be correctly formatted, as shown here:
// This is a doc comment for greeting.go.
// – prompt user for name.
// – wait for name.
// – print name.
//
// This is the second paragraph of this doc comment.
// `gofmt` (and `go doc`) will insert a blank line before it.
package main
import (
“fmt”
“strings”
)
func main() {
// This is not a doc comment. `gofmt` will NOT format it.
// – prompt user for name
// – wait for name
// – print name
// This is not a “second paragraph” because this is not a doc comment.
// It’s just more lines to this non-doc comment.
fmt.Println(“Please enter your name.”)
var name string
fmt.Scanln(&name)
name = strings.TrimSpace(name)
fmt.Printf(“Hi, %s! I’m Go!”, name)
}
Notice how the paragraphs are now aligned and separated by a blank line. This process of formatting doc comments ensures that your package’s documentation is easy to read and well-organized. And when your code is clean and well-documented, it makes it easier for others (and your future self) to understand and maintain the package. So, keep those doc comments flowing—your future developers will thank you!
Doc Comments
In the world of Go, there’s a special kind of comment that stands out—doc comments. These comments sit right above top-level declarations like package , func , const , var , and type . You might be wondering, “Why are they so special?” Well, these doc comments are like the official guidebook for a package, explaining what the package does and how to use it. They’re a little different from the regular comments you might use for quick clarifications or code explanations. Instead, they serve as the documentation for the package and all its exported names.
Now, if you’re new to Go, you might not know that in Go, an “exported” component is like a VIP guest—anything marked as exported can be accessed by other packages when they import your package. So, to make something “VIP” in your code, all you need to do is capitalize the name. Simple enough, right?
But here’s where it gets interesting: doc comments aren’t meant for the people who are maintaining the code. They’re written for users—the developers who will be using your package but may not want to dive into the nitty-gritty of your code. They just want to know how it works. And that’s where doc comments shine—they provide clarity without forcing users to look under the hood of your code.
When it comes to actually using these doc comments, users typically find them in three places:
- By running go doc in their terminal on a source file or directory.
- On pkg.go.dev, which is the go-to hub for all public Go packages.
- On a privately-hosted web server, using the godoc tool, which allows teams to set up their own private documentation portals.
For anyone developing a Go package, it’s essential to write a doc comment for every exported name. Sometimes, you might need one for unexported names too—especially if they’re important for the users.
Let’s take a look at an example. Here’s a simple one-line doc comment from a Go client library:
// Client manages communication with Caasify V2 API.
type Client struct {
…
}
It may seem simple, but this little comment is like a treasure in the code. It will be included with all other doc comments, forming a full guide for anyone using the package.
Now, let’s level up to a more detailed example:
// Do sends an API request and returns the API response. The API response is JSON decoded and stored in the value
// pointed to by v, or returned as an error if an API error has occurred. If v implements the io.Writer interface,
// the raw response will be written to v, without attempting to decode it.
func (c *Client) Do(ctx context.Context, req *http.Request, v interface{}) (*Response, error) {
…
}
This comment is much more detailed. It explains what the function does, what the parameters should look like, and how the function handles the response. Doc comments for functions are like a map—they tell users exactly how to use the function, what kind of data they should pass, and what to expect in return. It’s all about providing clarity and making sure that users don’t have to guess.
Sometimes, though, the reason behind certain code decisions needs explaining. For example, here’s a comment inside a function that clears up a design choice:
// Ensure the response body is fully read and closed
// before we reconnect, so that we reuse the same TCPConnection.
// Close the previous response’s body. But read at least some of
// the body so if it’s small the underlying TCP connection will be
// re-used. No need to check for errors: if it fails, the Transport
// won’t reuse it anyway.
This is a great example because it explains why error checking isn’t necessary when closing the response body. You might think that not checking for errors is a mistake, but this comment clears up why it’s perfectly fine. It shows how thoughtful comments can make your code easier to maintain and modify in the future.
When it comes to the most important doc comments, you can’t overlook the package-level comments. These are the big-picture comments that explain the entire package’s purpose and how to use it. Every package should have just one of these, and it’s usually placed right above the package name declaration. These comments often contain code examples or usage instructions to give users an immediate understanding of how to interact with the package.
Here’s an example of a package-level comment for the gofmt tool:
/*
Gofmt formats Go programs. It uses tabs for indentation and blanks for alignment.
Alignment assumes that an editor is using a fixed-width font.
Without an explicit path, it processes the standard input. Given a file, it operates on that file; given a directory, it operates on all .go files in that directory, recursively. (Files starting with a period are ignored.)
By default, gofmt prints the reformatted sources to standard output.
Usage:
gofmt [flags] [path …]
The flags are:
-d Do not print reformatted sources to standard output. If a file’s formatting is different than gofmt’s, print diffs to standard output.
…
*/
package main
As you can see, this package-level comment is pretty detailed, giving a high-level overview of what gofmt does and how users can interact with it. Package-level comments are crucial because they make it easy for users to grasp the full capabilities of the package without diving deep into the code.
Doc Comment Format While Go doesn’t enforce a strict format for doc comments, it does encourage a format that’s easy to read and understand. According to the Go creators, the godoc tool is somewhat similar to Python’s Docstring or Java’s Javadoc, but with a simpler design. The idea is to create good, readable comments that make sense whether godoc exists or not.
Here’s the cool part: Go lets you format your doc comments using a simplified subset of Markdown. This allows you to structure your comments with paragraphs, lists, examples, and even provide links to relevant references. When you format your comments this way, they’ll be rendered into neat and clean web pages, making them easy to browse.
Here’s an example of how doc comments are used in a simple “Hello World” Go program:
// This is a doc comment for greeting.go.
// – prompt user for name.
// – wait for name
// – print name.
// This is the second paragraph of this doc comment.
// `gofmt` (and `go doc`) will insert a blank line before it.
package main
import (
“fmt”
“strings”
)
func main() {
// This is not a doc comment. Gofmt will NOT format it.
// – prompt user for name
// – wait for name
// – print name
// This is not a “second paragraph” because this is not a doc comment.
// It’s just more lines to this non-doc comment.
fmt.Println(“Please enter your name.”)
var name string
fmt.Scanln(&name)
name = strings.TrimSpace(name)
fmt.Printf(“Hi, %s! I’m Go!”, name)
}
Notice how the doc comment above package main follows the formatting rules, with paragraphs and lists. But the comments inside the main() function aren’t considered doc comments, so gofmt won’t format them.
When you run gofmt or go doc on this code, the doc comment will be properly formatted, like this:
// This is a doc comment for greeting.go.
// – prompt user for name.
// – wait for name.
// – print name.
// //
// This is the second paragraph of this doc comment.
// `gofmt` (and `go doc`) will insert a blank line before it.
package main
import (
“fmt”
“strings”
)
func main() {
// This is not a doc comment. `gofmt` will NOT format it.
// – prompt user for name
// – wait for name
// – print name
// This is not a “second paragraph” because this is not a doc comment.
// It’s just more lines to this non-doc comment.
fmt.Println(“Please enter your name.”)
var name string
fmt.Scanln(&name)
name = strings.TrimSpace(name)
fmt.Printf(“Hi, %s! I’m Go!”, name)
}
Effective Go
Quickly Disabling Code
Imagine this: you’ve written some fresh new code and you’re excited to see it work. But then, things go south. Your application starts slowing down, and before you know it, everything’s falling apart. You’re left scrambling to find a solution. Don’t worry, though! You’ve got a simple tool to help you out in moments like these: the C-style /* and */ block comment tags. They’re your best friend when things go wrong.
Let’s break down how this works. The beauty of these block comment tags is how easy they make things. All you have to do is wrap a part of your code in /* and */ , and that section will be disabled without actually deleting it. Then, once you’ve figured out and fixed the issue, you can just remove the tags, and the code will be re-enabled like magic. Pretty cool, right?
Here’s a real example of how you might use it in a Go program:
func main() {
x := initializeStuff()
/* This code is causing problems, so we’re going to comment it out for now
someProblematicCode(x) */
fmt.Println(“This code is still running!”)
}
In this case, the function someProblematicCode(x) has been temporarily disabled using the block comment syntax. The rest of the program, though, keeps running just as it should. This lets you isolate the problem and focus on fixing it, without messing up the flow of the rest of your application.
When you have a bigger chunk of code, block comments are much more efficient than adding // at the start of every single line. Imagine having to do that for a long block of code—it’d be a nightmare! Block comments help keep everything neat and easy to read, even when you need to comment out a bunch of lines.
However, here’s the catch: while block comments are super helpful for testing and fixing things, they’re not meant to stick around in your code for long. The rule of thumb is to use // for regular comments and doc comments that will stay in your code long-term. It’s important to keep your code tidy. Leaving large sections of commented-out code can make your program harder to maintain. So, once you’ve sorted out the problem, make sure to remove those block comments to keep your code clean and easy to work with.
So, next time you’re coding in Go and run into trouble, remember that block comments are your go-to tool. You can use them to temporarily disable problematic code and keep your program running smoothly while you fix the issue.
Emacs Lisp: Comments and Documentation
Conclusion
In conclusion, mastering the art of writing clear and purposeful comments in Go is essential for maintaining clean, understandable, and effective code. Whether you’re using ordinary comments to explain the code’s purpose to developers or doc comments to provide valuable documentation for users, the key is clarity and consistency. By following best practices for formatting and explaining “why” code is written a certain way, you ensure that your Go code remains easy to maintain and collaborate on.As Go programming continues to evolve, embracing well-documented code will not only enhance readability but also support long-term project success. Remember, effective comments are not just about explaining what the code does, but also about making it easier for others to understand and work with in the future. Stay ahead of the curve by refining your commenting practices in Go—your future collaborators will thank you!