柒啊

V1

2023/03/17阅读:14主题:前端之巅同款

[3分钟]GO:创建好的包名

三、什么是好的包名

Write shy code - modules that don’t reveal anything unnecessary to other modules and that don’t rely on other modules' implementations. 编写谨慎的代码 - 不向其他模块透露任何不必要的模块,并且不依赖于其他模块的实现。 — Dave Thomas

每个 Go 语言的包实际上都是它一个小小的 Go 语言程序。 正如函数或方法的实现对调用者而言并不重要一样,包的公共API-其函数、方法以及类型的实现对于调用者来说也并不重要

一个好的 Go 语言包应该具有低程度的源码级耦合,这样,随着项目的增长,对一个包的更改不会跨代码库级联。 这些世界末日的重构严格限制了代码库的变化率以及在该代码库中工作的成员的生产率。

在本节中,我们将讨论如何设计包,包括包的名称命名类型以及编写方法和函数的技巧

3.1 一个好的包从它的名字开始

将你的包名用一个词来描述它。

我遵循的经验法则不是“我应该在这个包中放入什么类型的?”。相反,我要问是“该包提供的服务是什么?”通常这个问题的答案不是“这个包提供 X 类型”,而是“这个包提供 HTTP”。

贴士: 以包所提供的内容来命名,而不是它包含的内容。

3.1.1 好的包名应该是唯一的。

在你的项目里,每个包名应该是唯一的。如果你遵循“包名应该根据包是做什么的取名”这条建议,包名的唯一性很容易达到。如果你发现你有两个相似的包名,那可能是下面两种情况之一:

  1. 包的名字太普通了。
  2. 该包与另一个类似名称的包重叠了。在这种情况下,您应该检查你的设计,或考虑合并包。

3.2 避免使用类似 basecommonutil 的包名称

常见的不好的包名是utility 包。这个包主要是存放一些在编码过程中的帮助类和工具函数。因为这个包存放了很多不相关的函数,所以很难从这个包提供了什么 这方面来命名它。所以人们根据它所包含的内容来命名,取名为utilities

在包层次很深的大型项目中,人们想要在不出现循环依赖的情况下共享帮助类和工具函数,所以在这种项目中经常看到utilshelpers包。人们通过将帮助类和工具函数抽取 到新的包里来解决循环依赖,所以该包的出现是由于项目的设计问题,所以其名称不能体现他的目的和用途,这个包而仅仅是用来打破之前存在的循环依赖问题。

我推荐通过分析工具类被哪些包调用,并将这些工具类移动到需要调用他们的包中来优化 utilshelpers 包名。可能这会造成一定的工具函数代码冗余, 但是这也比在两个包之间引入依赖要好。

[A little] duplication is far cheaper than the wrong abstraction. ([一点点]重复比错误的抽象的性价比高很多。) — Sandy Metz

在使用 utility 程序的情况下,最好选多个包,每个包专注于单个方面,而不是选单一的整体包。

贴士: 使用复数形式命名 utility 包。例如 strings 来处理字符串。

当通用的功能性函数(译注:我认为是帮助类以及工具函数)及服务器或者客户端的一些公共类型被拆分为单独的包时,经常会出现 basecommon这种包名。 我觉得解决这种问题的办法是减少包的数量,将客户端、服务端和公共代码组合到一个以包的功能命名的包中。

例如,net/http 包没有 clientserver 的子包,而是有一个 client.goserver.go 文件,每个文件都有各自的类型, 还有一个 transport.go 文件,用于公共消息传输代码。

贴士: 标识符的名称包括其包名称。 重要的是标识符的名称包括其包的名称。

当由另一个包引用时,net/http 包中的 Get 函数变为 http.Get

当导入到其他包中时,strings 包中的 Reader 类型变为 strings.Reader

net 包中的 Error 接口显然与网络错误有关。

3.3 尽早 return 而不是深度嵌套

go的编码思维是尽早 return ,优先判断简单的情况,然后返回结果。

所以,go的代码没有很深的嵌套,go的代码是从上至下的代码,而不是一层一层的嵌套,多层嵌套的代码是从左至右的代码。

