《Go语言四十二章经》详细讲述Go语言规范与语法细节及开发中常见的误区,通过研读标准库等经典代码设计模式,启发读者深刻理解Go语言的核心思维,进入Go语言开发的更高阶段。

travis Go Report Card

《Go语言四十二章经》

作者:ffhelicopter(李骁) 时间:2018-04-15

前言

写《Go语言四十二章经》,纯粹是因为开发过程中碰到过的一些问题,踩到过的一些坑,感觉在Go语言学习使用过程中,有必要深刻理解这门语言的核心思维、清晰掌握语言的细节规范以及反复琢磨标准包代码设计模式,于是才有了这本书。

Go语言以语法简单、门槛低、上手快著称。但入门后很多人发现要写出地道的、遵循 Go语言思维的代码却是不易。

在刚开始学习中,我带着比较强的面向对象编程思维惯性来写代码。但后来发现,带着面向对象的思路来写Go 语言代码会很难继续写下去,或者说看了系统源代码或其他知名开源包源代码后,围绕着struct和interface来写代码会更高效,代码更美观。虽然有人认为,Go语言的strcut 和 interface 一起,配合方法,也可以理解为面向对象,这点我姑且认可,但开发中不要过意考虑这些。因为在Go 语言中,interface接口的使用将更为灵活,刻意追求面向对象,会导致你很难理解接口在Go 语言中的妙处。

作为Go语言的爱好者,在阅读系统源代码或其他知名开源包源代码时,发现大牛对这门语言的了解之深入,代码实现之巧妙优美,所以我建议你有时间多多阅读这些代码。网上有说Go大神的标准是“能理解简洁和可组合性哲学”,的确Go语言追求代码简洁到极致,而组合思想可谓借助于struct和interface两者而成为Go的灵魂。

function,method,interface,type等词语是程序员们接触比较多的关键字,但在Go语言中,你会发现,其有了更强大,更灵活的用法。当你彻底理解了Go语言相关基本概念,以及对其特点有深入的认知,当然这也这本书的目的,再假以时日多练习和实践,我相信你应该很快就能彻底掌握这门语言,成为一名出色的Gopher。

这本书适合Go语言新手来细细阅读,对于有一定经验的开发人员,也可以根据自己的情况,选择一些章节来看。

第一章到第二十六章主要讲Go语言的基础知识,其中第十七章的type,第十八章的struct,第十九章的interface,以及第二十章的方法,都是Go语言中非常非常重要的部分。

而第二十一章的协程,第二十二章的通道以及第二十三章的同步与锁,这三章在并发处理中我们通常都需要用到,需要弄清楚他们的概念和彼此间联系。

从第二十七章开始,到第三十八章,讲述了Go标准包中比较重要的几个包,可以仔细看源代码来学习大师们的编程风格。

从第三十九章开始到结尾,主要讲述了比较常用的第三方包,但由于篇幅有限,也就不展开来讲述,有兴趣的朋友可直接到相关开源项目详细了解。

最后,希望更多的人了解和使用Go语言,也希望阅读本书的朋友们多多交流。

祝各位Gopher们工作开心,愉快编码!

开始阅读

本书内容在github更新:https://github.com/ffhelicopter/Go42/blob/master/SUMMARY.md

>>>开始阅读 第一章 Go安装与运行

进阶阅读

《Go语言四十二章经》开源电子书升级版《深入学习Go语言》,在当当,天猫,京东有售,感谢各位对此书的支持与关注!

本书适合初次学习Go语言,以及对Go语言有初步了解的开发者,读者可通过本书努力在尽量短的时间内成长为一名合格的Go语言开发者。

go.png

rpcx 框架

最新更新,框架rpcx包含了服务发现、负载均衡、故障转移等服务治理能力,特整理了一些资料来说说这款框架,推荐中小团队使用。

>>> 开始阅读 rpcx 框架

最新分享

本人在 GitChat 的专栏分享《Gin 框架入门实践》! 我要报名参加

本专栏通过对 Gin 框架核心代码的详细解读,分块讲解各个模块的功能与原理,并结合实例深度探讨 Gin 原理与特性。区别于其他教程只讲使用不深入原理分析。

