我学习go的五个感悟(译)

原文

5 things about programming I learned with Go By MICHAŁ KONARSKI

Go在最近一段时间内开始变得十分流行。语言相关的论文和博客每天都在更新,新的golang相关的项目在github中也层出不穷。Go语言的会议也吸引了越来越多的开发者的关注。Go语言的时代已经来临,并且当选了TIOBE的2016年度语言,并一度进入流行度前十。

我一年前开始接触golang,然后决定试一试。经过一段时间的接触,我发现这绝对是一个值得学习的语言。即使你不打算长期使用,学习一段时间也会是你的编程技巧有很大的提升。接下来我会告诉大家我学习golang的过5点感悟,而且这五点感悟对其他编程语言也有用。

1. 具有动态类型特性的静态类型安全语言

我日常使用的编程语言是Ruby,我非常喜欢动态类型语言。这一特性使得语言非常容易学习、使用,并且开发效率非常高。随着项目越来越多、越来越复杂,代码变得不像其他静态类型安全语言那样安全可靠。及时我十分谨慎的测试代码,仍不能覆盖到所有的边缘状况,因此经常出现不希望出现的状况。那么,有没有哪种语言既有着动态语言的特性又有静态类型安全语言的可靠性。答案是肯定的,我们来讲一讲Go!

现在有一些争论是关于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,却同时得到类型安全的保障。

2. 比继承更好用的组合

在之前的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()

3. 解决并发问题的利器-- channels and goroutines

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 是非常简单但是非常有效的并发处理机制。

4. Don’t communicate by sharing memory, share memory by communicating

(这里标题没有翻译,因为英文大家更熟悉。)

传统的编程语言一般在标准库里提供多个线程访问同一块共享内存的方法。为了同步和避免同时访问一般采用加锁的方法。但是由于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的实现。

5. Go的异常处理

一般语言都有异常捕获和异常处理的概念。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,你很快就会发现它能够帮助你成为一个更好的程序员。

我学习go的五个感悟(译)的更多相关文章

  1. 我的MYSQL学习心得(五) 运算符

    我的MYSQL学习心得(五) 运算符 我的MYSQL学习心得(一) 简单语法 我的MYSQL学习心得(二) 数据类型宽度 我的MYSQL学习心得(三) 查看字段长度 我的MYSQL学习心得(四) 数据 ...

  2. 小菜学习设计模式(五)—控制反转(Ioc)

    写在前面 设计模式目录: 小菜学习设计模式(一)—模板方法(Template)模式 小菜学习设计模式(二)—单例(Singleton)模式 小菜学习设计模式(三)—工厂方法(Factory Metho ...

  3. python学习心得第五章

    python学习心得第五章 1.冒泡排序: 冒泡是一种基础的算法,通过这算法可以将一堆值进行有效的排列,可以是从大到小,可以从小到大,条件是任意给出的. 冒泡的原理: 将需要比较的数(n个)有序的两个 ...

  4. SpringMvc学习心得(五)控制器产生与构建

    SpringMvc学习心得(五)控制器产生与构建 标签: springspring mvc框架 2016-03-22 15:29 140人阅读 评论(0) 收藏 举报  分类: Spring(4)  ...

  5. opencv学习笔记(五)镜像对称

    opencv学习笔记(五)镜像对称 设图像的宽度为width,长度为height.(x,y)为变换后的坐标,(x0,y0)为原图像的坐标. 水平镜像变换: 代码实现: #include <ios ...

  6. RabbitMQ学习总结 第五篇:路由Routing

    目录 RabbitMQ学习总结 第一篇:理论篇 RabbitMQ学习总结 第二篇:快速入门HelloWorld RabbitMQ学习总结 第三篇:工作队列Work Queue RabbitMQ学习总结 ...

  7. WCF学习心得----(五)生成客户端

    WCF学习心得----(五)生成客户端 1.    通过Svcutil.exe工具直接生成客户端 1.1     将服务承载于IIS上 1.1.1 在IIS中新建网站,所示效果如下图: 1.1.2   ...

  8. Django学习笔记(五)—— 表单

    疯狂的暑假学习之  Django学习笔记(五)-- 表单 參考:<The Django Book> 第7章 1. HttpRequest对象的信息 request.path         ...

  9. VSTO学习笔记(五)批量编辑Excel 2010 x64

    原文:VSTO学习笔记(五)批量编辑Excel 2010 x64 近期因为工作的需要,经常要批量处理大量的Excel文件,如果纯手工一个个修改,非常的麻烦,于是写了这么一个帮助类,希望能对你有所帮助. ...

随机推荐

  1. JavaWeb 后端 <十三> 之 监听器 JSTL国际化

    1. 监听器 1.1   概述 监听器: 主要是用来监听特定对象的创建或销毁.属性的变化的! 是一个实现特定接口的普通java类! 对象: 自己创建自己用 (不用监听) 别人创建自己用 (需要监听) ...

  2. Java之初识

    今天开始学习Java 1.什么是Java? Java是1995年由sun公司推出的一门极富创造力的面向对象编程语言,是由Java之父詹姆斯格斯林博士设计的. Java名字的由来:据说,java刚刚设计 ...

  3. Java 9 揭秘(11. Java Shell)

    Tips 做一个终身学习的人. 在本章节中,主要介绍以下内容: 什么是Java shell JShell工具和JShell API是什么 如何配置JShell工具 如何使用JShell工具对Java代 ...

  4. POJ 3126 math(BFS)

    Prime Path Time Limit: 1000MS   Memory Limit: 65536K Total Submissions: 21581   Accepted: 11986 Desc ...

  5. 原生js简单实现双向数据绑定原理

    根据对象的访问器属性去监听对象属性的变化,访问器属性不能直接在对象中设置,而必须通过 defineProperty() 方法单独定义. 访问器属性的"值"比较特殊,读取或设置访问器 ...

  6. .NET Core 事件总线,分布式事务解决方案:CAP

    背景 相信前面几篇关于微服务的文章也介绍了那么多了,在构建微服务的过程中确实需要这么一个东西,即便不是在构建微服务,那么在构建分布式应用的过程中也会遇到分布式事务的问题,那么 CAP 就是在这样的背景 ...

  7. PHP+NGINX

    1. 下载php编译包/nginx编译包(建议先装nginx再装php, php编译包我用的是5.5.35) 2. 创建好安装目录(我的编译包放在/home下) mkdir -p /usr/local ...

  8. nodejs之querystring模块

    这里主要记下querystring模块的使用方法. querystring从字面上的意思就是查询字符串,一般是对http请求所带的数据进行解析.querystring模块只提供4个方法,在我看来,这4 ...

  9. 51nod_1100:斜率最大

    题目链接 斜率最大点对横坐标必相邻 #include <bits/stdc++.h> using namespace std; ; struct point { int x, y, pos ...

  10. Work 2(演讲类) (2017.06.29)