加入收藏 | 设为首页 | 会员中心 | 我要投稿 李大同 (https://www.lidatong.com.cn/)- 科技、建站、经验、云计算、5G、大数据,站长网!
当前位置: 首页 > 大数据 > 正文

转贴--Golang评估报告

发布时间:2020-12-16 19:02:15 所属栏目:大数据 来源:网络整理
导读:QQ群网友“四月份平民”编写的《Golang评估报告》,原文地址:https://docs.google.com/document/d/1NosYIbM6tfBqKh49BrHOngBfXuT1MfrvYXwc_ikwuMk/edit?pli=1 文章对Go语句的优缺点进行了详细地评估,很有参考价值,感兴趣的朋友可以看看。 1. Go简介 Go是

QQ群网友“四月份平民”编写的《Golang评估报告》,原文地址:https://docs.google.com/document/d/1NosYIbM6tfBqKh49BrHOngBfXuT1MfrvYXwc_ikwuMk/edit?pli=1

文章对Go语句的优缺点进行了详细地评估,很有参考价值,感兴趣的朋友可以看看。

1. Go简介

Go是由Google在2009年11月10日开源,2012年3月28日推出第一个正式版本的通用型编程语言,它为系统编程而设计。它是强类型化的语言,具有垃圾回收机制,并显式支持并发编程。程序由包构造,以此来提供高效的依赖管理功能。当前实现使用传统的 编译/链接 模型来生成可执行的二进制文件。



2. C/C++的缺陷

a.全局变量的初始化顺序

由于在C/C++中,全局变量的初始化顺序并不确定,因此依赖于全局变量初始化顺序的操作,可能会给程序带来不可预知的问题。


b.变量默认不会被初始化

由于变量默认不会被初始化,因此如果在程序中忘记初始化某个变量,就有可能造成一些奇怪的细节性错误,以至于在Coding Standard中都为之专门加以强调。


c.字符集的支持

C/C++最先支持的字符集是ANSI。虽然在C99/C++98之后提供了对Unicode的支持,但在日常的编码工作中却要在ANSI与Unicode之间来回转换,相当地繁琐。


d.复杂的继承模式

C++提供了单/多继承,而多继承则引入了大量的复杂性,比如“钻石型继承”等。细节请参阅《深度探索C++对象模型》。


e.对并发的支持

在C++中,并发更多的是通过创建线程来启用,而线程间的通信则是通过加锁共享变量来实现,很容易死锁。虽然在C++11中添加了并发处理机制,但这给本来就十分复杂的类型系统又添加了更重的负担。


f.不支持自动垃圾回收

关于这一点存在争议。内存泄漏是C/C++程序员经常遭遇到的问题,但随着垃圾回收算法的成熟,对于大多数开发者来说,自动回收带来的便利已经超过手工操作提高的效率。而在C++中虽然可使用智能指针来减少原生指针的使用,但不能杜绝它,因此这个问题仍然存在。


g.落后的包管理机制

C/C++中采用.h/.c(pp)文件来组织代码,这种方式使编译时间变得过于漫长;C++中的模板更让这个问题雪上加霜。


h.C++编译器总会私自生成一些代码

比如: 构造函数/析构函数/new/delete等。如果是动态库,当include不同版本的头文件时,容易生成版本不兼容的代码。



3.Go的优势

正如语言的设计者之一Rob Pike所说:

我们——Ken,Robert和我自己曾经是C++程序员,我们设计新的语言是为了解决那些我们在编写软件时遇到的问题。


这些问题中的大部分,就是在第2节中列举的内容。这一小节就是Go针对这些缺陷提出的解决方案。


a.Init

每个包都可以定义一个或多个init函数(原型为 func init()),init函数在包初次被导入时调用,同一个包内的多个init函数的执行的顺序是不定的,而如果这个包又导入了其他的包,则级连调用,所有包import完成,所有init函数执行完后,则开始main的执行。

而对于全局变量,以一个简单的例子来说明:

// package p

var gInt int


// package a

import “p”


// package b

import “p”


// package main

import (

“a”

“b” )


在package p中,我们定义了一个全局变量gInt,而p被package a,b所import,接着package main又按序import了a,b,即a在b前被import。a先import了p,所以此时gInt被初始化,这样就解决了C/C++中全局变量初始化顺序不一致的问题。


b.默认自动初始化

Go引入了零值的概念,即每个对象被创建的时候,默认初始化为它相应类型的零值。例如,string为””,指针为nil,int为0等等,这样就保证了变量在使用时,不会因为忘记初始化而出现一些莫名其妙的问题。此外,由于零值的引入,也方便了代码的编写。比如说sync包的mutex类型,在引入零值后,就能以如下方式使用:


var locker sync.Mutex

locker.Lock()

defer locker.Unlock()


而相应的C/C++代码,可能就要这样写了:

CRITICAL_SECTION locker

InitializeCriticalSection(&locker)

EnterCriticalSection(&locker)

LeaveCriticalSection(&locker)

DeleteCriticalSection(&locker)


忘记任何一步操作,都将造成dead lock或者其他的问题。


c.UTF-8

Go语言原生支持UTF-8编码格式。同时Go涉及到字符串的各种包,也直接为UTF-8提供了支持,比如:

str := "示例"

if str == "示例" {...}


d.只支持组合不支持继承

OOP在Go中是通过组合而非继承来实现的,因为“继承”存在一些弊端,比如:“不适应变化”,“会继承到不适用的功能”。所以在编码实践中一般建议优先使用组合而非继承。在Go中则更进一步,直接去掉了继承,只支持组合。在定义struct时,采用匿名组合的方式,也更好地实现了C++中的“实现”继承,而在定义interface时,也可以实现接口继承。比如:

type A struct{}

func (a A) HelloA() {

}


type B struct{}

func (b B) HelloB() {

}


type C struct {

A

B

}


c := &C{}

c.HelloA()

c.HelloB()


此时c就拥有了HelloA、HelloB两个方法,即我们很容易地实现了“实现继承”


e.Go程(goroutine)与信道(channel)

Go对并发的支持,采用的是CSP模型,即在代码编写的时候遵循“通过通信来共享内存,而非通过共享内存来通信”的原则。为此,Go提供了一种名为“Go程”的抽象。由于Go程是一种高于线程的抽象,因此它使用起来也就更加轻量方便。而当多个Go程需要通信的时候,信道就成为了它们之间的桥梁。例如:

func goroutine(pass chan bool) {

println("hello i’m in the goroutine")

pass <- true

}

func main() {

pass:= make(chan bool)

go goroutine(pass)

<-pass

println("passed")

}


代码中通过关键字chan来声明一个信道,在函数前加上关键字go来开启一个新的Go程。此Go程在执行完成后,会自动销毁。而在通信过程中,可通过<-操作符向信道中放入或从中取出数据。


f.自动垃圾回收

与C#、Java等语言类似,为了将程序员从内存泄漏的泥沼中解救出来,Go提供了自动垃圾回收机制,同时不再区分对象是来自于stack还是heap。

g.interface

除开Goroutine以外,Go语言的最大特色就是interface的设计,Go的interface与Java的interface,C++的虚基类是不同的,它是非侵入式的,即我们在定义一个struct的时候,不需要显式的说明它实现了哪一/几个interface,而只要某个struct定义了某个interface所声明的所有方法,则它就隐式的实现了那个interface,即所谓的duck-typing。

假设我要定义一个叫Shape的interface,它有Circle,Square,Triangle等实现类。

在java等语言中,我们是先在大脑中从多个实现中抽象出一个interface,即:

在定义Shape的时候,我们会先从实现类中得出共性,比如它们都可以计算面积,都可以被绘制出来,即Shape拥有Area与Show方法,在定义出了Shape过后,再定义Circle,Triangle等实现类,这些类都显式的从Shape派生,即我们先实现了接口再实现了“实现”,在实现“实现”的过程中,如果发现定义的接口不合适,因为“实现”是显式的指定了它派生自哪个基类,所以此时我们需要重构

public interface Shape

{

public float Area();

public void Show();

}

public class Circle : implements Shape

{

public float Area() { return ...}

public void Show() { ...}

}

(同理Square,Triangle)

而在Go中,因为interface是非侵入的,是隐式的,我们可以先实现Circle,Triangle等子类,在实现这些“实现类”的过程中,由于知识的增加,我们可以更好的了解哪些方法应该放到interface中,即在抽象的过程中完成了重构。


type Circle struct {}

func ( c Circle) Area() float32 {}

func ( c Circle) Show() {}

(同理Square,Triangle)


type Shape interface{

Area() float32

Show()

}

这样Circle,Square,Triangle就实现了Shape。

对于一个模块来说,只有模块的使用者才能最清楚的知道,它需要使用由 其他被使用模块提供的哪些方法,即interface应该由使用者定义,而被使用者在实现时,并不知道他会被哪些模块使用,所以它只需要实现自己就好了,不需要去关心接口的粒度是多细才合适这一类的琐碎问题,interface是由使用方按需定义,而不用事前规划。

Go 的interface与Java等的interface相比优势在于:

1.按需定义,最小化重构的代价

2.先实现后抽象,搭配结构嵌入,在编写大型软件的时候,我们的模块可以组织得耦合度更低。


h. Go命令

在unix/linux下为了编译程序的方便,都可能需要编写makefile.或者各种高级的自动构建工具(windows也存在类似的工具,只不过被各种强大的ide给隐藏在了背后),而Rob Pike等人当初发明Go的动机之一就是:”Google的大型的C++程序的编译时间过长”。所以为了达到:”编译Go程序时,作为程序员除开编写代码外,不需要编写任何配置文件或类似额外的东西。“这个目标,引入了Go命令族,通过Go命令族,你可以很容易实现的从在线repostory上获得开源代码,编译并执行代码,测试代码等功能,这与C/C++的处理方式相比,前进了一大步。


i. 自动类型推导

Go虽然是一门编译型语言,但是在编写代码的时候,却可以给你提供动态语言的灵活性,在定义一个变量的时候,你可以省略类型,而让编译器自动为之推导类型,这样减少了程序员的输入字数。

比如:

i := 0 ? var i int

s := “hello world ” ? var s string = “hello world”


j. 强制编码规范

在C/C++中,大家为大括号的位置采用K&R,还是ANSI,是使用tab还是whitespace,whitespace是2个字符,还是4个字符等琐碎的问题而争论不休,每个公司内部都定义了自己的Coding Standard.来强制约束,而随着互联网的蓬勃发展,开源项目的越发增多,这些小问题却影响了大家的工作效率,而有一条编程准则是”less is more”. 为了一致性,Go提供了专门的格式化命令 go fmt,用以统一大家的编码风格。

作为程序员,你在编写代码的时候,可以按你喜欢的风格编写,编写完成后 执行一下 go fmt命令,就可以将你的代码统一成Go的标准风格,这样你在接触到陌生的Go代码时,减少了因为编码风格差异带来的陌生感,强调了一致性。


k. 自带单元及性能测试工具

C/C++虽未提供官方的单元测试与性能测试的工具,但有大量的第三方的相关工具,而由于可能每个人接触的,喜欢的工具不一样,造成在交流时负担,鉴于此Go提供了官方测试工具go test. 你可以很方便就可以编写出单元测试用例.

比如这样就完成了一个单元测试的编写:

// example.go

func Add( a,b int ) int {

return a+b

}


// example_test.go

func TestAdd( t *test.T) {

a,b,result = 1,2,3

if result != Add(a,b) {

t.Printf(“failed”)

}

}


同理性能测试。

编写完成后执行 go test 就可完成测试


l. 云平台的支持

最近几年云计算发展得如火如荼,Go被称为“21世纪的C语言”,当然它也不能忽视这一块的需求,现在有大量的云计算平台支持Go语言开发,比如由官方维护的GAE,第三方的AWS, Heroku等,相关细节.

m. 简单的语法,入门快速,对于新成员很容易上手

Go本质上是一个C家族的语言,所以如果有C家族语言的经验,很容易上手。


4. Go的劣势

a. 调度器的不完善

b. 原生库太少/弱

c. 32bit上的内存泄漏

d. 无强大IDE支持


因为今年3.28日Go才推出Go1,所以目前Go还存在不足,a,c这两个缺陷2013年初的Go1.1可以解决,而b,d则需要等时间的积累才能完善。


5. Go的争议

a. 错误处理机制

在错误处理上,Go不像C++,Java提供了异常机制,而是采取检查返回值的方案来,这是目前Go最大争议之所在。


反对的理由:

1.每一步,都得做检查繁琐,原始。

2.返回的error类型可以通过 _ 给忽略掉


支持的理由:

1.在离错误发生最近的地方,可以最佳的处理错误

2.异常在crash后抛出的stack的信息,对于别有用心者,会泄漏关键信息,而对于最终用户,他将看不明白究竟发生了什么情况,而错误将会让你有机将stack信息用更有意义的信息所替换,提高了安全性与用户友好性。

3. 异常也可以默认处理

b. new与变量初始化

在Go中new与delete与C++中的含义是不一样的,delete用以删除 一个map项,而new用以获得一个指向某种类型对象的指针,而因为Go支持类似如下的语法:

type T struct {

}

obj := &T{} ? obj = new(T)

同时Go提供另一个关键字make,用以创建内建的对象,所以 &T{}这种语法与make合起来,就可以替代new,即new冗余了,这与Go的简单原则相悖。


c. For…range不能用于自定义类型

为了遍历的方便,Go 提供了 for range语法,但是这种构造只能用于builtin的类型如slice和map,chan上,而对于builtin类型 ,即使官方包container中的相关数据结构也不行,这降低了for range的易用性,而目前在不支持泛型的前提下,要实现一个很友好的for range 看起来还是很不容易的。


d. 不支持动态链接

目前Go只支持静态链接,这又是另一个引起争论的地方,争论双方 的论据就是动态链接的优、缺点,再此不再赘述。


e. 无泛型

现代的大多数编程语言都提供了泛型的支持,在Go1时中没有提供对 泛型的支持,按官方团队成员russ cox的说法,支持泛型要么降低编译器,要么降低程序员,要么降低运行效率,而这三个恰好与Go的快速,高效,易编写的目标是相冲突的,同时Go提供的interface{}可以降低对泛型期望。所以是否需要泛型也成了争论点


f. 首字母大写表示可见性

Go中只支持包级别的可见性,即无论变量,结构,方法,还是函数等,如果以大写字母开头,则他的可见性是公共的,在其他包中可以引用的,而以小写字母开头的则其可见性为其所在包,由于Go支持UTF8,而对于像中文这种没有大小写分别的字符在需要导出时,就会出现问题,关于这个问题,支持者的理由是:既然语言本身支持UTF8,那么在变量命名上就应该是一致的,不支持者的理由是,中国人用中文命名,日本人用日语命名.....,而且非要用类似中文这类符号编写的话,可以在中文符号前加一个英文符号.比如:

var 不可导出 int = 0

var E可导出 int = 0



6. 替代方案

a. Cgo

在前边的劣势部分有讲过,Go缺乏原生包,而现在世面上已经有大量的C实现的高质量的第三方库,比如openGL,openAL...,为了解决这个问题,Go引入一个叫做Cgo的命令,通过遵守简单的约定,就可以将一个C库wrapper成一个Go包,这也是为何在短短几年Go拥有了大量的高质量包的原因.cgo相关示例在此不再展示.


b. b/s

因为到目前为止Go尚未提供Gui相关的支持,同时在云计算时代,越发多的程序采用了b/s结构,而且Go对web编程提供了最完善的支持,所以如果程序需要提供界面,无论是本地程序,还是服务器程序,在当下建议使用b/s架构来替代。

(编辑:李大同)

【声明】本站内容均来自网络,其相关言论仅代表作者个人观点,不代表本站立场。若无意侵犯到您的权利,请及时与联系站长删除相关内容!

    推荐文章
      热点阅读