5 things about programming I learned with Go By MICHAŁ KONARSKIgit
Go在最近一段时间内开始变得十分流行。语言相关的论文和博客天天都在更新,新的golang相关的项目在github中也层出不穷。Go语言的会议也吸引了愈来愈多的开发者的关注。Go语言的时代已经来临,而且当选了TIOBE的2016年度语言,并一度进入流行度前十。程序员
我一年前开始接触golang,而后决定试一试。通过一段时间的接触,我发现这绝对是一个值得学习的语言。即便你不打算长期使用,学习一段时间也会是你的编程技巧有很大的提高。接下来我会告诉你们我学习golang的过5点感悟,并且这五点感悟对其余编程语言也有用。github
我平常使用的编程语言是Ruby,我很是喜欢动态类型语言。这一特性使得语言很是容易学习、使用,而且开发效率很是高。随着项目愈来愈多、愈来愈复杂,代码变得不像其余静态类型安全语言那样安全可靠。及时我十分谨慎的测试代码,仍不能覆盖到全部的边缘情况,所以常常出现不但愿出现的情况。那么,有没有哪一种语言既有着动态语言的特性又有静态类型安全语言的可靠性。答案是确定的,咱们来说一讲Go!golang
如今有一些争论是关于golang究竟是不是面相对象的编程语言[1] [2] 。可是golang有个面相对象语言的特性--接口。格式上来看,和面相对象的语言Java比较类似,一个包含不少方法的结构体:编程
type Animal interface { Speak() string }
固然,golang也有类的等价实现--结构体。结构体也能够是数据和方法的封装:安全
type Dog struct { name string }
而后咱们可使用该结构体做为方法接收器--receiver,相似于类的成员方法:网络
func (d Dog) Speak() string { return "Woof!" }
这不就是面相对象的三大特性之一--封装么。并发
和其余面相对象语言不一样的是,方法声明在结构体外。golang的做者但愿给结构体的使用者更多的灵活性。即便 你不是结构体的做者,你也能够自由的为它加上新的“成员方法”。异步
那咱们怎么作到相似多态呢?很简单:编程语言
func SaySomething(a Animal) { fmt.Println(a.Speak()) } dog := Dog{name: "Charlie"} SaySomething(dog)
Dog
实现了接口Animal
的全部方法,就能够做为Anmial
来使用,不须要主动的声明。这种行为被称为a statically typed duck typing。
“If it quacks like a duck, then it probably is a duck”.
正是由于接口的这种特性,可让咱们像使用动态类型语言同样使用golang,却同时获得类型安全的保障。
在以前的blog中我描述过一个问题,若是过分的使用面向对象的特性,咱们会让本身陷进去。举个例子,一个需求最初能够用一个类来建模,而后逐渐扩展,在某种程度上,继承彷佛是不断增加的需求的完美答案。不幸的是,这样作致使咱们有了一棵紧密相关的大树,在那里添加新的逻辑的同时想要保持简单性和避免代码重复是很是困难的。
我对这个故事的结论是,若是咱们想要减小在代码复杂性中迷失的风险,咱们须要避免继承而选择组合。改变观念很是困难,而使用一种不支持继承的语言可以帮得上忙,你猜的对,就是Go。
Go的结构体设计的时候没有继承的概念。Go语言设计者是想保持语言的简单和清爽。他们发现继承不是必须的,可是他们保留组合的特性。举个例子,汽车包含引擎和车身,使用两个interface来表示:
type Engine interface { Refill() } type Body interface { Load() }
如今,咱们须要建立一个结构体Vechicle
组合上述接口:
type Vehicle struct { Engine Body }
发现什么奇怪的地方了么?我故意省略了接口类型的字段名。所以,我使用了叫作嵌入(embedding)的特性。这样,咱们使用Vehicle
的实体能够直接调用接口中的方法。咱们能够方便的使用组合。代码以下:
vehicle := Vehicle{Engine: PetrolEngine{}, Body: TruckBody{}} vehicle.refill() vehicle.load()
channels 和 goroutines 是很是酷的工具帮助咱们解决并发问题。
Goroutines 是Go的 green threads 由go自行管理和调度,并且占用很是少的系统资源。
Channel 是一个管道,能够用做协程间的通讯。它可让协程间方便的进行异步通讯。
这里给出一个 Goroutine 和 Channel 共同工做的例子。假设咱们有个方法执行一个耗时的计算任务,咱们不但愿它阻塞进程,咱们能够这样作:
func HeavyComputation(ch chan int32) { // long, serious math stuff ch <- result }
正如你看到的,这个方法接受一个channel类型的参数,一旦计算出结果,就将结果放到channel中便可。那咱们怎么调用这个方法呢:
ch := make(chan int32) go HeavyComputation(ch)
这里的go
关键字能够很是方便的进行异步处理。Go会新建一个协程执行HeavyComputation(ch)
,而后程序能够不阻塞的执行其余任务。获取结果也很是简单:
result := <-ch
当ch
里有计算结果的时候,能够直接读出,不然将阻塞直到计算协程放入结果。
channels 和 goroutines 是很是简单可是很是有效的并发处理机制。
(这里标题没有翻译,由于英文你们更熟悉。)
传统的编程语言通常在标准库里提供多个线程访问同一块共享内存的方法。为了同步和避免同时访问通常采用加锁的方法。可是因为Go有 goroutines 和 channels 可使用其余的方法。与加锁的方式不一样,Go能够方便的使用channel
来实现,保证了同时只有一个协程可以改变其内容。Go 的官方文档给出了解释:
One way to think about this model is to consider a typical single-threaded program running on one CPU. It has no need for synchronization primitives. Now run another such instance; it too needs no synchronization. Now let those two communicate; if the communication is the synchronizer, there’s still no need for other synchronization.
这绝对不是一个新概念,可是对于不少人来讲,对于任何并发问题,加锁仍然是首选的解决方案。固然,这并不意味着锁是无效的。它能够用来实现简单的东西,好比原子计数器。可是对于更高层次的抽象,最好考虑不一样的东西。
注:我认为做者的意思是,更复杂的场景下,若是能更关心业务而不是加锁解锁的逻辑,系统会更加可靠。实际上channel的实现就是帮咱们集成了加锁和解锁的过程,当一个协程操做channel的时候,都会伴随的加锁和解锁的过程。想详细的了解,能够参考Go语言中channel的实现。
通常语言都有异常捕获和异常处理的概念。Go不一样,Go在设计的时候没有异常的概念。这仿佛把缺乏一个特性当成了Go的特性。可是仔细想,它是有用的。当出现错误的时候,咱们没有办法肯定究竟是那种错误--磁盘空间不足?IO网络问题?若是是捕获异常可能须要包含全部类型的异常。Go给出了不一样的解决方案,将错误当作返回值。
f, err := os.Open("filename.ext"). if err != nil { fmt.Println(err) return err } // do something with the file
坦白说,这不必定是最优雅的解决方案,但这是最有效的方法鼓励开发者处理错误。
Go是一种有趣的语言,它提供了一种不一样编写代码方法。它丢弃了一些咱们从其余语言中了解的一些特性,好比继承或异常。相反,它鼓励用户使用本身的工具集来解决问题。所以,若是您想要编写可维护的、干净的、健壮的代码,您不妨以一种不一样的、相似于Go的方式开始思考。这是一件好事,由于您在这里学到的技能能够在其余语言中成功使用。你的年龄可能会有所不一样,但我认为一旦你开始接触Go,你很快就会发现它可以帮助你成为一个更好的程序员。