如何在Go中编写注释

news/2024/11/30 18:53:29/

引言

几乎所有的编程语言都有一种向代码添加注释的语法,Go也不例外。注释(comment)是程序中使用人类语言解释代码如何工作或为什么要这样写的行。编译器会忽略它们,但细心的程序员不会。注释添加了宝贵的上下文,可以帮助您的合作者(以及您未来的自己)避免陷阱并编写更可维护的代码。

任何包中的普通注释都解释了该代码为什么做它所做的事情。它们是针对包开发人员的注意事项和警告。文档注释总结了包中每个组件的功能以及工作原理,并提供了示例代码和命令用法。它们是用户的官方包文档。

在本文中,我们将从几个Go包中查看一些真实的注释,不仅说明注释在Go中是什么样子的,还说明它们应该传达什么。

普通的评论

Go中的注释以两个斜杠(//)开始,然后是一个空格(不是必需的,但习惯用法),然后是注释。它可能出现在所涉及代码的上方或右侧。在上面,它会缩进以与代码对齐。

这个Hello World程序在它自己的一行中只包含一个注释:

hello.go

package mainimport "fmt"func main() {// 通过控制台打招呼fmt.Println("Hello, World!")
}

**注意:**如果你添加了与代码不一致的注释,gofmt工具会解决这个问题。该工具随您的Go安装一起提供,将Go代码(包括注释)格式化为通用格式,以便任何地方的Go代码看起来都是相同的,程序员不会因为制表符和空格而争论。作为一名Gopher (Go爱好者的称呼),您应该在编写Go代码时不断格式化代码,并且在将其提交到版本控制系统之前。你可以手动运行gofmt (gofmt -w hello.go),但更方便的是配置你的文本编辑器或IDE,使其在每次保存文件时运行。

由于这段注释很短,它可以作为行内注释出现在代码的右侧:

hello.go

. . .fmt.Println("Hello, World!") // 通过控制台打招呼
. . .

大多数注释都单独出现在一行中,除非它们非常简短。

较长的注释跨越多行。Go支持c风格的块注释,使用/**/标签来打开和关闭非常长的注释,但这些仅用于特殊情况。(稍后会详细介绍。)普通的多行注释以//开头,而不是使用块注释标签。

下面是一些带有许多注释的代码,每个注释都正确缩进。其中一个多行注释被突出显示:

color.go

package mainimport "fmt"const favColor string = "blue" // Could have chosen any colorfunc main() {var guess string// Create an input loopfor {// Ask the user to guess my favorite colorfmt.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)}
}

这些注释中的大多数实际上都是混乱的。这么小而简单的程序不应该包含这么多注释,而且其中大多数注释的含义在代码本身就很明显。您可以相信其他Go程序员能够理解Go语法、控制流、数据类型等基础知识。你不需要写注释来宣布代码将要遍历一个切片或将两个浮点数相乘。

然而,其中有一条注释是有用的。

好的评论可以解释为什么

在任何程序中,最有用的注释都不是解释代码做了什么或如何做,而是解释为什么这样做。有时没有为什么,即使这样也可以指出来,就像下面这段行内注释所做的那样:

color.go

const favColor string = "blue" // 可以选择任何颜色吗

这段注释说明了代码中没有的东西:值“blue”是程序员任意选择的。换句话说,//可以随意更改

然而,大多数代码都有一个为什么。这是Go标准库中的net/http包中的一个函数,其中包含两个非常有用的注释:

client.go

. . .
// 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 lineauth := lastReq.User.String() + "@"referer = strings.Replace(referer, auth, "", 1)}return referer
}
. . .

第一个突出显示的注释警告维护者不要更改下面的代码,因为它是为了符合HTTP协议的RFC(官方规范)而编写的。第二个高亮的注释承认下面的代码并不理想,暗示了维护者可能会如何尝试改进它,并警告他们这样做的危险。

这样的注释是必不可少的。它们防止维护者在不知情的情况下引入bug和其他问题,同时也可能邀请他们实现新的想法,但要谨慎。

func声明上面的注释也很有用,但方式不同。让我们接下来探讨这种评论。

文档注释

