简介
- 简介
- 一 为什么要学习新的语言
- 二 go语言的一点特性
- 三 go runtime
- 四 GOROOT GOPATH
- 五 Go Test
- 六 类型转换和类型断言
- 包管理
- 得到可执行文件
- 补充
- 学习路线
- 帮助文档
- 使用plantuml生成GO的类图
一 为什么要学习新的语言
我本来是Java方向的,为什么打算换“东家”呢?
-
程序员要多会一点,有人提出一个观点:即使这个语言过时了,学习一下也是很有必要的,因为这让你从另一个角度来观察问题,看到新的方式去解决问题。扩展的了解“什么死的,什么是可以变通的”。
-
多核化和集群化渐渐成为主流,而JAVA是上一个时代单机服务器时的产品,虽然它现在也在努力跟上潮流。
-
JAVA语法还是不够简单
熟悉java多线程的人都知道,wait方法的调用必须在synchronized块中。并且,实现线程要继承Thread或者实现Runnable。总之,在java中开发多线程程序细节很多,能力要求也很高,是面试题的重要来源地。
在未来的编程事业中,人们越来越关注实现业务本身,而实现业务所需的技术细节将主要由编程语言来实现。比如在Go语言中,实现一个线程将由一个关键字表示,学习的复杂性大大下降。
-
当然,决定语言发展趋势的因素有很多。若只考虑易用性,C语言早该回家抱孩子了。从长远讲,语言要么效率足够高,要么足够简单,处在中间状态的语言会很尴尬!
二 go语言的一点特性
所有函数参数都是传值的,只是一些参数底层结构的差异,导致它们传值的影响并不完全相同。
func re2(a *[2]int){
(*a)[1]= 3
}
func re3(a *[2]int){
a = nil
}
func Test2(){
a := &[2]int{1,2}
re2(a)
for _,v := range a {
fmt.Println(v)
}
re3(a)
fmt.Println(a)
}
输出:
1
3 // 改变a指向的结构的内容的值
&[1 3] // 改变a的值(失效)
三 go runtime
编程语言基本不会直接使用系统调用,而是
- 类似c语言,有c函数库,负责和底层的操作系统交互,一个c程序include一堆
.h
文件。 - 类似java语言,提供runtime,负责和底层的操作系统交互的同时,屏蔽操作系统差异,提供一套全新的内存管理、程序数据组织等抽象
每个go程序都会附带一个runtime,go程序运行前(即执行main函数前),会初始化内存分配器、垃圾回收器和goroutine调度器等。其中go runtime会事先申请一大段内存并自己管理,go程序并不直接向系统申请内存,这样可以减少系统调用,提高效率。
四 GOROOT GOPATH
GOROOT是go应用的安装目录,GOPATH指的是包加载路径,类似于java的classpath,(或者说maven的.m2
目录)你import说要使用这个包,go就会去$GOROOT/src
和$GOPATH/src
指定的目录中寻找。GOPATH可以指定多个路径,路径之间以分号(linux下冒号)分隔。
java中需要的jar包要下载(有的要交给maven管理),go中则通过go get
获取第三方库。go get
获取到一个完成的工程目录,并将其文件下载到GOPATH指定的第一个路径下。
GOPATH和java的classpath的不同之处:一个比较重要的地方是GOPATH与工作空间的关系。参见GOPATH与工作空间 强烈推荐,细读(go中包名与文件夹的关系等都有涉及)
Go’s standard directory layout looks like:
$GOPATH/src/project1,自定义项目带main.go
$GOPATH/src/project2,自定义项目不带main.go,只提供功能
$GOPATH/src/github.com/me/myproject
...
etc
编译完毕后
$GOPATH/bin/project1
$GOPATH/pkg/平台名/project2.a
$GOPATH/pkg/平台名/github.com/me/myproject.a
...
etc
这意味着我们的go项目的工作空间也最好在$GOPATH/src
。所以呢,写go项目
- 一个项目一个gopath,项目本身有src,bin等目录
- 项目在
$GOPATH/src
下,项目目录下无需src等目录,项目下的目录反倒跟包名等有关系了。
在实践中,推荐第二种方式(一些第三方工具比如govendor等是按这个规范来的),建立两个GOPATH目录
- ~/gorepos,负责存储第三方包
- ~/goprojects,在其src目录下创建自己的项目文件
五 Go Test
我们测试一个函数的功能,就必须要运行该函数,而这往往是由main函数开始触发的。在大型项目中,测试一个函数的功能,总是劳驾main函数很不方便,于是我们可以使用go test
功能。
假设存在a.go
文件(文件中包含Add方法),我们只要在相同目录下创建a_test.go
文件,在该目录下运行go test
即可。(这将运行该目录下所有”test”后缀文件中的带有“Test”前缀的方法)
package main
import (
"fmt"
"testing"
)
func Test_Add(t *testing.T) {
re := Add(3,4)
fmt.Println(re)
// 如果没有抛出error,则运行go test将显示“PASS”
}
六 类型转换和类型断言
类型断言貌似是,一个大类型,比如interface{}
,我怀疑它可能是字符串,则可以xxx.(string)
类型转换的情况比较多:
- 同一类型的转换,比如int64与int
- 某类型与字符串的转换,这个有专门的包
- 字符串与字符/short数组的转换,比如string与
[]uint8
等
包管理
Golang使用包(package)这种语法元素来组织源码,所有语法可见性均定义在package这个级别,与Java 、python等语言相比,这算不上什么创新,但与C传统的include相比,则是显得“先进”了许多。参见理解Golang包导入
一个非main包在编译后会生成一个.a
文件。
要依赖第三方包,就必须搞到第三方包的源码,这是Golang包管理的一个特点。
同一个目录下不同文件定义的方法(前提是此方法可见)彼此可以随意访问。但代码不能全写在一个文件里,也不能全写在一个目录下。那么一个文件引用不同目录的另一个文件就要import。
在java中,只有跨jar/项目引用类时,才需要将其它jar加入到本项目的classpath中,所以Golang使用包(package)这种语法元素来组织源码,project这个概念其实没什么卵用,要么包内共享,否则就是全局的GOROOT和GOPATH
import的是路径,不是包名。我们建议目录名和包名一致,实际可以不同。go的package是以绝对路径GOPATH/src
来寻址的,不要import相对路径。函数引用时,以包名为准。
得到可执行文件
编译 | install | |
---|---|---|
maven | mvn build | mvn install |
go | go build | go install |
go build
后面可以是文件,也可以是目录,但该目录必须包含带func main()
的文件,有且只有一个。所以,如果只是简单测一个功能,一个目录下可以有多个带func main()
的文件,直接go build xx.go
,go build 目录
则无法通过。
如果在mac下开发,要生成在linux下的可执行文件,可以
$ CGO_ENABLED=0 GOOS=linux GOARCH=amd64
$ go clean
$ go install
补充
erlang和go有很大的渊源,Joe Armstrong在他的论文中《面对软件错误构建可靠的分布式系统》是这样介绍erlang的。
- Erlang顺序化编程
- 并行编程
-
错误处理
不要把处理“正常情况” 的代码和处理异常的代码搅和在一起。 (这一点,其实java做的不错)
- 分布式编程
- 动态代码替换
由此我们可以发现,上述特性慢慢在语言层面提供支持(java的并行编程只是对操作系统调用的封装),而我们过去在学习一种语言时,其实只是了解了”顺序化编程”的部分。
总之,就是“面向’进程’编程(进程是程序的基本模块)”,进程之间的通信采用消息队列。至于为什么,可以参见大牛的论文。
学习路线
阅读现有的开源项目,参见go语言值得学习的开源项目推荐
谷歌官方维护了一个开源项目列表,https://github.com/golang/go/wiki/Projects
其中比较重要的一点,像java项目分模块一样,了解一个大型go语言项目如何组织。
帮助文档
使用plantuml生成GO的类图
作用 | 协议/规范 | 渲染工具 |
---|---|---|
markdown | markdown | macdown等 |
文本描述api | openapi | swagger等 |
文本描述uml | plantuml |
- 使用goland 安装插件 plantuml, plantuml 的正常工作还依赖java 和 GraphViz(mac 下
brew install GraphViz
) - plantuml 只是一个渲染工具,那么如何 对一个go 源文件 生成一个 puml 文件呢?国内一个大神写了一个 qquunn/go-package-plantuml
- go-package-plantuml 可执行文件 每次命令行执行也挺尴尬的,因此可以配置一个 goland external tool
这里有几个问题
- qquunn/go-package-plantuml 源码中直接写死了 输出文件名
/tmp/uml.txt
但理想状态是:在xx.go 中右键 “external tools ==> go-package-plantuml”,可以直接生成 对应的 puml 文件 - 因此要更改 qquunn/go-package-plantuml 源码 并重新go build/install,只改一行即可,大家可以自己试一下
-
go-package-plantuml 有几种执行方式
- 针对一个目录下的所有go 生成一个包含所有的 struct 的puml文件,这也是go-package-plantuml 推荐的方式,
go-package-plantuml --gopath $GOPATH$ --codedir $FileDir$ --outputfile $FileDir$.puml
,$GOPATH$
等是goland external tools 支持的全局变量,执行时自动替换为实际的值。 - 针对一个特定的go 生成一个特定的 puml 文件,
go-package-plantuml --gopath /Users/nali/gorepos --codedir $FilePath$ --outputfile $FileDir$/$FileNameWithoutAllExtensions$.puml
- 针对一个目录下的所有go 生成一个包含所有的 struct 的puml文件,这也是go-package-plantuml 推荐的方式,
- go-package-plantuml 会连边边角角的struct 也扫描一下,puml 是一个文本文件,不需要的struct 可以直接从里面删除,再由plantuml plugin 渲染类图。甚至一个类中,不重要的方法也可以直接剔掉。