专栏包含大量不同场景下的案例和代码实践,带领读者了解框架的具体使用,有助于读者更深入体会对应模块原理。对某些重要的知识点,专栏进一步进行了更多拓展,以达到读者更清晰理解与掌握知识点。

专栏的安全编程部分,结合 Gin 框架对 CORS 跨域资源共享、CSRF 跨站请求伪造、Cookie 安全、数据有效性、数据渲染的安全性问题等,都做了非常深入的讨论和实践。

目前有关 Web 开发中的安全性问题的讲解很少在框架类教程中出现,这也是本专栏根据实际开发情况,有针对性编写了相关内容,当然, Gin 框架在安全性方面也做的相当不错。

本专栏图文并茂,课程中的重要实例代码均有完整代码可供测试。

另外本人在 GitChat 有分享 《Go 语言错误与异常处理》主要介绍 Go 语言中错误与异常,进一步深入探讨错误与异常的区别以及实际开发中合适的处理方式。

带堆栈的错误信息在开发与bug追踪中都十分重要,可以帮助程序员更快定位错误发生位置。它拥有比标准库中错误处理更丰富的处理方式,可谓程序员调试之牛刀。

交流

虽然本书中例子都经过实际运行,但难免出现错误和不足之处,烦请您指出;如有建议也欢迎交流。

感谢以下网友对本书提出的修改建议: Joyboo 、林远鹏、Mr_RSI、magic-joker、3lackrush、Jacky2、tanjibo、wisecsj、eternal-flame-AD、isLishude、morya、adophper、ivanberry、xjl662750、huanglizhuo、xianyunyh、荣怡、pannz、yaaaaaaaan、sidbusy、NHibiki、awkj、yufy、lazyou、 liov 、飞翔不能的翔哥、橡_皮泥、刘冲_54ac、henng、slarsar

更新

本书会在GitHub持续更新!为了更简单表述清楚,某些章节的内容我会根据情况随时更新,也包括新发现的错误和缺陷。

随Go语言版本的不断迭代,本书也会不断修改完善相关章节的内容和代码。

这里提醒读者朋友,在GitHub将会在第一时间得到本书的最新更新,所以如发现任何问题还请先在GitHub上看看最新文章的情况。

推荐

下列清单是常用的第三方库。

WEB框架

1.Gin https://github.com/gin-gonic/gin

2.Beego https://github.com/astaxie/beego

3.martini https://github.com/go-martini/martini

HTTP

1.httprouter https://github.com/julienschmidt/httprouter

2.fasthttp https://github.com/valyala/fasthttp

3.mux https://github.com/gorilla/mux

JSON解析

1.json-iterator https://github.com/json-iterator/go

2.jsonparser https://github.com/buger/jsonparser

数据库以及ORM

1.LevelDB https://github.com/syndtr/goleveldb

2.BoltDB https://github.com/boltdb/bolt

3.MySQL https://github.com/go-sql-driver/mysql

4.tidb https://github.com/pingcap/tidb

5.ssdb https://github.com/ideawu/ssdb

6.gorm https://github.com/jinzhu/gorm

爬虫

1.Colly https://github.com/gocolly/colly

2.Goquery https://github.com/PuerkitoBio/goquery

中间件

1.redis https://github.com/go-redis/redis

2.ElasticSearch https://github.com/olivere/elastic

3.Alice https://github.com/justinas/alice

日志

1.zap https://github.com/uber-go/zap

错误处理

1.errors https://github.com/pkg/errors

消息队列

1.Nsq https://github.com/nsqio/nsq

Service Mesh

1.Istio https://github.com/istio/istio

RPC

1.rpcx https://github.com/smallnest/rpcx

2.grpc https://github.com/grpc/grpc-go

协程池

1.ants https://github.com/panjf2000/ants

视觉图像处理

1.bild https://github.com/anthonynsimon/bild

2.gmf https://github.com/3d0c/gmf

3.opencv https://github.com/hybridgroup/gocv

网络

1.KCP https://github.com/xtaci/kcp-go

2.frp https://github.com/fatedier/frp

测试

1.gock https://github.com/h2non/gock

2.goreporter https://github.com/360EntSecGroup-Skylar/goreporter

