go 语言设计的理解 - 工程化语言

用户头像
superman
关注
发布于: 2020 年 10 月 02 日

go语言设计的一点个人理解

go是面向工程实践设计的语言不追求理论上的严谨性,追求程序员书写简单,开发高效。

go 自身的灵活本身也是双刃剑,有导致其面向对象封装思想的丢失的潜在风险,不适合单个大规模项目。

go 在比较性,赋值,等方面内涵更晦涩,容易出错。

1:go语言设计哲学

面向工程实践设计的语言

不最求理论上的严谨性,而根据工作实践提炼出来的,追求程序员书写简单,开发高效。

语法简单,入手难度低。面对工作中经常使用的内容, 内置实现。

可能最初就是谷歌内部为了自己方便设计的工程语言,为方便快速开发设计

1:api语法简单,尽量减少代码量,提高开发效率

零变量(非指针自动初始化),类型推导(:=),多返回值,去除面向对象的继承等,内置协程等,看上去这些特点在编程思想上没有任何突破,也都是一些小技巧,但对程序员来说确是使用最多的地方,这些便利提供切实的编码效率提升。--提高开发效率就节省了最大的研发成本-人力,时间。 人+时间就是最贵的。

创造价值--可以是别人提供不了的独创技术与思想,也可以是对最常用的地方进行简化提高效率。

2:规范化

同一文件内属于一个包,大小写决定可见性

包引用了就必须使用,局部变量定义了就必须使用否则编译不过

测试支持:按规范命名,提供测试命令

按规范组织文件路径,可方便分享使用

工具链:go :vet,godoc ,get,fmt

3:构建

编译时只编译需要的代码文件,不需要的不编译。

go get方便拉取外部引用

直接生成单个可执行文件。all in one,方便微服务。

4:并发

现代的程序并发已经是比不可少的的内容,go语言内置协程,实际是线程被平台托管,类似java 管理对象内存一样。

5:面向对象的调整(封装的灵活性)

程序的抽象:数据结构与算法,即数据抽象与算法抽象。面向对象对两者进行和统一,提供类,将数据抽象与算法抽象统一了。但实际工作场景存在很多值需进行数据抽象或算法抽象的场景,比如很多值对象(贫血),很多处理就需要传递一个算法实现特定流程。java强制一切都是对象,会让程序员定义很多孤零零的类(只实现数据封装与单个函数封装-可能是静态函数)。在有些时候只需要结构的重用,算法重用,固化的使用对象反而不灵活。

go 与java 比较

结构体:结构体本身是值对象,同时保留指针。让程序员决定用值对象还是引用。方便定义值对象承载数据封装。

函数为一等成员:跟其他类型一样,可作为函数参数,结构体的成员,返回值等。

回归:将数据,函数封装的灵活性交给程序员。



6:为合作设计

按规范组织包结果,在代码库上的可直接获取 go get ,会递归下载需要包-类似maven

godoc 书写好代码文档,生成接口文档 ,可放在godoc上

go fmt 格式化代码



2:具体内容



2.1 工具链

以工程化,规范化为目标提供常用的工具,主要包括

规范化:go vet ,go fmt,  go doc ,godoc

编译运行相关:go build ,go run  ,go clean



2.2 面向对象

继承体系

组合替代继承,语法上匿名成员组合,使用时有继承的语法用法-可以用组合类型的对象简短调用组合内匿名成员的方法与字段-语法上的技巧(语法糖)。本质是组合,不是继承。不能将组合类型的对象赋值给器匿名成员类型的变量。

去掉了传统面向对象的一些特性,比如重载。严格意义上重写也没有了。

接口实现

鸭子类型,不用在定义时显示声明实现接口,根据其具有的方法决定。

实现者可以不引用接口定义。灵活,合作方便。

在提倡小接口的情况下尤其灵活。实现者不用引用很多接口。小接口类似函数(功能封装)一样传递。



2.3 并发

实实在在的简单,简单的同时也失去一些灵活性



简单:协程、通道 提供的新的工具,配合语法上的简单,可以方便简单实现并发,简单是实在的。

高效:要在特点场景才可能,有些场景反而失去了自己对线程的控制,可认为goruntine 共用底层的线程池。如果需要用多个线程池,分配不同的线程数处理不同任务类型,隔离彼此,可自己构建几个gorutine池。但其实这些goruntine池底层还是共用相同的物理线程池。有时达不到自己控制线程的效果。用go反而不便实现。 因此go并发是否高效要看具体场景,或者说是有条件下的高效。有时不高效,反而让控制失效。