出现在顶级(非缩进)声明之上的注释,如packagefuncconstvartype,被称为文档注释。之所以这样命名,是因为它们代表了包及其所有导出名称的官方文档。

注意:在Go中,exported的意思与public在某些语言中的意思相同:导出的组件是其他包在导入你的包时可能会使用的组件。要导出包中的任何顶级名称,只需将其大写即可。

文档注释解释了做什么怎么做

与我们刚才看到的普通注释不同,文档注释通常会解释代码做什么或如何做。这是因为它们不是为包的维护者准备的,而是为它的用户准备的,这些用户通常不想阅读或贡献代码。

用户通常会在以下三个地方阅读文档注释:

  1. 在他们的本地终端中,通过在单个源文件或目录上运行go doc

  2. 在pkg.go.dev上,任何公开的Go包的官方文档中心。

  3. 在您的团队使用godoc工具托管的私人运行的web服务器上。此工具可让您的团队为私有Go包创建自己的参考门户。

在开发Go包时,您应该为每个导出的名称编写文档注释。(CodeReviewComments。)这是godo (DigitalOcean API的Go客户端库)中的一行文档注释:

godo.go

// Client manages communication with DigitalOcean V2 API.
type Client struct {

像这样简单的文档注释似乎没有必要,但请记住,它将与所有其他文档注释一起出现,以全面记录包的每个可用组件。

下面是这个包的一段较长的文档注释:

godo.go

// 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) {
. . .
}

函数的文档注释应该清楚地指定预期的参数格式(如果不明显的话)和函数返回数据的格式。他们还可以总结该函数的工作原理。

Do函数的文档注释与函数内部的注释进行比较:

godo.go

	// 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.

这就像我们在net/http包中看到的评论。阅读这段代码的维护者可能会想,“为什么这段代码不检查错误呢?,然后添加错误检查。但是这条评论解释了为什么他们不需要这样做。它不像文档注释那样是高级的** what how **。

最高级别的文档注释是包注释。每个包都应该只包含一个包注释,概述包是什么以及如何使用它,包括代码和/或命令示例。包注释可以出现在任何源文件中,而且只能出现在package <name>声明之上的那个文件中。包注释通常出现在名为doc.go的单独文件中。

与我们看过的所有其他注释不同,包注释通常使用/**/,因为它们可能很长。以下是gofmt包注释的开头:

/*
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

那么文档注释的格式如何呢?他们可以(或必须)拥有什么样的结构?

文档注释有自己的格式

根据Go开发者的一篇旧博客文章:

Godoc在概念上与Python的文档字符串和Java的Javadoc有关,但它的设计更简单。godoc读取的注释不是语言结构(像Docstring那样),也必须有自己的机器可读的语法(像Javadoc那样)。Godoc注释只是好的注释,即使Godoc不存在,你也会想要阅读的那种注释。

虽然文档注释没有必须的格式,但它们可以选择使用Go文档中完全描述的“Markdown的简化子集”格式。在文档注释中,要以段落和列表的形式书写,以缩进的形式显示示例代码或命令,提供引用的链接等。当文档注释按照这种格式结构良好时,它们就可以呈现为漂亮的网页。

以下是添加到[如何用Go编写第一个程序]中的扩展Hello World程序greeting.go中的一些注释。

greeting.go

// 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 mainimport ("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 stringfmt.Scanln(&name)name = strings.TrimSpace(name)fmt.Printf("Hi, %s! I'm Go!", name)
}

package main上面的注释是一个文档注释。它试图使用文档注释格式的段落和列表的概念,但并不完全正确。gofmt工具将把它塑造成这种格式。运行gofmt greeting.go将打印以下内容:

// 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 mainimport ("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 stringfmt.Scanln(&name)name = strings.TrimSpace(name)fmt.Printf("Hi, %s! I'm Go!", name)
}

注意:

  1. 文档注释第一段中列出的项目现在对齐了。

  2. 第一段和第二段之间现在有一个空行。

3.main()中的注释没有被格式化,因为gofmt识别出它不是文档注释。(但如前所述,gofmt会将所有注释对齐到与代码相同的缩进。)

运行go doc greeting.go将格式化并打印文档注释,但不是main()中的文档注释:

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.

如果你始终正确地使用这种文档注释格式,包的用户会感谢你提供了易于阅读的文档。

阅读文档注释上的官方参考页面来学习如何写好它们的一切。

快速禁用代码

你是否曾经写过一些新代码,让你的应用程序变慢,甚至更糟,破坏了一切?另一种情况是使用c风格的/**/标签。你可以通过在代码前面加一个/*、后面加一个*/来快速禁用有问题的代码。用这些标签把有问题的代码包裹起来,把它变成一个块注释。然后,当您修复了它导致的任何问题后,您可以通过删除这两个标签重新启用代码。

