Go语言特性小结-2017.03.29
学习背景
作为动态类型语言的PHP在工程实现上提供了很多便利,但也导致编码上的随意程度越来越高,恰巧内部推出了一个比赛,主题是在限定条件的服务器环境下,如何实现一个最快的echo server。技术方案当然是不设限制,由于在去年下半年涉及了一部分流式分发系统的设计内容,加上组内大牛的一直推广,对golang产生了兴趣。所以最终选择了go1.6方向的调研,学习过程中的一些point会汇总在这儿。
参考资料
正文
语言特性
-
golang中不存在类、对象的概念,因此是一个基于并发模型的语言,并不是完整的面向对象编程。替代方法是以type为中心,定义类型方法,也可以达到部分对象的功能,好处是代码组织上有迹可寻,同一类的用同一个type+一组类型方法即可。同时,类型方法支持值传递和引用传递,以面向对象的角度看,会在编程过程中,大部分使用引用传递。
-
以package来组织代码,名为main的package是整个项目的入口,其余的package以库的形式起作用,被import引进相关的package。目前理解的是,go为了做到环境无关,把相关的库都静态编译成一个可执行文件,因此编译出来的go程序明显体积都大于代码,这是编译类语言中比较少见的。
-
main方法作为程序的入口,在main之前执行的为init方法(每个源文件可以且只可以包含一个init方法),是一个现代的用法,更贴近于我们在业务开发中为流程准备的hook。因此该方法在业务上也通常用来:
- 程序执行之前对环境、数据进行检验或者修复,保证程序状态正确;
- 在main程序之前调用后台执行的gorutine。
目前看,对init的使用要慎重,因为顺序不可控,起码在ide(gogland)里是如此。这是一个谬误,init的调用是严格有顺序的,按照import的顺序来进行调用。验证参考《go语言的初始化顺序,包,变量,init》结论就是,在一个go的main package文件中, 初始化顺序规则: 引入的包 -> 当前包中的变量常量 -> 当前包的init -> main函数。
-
go语言对格式、引入代码是否被使用的校验是很严格的,如果引入了package、定义了变量,但在程序执行流程中没有使用,则编译无法通过。
-
go为
强类型(值得推敲的一句)语言《强、弱、静态、动态类型》,因此在值类型变换时要注意精度的问题,数据类型转换可能会有精度损失。fmt.Printf打印各种类型需要熟记,例如bool类型的%t,golang结构独有的%v、%+v等等。输入输出可以参考一下这篇blog。可以对字符类型做Type操作,但这个操作和别名不完全相同,因为无法调用原有类型的函数:Type TZ int 将int类型一个命名为TZ。 -
区分make和new,首先这两个关键字都是golang语言预留的用于内存分配的原语,
-
4种引用类型:map slice channel interface 其中前三者都常用make初始化。
-
go get = git clone + go install 因为gw问题使用:http_proxy=http://localhost:8123 go get来安装所需的包
-
go官方所有的源码包都托管在github上,因此,理论上去github上都可以下载
-
同一代码块内的defer,后声明先执行,是一个栈的结构,后声明在回收过程中先声明
-
import (_ "xxx/fff") 下划线代表只需要执行包中的init,并不需要所有内容,因此使用这种方式import的包,内部的方法依然无法直接调用。import(. "xxxx/fff")这种引入方式,则不需要使用报名称引用方法,问题是使用这种引用方式的话,多个包内有同名函数在编译过程中会出冲突,因此包前缀在这里是个类似于命名空间的功能。第三种特别的写法为import(test "xxx/fff")表示给这个包一个别名,不需要使用package的名字,直接使用别名(这种使用场景需要进一步摸索-> todo
-
为了更快的开发http服务,学习beego。作为一个restful框架,提供了很多方便的工具模块,因此不限于http web server。参考地址beego官方网站
-
golang各个包的init加载顺序是可控的,因此可以大量使用(eg:beego)执行流程如下图:
-
匿名引用是golang面向对象的核心,一直质疑一个问题如果实现一个接口所有的func就默认是这个接口的实现(implement)那么在代码组织或者阅读上天生有缺陷,这种隐式实现的方式有一定问题,目前(17.09.06)选择只要是struct实现了某种接口的情况下就在这个struct中增加匿名的该接口,但问题就在于,这样的情况,即使struct不实现interface定义的func,依然可以算作一种interface的实现,因此不能说是很完美的解决了这个问题。具体参考Golang中的面向对象继承
-
new和make的区别深入学习golang。new出来的数据结构是一个指向该结构的指针,make只创建slice channel map,并且返回的是值。new会把相关结构的值置为零值(bool 为false int 为0 string为"")
-
golang中没有构造函数,通常创建一个对象都是由全局函数Newxxx(... interface{})(*T) T为要创建的对象。
-
switch case 中专门应对interface{}变量,增加了一个value.(type)写法,可以根据空接口具体的数据类型来做不同的操作。
-
计算机语言上的反射指的是支持程序运行时的状态检测
-
select下面case命中的执行是没有明确顺序的,select 配合多条channel来使用,更多是用来传递信号,如果要传递大量数据的话合适么?这个需要调研
-
make只能创建切片、channel与map,之所以这么划分,是因为此三者在使用前都需要提前分配空间(类似于malloc),同时返回是一个具体数据结构的实例(做了初始化)。new
踩小坑
-
有时使用go build | go install时候编译不过,清除一下pkg库。TODO:梳理一下golang编译过程文件之间的依赖关系。
-
注意包之间的引用,不能直接用包名来作为形参引用,在编译过程会被拒绝。
-
网络和并发是golang的两大核心优势,因此我们在学习过程中也要持续关注语言的应用场景。golang被称为互联网的c语言,在需要写server的时候,使用起来很方便。而实际上一个server需要关注的也就是这两部分,在并发中golang提供的channel和goroutine是并发相关的两大feature。
-
使用yaml配置文件解析包的过程中,yaml的key只能用小写,如果混合大小写,则不能识别
-
花了半个下午,踩了个坑... 在做producer consumer的时候,没有先把消费的gorutine先启来,先运行select,阻塞住了。导致程序不向下运行,还没定位到问题,脑子没有跟上。从现象上看,一边开会,一边写代码的效率,大概为原来的1/3。
-
gogland上有按步调试,但是暂时还没有很好的使用起来。
-
太久没有使用编译型语言,对于struct内map在数据定义过程中只是个声明,在使用时需要先make初始化
-
为啥在写代码之前没有好好梳理这种知识?关于golang实现继承和组合的方法。主要是2点:1. 如何用struct + method(值方法 | 引用方法)实现类;2. 如何用struct实现继承:匿名接口|匿名struct|匿名struct指针
-
昨天解了自己心中的一个问题,关于对interface编程,为何不能用interface的实现struct直接替代在函数中对应interface的形参,会报一个错误( MyType does not implement Stringer (String method has pointer receiver)),在这种情况下使用引用传递是没问题的,参考stackoverflow:
//数据结构定义 type Stringer interface { String() string } type MyType struct { value string } func (m *MyType) String() string { return m.value } //错误用法 m := MyType{value: "something"} var s Stringer s = m // cannot use m (type MyType) as type Stringer in assignment: // MyType does not implement Stringer (String method has pointer receiver) //正确用法1 s = &m fmt.Println(s) //正确用法2是在定义时把struct的方法定义成值类型,但是这种用法很多时候无法满足需要,也就是需要在函数中修改MyType内部变量的时候,值引用不符合场景 func (m MyType) String() string{ return m.value }
这些都是语言的特性,其中还包括在 structs and embedding 中的特点。struct A实现了interface x,当struct B 包含Struct A 时,将Struct B直接赋值给interface x类型会报错,需要将struct *B赋值给interface x类型;或者让struct B包含struct *A,则可以直接对interface x类型赋值,当然在这种情况下,struct *B也可以直接对interface x类型赋值。
- 一个可能会出现的错误:panic: runtime error: index out of range in Go。因为引用了无法索引的数组/切片下标。
- 为什么我们不用struct指针方法的模式做构造函数?因为无法将这样初始化出来的内容直接赋给其实现的接口,接口及其实现之间的赋值只能用指针。
- 可以对外返回函数局部变量的指针,然后可用。我们理解简单类型声明即可用,因为变量空间开辟在栈上,struct等复杂类型声明之后需要显示的在堆上开辟空间,make/new 这种理解目前还没有理论来佐证。
- redis-client一个坑 连续new两个Client,后者的db会切到与前一个相同的db,之后来解除,TA这么搞 就没法做多库的一致性hash了
- 数据的位数很重要,昨天利用snowflake算法写一个发号器,以ip string 2 int 来做其中的主机标志段,果然没长脑子的溢出了,浪费了很久的时间
想知道
- 在gogland里面定义一个叫producer.yml的文件,结果没有语法提示 pro.yml就行?!为了个啥?(命名问题)
- 如何在golang中面向接口编程?写的时候都像个样子,真正用的时候就怂了。需要将抽象的接口具象到具体的struct,这个动作什么时候实施?匿名成员-> 结构体方法(值传递、指针传递)
- PHP和JS允许使用变量作为函数名,或者使用CALL、CALL_STATIC这种类似的方法来调用一个命名为aaa的方法。而go作为静态类型语言,需要预先定义好方法,目前用switch...case...的方法去调用对应的func,只能使用静态工厂模式,过多的使用硬编码。