go语言复数包_Go语言中包的风格指南

Go 语言也有自己的命名与代码组织规则。漂亮的代码,布局清晰、易读易懂,就像是设计严谨的 API 一样。拿到代码,用户首先看到和接触的就是布局、命名还有包的结构。

这篇文章不是为了给大家设立硬性的规定,而是用实践引导大家形成自己的规则。因为具体问题要具体分析,通过自己的判断才能挑选出最恰当的规则。

## 包

所有的 Go 代码都是以包的形式组织起来的。Go 中的包就是目录或文件夹里面包括一个或者多个以 `.go` 结尾的文件。用目录或文件夹这样的方式来管理代码,与电脑管理目录或文件夹是一样一样的。

所有的 Go 代码都是放在包里的,且只能是通过包来进行访问。理解并且建立良好的包的习惯,可以帮助写出高效的代码。

* * *

## 包的组织

我们先聊聊如何组织 Go 代码,解释一下定位 Go 包的规范。

### 使用多个文件

一个包就是一个或者多个文件的目录。先把代码切分成符合逻辑且易读的多个文件。

例如,根据文件处理 HTTP 的内容不一样,一个 HTTP 包可以被切分成多个文件。在下面这个例子中,一个 HTTP 包被切成下列文件:头部类型定义和代码,cookie 类型定义加代码,还有实际 HTTP 功能的实现和包说明文档。

- doc.go // 包说明文档

- headers.go // HTTP 头部类型定义和代码

- cookies.go // HTTP cookie 类型定义和代码

- http.go // HTTP 客户端实现,请求和返回类型,等等

### 聚合类型定义

首要规则是,把类型定义尽量都聚合到他们被引用的地方。这让代码的维护者(不仅仅局限于代码的原作者)更易于找到类型的定义。比如,头结构体类型最好就是放在 `headers.go` 文件当中。

$ cat headers.go

package http

// Header 表示一个 Http 头部结构体定义

type Header struct {...}

虽然 Go 语言本身并没有严格地要求你必须在文件哪个部分定义类型,但是把核心类型的定义都放在文件的最上面是没错的。

### 根据功能进行安排

在其他语言中,通常都是把类型定义聚合到一个包里,叫作模型或者类别。在 Go 语言中,则是通过代码的功能职责来进行安排的。

```go

package models // 千万别叫这个名字

// User 代表系统中的一个用户

type User struct {...}

```

不要创建一个命名为 models 的包,然后在里面定义所有的实体类型。在这个例子中,User 类型应该定义在服务层的包中。

```go

package mngtservice

// User 代表系统中的一个客户

type User struct {...}

func UsersByQuery(ctx context.Context, q *Query) ([]*User, *Iterator, error)

func UserIDByEmail(ctx context.Context, email string) (int64, error)

```

### 优化 godoc

越早使用 godoc 越好,尤其是在初期设计包的 API 的时候。使用 godoc,你可以清楚知道自己的构思用文档表达出来是个什么。有时候,可视化也对设计有影响。因为 godoc 需要放在一个独立的包里,所以可以慢慢地来进行优化,让文档越来越容易理解。执行命令 `godoc -http=` 来启动本地的 godoc 文档服务。

### 举几个例子说明一下

有些情况下,你可能没办法把所有相关的类型定义都写在一个包里。因为这样做可能会很繁琐。或者你只是想发布一个实现了单个包中通用接口的具体功能,或者这些类型是由第三方包提供的。下面给出几个例子来说明和理解这些情况。

$ godoc cloud.google.com/go/datastore

func NewClient(ctx context.Context, projectID string, opts ...option.ClientOption) (*Client, error)

...

NewClient 这个方法里有一个结构参数 option.ClientOptions,但是这个结构的定义既不在 datastore 包里,也不在提供所有 option 类型的 option 包里。

$ godoc google.golang.org/extraoption

func WithCustomValue(v string) option.ClientOption

...