problematic.go

. . .
func main() {x := initializeStuff()/* This code is causing problems, so we're going to comment it out for nowsomeProblematicCode(x)*/fmt.Println("This code is still running!")
}

对于较长的代码块,使用这些标签比在每行有问题的代码的开头添加//要方便得多。作为一种约定,使用//表示普通注释和文档注释,它们将无限期地存在于代码中。仅在测试期间临时使用/**/标签(或如前所述用于包注释)。不要在程序中长时间地留下注释代码片段。

总结

通过在你所有的Go程序中编写富有表现力的注释,你可以:

  1. 防止你的合作者破坏东西。

  2. 帮助未来的自己,他有时已经忘记代码最初为什么要这样写。

  3. 给包的用户一个参考,他们可以在不深入代码的情况下阅读。


http://www.ppmy.cn/news/1129162.html

相关文章

C# Task任务详解

文章目录 前言Task返回值无参返回有参返回 async和await返回值await搭配使用Main async改造 Task进阶Task线程取消测试用例超时设置 线程暂停和继续测试用例 多任务等最快多任务全等待 结论 前言 Task是对于Thread的封装,是极其优化的设计,更加方便了我…

电脑显示系统错误怎么办?

有时我们在开机时会发现电脑无法开机,并显示系统错误,那么这该怎么办呢?下面我们就一起来了解一下。 方法1. 替换SAM文件解决问题 1. 重启电脑并进入安全模式。 Win8/10系统:在启动电脑看到Windows标志时,长按电源键…

VS编译器常见的错误

以上问题在编译器中出现可以在编译器中最上面加入: #define_CRT_SECURE_NO_WARNINGS 或者将scanf修改为scanf_s 一定要在最上端!!!最上端!!!最上端加入!!! 虽…

成都瀚网科技:抖音上线地方方言自动翻译功能

为了让很多方言的地域历史、文化、习俗能够以短视频的形式生产、传播和保存,解决方言难以被更多用户阅读和理解的问题,平台正式上线推出当地方言自动翻译功能。创作者可以利用该功能,将多个方言视频“一键”转换为普通话字幕供大众观看。 具体…

K8S-EverNote同步

Node污点 释义看文档就好 https://kubernetes.io/zh-cn/docs/concepts/scheduling-eviction/taint-and-toleration/ 污点是Node的属性 容忍度是Pod的属性 用来标记各自特征的,通常协同工作。 举个例子, 一个Node的污点 kubectl taint nodes node1 key1v…

箱讯科技成功闯入第八届“创客中国”全国总决赛—在国际物流领域一枝独秀

添加图片注释,不超过 140 字(可选) 2023年9月26日,第八届“创客中国”数字化转型中小企业创新创业大赛决赛在贵州圆满收官。 经过初赛、复赛、决赛的激烈角逐,箱讯科技与众多强劲对手同台竞技,最终凭借出…

K8sGPT,基于 AI 的云原生终极工具

随着人工智能和机器学习的兴起,企业和组织越来越多地寻找创新方法来利用这些技术来获得竞争优势。 该领域最强大的工具之一便是 K8sGPT,即基于 Kubernetes 的 GPT,它将 Kubernetes 编排的优势与 GPT 模型的高级自然语言处理能力结合在一起。 …

Spring工具类--ReflectUtils的使用

原文网址:Spring工具类系列--ReflectUtils的使用_IT利刃出鞘的博客-CSDN博客 简介 本文介绍Spring的ReflectUtils的使用。 ReflectUtils工具类的作用:便利地进行反射操作。 Spring还有一个工具类:ReflectionUtils,它们在功能上…