指导原则
- 简单性
- 可读性
程序应该被写来让人们阅读,只是顺便为了机器执行 - 生产力
标识符
-
标识符是一个用来表示名称的花哨单词; 变量的名称,函数的名称,方法的名称,类型的名称,包的名称等
-
命名不佳是设计不佳的症状
-
可读性是良好代码的定义质量
-
选择标识符是为了清晰,而不是简洁
-
好的名字很简洁
-
好的名字应该是可预测的
-
Go 语言推荐短变量名的风格
-
短变量名称在声明和上次使用之间的距离很短时效果很好。
-
长变量名称需要证明自己的合理性; 名称越长,需要提供的价值越高。冗长的名称与页面上的重量相比,信号量较小。
-
请勿在变量名称中包含类型名称。
-
常量应该描述它们持有的值,而不是该如何使用。
-
对于循环和分支使用单字母变量,参数和返回值使用单个字,函数和包级别声明使用多个单词-
-
方法、接口和包使用单个词。
-
请记住,包的名称是调用者用来引用名称的一部分,因此要好好利用这一点。
-
不要用变量类型命名你的变量
-
声明变量但没有初始化时,请使用 var
-
在声明和初始化时,使用 :=
注释
-
注释应该解释其作用。
-
注释应该解释其如何做的。
-
注释应该解释其原因。
-
关于变量和常量的注释应描述其内容而非其目的 向变量或常量添加注释时,该注释应描述变量内容,而不是变量目的。
-
对于没有初始值的变量,注释应描述谁负责初始化此变量。
-
公共符号始终要注释
-
Google Style
任何既不明显也不简短的公共功能必须予以注释。
无论长度或复杂程度如何,对库中的任何函数都必须进行注释 -
不需要注释实现接口的方法。如下操作不需要
// Read implements the io.Reader interface
func (r *FileReader) Read(buf []byte) (int,error)
- 在编写函数之前,请编写描述函数的注释。 如果你发现很难写出注释,那么这就表明你将要编写的代码很难理解。
- 不要注释不好的代码,将它重写 与其注释一段代码,不如重构它
- 好的代码是最好的文档。 在即将添加注释时,请问下自己,“如何改进代码以便不需要此注释?' 改进代码使其更清晰。
包的设计
- 编写谨慎的代码 - 不向其他模块透露任何不必要的模块,并且不依赖于其他模块的实现。
- 每个 Go 语言的包实际上都是它一个小小的 Go 语言程序。 正如函数或方法的实现对调用者而言并不重要一样,包的公共 API - 其函数、方法以及类型的实现对于调用者来说也并不重要。
- 以包所提供的内容来命名,而不是它包含的内容
- 好的包名应该是唯一的
- 避免使用类似 base, common或 util的包名称
- ([一点点] 重复比错误的抽象的性价比高很多。)
- 使用复数形式命名 utility包。例如 strings来处理字符串。
- 标识符的名称包括其包名称。
重要的是标识符的名称包括其包的名称。
当由另一个包引用时, net/http包中的 Get 函数变为 http.Get。
当导入到其他包中时, strings包中的 Reader类型变为 strings.Reader。
net包中的 Error接口显然与网络错误有关。 - 尽早 return而不是深度嵌套
- 让零值更有用
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()
}
- 避免包级别状态
- 使用接口来描述函数或方法所需的行为。避免使用全局状态。
- 将相关变量作为字段移动到需要它们的结构上。
使用接口来减少行为与实现之间的耦合。
项目结构
-
考虑更少,更大的包
-
除 cmd/和 internal/之外的每个包都应包含一些源代码。
-
在 Go 语言中,我们只有两个访问修饰符, public和 private 由标识符的第一个字母的大小写表示。 如果标识符是公共的,则其名称以大写字母开头,该标识符可用于任何其他 Go 语言包的引用。
-
来自 Java? 如果您来自 Java或 C#,请考虑这一经验法则 -- Java包相当于单个 .go源文件。 - Go 语言包相当于整个 Maven模块或 .NET程序集。
-
通过 import语句将代码排列到文件中
-
开始时使用一个 .go文件。为该文件指定与文件夹名称相同的名称。例如: package http应放在名为 http的目录中名为 http.go的文件中。
随着包的增长,您可能决定将各种职责任务拆分为不同的文件。例如, messages.go包含 Request和 Response类型, client.go包含 Client类型, server.go包含 Server类型。
如果你的文件中 import的声明类似,请考虑将它们组合起来。或者确定 import集之间的差异并移动它们。
不同的文件应该负责包的不同区域。 messages.go可能负责网络的 HTTP请求和响应, http.go可能包含底层网络处理逻辑, client.go和 server.go实现 HTTP业务逻辑请求的实现或路由等等。 -
Go 编译器并行编译每个包。 在一个包中,编译器并行编译每个函数(方法只是 Go 语言中函数的另一种写法)。 更改包中代码的布局不会影响编译时间。
-
优先内部测试再到外部测试 http2_test.go 这就是内部测试
-
go tool还支持一个特殊的包声明,以 test为结尾,即 package http_test
-
建议在编写单元测试时使用内部测试。这样你就可以直接测试每个函数或方法,避免外部测试干扰
-
应该将 Example测试函数放在外部测试文件中。这确保了在 godoc中查看时,示例具有适当的包名前缀并且可以轻松地进行复制粘贴
-
避免复杂的包层次结构,抵制应用分类法
Go 语言包的层次结构对于 go tool没有任何意义除了下一节要说的。 例如, net/http包不是一个子包或者 net包的子包。
如果在项目中创建了不包含 .go文件的中间目录,则可能无法遵循此建议 -
使用 internal包来减少公共 API go tool会识别一个特殊的文件夹名称 - 而非包名称 - internal/可用于放置对项目公开的代码,但对其他项目是私有的。
.../a/b/c/internal/d/e/f的包只能通过以 .../a/b/c/为根目录的代码被导入。 它无法通过.../a/b/g` 或任何其他仓库中的代码导入。 -
确保 main包内容尽可能的少 因为 main.main充当单例; 程序中只能有一个 main函数,包括 tests
-
main应该做解析 flags,开启数据库连接、开启日志等,然后将执行交给更高一级的对象。
API 设计
- (API 应该易于使用且难以被误用)
- 警惕采用几个相同类型参数的函数
- 具有多个相同类型参数的 API 难以正确使用
- 为其默认用例设计 API
- 不鼓励使用 nil作为参数 nil行为是病毒式的
- 不要在同一个函数签名中混合使用可为 nil和不能为 nil的参数
- 首选可变参数函数而非 []T参数
- 让函数定义它们所需的行为
- 函数参数可以采用接口隔离原则
错误处理
- https://dave.cheney.net/2014/12/24/inspecting-errors
- https://dave.cheney.net/2016/04/07/constant-errors
- 通过消除错误来消除错误处理
我不是说 “删除你的错误处理”。我的建议是,修改你的代码,这样就不用处理错误了。
<软件设计哲学>
- 当遇到难以忍受的错误处理时,请尝试将某些操作提取到辅助程序类型中。
- 错误只处理一次
- 为错误添加相关内容
- 为错误添加相关内容 错误注释 #
could not write config: write failed: input/output error - 使用 github.com/pkg/errors包装 errors 检查它是否为 nil。输出或记录它。
并发
- 如果 Go 语言程序的 main.main函数返回,无论程序在一段时间内启动的其他 goroutine在做什么, Go 语言程序会无条件地退出。
- 许多 Go 程序员过度使用 goroutine,特别是刚开始时。 与生活中的所有事情一样,适度是成功的关键。
- 将并发性留给调用者
- 保持自己忙碌或做自己的工作
- 如果你的函数启动了 goroutine,你必须为调用者提供一种明确停止 goroutine的方法。 把异步执行函数的决定留给该函数的调用者通常会更容易些
- 永远不要启动一个停止不了的 goroutine。
- 正如 Go 语言中的函数将并发性留给调用者一样,应用程序应该将监视其状态和检测是否重启的工作留给另外的程序来做。 不要让你的应用程序负责重新启动自己,最好从应用程序外部处理该过程。
- 只在 main.main或 init函数中的使用 log.Fatal。 log.Fatal调用 os.Exit,它将无条件地退出程序; defer不会被调用,其他 goroutines也不会被通知关闭,程序就停止了。 这使得编写这些函数的测试变得困难。
网友评论