Go技巧 - 快速学习官方库ast
当我们谈及Go
语言底层时,往往会聊GMP
相关的并发原理,或者是以reflect
为代表的反射处理,它们也是面试中的常客。
而我今天想推荐的一个底层库 - ast
,全名抽象语法树(abstract syntax tree),不仅能让我们进一步掌握Go
的基础语法,更是一个开发标准化和提效工具的关键技能。
AST的基本概念
ast
的官方概念理解起来比较复杂,有兴趣的可以参考这篇知乎,或者阅读更专业的资料。
对初学者来说,抓住上图中的两个关键因素:
- 树 - 了解树相关的深度遍历算法
- 节点 - 各语法特征,如变量、条件语句等
Go的AST节点类型
学习ast
,从它底层的数据定义入手会让我们事半功倍。
ast
语法的核心抽象是Node
,定义如下:
1 | type Node interface { |
它的作用就是解析出对应语法的位置。整个Node
相关的接口与实现比较复杂,我以网上的一个版本为例:
整个语法树很庞杂,每个接口与结构体都有自己的一些特点。为了方便大家加深这部分的理解,我们从一个具体的case入手
AST的示例代码
示例代码如下,即解析本身main.go文件,打印出import的库与类型定义。
1 | package main |
其余语法的使用,大家可以参考前面图中的Node
接口与实现的定义,对照着实现。
为了加深大家对ast
这棵树的理解,我们再细化一下上面的例子。
AST的两种处理思路
示例中的import
代码,即
1 | import ( |
它对应的ast
中的结构体是
1 | type GenDecl struct { |
而每个import选项,则是
1 | // 抽象 |
所以,我们可以有两种方式来访问到每个ImportSpec
:
1 | func (vi *MyVisitor) Visit(node ast.Node) ast.Visitor { |
在用ast
编写相关工具时,我建议优先按思路2去编写代码,它的优势在于两点:
- 扩大上下文信息 - 除了基本的
ImportSpec
,还可以使用父节点GenDecl
中的共用信息 - 代码可读性高 - 先处理父节点,解析到具体的结构体,再进行各个子节点的处理,思路很自然
当整个Visit
处理的内容比较多时,就需要进行一定的拆分,用递归来减少复杂度。
AST在日常工作中的使用示例
ast
的特性看起来很酷,但很少会直接应用在项目的代码里。
不过,作为官方支持、可用来分析Go
代码的库,它常用于制作二进制工具,在不同的场景使用:
- 进阶性的代码规范性检查 - 如检查某层代码的import情况,保证分层规范
- 自定义的代码生成 - 如根据注释自动生成定义,根据方法生成mock接口
- 编译前统一对库或方法的替换 - 在编译前,对某些特定的库或方法进行替换,修改原
go
文件
在优秀的框架中,ast
往往与标准化相辅相成,形成正反馈:代码标准化的程度越高,ast
就越能提升自动化、保障质量;ast
应用得越广泛,代码的标准化程度自然就越高。
编程思考 - 业务也是技术人员的核心能力
最近,为了项目的推进,我和大量的开发人员进行了沟通,发现部分新人对“业务”的认知有明显偏差。
技术有两个极致的方向:
- 底层理论性的研究
- 面向业务的trade-off
前者是为技术领域开辟新的领域,只有极少的研究员会参与这类工作;而面向业务的trade-off则是大多数开发者能接触到的终极目标,难点往往在于:
- 技术储备广 - 有多样的解决方案,各有利弊
- 业务认知深 - 洞察业务的核心价值
- 技术 ✖️ 业务 - 两者结合时的决策能力
技术是开发者的立身之本,而业务是公司的立身之本。 优秀的开发者并不在于能想出100个技术方案,而是能提出3个各有利弊的关键技术方案、并且能根据业务情况给出自己的意见。
工作生活 - 如果无法理解,也请包容他人
我有段时间心理洁癖非常严重:我会尝试各种方法、各种角度去理解一些人,但如果用尽方法、仍无法理解对方的所作所为,我就会对其非常反感(当然,这样的人是极少数的)。
理解是一个很理想的方式,能让我更深刻地了解对方,也更适合深度、长期的关系。但在现实生活中,我们很难投入那么多的时间与情绪去熟悉每个人,也因不可避免的个人认知偏差导致误会。
所以,在和大部分人相处时,包容是一种让自己更轻松的方式。
Github: https://github.com/Junedayday/code_reading
Blog: http://junes.tech/
Bilibili: https://space.bilibili.com/293775192
公众号: golangcoding