chan :go 提倡协程间通过chan(通道,类似生产消费队列)进行通信,chan本质是个生产消费队列,但语言原生内置,在加上跟for rang ,select default 等的语法配合,用chan 实现协程间通信非常简单优雅。



2.4 函数是一等成员

1:函数类型定义与函数声明

函数类型定义:定义个类型,这个类型底层是个函数。

函数声明:定义一个函数实现,包括函数体

函数成员定义:定义函数变量,将一个函数赋值给这个变量,函数可作为函数参数,返回值,结构体的成员等存在。

2:函数类型定义特色

不同函数类型的转型:

两个函数类型名称,类型名不同,但签名相同,如果需要赋值,使用转型。

type F func(a int)
type F2 func(a int)
func print( a int){
fmt.Print(a)
}
func gg(){
var f1 F
f=print
f(1)
var f2 F2
f2=F2(f1) //需要转型
f2(0)
}

给函数类型定义方法:

函数类型定义,与其他类型定义一样,也可以给这个类型添加方法。

type HandlerFunc func(ResponseWriter, *Request)
// ServeHTTP calls f(w, r). //给函数类型定义方法
func (f HandlerFunc) ServeHTTP(w ResponseWriter, r *Request) {
f(w, r)
}



2.5 内置标准库

后发的优势,将目前用的比较多的内容作为内置库实现,省去引用第三方库,方便。

json,http ,加解密等。



3:go的缺点



3.1 异常困惑

java 的异常 用习惯后,go 的遍地 if err!=nil 会让人很难忍受。

自动往上层抛:java try catch 解决预料内及不可预料异常,如果不处理会自动向上层抛异常,代码内不用手工主动抛出调用其他函数的异常, 感觉很流畅。

异常的类型:异常也像类一样有派别,可以声明预料内的异常,让调用者处理。调用者知道看声明知道会出现哪些类型的异常。java 在这方便是可以在函数中声明多种类型的可预计异常。

go 分为err ,跟panic ,panic 可认为是不可控异常-预料外的。err是预料内的错误。

panic 可自动往上层抛,err不会能,如果忽略就丢失了,没法在外层统一处理,所以代码中要有很多err!=nil 的判断。如果调用一个方法只有反馈的err,程序员忘记处理,就自动丢了,这是很危险的事。



err的类型:err 没有在函数处声明再细一步的分类,调用者通过函数声明不清楚都有哪些预计内的err,要么统一处理,如果要根据err类型采用不同的策略,只有去了解实现后才知道,然后用接口断言(判断返回的err接口具体是哪种类型)判断具体err的类型。这无疑增加调用者的难度,返回哪些预料内错误应该是函数实现者的责任,然后并声明出来的。想象一下,调用这调用一个方法,判断err类型还要看函数的实现,是不是要奔溃。



3.2 接口实现-鸭子类型

实现者不显示定义实现的接口其实是把的双刃剑,灵活的同时也少了规范化,你调整方法时会不会不小心就不符合接口的约束了?当有多种实现者时,如何找到接口的合适的实现者本身并不直观。实现者本身可能清楚,使用者可能就不清楚了。



go的思想可能更偏向于接口后定义,功能提供者实现功能,使用者按自己需要定义接口。或更符合依赖倒置(接口由使用者定义,而不是提供者定义)。



3.3 灵活的面向对象的潜在危害

go可以实现简单的面向对象,部分面向对象的特性没有,算法与数据的封装不是强制性的,如果从go开始编程,思想上可能就不会形成严谨面向对象的思想。

而面向对象有强制封装的意思,如果因为灵活而忽略应该的封装,可能会思想纠过头。面向对象这么长时间建立起程序员的封装的教育再次丢失。而合理封装对于大规模合作的项目又非常重要。



感觉go 不适合大块头的项目,并不是语言本身不能,而是语言的特点导致程序员在用go实现时没有严格的面向对象的约束,可能导致代码组织上出现问题。后续如何找到或培养有很好的面向对象思维的go程序员?

当然目前大块头的单个项目本身不是主流了。项目本身拆分为多个小项目-微服务模式的不算。

3.4 GO 有些概念更复杂晦涩

3.4.1 可比较性

