乐趣区

关于golang:作为-Gopher你知道-Go-的注释即文档应该怎么写吗

刚入门 Go 开发时,在开源我的项目的主页上咱们常常能够看到这样的一个徽章:

点击徽章,就能够关上 https://pkg.go.dev/ 的网页,网页中给出了这个开源我的项目所对应的 Go 文档。在刚接触 Go 的时候,我一度认为,pkg.go.dev 下面的文档是须要开发者上传并审核的——要不然那些文档咋都显得那么业余呢。

然而当我写本人的轮子时,缓缓的我就发现并非如此。划重点:在 pkg.go.dev 上的文档,都是 Go 主动从开源我的项目的工程代码中爬取、格式化后展示进去的。换句话说,每个人都能够写本人的 GoDoc 并且展现在 pkg.go.dev 上,只须要听从 GoDoc 的格局规范即可,也不须要任何审核动作。

本文章的目标是通过例子,简要阐明 GoDoc 的格局,让读者也能够本人写一段高大上的 godoc。以下内容以我本人的 jsonvalue 包为例子。其对应的 GoDoc 在这里。读者能够点开,并与代码中的内容做参考比照。

什么是 GoDoc

顾名思义,GoDoc 就是 Go 语言的文档。在理论利用中,godoc 可能能够指以下含意:

  1. 在 2019.11 月之前,示意 https://godoc.org 中的内容
  2. 当初 godoc.org 曾经下线,会重定向到 pkg.go.dev,并且其性能也都从新迁徙到这下面——下文以“pkg.go.dev”指代这个含意
  3. Go 开发工具的一个命令,就叫做 godoc——下文间接以“godoc”指代这个工具
  4. pkg.go.dev 的相干命令,被叫做 pkgsite,代码托管在 GitHub 上——下文以“pkgsite”指代这个工具
  5. Go 工具包的文档以及生成该文档所相干的格局——下文以“GoDoc”指代这个含意

目前的 godoc 和 pkgsite 有两个作用,一个是用来本地调试本人的 GoDoc 显示成果;另一个是在无奈迷信上网的时候,用来本地搭建 GoDoc 服务器之用。

godoc 命令

咱们从工具命令开始讲起吧。在 2019 年之前,Go 应用的是 godoc 这个工具来格式化和展现 Go 代码中自带的文档。当初这个命令曾经不再蕴含于 Go 工具链中,而须要额定装置:

go get -v golang.org/x/tools/cmd/godoc

godoc 命令有多种模式和参数,这里咱们列出最罕用和最简便的模式:

cd XXXX; godoc -http=:6060

其中 XXXX 是蕴含 go.mod 的一个仓库目录。假如 XXX 是我的 jsonvalue 库的本地目录,依据 go.mod,这个库的地址是 github.com/Andrew-M-C/go.jsonvalue,那么我就能够在浏览器中关上 http://${IP}:${PORT}/pkg/github.com/Andrew-M-C/go.jsonvalue/,就能够拜访我的 jsonvalue 库的 GoDoc 页面了,如下图所示:

pkgsite 命令

正如前文所说,当初 Go 官网保护和应用的是 pkg.go.dev,因而本文次要阐明 pkgsite 的用法。

以后的 pkgsite 要求 Go 1.18 版,因而请把 Go 版降级到 1.18。而后咱们须要装置 pkgsite:

go install golang.org/x/pkgsite/cmd/pkgsite@latest

而后和 godoc 相似:

cd XXXX; pkgsite -http=:6060

一样用 jsonvalue 举例。浏览器的地址与 godoc 相似,然而少了 pkg/: http://${IP}:${PORT}/github.com/Andrew-M-C/go.jsonvalue/,页面如下图所示:

pkg.go.dev 内容

总体内容

因为笔者在 jsonvalue 中对 GoDoc 玩得比拟多,因而还是以这个库为例子。咱们关上 pkg.go.dev 中相干包的主页,能够看到这些内容:

  • A – 以后 package 的残缺门路
  • B – 以后 package 的名称,其中的 module 示意这是一个合乎 go module 的包
  • C – 以后 package 的一些根底信息,包含最新版本、公布工夫、证书、依赖的包数量(包含零碎包)、被援用的包数量
  • D – 如果以后 package 蕴含 README 文件,则展现 README 文件的内容
  • E – 以后 package 内的 comment as document 文档内容
  • F – 以后 package 的文件列表,能够点击疾速浏览
  • G – 以后 package 的子目录列表

如果你的 README (markdown 格局) 有子标题,那么 pkgsite 会生成 README 下的二级目录索引。Markdown 的格局在本文就不予阐明,置信码农们都耳熟能详了。

Documentation

让咱们点开 Documentation,一个残缺的 package,可能蕴含以下这些内容:

