当前位置: 首页 > 科技观察

Go:十大特色,你知道吗?

时间:2023-03-12 10:20:36 科技观察

大家好,我是程序员幽灵。作为一种相对较新的语言,Go脱颖而出的原因有很多。本文讨论了使其与其他语言不同的10个特性。Go的创建者RobertGriesemer[1]、RobPike[2]和KenThompson[3]在Google工作,大规模扩展的挑战激发了他们将Go设计为具有大型代码库的项目的快速高效解决方案。编程解决方案,由多个开发人员管理,具有严格的性能要求,并且跨多个网络和处理核心。Go的创始人在创建新语言时也借此机会学习其他编程语言的优点、缺点和疏忽。结果是一种干净、清晰和功能性的语言,具有相对较少的命令和功能集。本文将介绍Go的10个特性(根据我个人的观察)使其有别于其他语言。1.Go始终在构建中包含运行时Go运行时提供内存分配、垃圾收集、并发支持和网络等服务。它被编译到每个Go二进制文件中。这与许多其他语言不同,其中许多语言使用虚拟机并且需要与程序一起安装才能正常工作。将运行时直接包含在二进制文件中使得分发和运行Go程序变得非常容易,并且避免了运行时和程序之间的不兼容。Python、Ruby和JavaScript等语言的虚拟机也没有针对垃圾收集和内存分配进行优化,这解释了Go优于其他类似语言的速度。例如,Go尽可能多地存储在堆栈[4]上,其中数据的排序比堆[5]更快。稍后会详细介绍。关于Go的静态二进制文件的最后一件事是它们启动非常快,因为不需要运行外部依赖项。如果您使用GoogleAppEngine[6]之类的服务,这将很有用,这是一种在GoogleCloud上运行的平台即服务,可将您的应用程序扩展到零实例以节省云成本。当收到新请求时,AppEngine可以在眨眼间启动您的Go程序实例。在Python或Node中的相同体验通常会导致3-5秒(或更长时间)的等待,因为新实例也会启动所需的虚拟环境。2.Go没有集中托管的程序相关服务为了访问已发布的Go程序,开发人员不依赖集中托管的服务,例如MavenCentralforJava[7]或NPMforJavaScript[8]。相反,项目通过其源代码存储库共享,通常是GitHub。goget/install命令行允许以这种方式下载存储库。为什么我喜欢这个功能?我一直认为集中托管的依赖服务(如MavenCentral、PIP和NPM)是令人生畏的黑盒,可以抽象出下载和安装依赖项(以及依赖项的依赖项)的麻烦,但是当依赖项错误发生时,它不可避免地触发可怕的赛车心跳(我经历的太多了)。很多时候,我感到很沮丧,因为我从来没有完全理解它们的内部工作原理。通过取消中央服务,安装、版本控制和管理Go项目依赖项的过程变得更加清晰。(当然,有些人更喜欢集中托管。)另外,让其他人可以使用模块就像将它放入版本控制系统一样简单,这是一种非常容易分发程序的方法。3.Go是按值调用在Go中,当您提供原始类型(数字、布尔值或字符串)或结构(类对象的粗略等价物)作为函数的参数时,Go总是创建变量值的副本。在许多其他语言如Java、Python和JavaScript中,原始类型是按值传递的[9],但是对象(类实例)是按引用传递的,这意味着接收函数实际上接收的是指向原始对象的指针,而不是它的副本。这意味着在接收函数中对对象所做的任何更改都将反映在原始对象中。在Go中,结构和原始类型默认是按值传递的,可以选择使用星号运算符[10]传递指针://passbyvaluefuncMakeNewFoo(fFoo)(Foo,error){f.Field1="Newval"f.Field2=f.Field2+1returnf,nil}上面的函数接收了Foo的一个拷贝,返回了一个新的Foo对象。//passbyreferencefuncMutateFoo(f*Foo)error{f.Field1="Newval"f.Field2=2returnnil}上面的函数接收指向Foo的指针并改变原始对象。按值调用和按引用调用之间的这种明显区别使您的意图显而易见,并减少了调用函数无意中改变传入对象的可能性(许多初学者开发人员难以掌握的东西)。正如麻省理工学院总结的那样[11]:“可变性使得更难理解你的程序在做什么,也更难执行合同”。更重要的是,按值调用显着减少了垃圾收集器的工作,这意味着更快、内存效率更高的应用程序。这篇论文[12]得出结论,指针跟踪(从堆中检索指针值)比从连续堆栈中检索值慢10到20倍。要记住的一个好的经验法则是:从内存中读取最快的方法是顺序读取它,这意味着最小化随机存储在RAM中的指针数量。4.defer关键字在NodeJS中,在我开始使用knex.js[13]之前,我会通过创建一个数据库池并在池中为每个函数打开一个新连接来手动管理代码中的数据库连接,一旦需要的数据库CRUD功能完成,连接在功能结束时释放。这有点维护噩梦,因为如果我不在每个函数结束时释放连接,未释放的数据库连接数会慢慢增长,直到池中没有更多可用连接,然后中断应用程序.现实情况是程序经常需要释放、清理和执行资源、文件、连接等,因此Go引入了defer关键字作为一种有效的管理方式。任何以defer开头的语句都会推迟其调用,直到周围的函数退出。这意味着您可以将清理/拆卸代码放在函数的顶部(很明显),因为知道一旦函数完成它就会完成它的工作。funcmain(){iflen(os.Args)<2{log.Fatal("nofilespecified")}f,err:=os.Open(os.Args[1])iferr!=nil{log.Fatal(err)}deferf.Close()data:=make([]byte,2048)for{count,err:=f.Read(data)os.Stdout.Write(data[:count])iferr!=nil{iferr!=io.EOF{log.Fatal(err)}break}}}在上面的例子中,文件关闭方法被延迟了。我喜欢这种在函数顶部声明内务管理意图然后忘记它的模式,因为知道一旦函数退出它就会完成它的工作。5.Go结合了函数式编程的最佳特性函数式编程是一种高效且富有创造性的范式,值得庆幸的是,Go结合了函数式编程的最佳特性。在Go中:—函数是值,这意味着它们可以作为值添加到映射中,作为参数传递给其他函数,设置为变量,并从函数返回(称为“高阶函数”,通常在Go中用于创建中间件使用装饰者模式)。—可以自动创建和调用匿名函数。—在其他函数内部声明的函数允许闭包(在函数内部声明的函数能够访问和修改在外部函数中声明的变量)。在惯用的Go中,闭包被广泛用于限制函数的范围并设置函数在其逻辑中使用的状态。funcStartTimer(namestring)func(){t:=time.Now()log.Println(name,"started")returnfunc(){d:=time.Now().Sub(t)log.Println(name,"take",d)}}funcRunTimer(){stop:=StartTimer("Mytimer")deferstop()time.Sleep(1*time.Second)}上面是一个闭包的例子。“StartTimer”函数返回一个新函数,该函数通过闭包可以访问在其起始范围内设置的“t”值。然后,此函数可以将当前时间与“t”的值进行比较,从而创建一个有用的计时器。感谢MatRyer[14]提供的这个例子。6.Go具有隐式接口实现任何阅读过SOLID[15]编码和设计模式[16]文献的人都可能听说过“优先组合而不是继承”的口头禅。简而言之,这表明您应该将业务逻辑分解为不同的接口,而不是依赖于父类的属性和逻辑的分层继承。另一种流行的方法是“针对接口而不是实现编程”:API应该只发布其预期行为(其方法签名)的契约,而不是详细说明如何实现该行为。两者都指出了接口在现代编程中的至关重要性。所以Go支持接口也就不足为奇了。事实上,接口是Go中唯一的抽象类型。但是,与其他语言不同,Go中的接口不是显式实现的,而是隐式实现的。具体类型不声明它实现接口。相反,如果具体类型的方法集包含底层接口的所有方法集,则Go认为该对象实现了接口。这种隐式接口实现(正式称为结构类型)允许Go强制执行类型安全和解耦,保留动态语言中表现出的大部分灵活性。相反,显式接口将客户端和实现绑定在一起,例如在Java中替换依赖比在Go中要困难得多。//thisisaninterfacedeclaration(calledLogic)typeLogicinterface{Process(datastring)string}typeLogicProviderstruct{}//这是在LogicProviderstructfunc(lpLogicProvider)Process(datastring)string上调用'Process'的方法{//businesslogic}//thisistheclientstructwiththeLogicinterfaceasaproductytypeClient(Logicstruct)(LogicinterfaceasaproductytypeClient)(Logicstruct}{//getdatafromsomewherec.L.Process(data)}funcmain(){c:=Client{L:LogicProvider{},}c.Program()}LogicProvider中没有声明它实现了Logic接口。这意味着客户端以后可以很方便的更换他们的逻辑提供者,只要逻辑提供者包含底层接口(Logic)的所有方法集即可。7.错误处理Go中的错误处理与其他语言有很大的不同,简单来说,Go处理错误通过返回一个error类型的值作为函数的最后一个返回值。当函数按预期执行时,为error参数返回nil,否则为错误值回。然后,调用函数检查错误返回值,并处理错误或引发自己的错误。//函数返回anintandanerrorfunccalculateRemainder(numeratorint,denominatorint)(int,error){//Errorreturnedifdenominator==0{return9,errors.New("denominatoris0")}//Noerrorreturnedreturnnumerator/denominator,nil}Go这样做是有原因的:它迫使编码人员考虑异常并妥善处理它们。传统的try-catch异常还会在代码中添加至少一个新的代码路径,并以一种难以遵循的方式缩进代码。Go更喜欢将“快乐路径”视为未缩进的代码,在“快乐路径”完成之前识别并返回任何错误。8.并发性可以说是Go最著名的特性,并发性允许任务在机器或服务器上的多个可用内核上并行运行。当单独的进程不相互依赖(不需要按顺序运行)并且计时性能至关重要时,并发性最有意义。这通常是I/O要求的情况,其中从磁盘或网络读取或写入磁盘或网络比除了最复杂的内存进程之外的所有进程都慢几个数量级。函数调用前的“go”关键字将启动运行该函数的并发goroutine。funcprocess(valint)int{//dosomethingwithval}//foreachvaluein'in',runtheprocessfunctionconcurrently,//andreadtheresultofprocessto'out'funcrunConcurrently(in<-chanint,outchan<-int){gofunc(){forval:=rangein{result:=process(val)out<-result}}}Go中的并发是一个深入且相当高级的特性,但在有意义的地方,它提供了一种确保程序最佳性能的有效方法。9、Go标准库Go有“电池包容”的概念,现代编程语言的很多要求都集成到标准库中,让程序员的生活更轻松。如前所述,Go是一种相对年轻的语言,这意味着标准库中满足了现代应用程序的许多问题/需求。首先,Go为网络(尤其是HTTP/2)和文件管理提供了世界级的支持。它还提供原生JSON编码和解码。因此,设置一个服务器来处理HTTP请求并返回响应(JSON或其他)非常简单,这解释了Go在开发基于REST的HTTPWeb服务方面的流行。正如MatRyer[17]也指出的那样,标准库是开源的,是学习Go最佳实践的绝佳方式。10.调试:GoPlayground任何语言的调试都是一个关键要求。大多数语言依赖于第三方在线工具或巧妙的IDE来提供调试工具,使开发人员能够快速检查他们的代码。Go提供了GoPlayground—https://go.dev/play一个免费的在线工具,您可以在其中试用和分享小程序。这是一个非常有用的工具,使调试成为一种简单的练习。如果我没记错的话,Go应该已经开始了playground,后来发布的语言也提供了类似的功能,比如Rust和Swift。小结除了上面介绍的10个特性,你觉得还有其他Go特有的特性吗?参考文献[1]RobertGriesemer:https://en.wikipedia.org/wiki/Robert_Griesemer[2]RobPike:https://en.wikipedia.org/wiki/Rob_Pike[3]KenThompson:https://en.wikipedia.org/wiki/Ken_Thompson[4]堆栈:https://en.wikipedia.org/wiki/Stack-based_memory_allocation[5]堆:https://www.educba.com/what-is-heap-memory/[6]GoogleAppEngine:https://cloud.google.com/appengine[7]MavenCentral:https://search.maven.org/[8]NPM:https://www.npmjs.com/[9]按值传递:https://itnext.io/the-power-of-functional-programming-in-javascript-cc9797a42b60[10]指针:https://www.ardanlabs.com/blog/2017/05/language-mechanics-on-stacks-and-pointers.html[11]总结:http://web.mit.edu/6.031/www/fa20/classes/08-immutability/[12]本文:https://www.forrestthewoods.com/blog/memory-bandwidth-napkin-math/[13]knex.js:https://knexjs.org/[14]MatRyer:https://twitter.com/matryer[15]SOLID:https://en.wikipedia.org/wiki/SOLID[16]设计模式:https://en.wikipedia.org/wiki/Software_design_pattern[17]MatRyer:https://twitter.com/matryer