Owner
ffhelicopter
A Gopher
ffhelicopter
Comments
  • 关于23.1 同步锁代码示例的错误

    关于23.1 同步锁代码示例的错误

    下面代码通过3个goroutine来体现sync.Mutex 对资源的访问控制特征:

    
    package main
    
    import (
        "fmt"
        "sync"
        "time"
    )
    
    func main() {
        wg := sync.WaitGroup{}
    
        var mutex sync.Mutex
        fmt.Println("Locking  (G0)")
        mutex.Lock()
        fmt.Println("locked (G0)")
        wg.Add(3)
    
        for i := 1; i < 4; i++ {
            go func(i int) {
                fmt.Printf("Locking (G%d)\n", i)
                mutex.Lock()
                fmt.Printf("locked (G%d)\n", i)
    
                time.Sleep(time.Second * 2)
                mutex.Unlock()
                fmt.Printf("unlocked (G%d)\n", i)
                wg.Done()
            }(i)
        }
    
        time.Sleep(time.Second * 5)
        fmt.Println("ready unlock (G0)")
        mutex.Unlock()
        fmt.Println("unlocked (G0)")
    
        wg.Wait()
    }
    
    程序输出:
    Locking  (G0)
    locked (G0)
    Locking (G1)
    Locking (G3)
    Locking (G2)
    ready unlock (G0)
    unlocked (G0)
    locked (G1)
    unlocked (G1)
    locked (G3)
    locked (G2)
    unlocked (G3)
    unlocked (G2)
    

    这里的程序输出应该是不对的,在unlocked (G3)输出之前,G2还没有取到锁,不可能输出locked (G2)。

  • Go slice 不是指针,而是包含指针的结构体

    Go slice 不是指针,而是包含指针的结构体

    第12章,slice的介绍基本是错的。

    绝对不要用指针指向 slice,切片本身已经是一个引用类型,所以它本身就是一个指针!

    package main
    
    import "fmt"
    
    func main() {
    	x := []int{0, 0, 0}
    	test(x)
    	fmt.Println(x)
    }
    
    func test(i []int) {
    	i = []int{1, 2, 3}
    }
    

    输出 [0 0 0],如果按照作者的理论 slice 是指针的话,结果应该是 [1 2 3]

    错太多了,建议作者再读读 https://blog.golang.org/go-slices-usage-and-internals

  • 第二十三章 同步与锁 程序输出是否有误?

    第二十三章 同步与锁 程序输出是否有误?

    原版:

    程序输出:
    Locking  (G0)
    locked (G0)
    Locking (G1)
    Locking (G3)
    Locking (G2)
    ready unlock (G0)
    unlocked (G0)
    locked (G1)
    unlocked (G1)
    locked (G3)
    locked (G2)
    unlocked (G3)
    unlocked (G2)
    

    输出不应该是locked g3,unlock g3,再去locked g2么?

  • 23.1 同步锁示例中G1解锁后,G2和G3中一个加锁后另一个需要等待锁释放后才能加锁

    23.1 同步锁示例中G1解锁后,G2和G3中一个加锁后另一个需要等待锁释放后才能加锁

    package main
    
    import (
    	"fmt"
    	"sync"
    	"time"
    )
    
    func main() {
    	wg := sync.WaitGroup{}
    
    	var mutex sync.Mutex
    	fmt.Println("Locking  (G0)")
    	mutex.Lock()
    	fmt.Println("locked (G0)")
    	wg.Add(3)
    
    	for i := 1; i < 4; i++ {
    		go func(i int) {
    			fmt.Printf("Locking (G%d)\n", i)
    			mutex.Lock()
    			fmt.Printf("locked (G%d)\n", i)
    
    			time.Sleep(time.Second * 2)
    			mutex.Unlock()
    			fmt.Printf("unlocked (G%d)\n", i)
    			wg.Done()
    		}(i)
    	}
    
    	time.Sleep(time.Second * 5)
    	fmt.Println("ready unlock (G0)")
    	mutex.Unlock()
    	fmt.Println("unlocked (G0)")
    
    	wg.Wait()
    }
    

    程序输出: Locking (G0) locked (G0) Locking (G1) Locking (G3) Locking (G2) ready unlock (G0) unlocked (G0) locked (G1) unlocked (G1) locked (G3) unlocked (G3) locked (G2) unlocked (G2)

  • 关于31.4 bufio包代码示例的错误

    关于31.4 bufio包代码示例的错误

    本小节最后的示例代码如下:

    package main
    
    import (
        "bufio"
        "fmt"
        "strings"
    )
    
    func main() {
        sr := strings.NewReader("ABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890")
        buf := bufio.NewReaderSize(sr, 0) //默认16
        b := make([]byte, 10)
    
        fmt.Println("==", buf.Buffered()) // 0
        S, _ := buf.Peek(5)
        fmt.Printf("%d ==  %q\n", buf.Buffered(), s) // 
        nn, er := buf.Discard(3)
        fmt.Println(nn, er)
    
        for n, err := 0, error(nil); err == nil; {
            fmt.Printf("Buffered:%d ==Size:%d== n:%d==  b[:n] %q ==  err:%v\n", buf.Buffered(), buf.Size(), n, b[:n], err)
            n, err = buf.Read(b)
            fmt.Printf("Buffered:%d ==Size:%d== n:%d==  b[:n] %q ==  err: %v == s: %s\n", buf.Buffered(), buf.Size(), n, b[:n], err, s)
        }
    
        fmt.Printf("%d ==  %q\n", buf.Buffered(), s)
    }
    

    其中第16行S应该为s。

  • string转[]byte的陷阱

    string转[]byte的陷阱

    在string转[]byte中,有个非常典型的例子。有同学可以解释下为什么吗? package main import "fmt" func main() { s := []byte("") s1 := append(s, 'a') s2 := append(s, 'b') //fmt.Println(s1, "==========", s2) fmt.Println(string(s1), "==========", string(s2)) } // 注释时候输出是 b ========== b // 取消注释输出是 [97] ========== [98] a ========== b // 如果s不是空字符串又不一样。

  • 关于28.2 指针运算代码示例的错误

    关于28.2 指针运算代码示例的错误

    但是对于v.j来说,怎么来得到它在内存中的地址呢?其实我们可以获取它相对于v的偏移量(unsafe.Sizeof可以为我们做这个事),但上面的代码并没有这样去实现。各位别急,一步步来。 var j *int64 = (*int64)(unsafe.Pointer(uintptr(unsafe.Pointer(v)) + uintptr(unsafe.Sizeof(int64(0))))) 其实我们已经知道v是有两个成员的,包括i和j,并且在定义中,i位于j的前面,而i是int32类型,也就是说i占4个字节。所以j是相对于v偏移了4个字节。您可以用uintptr(4)或uintptr(unsafe.Sizeof(int64(0)))来做这个事。unsafe.Sizeof方法用来得到一个值应该占用多少个字节空间。注意这里跟C的用法不一样,C是直接传入类型,而Go 语言是传入值。

    以上这一段表述中将uintptr(4)等同于uintptr(unsafe.Sizeof(int64(0))),但其实uintptr(unsafe.Sizeof(int64(0)))==uintptr(8),但是这里var j *int64 = (*int64)(unsafe.Pointer(uintptr(unsafe.Pointer(v)) + uintptr(unsafe.Sizeof(int64(0)))))是可以正确取到j的值,是因为这里发生了对齐,除了i本身占用4字节,还有4个字节由编译器进行填充,导致unsafe.Offsetof(v.j)和uintptr(unsafe.Sizeof(int64(0)))在64位机器上,值都是8。原文说j是相对于v偏移了4个字节,实际上是错误的,而且也没有说清楚为什么使用uintptr(unsafe.Sizeof(int64(0)))。

  • 第十章关于strings.Join和strings.Builder效率比较的问题

    第十章关于strings.Join和strings.Builder效率比较的问题

    参考源码,Join本身就是调用了套了一个Builder来存新字符串,所以这两个效率应该是一样的? go1.15.2 darwin/amd6

    func Join(elems []string, sep string) string {
    	switch len(elems) {
    	case 0:
    		return ""
    	case 1:
    		return elems[0]
    	}
    	n := len(sep) * (len(elems) - 1)
    	for i := 0; i < len(elems); i++ {
    		n += len(elems[i])
    	}
    
    	var b Builder
    	b.Grow(n)
    	b.WriteString(elems[0])
    	for _, s := range elems[1:] {
    		b.WriteString(sep)
    		b.WriteString(s)
    	}
    	return b.String()
    }
    

    ~~另外似乎strings.Builder是个改写版的迷你bytes.Buffer?~~