大节 阐明
Overview 这是整个 package 的概览阐明,取的是 go 代码中的“包正文”局部
Index 这是整个 GoDoc 内容的总目录,蕴含了所有可导出的函数、办法、常量、变量和示例代码
Variables 这里列出了所有可导出变量。实际上一个封装得比拟好的 package,这里点进去之后应该是空的
Functions 所有的可导出函数(返回可导出类型的函数除外)
Types 所有的可导出类型及其办法,以及可能生成对应类型的可导出函数列表(比方各种构造函数)

其实 Documentation 的内容,就是 GoDoc。Go 秉承“正文即文档”的理念,其中 pkg.go.devgodocpkgsite 都应用同一套 GoDoc 格局,三者都依照该格局从文档的正文中提取,并生成文档。

上面咱们具体来说明一下 GoDoc 的语法。

GoDoc 语法

在 GoDoc 中,以后 package 的所有可导出类型,都会在 pkg.go.dev 页面中展现进去,即使某个可导出类型没有任何的正文,GoDoc 也会将这个可导出内容的原型展现进去——当然了,咱们应该时时刻刻记住:所有的可导出内容,都应该写好正文。

GoDoc 反对 ///* ... */ 两种模式的正文符。然而笔者还是举荐应用 //,这也是目前的正文符支流,而且大部分 IDE 也都反对一键将多行文本间接转为正文(比方 Mac 的 VsCode,应用 command + /)。尽管 /* */ 在多行正文中十分不便,但一旦看到这个,总感觉如同是上古时代的代码 (狗头)。

绑定 GoDoc 与指定类型

对于任意一个可导出内容,紧跟着代码定义上方一行的正文,都会被视为该内容的 GoDoc,从而被提取进去。比如说:

// 这一行,会被视为 SomeTypeA 的 GoDoc,// 因为它紧挨着 SomeTypeA 的定义。type SomeTypeA struct{}

// 这一行与 SomeTypeB 的定义之间隔了一行,// 所以并不会认为是 SomeTypeB 的 GoDoc。type SomeTypeB struct{}

/*
应用这种正文符的正文也是同理,因为整个正文块紧挨着 SomeTypeC 的定义,因而会被视为 SomeTypeC 的正文。*/
type SomeTypeC struct{}

这三个类型在 pkgsite 页面上的展现成果是这样的:

然而,请读者留神,依照 Go 官网的举荐,代码正文的第一个单词,应该是被正文的内容自身。比方前文中,SomeTypeA 的正文应该是 // SomeTypeA 结尾。下文开始将会对立应用这一标准。

换行(段落)

读者能够留神到,前文中的所有无效正文,我都换了一行;然而在 pkgsite 的页面展现中,并没有产生换行。

实际上,在正文中如果只是单纯的一个换行另写正文的话,在页面是不会将其当作 新的一段 来对待的,GoDoc 的逻辑,也仅仅渲染完这一行之后,再加一个空格,而后持续渲染下一行。

如果要在同一个正文块中新加一个段落,那么咱们须要插入一行空正文,如下:

// SomeNewLine 只是用来展现如何在 GoDoc 中换行。//
// 你看,这就是新的一行了,耶~✌️
func SomeNewLine() error {return nil}

内嵌代码

如果有需要的话,咱们能够在正文中内嵌一小段代码,代码会被独立为一个段落,并且应用等宽字符展现。比方上面的一个例子:

// IntsElem 用于不 panic 地从一个 int 切片中读取元素,并且返回值和理论在切片中的地位。//
// 不论是任何状况,如果切片长为 0,则 actual Index 返回 -1.
//
// 依据参数 index 能够有几种状况://
// - 零值,则间接取切片的第一个值
//
// - 正值,则从切片 0 地位开始,如果遇到切片完结了,那么就循环从头开始数
//
// - 负值,则示意逆序,此时则循环从切片的最初一个值开始数
//
// 负值的例子:
//
//    sli := []int{0, -1, -2, -3}
//    val, idx := IntsElem(sli, -2)
//
// 返回得 val = -2, idx = 2
func IntsElem(ints []int, index int) (value, actualIndex int) {// ......}

总结一下:在正文块中,如果局部正文行合乎以下规范之一,则视为代码块:

  • 正文行以制表符 \t 结尾
  • 正文行以以多于一个空格(包含制表符)结尾

一般正文和代码块之间能够不必专门的空正文行,但集体倡议还是加上比拟好。

Overview 局部

在 Documentation 中的 Overview 局部,是整个 package 的阐明,这种类型的正文,被称为“包正文”。包正文是写在 go 文件最开始的 package xxx 下面。尽管 GoDoc 没有限度、然而 Go 官网倡议包正文该当以 // Package xxx 结尾作为文本的主语。

如果在一个 package 中,有多个文件都蕴含了包正文,那么 GoDoc 会依照文件的字典序,顺次展现这些文件中的包正文。但这样可能会带来凌乱,因而一个 package 咱们该当 只在一个文件 中写包正文。

一般而言,咱们能够抉择以下的文件写包正文:

  • 很多 package 上面会有一个与 package 名称同名的 xxx.go 文件,那咱们能够对立就在这个文件里写包正文,比方这样;
  • 如果 xxx.go 文件自身承载了较多代码,或者是包正文比拟长,那么咱们能够专门开一个 doc.go 文件,用来写包正文,比方这样。

弃用代码申明

Go 所应用的版本号是 vX.Y.Z 的模式,依照官网的思维,每当 package 降级时,尽量不要降级大版本 X 值,这也同时代表着,本次降级是齐全向前兼容的。然而实际上,咱们在做一些小版本或中版本升级时,有些函数 / 类型可能不再举荐应用。此时,GoDoc 提供了一个关键字 Deprecated:,作为整个正文块的第一个单词,比方咱们能够这么写:

// Deprecated: ElemAt 这个函数弃用,后续请迁徙到 IntsElem 函数中.
func ElemAt(ints []int, index int) int {// ......}

针对 deprecated 的内容,pkgsite 一方面会在目录中标识进去:

此外,在注释中,也会刻意用灰色字体低调展现,并且暗藏正文注释,须要点开能力显示:

代码示例文档

读者如果看我 jsonvalue 的文档,在 At() 函数下,除了上文提到的文档注释之外,还有五个代码示例:

那么,文档中的代码示例又应该如何写呢?

首先,咱们应该新建至多一个文件,专门用来寄存示例代码。比方我就把示例代码写在了 example_jsonvalue_test.go 文件中。这个文件的 package 不得 与以后包名雷同,而应该命名为 包名_test 的格局。

此外,须要留神的是,示例代码文件也属于单元测试文件的内容,当执行 go test 的时候,示例文件也会纳入测试逻辑中。

示例代码的申明

如何申明一个示例代码,这里我举两个例子。首先是在 At() 函数下名为“Example (1)”的示例。在代码中,我把这个函数命名为:

func ExampleSet_At_1() {......}

这个函数命名有几个局部:

函数名组成部分 阐明
Example 这是示例代码的固有结尾
Set 示意这是类型 Set 的示例
第一个下划线 _ 分隔符,在这个分隔符前面的,是 Set 类型的成员函数名
At 示意这是函数 At() 的示例,搭配后面的内容,则示意这是类型 Set 的成员函数 At() 的示例
第二个下划线 _ 分隔符,在这个分隔符前面的内容,是示例代码的额定阐明
1 这是示例代码的额定阐明,也就是后面“Example (1)”括号里的局部

另外,示例代码中应该蕴含规范输入内容,这样便于读者理解执行状况。规范输入内容在函数内的最初,采纳 // Output: 独自起一行结尾,剩下的每一行规范输入写一行正文。

绝对应地,如果你想要给(不属于任何一个类型的)函数写示例的话,则去掉上文中对于“类型”的字段;如果你不须要示例的额定说明符,则去掉“额定阐明”字段。比如说,我给类型 Opt 写的示例就只有一个,在代码中,只有一行:

func ExampleOpt() {........}

甚至连示例阐明都没有。

如果一个元素蕴含多个例子,那么 godoc 会依照字母序对示例及其相应的阐明排序。这也就是为什么我罗唆在 At() 函数中,示例标为一二三四五的起因,因为这是我心愿读者浏览示例的程序。

在官网上公布 GoDoc

好了,当你写好了本人的 GoDoc 之后,总不是本人看本人自娱自乐吧,总归是要公布进去给大家看的。

其实公布也很简略:当你将蕴含了 godox 的代码 push 之后(比方公布到 github 上),就能够在浏览器中输出 https://pkg.go.dev/${package 路径名}。比方 jsonvalue 的 Github 门路(也等同于 import 门路)为 github.com/Andrew-M-C/go.jsonvalue,因而输出 https://pkg.go.dev/github.com/Andrew-M-C/go.jsonvalue

如果这是该页面第一次进入,那么 pkg.go.dev 会首先获取、解析和更新代码仓库中的文档内容,并且格式化之后展现。在 pkg.go.dev 中,如果可能找到 package 的最新的 tag 版本,那么会列出 tag(而不是骨干分支)上的 GoDoc。

接下来更重要的是,把这份官网 GoDoc 的链接,附到你本人的 README 中。咱们能够进入 pkg.go.dev 的徽章生成页

输出仓库地址就能够看到相应的徽标的链接了。有 htmlmarkdown 格局任君抉择。

参考资料

  • 万字长文解读 pkg.go.dev 的设计和实现
  • pkg.go.dev 源码

本文章采纳 常识共享署名 - 非商业性应用 - 雷同形式共享 4.0 国内许可协定 进行许可。

本文最早公布于 云 + 社区,也是自己的博客。

原作者:amc,欢送转载,但请听从上述协定注明出处。

原文题目:作为 Gopher,你晓得 Go 的正文即文档应该怎么写吗?

公布日期:2022/03/24

原文链接:https://segmentfault.com/a/1190000041604192。

另:本文局部内容与笔者以前公布过的《如何写高大上的 godoc》一文相似,但过后成文与还没有 pkg.go.dev 的时代,很多内容曾经掉队。因而我从新写了这篇。

退出移动版