具体解释见原文及翻译,简短说明为个人理解。

3.4 让零值更有用

假设变量没有初始化,每个变量声明都会自动初始化为与零内存的内容相匹配的值。 这就是零值。 值的类型决定了其零值; 对于数字类型,它为 0,对于指针类型为 nilslicesmapchannel 同样是 nil

始终设置变量为已知默认值的属性对于程序的安全性和正确性非常重要,并且可以使 Go 语言程序更简单、更紧凑。 这就是 Go 程序员所说的“给你的结构一个有用的零值”。

对于 sync.Mutex 类型。sync.Mutex 包含两个未公开的整数字段,它们用来表示互斥锁的内部状态。 每当声明 sync.Mutex 时,其字段会被设置为 0 初始值。sync.Mutex 利用此属性来编写,使该类型可直接使用而无需初始化。

type MyInt struct {
 mu  sync.Mutex
 val int
}

func main() {
 var i MyInt

 // i.mu is usable without explicit initialisation.
 i.mu.Lock()
 i.val++
 i.mu.Unlock()
}

另一个利用零值的类型是 bytes.Buffer。您可以声明 bytes.Buffer 然后就直接写入而无需初始化。

func main() {
 var b bytes.Buffer
 b.WriteString("Hello, world!\n")
 io.Copy(os.Stdout, &b)
}

切片的一个有用属性是它们的零值 nil。如果我们看一下切片运行时 header 的定义就不难理解:

type slice struct {
        array *[...]T // pointer to the underlying array
        len   int
        cap   int
}

此结构的零值意味着 lencap 的值为 0,而 array(指向保存切片的内容数组的指针)将为 nil。这意味着你不需要 make 切片,你只需声明它即可。

func main() {
 // s := make([]string, 0)
 // s := []string{}
 var s []string

 s = append(s, "Hello")
 s = append(s, "world")
 fmt.Println(strings.Join(s, " "))
}

注意: var s []string 类似于它上面的两条注释行,但并不完全相同。值为 nil 的切片与具有零长度的切片就可以来相互比较。以下代码将输出 false

func main() {
 var s1 = []string{}
 var s2 []string
 fmt.Println(reflect.DeepEqual(s1, s2))
}

nil pointers -- 未初始化的指针变量的一个有用属性是你可以在具有 nil 值的类型上调用方法。它可以简单地用于提供默认值。

type Config struct {
 path string
}

func (c *Config) Path() string {
 if c == nil {
  return "/usr/home"
 }
 return c.path
}

func main() {
 var c1 *Config
 var c2 = &Config{
  path: "/export",
 }
 fmt.Println(c1.Path(), c2.Path())
}

3.5 避免包级别状态

编写可维护程序的关键是它们应该是松散耦合的 - 对一个程序包的更改应该很少影响另一个不直接依赖于第一个程序包的程序包。

在 Go 语言中有两种很好的方法可以实现松散耦合

  1. 使用接口来描述函数或方法所需的行为。
  2. 避免使用全局状态。

在 Go 语言中,我们可以在函数方法范围以及范围内声明变量。当变量是公共的时,给定一个以大写字母开头的标识符, 那么它的范围对于整个程序来说实际上是全局的 - 任何包都可以随时观察该变量的类型和内容。

可变全局状态在你的程序的独立部分之间引入了紧密的耦合,因为全局变量成为你程序中每个函数的隐形参数。任何依赖全局变量的函数,如果该变量的类型发生变化,就会被破坏。 任何依赖于全局变量状态的函数,如果程序的另一部分改变了该变量,都会被破坏。

如果要减少全局变量所带来的耦合

  1. 相关变量作为字段移动到需要它们的结构上。
  2. 使用接口来减少行为实现之间的耦合

内容学习于该博客:英文博客[1]

同时借鉴于该翻译:中文翻译[2]

参考资料

[1]

英文博客: https://dave.cheney.net/practical-go/presentations/qcon-china.html

[2]

中文翻译: https://github.com/llitfkitfk/go-best-practice/blob/master/README.md

分类:

后端

标签:

Golang

作者介绍

柒啊
V1