如果你设计的 API 需要引入很多非标准库的包,那么,添加 [Go 示例](https://blog.golang.org/examples) 通常会很有用,以便为用户提供一些工作代码。

样例可以提高藏地较深的包的暴光率。例如,datastore.NewCLient 这样的结构需要引用额外的 option 包。提供样例,就可以让用户知道,还有一个 option 包。

### 不要在 main 文件中导出

标识符可以被 [导出](https://golang.org/ref/spec#Exported_identifiers),以允许从外部包来使用它。

main 包是不能被导入的,所以从 main 包中导出标记符是没有必要的。如果你要把包编译成二进制文件,就不要从 main 包中导出标记符。

这条规则也有例外,那就是 main 包被编译成了 .so 文件、.a 文件或者 Go 插件。在这种情况下,Go 代码被其他语言通过 [cgo 的导出功能](https://golang.org/cmd/cgo/#hdr-C_references_to_Go) 使用,那标识符的导出就是必要的了。

* * *

## 包的命名

包的名字与导入路径,都是很重要的标识,它们会告诉你这个包里有哪些内容。按规则给包命名不仅可以提高你的代码的质量,也间接地提高这个包的使用者的代码水平。

### 只用小写

包的名字应只用小写。不要用下划线式,也不要用驼峰式。Go 官方博文 [关于包命名的综合指南](https://blog.golang.org/package-names) 中有多个不同情形的样例。

### 简短而有意义

包的名字需要简短,但应该唯一且有意义。用户从包的名字中就能直接理解这个包的作用。

避免泛泛的包名,例如 "common", "util"。

```go

import "pkgs.org/common" // 可千万别这样写

```

避免重名,万一用户要同时引入并使用这两个同名包。

如果命名上确实有困难,可能是因为设计的代码结构与整体构架本身就有问题。

### 精简引入路径

避免暴露自定义的仓库结构(repository structure)给包的用户。谨遵 GOPATH 的规定。避免在引入路径中出现包含 src/, pkg/ 命名的路径。

github.com/user/repo/src/httputil // 可千万别这么做,不要使用 SRC !!

github.com/user/repo/gosrc/httputil // 可千万别这么做,不要使用 GOSRC !!

### 使用单数

在 Go 语言中,包的名字不要使用复数。从其他语言转过来的程序员会觉得很别扭,因为在先前使用的语言中,已经形成了使用复数的习惯。给包命名的时候,用 httputil,不用 httputils!

```go

package httputils // 用单数,不用复数

```

### 别名也应该遵守规则

如果你在引入多个相同名字的包,你可以在本地修改这些包的名字。别名也需要遵守本文提到的规则。并没有规则指明需要修改哪一个包的名字。如果你在修改标准库包的名字,最好加一个前缀来做区别,毕竟是 “Go 标准库” 中的包,比如,可以修改为 `gourl`、`goioutil`。

```go

import (

gourl "net/url"

"myother.com/url"

)

```

### 强制使用虚拟 URL

`go get` 支持通过另外一种 URL 来获取包,这个 URL 与包仓库的 URL 的名字不一样。这个不一样的 URL 叫做虚拟 URL,需要准备一个页面,里面包含可被 Go 工具识别的详细的元标签。你可以使用虚拟 URL 通过自定义域名和路径来提供包的服务。

例如,

$ go get cloud.google.com/go/datastore

在后台去查看来自 `https://code.googlesource.com/gocloud` 的源码,把它加到你的工作区当中去,这个工作区是定义在 $GOPATH/src/cloud.google.com/go/datastore 下面的。

假定 code.googlesource.com/gocloud 已经在包里了,那能不能通过这个 URL 来使用这个包呢?答案是 NO,因为开启了强制使用虚拟 URL。

实际使用中,在包里添加了一个引入声明。Go 工具就不允许从任何其他路径来引入这个包,并且会给用户一个友好地错误提示。如果你没有开启强制使用虚拟 URL,那么就会有出现两个一样的包,并且因为不同的命名空间,它们不能放在一起使用。

```go

package datastore // import "cloud.google.com/go/datastore"

```

* * *

## 包说明文档

记得要给包写说明文档。包说明文档最可以阐明包的功能。对于非 main 包来讲,godoc 都是以 "Package {包名}" 开头,并且附上一个描述说明。对于 main 包来讲,文档就是用来说明程序的功能的。

```go

// Package ioutil 实现了一些输入或输出效用功能

package ioutil

// gops 命令会列出所有在系统中跑的进程

package main

// helloworld 样例来展示如何使用 x 功能

package main

```

### 使用 doc.go 文件

有时候,包里的文档说明内容会很多,尤其是包括详细的用法说明与指导。将包的 godoc 移到 `doc.go` 这个文件中去。 (参考样例 [doc.go](https://github.com/GoogleCloudPlatform/google-cloud-go/blob/master/datastore/doc.go))

本文由 GCTT 原创编译,Go语言中文网 荣誉推出

本文由 GCTT 原创翻译,Go语言中文网 首发。也想加入译者行列,为开源做一些自己的贡献么?欢迎加入 GCTT!

翻译工作和译文发表仅用于学习和交流目的,翻译工作遵照 CC-BY-NC-SA 协议规定,如果我们的工作有侵犯到您的权益,请及时联系我们。

欢迎遵照 CC-BY-NC-SA 协议规定 转载,敬请在正文中标注并保留原文/译文链接和作者/译者等信息。

文章仅代表作者的知识和看法,如有不同观点,请楼下排队吐槽

有疑问加站长微信联系(非本文作者))

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值