最近两个月没有好好的看书学习,导致博客也水了两个月没写什么正经的。上周收到仓鼠🐹君萌萌哒的邮件之后,又激起了我写博客的欲望。由于自己最近灵感枯竭,所以我决定翻译一篇别人的O(∩_∩)O~。作为一个一直想学 Go,但想了好久还没入门的人,我挑了篇写 Go 的,顺便帮自己熟悉一下 Go。原文是作者根据自己 GolangUK 的
所整理的,全文以 SOLID 原则为线路讲述了什么样的 Go 代码才算是好代码,当然 SOLID 原则也适用于其他语言。原文比较长,所以准备分成上下两部分,也有十分非常以及特别大的可能是上中下(捂脸)。
咳咳,我果然是打脸体质,下翻译了一句就放弃了。不过,我把它交给了超靠谱的小伙伴。想看下的请移步
捂。。。。。。。。还是不捂了,脸已经丢没了🙈
Dave Cheney
世界上有多少个 Go 语言开发者?
介个世界上有多少 Go 开发者捏?在脑海中想一个数字,我们会在最后回到这个话题。
Code review
有多少人将 code review 当做自己工作的一部分?[听演讲的人都举起了手]。为什么要做 code review?[一些人回答为了阻止不好的代码]
如果 code review 是为了捕捉到不好的代码,那么问题来了,你怎么判断你正在 review 的代码是好还是不好呢?
我们可以很容易的说出“这代码好辣眼睛”或者“这源码写的太吊了”,就像说“这画真美”,“这屋子真大气”一样。但是这些都是主观的,我希望找到一些客观的方法来衡量代码是好还是不好。
Bad code
下面看一下在 code review 中,一段代码有哪些特点会被认为是不好的代码。
- Rigid 代码是不是很僵硬?是否由于严格的类型和参数导致修改代码的成本提高
- Fragile 代码是不是很脆弱?是否一点小的改动就会造成巨大的破坏?
- Immobile 代码是否难以重构?
- Complex 代码是否是过度设计?
- Verbose 当你读这段代码时,能否清楚的知道它是做什么的?
👆这些都不是什么好听的词,没有人希望在别人 review 自己代码时听到这些词。
Good design
了解了什么是不好的代码之后,我们可以说“我不喜欢这段代码因为它不易于修改”或者“这段代码并没有清晰的告诉我它要做什么”。但这些并没有带来积极的引导。
如果我们不仅仅可以描述不好的设计,还可以客观的描述好的设计,是不是更有助于提高呢。
SOLID
2002年,Robert Martin
出版了《敏捷软件开发:原则、模式与实践》一书,在书中他描述了可重用软件设计的五个原则,他称之为 SOLID 原则(每个原则的首字母组合在一起)。
- 单一责任原则
- 开放封闭原则
- 里氏替换原则
- 接口分离原则
- 依赖倒置原则
这本书有点过时了,书中谈论的语言都已经超过了十年之久。尽管如此,在谈论什么样的 Go 代码才是好代码时,SOLID 的原则依然可以给我们一些启发。
So,这也就是我花时间想在本文和大家一起讨论的。
单一责任原则
SOLID 原则中的第一个原则就是单一责任原则
。Robert C Martin
说过 A class should have one, and only one, reason to change(修改某个类的时候,原因有且只有一个),说白了就是,一个类只负责一项职责。
虽然 Go 语言中并没有类的概念–但我们有更鹅妹子嘤的 composition
(组合)的特性。
为什么修改一段代码只负责一项职责如此重要呢?如果一个类有两个职责R1,R2,那么修改R1时,可能会导致也要修改R2。修改代码是痛苦的,但更痛苦的是修改代码的原因是由于修改其他代码引起的。
所以当一个类只负责一个功能领域中的相应职责时,可以修改的它的原因也就最大限度的变少了。
耦合 & 内聚
这两个词是用来形容一段代码是否易于修改的。
耦合
是指两个东西需要一起修改—对其中一个的改动会影响到另一个。
另一个相关但独立的概念是内聚
,一般指相互吸引的迷之力量。
在软件开发领域中,内聚常常用来描述一段代码内各个元素彼此结合的紧密程度。
下面我准备从 Go 的包模型开始,聊聊 Go 开发中的耦合与内聚。
包名
在Go中,所有代码都必须有一个所属的包。一个包名要描述它的用途,同时也是命名空间的前缀。下面是 Go 标准库中一些好的包名:
- net/http,提供 http 的客户端和服务端。
- os/exec,可以运行运行外部命令。
- encoding/json,实现了 JSON 文件的编码和解码。
不好的包名
现在让我们来喷一些不好的包名。这些包名并没有很好的展现出它们的用途,当然了前提是它们有-_-|||。
- package server 是提供什么?。。。好吧就当是提供一个服务端吧,但是是什么协议呢?
- package private 是提供什么?一些我不应该看👀的东西?
- 还有 package common, package utils,同样无法清楚的表达它们的用途,开发者也不易保持它们功能的专一性。
上面这些包很快就会变成堆放杂七杂八代码的垃圾堆,而且会由于功能太杂乱而频繁修改。
Go 中的 UNIX 哲学
在我看来,任何关于解耦设计的讨论如果没有提到 Doug McIlroy
的 UNIX 哲学
都是不完整的。UNIX 哲学就是主张将若干简洁,清晰的模块组合起来完成复杂的任务,而且通常情况下这个任务都不是原作者所能预想到的。
我想 Go 中的包正体现了 UNIX 哲学的精神。因为每一个包都是一个拥有单一责任的简洁的 Go 程序。
开放封闭原则
第二个原则,也就是 SOLID 当中的 O,是由 Bertrand Meyer
提出的开放封闭原则
。1988年,Bertrand Mey 在他的著作《面向对象软件构造》一书中写道:Software entities should be open for extension,but closed for modification(软件实体应当对扩展开放,对修改关闭)。
那么这个n年前的建议在 Go 语言中是如何应用的呢?
package main
import (
"fmt"
)
type A struct {
year int
}
func (a A) Greet() {
fmt.Println("Hello GolangUK", a.year)
}
type B struct {
A
}
func (b B) Greet() {
fmt.Println("Welcome to GolangUK", b.year)
}
func main(){
var a A
a.year = 2016
var b B
b.year = 2016
a.Greet()
b.Greet()
}
上面的代码中,我们有类型A,包含属性 year 和一个方法 Greet。我们还有类型B,B中嵌入(embedding)了类型A,并且B提供了他自己的 Greet 方法,覆盖了A的。
嵌入不仅仅是针对方法,还可以通过嵌入使用被嵌入类型的属性。我们可以看到,在上面的例子中,因为A和B定义在同一个包中,所以B可以像使用自己定义的属性一样使用A中的 private 的属性 year。
所以,嵌入是实现 Go 类型对扩展开放非常鹅妹子嘤的手段。
package main
import (
"fmt"
)
type Cat struct{
Name string
}
func (c Cat) Legs() int {
return 4
}
func (c Cat) PrintLegs() {
fmt.Printf("I have %d legs\n", c.Legs())
}
type OctoCat struct {
Cat
}
func (c OctoCat) Legs() int {
return 5
}
func main() {
var octo OctoCat
fmt.Printf("I have %d legs\n", octo.Legs())
octo.PrintLegs()
}
在这个例子中,我们有一个 Cat 类型,它拥有一个 Legs 方法可以获得腿的数目。我们将 Cat 类型嵌入到一个新类型 OctoCat 中,然后声明 Octocat 有5条腿。然而,尽管 OctoCat 定义了它自己的 Legs 方法返回5,在调用 PrintLegs 方法时依旧会打印“I have 4 legs”。
这是因为 PrintLegs 方法是定义在 Cat 类型中的,它将 Cat 作为接收者,所以会调用 Cat 类型的 Legs 方法。Cat 类型并不会感知到它被嵌入到其他类型中,所以它的方法也不会被更改。
所以,我们可以说 Go 的类型是对扩展开放,对修改关闭的。
实际上,Go 类型中的方法比普通函数多了一点语法糖—-将接收者作为一个预先声明的形参。(译者注:这块理解了好久😖。。。,不懂得可以看这篇
)
func (c Cat) PrintLegs() {
fmt.Printf("I have %d legs\n", c.Legs())
}
func PrintLegs(c Cat) {
fmt.Printf("I have %d legs\n", c.Legs())
}
由于 Go 并不支持函数重载,所以 OctoCat 类型并不能替代 Cat 类型。这也将引出下一个原则—里氏替换原则。
且听下回分解。。。。。。。
——————————————别看我,我只是个傲娇的分割线———————————————————————
终于完成了上的部分↖(^ω^)↗,尽量在下周完成下。由于并不了解 Go 难免会有错误或翻译生硬的地方,欢迎指正错误,欢迎一起讨论~(≧▽≦)/~。
都看到这了,关注个公众号再走吧🙈