当前位置:首页 > 数码 > Go的元编程-代码生成与AST操作 (go 元编程)

Go的元编程-代码生成与AST操作 (go 元编程)

admin4个月前 (05-14)数码33

当天咱们要一同讨论Go言语中的元编程,特意是代码生成和形象语法树(AST)操作。

元编程是一种编程范式,它准许咱们在编译时或运转时灵活地生成或修正代码。

代码生成的概念与运行

什么是代码生成?

代码生成是一种编程技术,经过它,程序可以智能地生成源代码。这在很多场景下都十分有用,比如:ORM映射、协定缓冲区(ProtocolBuffers)等。

为什么须要代码生成?

如何启动代码生成?

Go言语提供了gogenerate命令来繁难地启动代码生成。

//go:generategorunyour_generator.go

形象语法树(AST)操作

什么是AST?

形象语法树(AbstractSyntaxTree,简称AST)是源代码的树形示意,用于形容代码结构。

为什么须要操作AST?

如何操作AST?

Go言语的go/ast包提供了丰盛的API来操作AST。

Go的元编程fset:=token.NewFileSet()node,err:=parser.ParseFile(fset,"your_file.go",nil,parser.ParseComments)

代码生成与AST操作的联合

在复杂的运行场景下,咱们或许须要将代码生成和AST操作联合起来。例如,咱们可以先生成一段代码的AST,而后对其启动一系列的修正和提升,最后再将AST转回源代码。

最佳通常与留意事项

Go元编程的实践案例

为了更深化地理解Go的元编程,让咱们来看几个实践的运行案例。

案例1:智能生成API文档

假定你有一个大型的Web服务名目,其中有数十个API接口。手动编写和保养这些API文档会是一项渺小的上班。这时,你可以编写一个代码生成工具,该工具会扫描你的代码库,智能提取无关API接口的注释和元数据,并生成相应的API文档。

案例2:ORM代码生成

在数据库操作中,对象相关映射(ORM)是一种经常出现的形式。经过编写一个代码生成器,你可以智能地为每个数据库表生成相应的Go结构体和CRUD方法。

案例3:智能化测试代码生成

测试是软件开发中无法或缺的一局部。经过元编程,你可以智能生成少量的测试用例,从而提高测试的笼罩率和品质。

Go元编程的局限性

只管Go的元编程十分弱小,但它也有一些局限性:

Go言语的元编程,尤其是代码生成和AST操作,为开发者提供了弱小而灵敏的工具。但是,这些工具也须要审慎经常使用,以确保它们能真正提高开发效率和代码品质,而不是成为保养的累赘。

经过本文,我宿愿你能片面了解Go言语在元编程方面的才干和运行,以及如何在实践名目中正当应用这些工具。


如何使用AST生成程序的控制流图