java 中可赋值的变量间就可以比较。可以用==比较,可以作为map的key.

go 中 切片,函数,map 都是不可比较的,不能用==比较两个切片或函数,包含不可比较成员的结构,数组也不可比较(不可比较的传染性)。 不可比较的不能作为map的key ,如果接口指向不可比较的结构体,接口变量间的比较也会异常。接口可以作为map的可以,接口间比较也可以,但这些都是在编译器间可以,运行期如果接口指向了不可比较对象,都会发生panic 。

而这种被切片与函数不可比较延伸到结构的问题,在编码编译期间是不报错的。运行期间才报错。

type S struct{
f func()
}
func main(){
var s1 S
var i1 interface{}=s1
var m map[interface{}]int =make(map[interface{}]int)
m[i1]=2//运行时报 :panic: runtime error: hash of unhashable type main.S
var i2 interface{}=s1
if i2==i1{
fmt.Print("比较成功")//panic: runtime error: comparing uncomparable type main.S
}
}



不可比较具有传染性,因此结构体,接口是否可比较,你必须深入到所有层级,所有成员都是可比较的才行。如果运行很好的程序,某一天将结构体添加了一个字段,即便不使用这个字段,也会导致运行异常。



3.4.2 赋值与拷贝

数据类型

基本类型:数值型,字符串,字符,布尔型。

复合型:数组,切片,map,结构体

函数与接口

channel

指针。



赋值的场景

显示赋值 a=b ,函数调用传递实参 ,方法调用接受者,for rang 获取取容器中的值 。

赋值给接口(也是显示赋值),这些场景都会发送赋值操作。



赋值时发送了什么

java中除基本类型外都是对象,都是引用,不管赋值还是传参等,操作的都是同一个对象。底层数据是一份。

go中,每次赋值都是拷贝一份,类型本身是指针型时,操作的还是相同的底层数据, 其他的都要理解为拷贝。先执行:a=b 在判断 : a==b? 很多时候都不成立(不能比较,比较不通过,通过了要明白实际也并不是一个,而是两个)。

map,channel ,函数,指针赋值后可以认为仍然是同一个,操作时是对相同的底层数据进行操作。

其他都认为是拷贝,如果要对原始对象操作只能取指针再操作。

有时不能取址:map里放入的v,都是不可取地址的,将一个结构体放入map后,只能读取,不能对这个结构体的某个成员进行赋值。将map的v 存放结构体指针,才可以实现修改v的成员。

type Student struct {
age int
}
func TestStudentInMap(){
s:=Student{age:1}
m:=make(map[int]Student)
m[1]=s //****拷贝*******
s.age=2
//m[1].age++ //不允许
for _,v :=range m{//****拷贝*******
v.age=3 //v 是新的拷贝,修改后map里的 student 不会变
}
fmt.Printf("s.age=%d \n",s.age) //s.age=2
fmt.Printf("map[1].age=%d \n",m[1].age) //m[1].age=1
s2:=m[1] //****拷贝*******
s2.age=4
fmt.Printf("s2.age=%d \n",s2.age) //s2.age=4
fmt.Printf("map[1].age=%d \n",m[1].age) //m[1].age=1
m[1]=s2 //****拷贝*******
fmt.Printf("s.age=%d \n",s2.age) //s2.age=4
fmt.Printf("map[1].age=%d \n",m[1].age) //m[1].age=4
}
//在map中直接放入结构体的指针,可以实现修改map中的对象与外面变量指向相同对象
func TestStudentInMap2(){
s:=Student{age:1}
m:=make(map[int]*Student)
m[1]=&s //****拷贝地址*******
s.age=2
fmt.Printf("map[1].age=%d \n",m[1].age)//m[1].age=2
m[1].age++ //允许
fmt.Printf("s.age=%d \n",s.age) //s.age=3
for _,v :=range m{//****拷贝地址*******
v.age=4 //v 是新的拷贝地址,修改后map里的 student 会变
}
fmt.Printf("s.age=%d \n",s.age) //s.age=4
fmt.Printf("map[1].age=%d \n",m[1].age) //m[1].age=4
}



go的赋值--不直观--第一眼看到的可能跟实际不一样



发布于: 2020 年 10 月 02 日 阅读数: 57
用户头像

superman

关注

还未添加个人签名 2018.07.20 加入

还未添加个人简介

评论

发布
暂无评论
go语言设计的理解-工程化语言