对一个有结构化编程特性的编程语言而言,抽象语法树(AST)与“流分析”(flowanalysis)不够兼容的地方在于:这些编程语言会有分支、循环、跳转(break/continue,或者像C#那样受限的goto)、异常处理等语法结构,它们隐含了不连续的控制流;而AST的结构虽然已经省略了一些语法细节,但仍然贴近语言的语法,所以AST里控制流往往是隐含在上述语法结构对应的节点里。 更糟糕的是,这些编程语言中许多都有条件表达式(典型的是?:三元运算符)以及带有短路求值语义的逻辑运算表达式(典型的是&&、||二元运算符),这些“表达式”内也隐含控制流,更是藏得深。 所以正统做法推荐的是在做数据流分析之前,先把AST转换为一种更细粒度的、把控制流显式暴露出来的中间表示(IR)。 虎书教的做法正是如此,第7章介绍从AST生成IR,第8章介绍了IR里的控制流与基本块。 前面@vczh和@陈炜的回答都提到了这一点。 @邵成的回答提到龙书第8~9章,我觉得把第6章(特别是6.6ControlFlow)也包括进来更好。 对数据流分析来说,IR无论是树形、DAG还是线性形式都没关系,只要控制流和数据依赖易于分析就好。 SSA形式的IR可以把两者都显式暴露出来,特别是use-def关系(并且有些IR会额外维护def-use关系),所以在现代编译器和程序分析器里比较流行。

GO语言商业案例(十八):stream

切换到新语言始终是一大步,尤其是当您的团队成员只有一个时有该语言的先前经验。现在,Stream 的主要编程语言从 Python 切换到了 Go。这篇文章将解释stream决定放弃 Python 并转向 Go 的一些原因。

Go 非常快。性能类似于 Java 或 C++。对于用例,Go 通常比 Python 快 40 倍。

对于许多应用程序来说,编程语言只是应用程序和数据库之间的粘合剂。语言本身的性能通常并不重要。然而,Stream 是一个API 提供商,为 700 家公司和超过 5 亿最终用户提供提要和聊天平台。多年来,我们一直在优化 Cassandra、PostgreSQL、Redis 等,但最终,您会达到所使用语言的极限。Python 是一门很棒的语言,但对于序列化/反序列化、排名和聚合等用例,它的性能相当缓慢。我们经常遇到性能问题,Cassandra 需要 1 毫秒来检索数据,而 Python 会花费接下来的 10 毫秒将其转换为对象。

看看我如何开始 Go 教程中的一小段 Go 代码。(这是一个很棒的教程,也是学习 Go 的一个很好的起点。)

如果您是 Go 新手,那么在阅读那个小代码片段时不会有太多让您感到惊讶的事情。它展示了多个赋值、数据结构、指针、格式和一个内置的 HTTP 库。当我第一次开始编程时,我一直喜欢使用 Python 更高级的功能。Python 允许您在编写代码时获得相当的创意。例如,您可以:

这些功能玩起来很有趣,但是,正如大多数程序员会同意的那样,在阅读别人的作品时,它们通常会使代码更难理解。Go 迫使你坚持基础。这使得阅读任何人的代码并立即了解发生了什么变得非常容易。 注意:当然,它实际上有多“容易”取决于您的用例。如果你想创建一个基本的 CRUD API,我仍然推荐 Django + DRF或 Rails。

作为一门语言,Go 试图让事情变得简单。它没有引入许多新概念。重点是创建一种非常快速且易于使用的简单语言。它唯一具有创新性的领域是 goroutine 和通道。(100% 正确CSP的概念始于 1977 年,所以这项创新更多是对旧思想的一种新方法。)Goroutines 是 Go 的轻量级线程方法,通道是 goroutines 之间通信的首选方式。Goroutines 的创建非常便宜,并且只需要几 KB 的额外内存。因为 Goroutine 非常轻量,所以有可能同时运行数百甚至数千个。您可以使用通道在 goroutine 之间进行通信。Go 运行时处理所有复杂性。goroutines 和基于通道的并发方法使得使用所有可用的 CPU 内核和处理并发 IO 变得非常容易——所有这些都不会使开发复杂化。与 Python/Java 相比,在 goroutine 上运行函数需要最少的样板代码。您只需在函数调用前加上关键字“go”:

Go 的并发方法很容易使用。与 Node 相比,这是一种有趣的方法,开发人员必须密切关注异步代码的处理方式。Go 中并发的另一个重要方面是竞争检测器。这样可以很容易地确定异步代码中是否存在任何竞争条件。

我们目前用 Go 编写的最大的微服务编译需要 4 秒。与以编译速度慢而闻名的 Java 和 C++ 等语言相比,Go 的快速编译时间是一项重大的生产力胜利。我喜欢在程序编译的时候摸鱼,但在我还记得代码应该做什么的同时完成事情会更好。

首先,让我们从显而易见的开始:与 C++ 和 Java 等旧语言相比,Go 开发人员的数量并不多。根据StackOverflow的数据, 38% 的开发人员知道 Java, 19.3% 的人知道 C++,只有 4.6% 的人知道 Go。GitHub 数据显示了类似的趋势:Go 比 Erlang、Scala 和 Elixir 等语言使用更广泛,但不如 Java 和 C++ 流行。幸运的是,Go 是一种非常简单易学的语言。它提供了您需要的基本功能,仅此而已。它引入的新概念是“延迟”声明和内置的并发管理与“goroutines”和通道。(对于纯粹主义者来说:Go 并不是第一种实现这些概念的语言,只是第一种使它们流行起来的语言。)任何加入团队的 Python、Elixir、C++、Scala 或 Java 开发人员都可以在一个月内在 Go 上发挥作用,因为它的简单性。与许多其他语言相比,我们发现组建 Go 开发人员团队更容易。如果您在博尔德和阿姆斯特丹等竞争激烈的生态系统中招聘人员,这是一项重要的优势。

对于我们这样规模的团队(约 20 人)来说,生态系统很重要。如果您必须重新发明每一个小功能,您根本无法为您的客户创造价值。Go 对我们使用的工具有很好的支持。实体库已经可用于 Redis、RabbitMQ、PostgreSQL、模板解析、任务调度、表达式解析和 RocksDB。与 Rust 或 Elixir 等其他较新的语言相比,Go 的生态系统是一个重大胜利。它当然不如 Java、Python 或 Node 之类的语言好,但它很可靠,而且对于许多基本需求,你会发现已经有高质量的包可用。

Gofmt 是一个很棒的命令行实用程序,内置在 Go 编译器中,用于格式化代码。就功能而言,它与 Python 的 autopep8 非常相似。我们大多数人并不真正喜欢争论制表符与空格。格式的一致性很重要,但实际的格式标准并不那么重要。Gofmt 通过使用一种正式的方式来格式化您的代码来避免所有这些讨论。

Go 对协议缓冲区和 gRPC 具有一流的支持。这两个工具非常适合构建需要通过 RPC 通信的微服务。您只需要编写一个清单,在其中定义可以进行的 RPC 调用以及它们采用的参数。然后从这个清单中自动生成服务器和客户端代码。生成的代码既快速又具有非常小的网络占用空间并且易于使用。从同一个清单中,您甚至可以为许多不同的语言生成客户端代码,例如 C++、Java、Python 和 Ruby。因此,内部流量不再有模棱两可的 REST 端点,您每次都必须编写几乎相同的客户端和服务器代码。.

Go 没有像 Rails 用于 Ruby、Django 用于 Python 或 Laravel 用于 PHP 那样的单一主导框架。这是 Go 社区内激烈争论的话题,因为许多人主张你不应该一开始就使用框架。我完全同意这对于某些用例是正确的。但是,如果有人想构建一个简单的 CRUD API,他们将更容易使用 Django/DJRF、Rails Laravel 或Phoenix。对于 Stream 的用例,我们更喜欢不使用框架。然而,对于许多希望提供简单 CRUD API 的新项目来说,缺乏主导框架将是一个严重的劣势。

Go 通过简单地从函数返回错误并期望调用代码来处理错误(或将其返回到调用堆栈)来处理错误。虽然这种方法有效,但很容易失去问题的范围,以确保您可以向用户提供有意义的错误。错误包通过允许您向错误添加上下文和堆栈跟踪来解决此问题。另一个问题是很容易忘记处理错误。像 errcheck 和 megacheck 这样的静态分析工具可以方便地避免犯这些错误。虽然这些变通办法效果很好,但感觉不太对劲。您希望该语言支持正确的错误处理。

Go 的包管理绝不是完美的。默认情况下,它无法指定特定版本的依赖项,也无法创建可重现的构建。Python、Node 和 Ruby 都有更好的包管理系统。但是,使用正确的工具,Go 的包管理工作得很好。您可以使用Dep来管理您的依赖项,以允许指定和固定版本。除此之外,我们还贡献了一个名为的开源工具VirtualGo,它可以更轻松地处理用 Go 编写的多个项目。

我们进行的一个有趣的实验是在 Python 中使用我们的排名提要功能并在 Go 中重写它。看看这个排名方法的例子:

Python 和 Go 代码都需要执行以下操作来支持这种排名方法:

开发 Python 版本的排名代码大约花了 3 天时间。这包括编写代码、单元测试和文档。接下来,我们花了大约 2 周的时间优化代码。其中一项优化是将分数表达式 (simple_gauss(time)*popularity) 转换为抽象语法树. 我们还实现了缓存逻辑,可以在未来的特定时间预先计算分数。相比之下,开发此代码的 Go 版本大约需要 4 天时间。性能不需要任何进一步的优化。因此,虽然 Python 的最初开发速度更快,但基于 Go 的版本最终需要我们团队的工作量大大减少。另外一个好处是,Go 代码的执行速度比我们高度优化的 Python 代码快大约 40 倍。现在,这只是我们通过切换到 Go 体验到的性能提升的一个示例。

与 Python 相比,我们系统的其他一些组件在 Go 中构建所需的时间要多得多。作为一个总体趋势,我们看到 开发 Go 代码需要更多的努力。但是,我们花更少的时间 优化 代码以提高性能。

我们评估的另一种语言是Elixir.。Elixir 建立在 Erlang 虚拟机之上。这是一种迷人的语言,我们之所以考虑它,是因为我们的一名团队成员在 Erlang 方面拥有丰富的经验。对于我们的用例,我们注意到 Go 的原始性能要好得多。Go 和 Elixir 都可以很好地服务数千个并发请求。但是,如果您查看单个请求的性能,Go 对于我们的用例来说要快得多。我们选择 Go 而不是 Elixir 的另一个原因是生态系统。对于我们需要的组件,Go 有更成熟的库,而在许多情况下,Elixir 库还没有准备好用于生产环境。培训/寻找开发人员使用 Elixir 也更加困难。这些原因使天平向 Go 倾斜。Elixir 的 Phoenix 框架看起来很棒,绝对值得一看。

Go 是一种非常高性能的语言,对并发有很好的支持。它几乎与 C++ 和 Java 等语言一样快。虽然与 Python 或 Ruby 相比,使用 Go 构建东西确实需要更多时间,但您将节省大量用于优化代码的时间。我们在Stream有一个小型开发团队,为超过 5 亿最终用户提供动力和聊天。Go 结合了 强大的生态系统 、新开发人员的 轻松入门、快速的性能 、对并发的 可靠支持和高效的编程环境 ,使其成为一个不错的选择。Stream 仍然在我们的仪表板、站点和机器学习中利用 Python 来提供个性化的订阅源. 我们不会很快与 Python 说再见,但今后所有性能密集型代码都将使用 Go 编写。我们新的聊天 API也完全用 Go 编写。

免责声明:本文转载或采集自网络,版权归原作者所有。本网站刊发此文旨在传递更多信息,并不代表本网赞同其观点和对其真实性负责。如涉及版权、内容等问题,请联系本网,我们将在第一时间删除。同时,本网站不对所刊发内容的准确性、真实性、完整性、及时性、原创性等进行保证,请读者仅作参考,并请自行核实相关内容。对于因使用或依赖本文内容所产生的任何直接或间接损失,本网站不承担任何责任。

标签: Go

“Go的元编程-代码生成与AST操作 (go 元编程)” 的相关文章

在-使用-应用-中构建高性能-Echo-Web-Go (在使用应急法压缩时间时,不一定)

在-使用-应用-中构建高性能-Echo-Web-Go (在使用应急法压缩时间时,不一定)

Echo是一个高性能且易用的框架,用于构建Go语言编写的Web应用。它具备灵活的路由功能、方便的请求处理、强大的中间件支持和简单的JSON操作,非常适合快速开发API服务和Web应用。 快速...

etcd-的依赖问题最终得到解决-Go (etcd的英文全称)

etcd-的依赖问题最终得到解决-Go (etcd的英文全称)

几年前,我经常接触到一组微服务相关组件:gRPC、gRPC 网关、etcd、Protobuf 和 protoc-gen-go。一开始,它们都能很好地协同工作,并且随着新版本的发布而持续更新。...

Go-内存调配优化-在结构体中充沛应用内存 (go 内存)

Go-内存调配优化-在结构体中充沛应用内存 (go 内存)

在经常使用Golang启动内存调配时,咱们须要遵照一系列规定。在深化了解这些规定之前,咱们须要先了解变量的对齐形式。 Golang的unsafe包中有一个函数Alignof,签名如下: f...

内存优化与渣滓搜集-深化探求优化程序性能的最佳通常-Go (内存优化与渣子有关吗)

内存优化与渣滓搜集-深化探求优化程序性能的最佳通常-Go (内存优化与渣子有关吗)

Go提供了智能化的内存治理机制,但在某些状况下须要更精细的微调从而防止出现OOM失误。本文将讨论Go的渣滓搜集器、运行程序内存优化以及如何防止OOM(Out-Of-Memory)失误。 Go...

的原因-Go-Map-语言不支持并发读写 (的原因英语)

的原因-Go-Map-语言不支持并发读写 (的原因英语)

在Go语言的设计中,为了防止数据竞态,不同于一些其他语言,map并没有提供内置的锁机制。这样设计的目的是为了鼓励开发者使用更加精细的同步措施,以适应不同的并发场景。 Map的数据结构...

内存效率-多用途-语言中使用切片代替数组的优点-动态大小-Go (内存使用效率)

内存效率-多用途-语言中使用切片代替数组的优点-动态大小-Go (内存使用效率)

引言 在 Go 语言中,数组是一种固定长度的数据结构,而切片则是一种可变长度的数据结构。虽然数组和切片都可以存储相同类型的数据元素,但切片在使用上有其独到的优势,本文将通过介绍切片的特性来解释为...

14条超乎想象的Go接口最佳实践

14条超乎想象的Go接口最佳实践

近年来,越来越多的开发者开始关注 Go 语言,它以其高效、简洁和高并发性而闻名。在 Go 语言中,接口是一个非常强大的特性,它可以帮助我们定义和实现不同的行为。本文将介绍 Go 语言中接口设计的一...

b-Go-成功线程安保的缓存-sync.Map-中经常使用-b

b-Go-成功线程安保的缓存-sync.Map-中经常使用-b

缓存是提升现代运行程序性能的关键方面。它准许您存储并极速检索低廉操作的结果或经常访问的数据,缩小了重复从新计算或失掉数据的须要。在本文中,咱们将讨论如何经常使用sync.Map包在Go中成功线程安...